From 8493cd00117b4c259320e1c43bc50909682a0eac Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Wed, 9 Jan 2019 19:27:57 -0800 Subject: [PATCH 01/20] Make a backup copy of GeoPointMapActivity, as GeoPointGoogleMapActivity. --- collect_app/src/main/AndroidManifest.xml | 3 + .../activities/GeoPointGoogleMapActivity.java | 550 ++++++++++++++++++ .../res/layout/geopoint_google_layout.xml | 106 ++++ 3 files changed, 659 insertions(+) create mode 100644 collect_app/src/main/java/org/odk/collect/android/activities/GeoPointGoogleMapActivity.java create mode 100644 collect_app/src/main/res/layout/geopoint_google_layout.xml diff --git a/collect_app/src/main/AndroidManifest.xml b/collect_app/src/main/AndroidManifest.xml index 0b85c908a8a..5b4c42e29e6 100644 --- a/collect_app/src/main/AndroidManifest.xml +++ b/collect_app/src/main/AndroidManifest.xml @@ -201,6 +201,9 @@ the specific language governing permissions and limitations under the License. + diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointGoogleMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointGoogleMapActivity.java new file mode 100644 index 00000000000..fc14ce8fefe --- /dev/null +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointGoogleMapActivity.java @@ -0,0 +1,550 @@ +/* + * Copyright (C) 2011 University of Washington + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.odk.collect.android.activities; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.Intent; +import android.graphics.Color; +import android.location.Location; +import android.os.Bundle; +import android.provider.Settings; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.Window; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.TextView; + +import com.google.android.gms.location.LocationListener; +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener; +import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; + +import org.odk.collect.android.R; +import org.odk.collect.android.location.client.LocationClient; +import org.odk.collect.android.location.client.LocationClients; +import org.odk.collect.android.spatial.MapHelper; +import org.odk.collect.android.utilities.GeoPointUtils; +import org.odk.collect.android.utilities.ToastUtils; +import org.odk.collect.android.widgets.GeoPointWidget; + +import java.text.DecimalFormat; + +import timber.log.Timber; + +import static org.odk.collect.android.utilities.PermissionUtils.areLocationPermissionsGranted; + +/** + * Version of the GeoPointMapActivity that uses the new Maps v2 API and Fragments to enable + * specifying a location via placing a tracker on a map. + * + * @author guisalmon@gmail.com + * @author jonnordling@gmail.com + */ +public class GeoPointGoogleMapActivity extends BaseGeoMapActivity implements OnMarkerDragListener, OnMapLongClickListener, + LocationClient.LocationClientListener, LocationListener { + + private static final String LOCATION_COUNT = "locationCount"; + + private GoogleMap map; + private MarkerOptions markerOptions; + private Marker marker; + private LatLng latLng; + + private TextView locationStatus; + private TextView locationInfo; + + private LocationClient locationClient; + + private Location location; + private ImageButton reloadLocation; + + private boolean isDragged; + private ImageButton showLocation; + + private int locationCount; + + //private KmlLayer kk; + + private AlertDialog errorDialog; + + private AlertDialog zoomDialog; + private View zoomDialogView; + + private Button zoomPointButton; + private Button zoomLocationButton; + private ImageButton clearPointButton; + + private boolean setClear; + private boolean captureLocation; + private boolean foundFirstLocation; + private boolean readOnly; + private boolean draggable; + private boolean intentDraggable; + private boolean locationFromIntent; + + private boolean isMapReady; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (!areLocationPermissionsGranted(this)) { + finish(); + return; + } + + requestWindowFeature(Window.FEATURE_NO_TITLE); + + if (savedInstanceState != null) { + locationCount = savedInstanceState.getInt(LOCATION_COUNT); + } + + try { + setContentView(R.layout.geopoint_layout); + + } catch (NoClassDefFoundError e) { + Timber.e(e, "Google maps not accessible due to: %s ", e.getMessage()); + ToastUtils.showShortToast(R.string.google_play_services_error_occured); + finish(); + return; + } + + locationStatus = findViewById(R.id.location_status); + locationInfo = findViewById(R.id.location_info); + reloadLocation = findViewById(R.id.reload_location); + showLocation = findViewById(R.id.show_location); + + locationClient = LocationClients.clientForContext(this); + + isMapReady = false; + ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMapAsync(googleMap -> { + setupMap(googleMap); + locationClient.setListener(this); + locationClient.start(); + }); + } + + @Override + protected void onStop() { + locationClient.stop(); + super.onStop(); + } + + public void returnLocation() { + Intent i = new Intent(); + if (setClear || (readOnly && latLng == null)) { + i.putExtra(FormEntryActivity.LOCATION_RESULT, ""); + setResult(RESULT_OK, i); + + } else if (isDragged || readOnly || locationFromIntent) { + Timber.i("IsDragged !!!"); + i.putExtra( + FormEntryActivity.LOCATION_RESULT, + latLng.latitude + " " + latLng.longitude + " " + + 0 + " " + 0); + setResult(RESULT_OK, i); + } else if (location != null) { + Timber.i("IsNotDragged !!!"); + + i.putExtra( + FormEntryActivity.LOCATION_RESULT, + getResultString(location) + ); + setResult(RESULT_OK, i); + } + finish(); + } + + public String getResultString(Location location) { + return String.format("%s %s %s %s", location.getLatitude(), location.getLongitude(), location.getAltitude(), location.getAccuracy()); + } + + private String truncateFloat(float f) { + return new DecimalFormat("#.##").format(f); + } + + @SuppressLint("MissingPermission") // Permission handled in Constructor + private void setupMap(GoogleMap googleMap) { + map = googleMap; + if (map == null) { + ToastUtils.showShortToast(R.string.google_play_services_error_occured); + finish(); + return; + } + helper = new MapHelper(this, map, selectedLayer); + map.setMyLocationEnabled(true); + map.getUiSettings().setCompassEnabled(true); + map.getUiSettings().setMyLocationButtonEnabled(false); + map.getUiSettings().setZoomControlsEnabled(false); + + markerOptions = new MarkerOptions(); + helper = new MapHelper(this, map, selectedLayer); + + ImageButton acceptLocation = findViewById(R.id.accept_location); + + acceptLocation.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + returnLocation(); + } + }); + + reloadLocation.setEnabled(false); + reloadLocation.setOnClickListener(v -> { + removeMarker(); + latLng = new LatLng(location.getLatitude(), location.getLongitude()); + if (marker == null) { + addMarker(); + if (draggable && !readOnly) { + marker.setDraggable(true); + } + } + zoomToPoint(); + }); + + // Focuses on marked location + //showLocation.setClickable(false); + showLocation.setEnabled(false); + showLocation.setOnClickListener(v -> showZoomDialog()); + + // Menu Layer Toggle + ImageButton layers = findViewById(R.id.layer_menu); + layers.setOnClickListener(v -> helper.showLayersDialog()); + zoomDialogView = getLayoutInflater().inflate(R.layout.geo_zoom_dialog, null); + zoomLocationButton = zoomDialogView.findViewById(R.id.zoom_location); + zoomLocationButton.setOnClickListener(v -> { + zoomToLocation(); + zoomDialog.dismiss(); + }); + + zoomPointButton = zoomDialogView.findViewById(R.id.zoom_saved_location); + zoomPointButton.setOnClickListener(v -> { + zoomToPoint(); + zoomDialog.dismiss(); + }); + + clearPointButton = findViewById(R.id.clear); + clearPointButton.setEnabled(false); + clearPointButton.setOnClickListener(v -> { + removeMarker(); + if (location != null) { + reloadLocation.setEnabled(true); + // locationStatus.setVisibility(View.VISIBLE); + } + // reloadLocation.setEnabled(true); + locationInfo.setVisibility(View.VISIBLE); + locationStatus.setVisibility(View.VISIBLE); + draggable = intentDraggable; + locationFromIntent = false; + overlayMyLocationLayers(); + }); + + Intent intent = getIntent(); + if (intent != null && intent.getExtras() != null) { + if (intent.hasExtra(GeoPointWidget.DRAGGABLE_ONLY)) { + draggable = intent.getBooleanExtra(GeoPointWidget.DRAGGABLE_ONLY, false); + intentDraggable = draggable; + if (!intentDraggable) { + // Not Draggable, set text for Map else leave as placement-map text + locationInfo.setText(getString(R.string.geopoint_no_draggable_instruction)); + } + } + + if (intent.hasExtra(GeoPointWidget.READ_ONLY)) { + readOnly = intent.getBooleanExtra(GeoPointWidget.READ_ONLY, false); + if (readOnly) { + captureLocation = true; + clearPointButton.setEnabled(false); + } + } + + if (intent.hasExtra(GeoPointWidget.LOCATION)) { + double[] location = intent.getDoubleArrayExtra(GeoPointWidget.LOCATION); + latLng = new LatLng(location[0], location[1]); + captureLocation = true; + reloadLocation.setEnabled(false); + draggable = false; // If data loaded, must clear first + locationFromIntent = true; + + } + } + /*Zoom only if there's a previous location*/ + if (latLng != null) { + locationInfo.setVisibility(View.GONE); + locationStatus.setVisibility(View.GONE); + showLocation.setEnabled(true); + addMarker(); + foundFirstLocation = true; + zoomToPoint(); + } + + helper.setBasemap(); + + isMapReady = true; + upMyLocationOverlayLayers(); + } + + private void upMyLocationOverlayLayers() { + if (!locationClient.isMonitoringLocation() || !isMapReady) { + return; + } + + // Make sure we can access Location: + if (!locationClient.isLocationAvailable()) { + showGPSDisabledAlertToUser(); + + } else { + overlayMyLocationLayers(); + } + } + + private void overlayMyLocationLayers() { + if (draggable && !readOnly) { + map.setOnMarkerDragListener(this); + map.setOnMapLongClickListener(this); + + if (marker != null) { + marker.setDraggable(true); + } + } + } + + @Override + public void onLocationChanged(Location location) { + if (setClear) { + reloadLocation.setEnabled(true); + } + + Location previousLocation = this.location; + this.location = location; + + if (location != null) { + Timber.i("onLocationChanged(%d) location: %s", locationCount, location); + + if (previousLocation != null) { + enableShowLocation(true); + + if (!captureLocation && !setClear) { + latLng = new LatLng(location.getLatitude(), location.getLongitude()); + addMarker(); + reloadLocation.setEnabled(true); + } + + if (!foundFirstLocation) { + //zoomToPoint(); + showZoomDialog(); + foundFirstLocation = true; + } + + String locationString = getAccuracyStringForLocation(location); + locationStatus.setText(locationString); + } + + } else { + Timber.i("onLocationChanged(%d) null location", locationCount); + } + } + + @Override + public void onMarkerDrag(Marker arg0) { + + } + + @Override + public void onMarkerDragEnd(Marker marker) { + latLng = marker.getPosition(); + isDragged = true; + captureLocation = true; + setClear = false; + map.animateCamera( + CameraUpdateFactory.newLatLngZoom(latLng, map.getCameraPosition().zoom)); + + } + + @Override + public void onMarkerDragStart(Marker arg0) { + + } + + @Override + public void onMapLongClick(LatLng latLng) { + this.latLng = latLng; + if (marker == null) { + addMarker(); + } else { + marker.setPosition(latLng); + } + enableShowLocation(true); + marker.setDraggable(true); + isDragged = true; + } + + private void enableShowLocation(boolean shouldEnable) { + if (showLocation != null) { + showLocation.setEnabled(shouldEnable); + } + } + + private void zoomToLocation() { + LatLng here = new LatLng(location.getLatitude(), location.getLongitude()); + map.animateCamera(CameraUpdateFactory.newLatLngZoom(here, 16)); + } + + private void zoomToPoint() { + if (latLng != null) { + map.animateCamera(CameraUpdateFactory.newLatLngZoom(marker.getPosition(), 16)); + } + + } + + public void showZoomDialog() { + + if (zoomDialog == null) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.zoom_to_where)); + builder.setView(zoomDialogView) + .setNegativeButton(R.string.cancel, (dialog, id) -> dialog.cancel()) + .setOnCancelListener(dialog -> { + dialog.cancel(); + zoomDialog.dismiss(); + }); + zoomDialog = builder.create(); + } + //If feature enable zoom to button else disable + if (zoomLocationButton != null) { + if (location != null) { + zoomLocationButton.setEnabled(true); + zoomLocationButton.setBackgroundColor(Color.parseColor("#50cccccc")); + zoomLocationButton.setTextColor(themeUtils.getPrimaryTextColor()); + } else { + zoomLocationButton.setEnabled(false); + zoomLocationButton.setBackgroundColor(Color.parseColor("#50e2e2e2")); + zoomLocationButton.setTextColor(Color.parseColor("#FF979797")); + } + + if (latLng != null & !setClear) { + zoomPointButton.setEnabled(true); + zoomPointButton.setBackgroundColor(Color.parseColor("#50cccccc")); + zoomPointButton.setTextColor(themeUtils.getPrimaryTextColor()); + } else { + zoomPointButton.setEnabled(false); + zoomPointButton.setBackgroundColor(Color.parseColor("#50e2e2e2")); + zoomPointButton.setTextColor(Color.parseColor("#FF979797")); + } + } + + zoomDialog.show(); + } + + private void showGPSDisabledAlertToUser() { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); + + alertDialogBuilder.setMessage(getString(R.string.gps_enable_message)) + .setCancelable(false) + .setPositiveButton(getString(R.string.enable_gps), + (dialog, id) -> { + startActivityForResult( + new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 0); + errorDialog = null; + }); + + alertDialogBuilder.setNegativeButton(getString(R.string.cancel), + (dialog, id) -> { + dialog.cancel(); + errorDialog = null; + }); + + errorDialog = alertDialogBuilder.create(); + errorDialog.show(); + } + + // remove the marker and disable the trash button. + private void removeMarker() { + if (marker != null) { + marker.remove(); + latLng = null; + marker = null; + isDragged = false; + captureLocation = false; + clearPointButton.setEnabled(false); + setClear = true; + } + } + + // add the marker and enable the trash button. + private void addMarker() { + if (marker == null) { + markerOptions.position(latLng); + marker = map.addMarker(markerOptions); + clearPointButton.setEnabled(true); + captureLocation = true; + setClear = false; + } + } + + @Override + public void onClientStart() { + locationClient.requestLocationUpdates(this); + upMyLocationOverlayLayers(); + } + + @Override + public void onClientStartFailure() { + + } + + @Override + public void onClientStop() { + + } + + /** + * For testing purposes only. + * + * @param mapReady Whether or not the Google Map is ready. + */ + public void setMapReady(boolean mapReady) { + isMapReady = mapReady; + } + + public void setCaptureLocation(boolean captureLocation) { + this.captureLocation = captureLocation; + } + + public AlertDialog getErrorDialog() { + return errorDialog; + } + + public String getLocationStatus() { + return locationStatus.getText().toString(); + } + + public String getAccuracyStringForLocation(Location location) { + return getString(R.string.location_provider_accuracy, GeoPointUtils.capitalizeGps(location.getProvider()), + truncateFloat(location.getAccuracy())); + } + + public AlertDialog getZoomDialog() { + return zoomDialog; + } + +} diff --git a/collect_app/src/main/res/layout/geopoint_google_layout.xml b/collect_app/src/main/res/layout/geopoint_google_layout.xml new file mode 100644 index 00000000000..b2b092c6ea0 --- /dev/null +++ b/collect_app/src/main/res/layout/geopoint_google_layout.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c609aba23539cffd0b3da9555cd42ef1bd4853fb Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Wed, 9 Jan 2019 19:39:48 -0800 Subject: [PATCH 02/20] Add "animate" parameter to all map viewport movement methods. --- .../android/activities/GeoShapeActivity.java | 8 +++--- .../android/activities/GeoTraceActivity.java | 10 +++---- .../android/map/GoogleMapFragment.java | 28 +++++++++++++------ .../odk/collect/android/map/MapFragment.java | 13 +++++---- .../collect/android/map/OsmMapFragment.java | 22 +++++++++------ 5 files changed, 50 insertions(+), 31 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeActivity.java index 5882f7d5b46..81c0030b160 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeActivity.java @@ -162,21 +162,21 @@ private void initMap(MapFragment newMapFragment) { zoomLocationButton = zoomDialogView.findViewById(R.id.zoom_location); zoomLocationButton.setOnClickListener(v -> { - map.zoomToPoint(map.getGpsLocation()); + map.zoomToPoint(map.getGpsLocation(), true); zoomDialog.dismiss(); }); zoomPointButton = zoomDialogView.findViewById(R.id.zoom_saved_location); zoomPointButton.setOnClickListener(v -> { - map.zoomToBoundingBox(map.getPointsOfPoly(featureId), 0.6); + map.zoomToBoundingBox(map.getPointsOfPoly(featureId), 0.6, true); zoomDialog.dismiss(); }); map.setGpsLocationEnabled(true); if (restoredMapCenter != null && restoredMapZoom != null) { - map.zoomToPoint(restoredMapCenter, restoredMapZoom); + map.zoomToPoint(restoredMapCenter, restoredMapZoom, false); } else if (!points.isEmpty()) { - map.zoomToBoundingBox(points, 0.6); + map.zoomToBoundingBox(points, 0.6, false); } else { map.runOnGpsLocationReady(this::onGpsLocationReady); } diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoTraceActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoTraceActivity.java index 16cadfaa199..fd8215ee265 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoTraceActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoTraceActivity.java @@ -302,9 +302,9 @@ public void initMap(MapFragment newMapFragment) { map.setGpsLocationEnabled(true); map.setGpsLocationListener(this::onGpsLocation); if (restoredMapCenter != null && restoredMapZoom != null) { - map.zoomToPoint(restoredMapCenter, restoredMapZoom); + map.zoomToPoint(restoredMapCenter, restoredMapZoom, false); } else if (!points.isEmpty()) { - map.zoomToBoundingBox(points, 0.6); + map.zoomToBoundingBox(points, 0.6, false); } else { map.runOnGpsLocationReady(this::onGpsLocationReady); } @@ -391,13 +391,13 @@ private void buildDialogs() { zoomLocationButton = zoomDialogView.findViewById(R.id.zoom_location); zoomLocationButton.setOnClickListener(v -> { - map.zoomToPoint(map.getGpsLocation()); + map.zoomToPoint(map.getGpsLocation(), true); zoomDialog.dismiss(); }); zoomPointButton = zoomDialogView.findViewById(R.id.zoom_saved_location); zoomPointButton.setOnClickListener(v -> { - map.zoomToBoundingBox(map.getPointsOfPoly(featureId), 0.6); + map.zoomToBoundingBox(map.getPointsOfPoly(featureId), 0.6, true); zoomDialog.dismiss(); }); } @@ -487,7 +487,7 @@ private void onGpsLocationReady(MapFragment map) { private void onGpsLocation(MapPoint point) { if (modeActive) { - map.setCenter(point); + map.setCenter(point, false); } } diff --git a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java index aa92647b307..6576a12884a 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java @@ -27,6 +27,7 @@ import android.support.v4.app.FragmentActivity; import com.google.android.gms.location.LocationListener; +import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.SupportMapFragment; @@ -123,12 +124,12 @@ public GoogleMap getGoogleMap() { return new MapPoint(target.latitude, target.longitude); } - @Override public void setCenter(@Nullable MapPoint center) { + @Override public void setCenter(@Nullable MapPoint center, boolean animate) { if (map == null) { // during Robolectric tests, map will be null return; } if (center != null) { - map.moveCamera(CameraUpdateFactory.newLatLng(toLatLng(center))); + moveOrAnimateCamera(CameraUpdateFactory.newLatLng(toLatLng(center)), animate); } } @@ -139,20 +140,21 @@ public GoogleMap getGoogleMap() { return map.getCameraPosition().zoom; } - @Override public void zoomToPoint(@Nullable MapPoint center) { - zoomToPoint(center, POINT_ZOOM); + @Override public void zoomToPoint(@Nullable MapPoint center, boolean animate) { + zoomToPoint(center, POINT_ZOOM, animate); } - @Override public void zoomToPoint(@Nullable MapPoint center, double zoom) { + @Override public void zoomToPoint(@Nullable MapPoint center, double zoom, boolean animate) { if (map == null) { // during Robolectric tests, map will be null return; } if (center != null) { - map.moveCamera(CameraUpdateFactory.newLatLngZoom(toLatLng(center), (float) zoom)); + moveOrAnimateCamera( + CameraUpdateFactory.newLatLngZoom(toLatLng(center), (float) zoom), animate); } } - @Override public void zoomToBoundingBox(Iterable points, double scaleFactor) { + @Override public void zoomToBoundingBox(Iterable points, double scaleFactor, boolean animate) { if (map == null) { // during Robolectric tests, map will be null return; } @@ -166,11 +168,11 @@ public GoogleMap getGoogleMap() { count++; } if (count == 1) { - zoomToPoint(lastPoint); + zoomToPoint(lastPoint, animate); } else if (count > 1) { final LatLngBounds bounds = expandBounds(builder.build(), 1 / scaleFactor); new Handler().postDelayed(() -> { - map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0)); + moveOrAnimateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0), animate); }, 100); } } @@ -197,6 +199,14 @@ protected LatLngBounds expandBounds(LatLngBounds bounds, double factor) { return new LatLngBounds(new LatLng(south, west), new LatLng(north, east)); } + protected void moveOrAnimateCamera(CameraUpdate movement, boolean animate) { + if (animate) { + map.animateCamera(movement); + } else { + map.moveCamera(movement); + } + } + @Override public int addDraggablePoly(@NonNull Iterable points, boolean closedPolygon) { int featureId = nextFeatureId++; features.put(featureId, new DraggablePoly(map, points, closedPolygon)); diff --git a/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java index 7608b256e71..3b2df9002d8 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java @@ -58,20 +58,23 @@ public interface MapFragment { */ double getZoom(); - /** Centers the map view on the given point, leaving zoom level unchanged. */ - void setCenter(@Nullable MapPoint center); + /** + * Centers the map view on the given point, leaving zoom level unchanged, + * possibly with animation. + */ + void setCenter(@Nullable MapPoint center, boolean animate); /** * Centers the map view on the given point, zooming in to a close-up level * deemed appropriate by the implementation, possibly with animation. */ - void zoomToPoint(@Nullable MapPoint center); + void zoomToPoint(@Nullable MapPoint center, boolean animate); /** * Centers the map view on the given point with a zoom level as close as * possible to the given zoom level, possibly with animation. */ - void zoomToPoint(@Nullable MapPoint center, double zoom); + void zoomToPoint(@Nullable MapPoint center, double zoom, boolean animate); /** * Adjusts the map's viewport to enclose all of the given points, possibly @@ -81,7 +84,7 @@ public interface MapFragment { * to occupy at most 80% of the width and 80% of the height of the viewport, * ensuring a margin of at least 10% on all sides. */ - void zoomToBoundingBox(Iterable points, double scaleFactor); + void zoomToBoundingBox(Iterable points, double scaleFactor, boolean animate); /** * Adds a polyline or polygon to the map with the given sequence of vertices. diff --git a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java index b06bac03bd0..a7d62e06bf0 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java @@ -130,9 +130,13 @@ public MapView getMapView() { return fromGeoPoint(map.getMapCenter()); } - @Override public void setCenter(@Nullable MapPoint center) { + @Override public void setCenter(@Nullable MapPoint center, boolean animate) { if (center != null) { - map.getController().setCenter(toGeoPoint(center)); + if (animate) { + map.getController().animateTo(toGeoPoint(center)); + } else { + map.getController().setCenter(toGeoPoint(center)); + } } } @@ -140,11 +144,13 @@ public MapView getMapView() { return map.getZoomLevel(); } - @Override public void zoomToPoint(@Nullable MapPoint center) { - zoomToPoint(center, POINT_ZOOM); + @Override public void zoomToPoint(@Nullable MapPoint center, boolean animate) { + zoomToPoint(center, POINT_ZOOM, animate); } - @Override public void zoomToPoint(@Nullable MapPoint center, double zoom) { + @Override public void zoomToPoint(@Nullable MapPoint center, double zoom, boolean animate) { + // We're ignoring the 'animate' flag because OSMDroid doesn't provide + // support for simultaneously animating the viewport center and zoom level. if (center != null) { // setCenter() must be done last; setZoom() does not preserve the center. map.getController().setZoom((int) Math.round(zoom)); @@ -152,7 +158,7 @@ public MapView getMapView() { } } - @Override public void zoomToBoundingBox(Iterable points, double scaleFactor) { + @Override public void zoomToBoundingBox(Iterable points, double scaleFactor, boolean animate) { if (points != null) { int count = 0; List geoPoints = new ArrayList<>(); @@ -163,7 +169,7 @@ public MapView getMapView() { count++; } if (count == 1) { - zoomToPoint(lastPoint); + zoomToPoint(lastPoint, animate); } else if (count > 1) { // TODO(ping): Find a better solution. // zoomToBoundingBox sometimes fails to zoom correctly, either @@ -174,7 +180,7 @@ public MapView getMapView() { // did it, not because it's known to be the best solution. final BoundingBox box = BoundingBox.fromGeoPoints(geoPoints) .increaseByScale((float) (1 / scaleFactor)); - new Handler().postDelayed(() -> map.zoomToBoundingBox(box, false), 100); + new Handler().postDelayed(() -> map.zoomToBoundingBox(box, animate), 100); } } } From f1aee90e56fa5f098d5c1811894e2553074f731d Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Wed, 9 Jan 2019 19:44:36 -0800 Subject: [PATCH 03/20] Rename MapFragment.getPointsOfPoly to getPolyPoints; rename DraggablePoly to PolyFeature. --- .../android/activities/GeoShapeActivity.java | 12 +++++------ .../android/activities/GeoTraceActivity.java | 20 +++++++++---------- .../android/map/GoogleMapFragment.java | 16 +++++++-------- .../odk/collect/android/map/MapFragment.java | 2 +- .../collect/android/map/OsmMapFragment.java | 16 +++++++-------- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeActivity.java index 81c0030b160..ad397c49f1a 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeActivity.java @@ -104,11 +104,11 @@ public MapFragment createMapFragment() { super.onSaveInstanceState(state); state.putParcelable(MAP_CENTER_KEY, map.getCenter()); state.putDouble(MAP_ZOOM_KEY, map.getZoom()); - state.putParcelableArrayList(POINTS_KEY, new ArrayList<>(map.getPointsOfPoly(featureId))); + state.putParcelableArrayList(POINTS_KEY, new ArrayList<>(map.getPolyPoints(featureId))); } @Override public void onBackPressed() { - if (!formatPoints(map.getPointsOfPoly(featureId)).equals(originalShapeString)) { + if (!formatPoints(map.getPolyPoints(featureId)).equals(originalShapeString)) { showBackDialog(); } else { finish(); @@ -168,7 +168,7 @@ private void initMap(MapFragment newMapFragment) { zoomPointButton = zoomDialogView.findViewById(R.id.zoom_saved_location); zoomPointButton.setOnClickListener(v -> { - map.zoomToBoundingBox(map.getPointsOfPoly(featureId), 0.6, true); + map.zoomToBoundingBox(map.getPolyPoints(featureId), 0.6, true); zoomDialog.dismiss(); }); @@ -203,7 +203,7 @@ private void clear() { } private void showClearDialog() { - if (!map.getPointsOfPoly(featureId).isEmpty()) { + if (!map.getPolyPoints(featureId).isEmpty()) { new AlertDialog.Builder(this) .setMessage(R.string.geo_clear_warning) .setPositiveButton(R.string.clear, (dialog, id) -> clear()) @@ -222,7 +222,7 @@ private void showBackDialog() { } private void finishWithResult() { - List points = map.getPointsOfPoly(featureId); + List points = map.getPolyPoints(featureId); // Allow an empty result (no points), or a polygon with at least // 3 points, but no degenerate 1-point or 2-point polygons. @@ -308,7 +308,7 @@ private void showZoomDialog() { zoomLocationButton.setTextColor(Color.parseColor("#FF979797")); } - if (!map.getPointsOfPoly(featureId).isEmpty()) { + if (!map.getPolyPoints(featureId).isEmpty()) { zoomPointButton.setEnabled(true); zoomPointButton.setBackgroundColor(Color.parseColor("#50cccccc")); zoomPointButton.setTextColor(themeUtils.getPrimaryTextColor()); diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoTraceActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoTraceActivity.java index fd8215ee265..0712636509b 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoTraceActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoTraceActivity.java @@ -150,7 +150,7 @@ public MapFragment createMapFragment() { super.onSaveInstanceState(state); state.putParcelable(MAP_CENTER_KEY, map.getCenter()); state.putDouble(MAP_ZOOM_KEY, map.getZoom()); - state.putParcelableArrayList(POINTS_KEY, new ArrayList<>(map.getPointsOfPoly(featureId))); + state.putParcelableArrayList(POINTS_KEY, new ArrayList<>(map.getPolyPoints(featureId))); state.putBoolean(BEEN_PAUSED_KEY, beenPaused); state.putBoolean(MODE_ACTIVE_KEY, modeActive); state.putInt(TRACE_MODE_KEY, traceMode); @@ -198,7 +198,7 @@ public void initMap(MapFragment newMapFragment) { pauseButton = findViewById(R.id.pause); pauseButton.setOnClickListener(v -> { playButton.setVisibility(View.VISIBLE); - if (!map.getPointsOfPoly(featureId).isEmpty()) { + if (!map.getPolyPoints(featureId).isEmpty()) { clearButton.setEnabled(true); } pauseButton.setVisibility(View.GONE); @@ -214,7 +214,7 @@ public void initMap(MapFragment newMapFragment) { ImageButton saveButton = findViewById(R.id.geotrace_save); saveButton.setOnClickListener(v -> { - if (!map.getPointsOfPoly(featureId).isEmpty()) { + if (!map.getPolyPoints(featureId).isEmpty()) { polygonOrPolylineDialog.show(); } else { finishWithResult(); @@ -250,9 +250,9 @@ public void initMap(MapFragment newMapFragment) { Button polygonSave = polygonOrPolylineView.findViewById(R.id.polygon_save); polygonSave.setOnClickListener(v -> { - if (map.getPointsOfPoly(featureId).size() > 2) { + if (map.getPolyPoints(featureId).size() > 2) { // Close the polygon. - map.appendPointToPoly(featureId, map.getPointsOfPoly(featureId).get(0)); + map.appendPointToPoly(featureId, map.getPolyPoints(featureId).get(0)); polygonOrPolylineDialog.dismiss(); finishWithResult(); } else { @@ -263,7 +263,7 @@ public void initMap(MapFragment newMapFragment) { Button polylineSave = polygonOrPolylineView.findViewById(R.id.polyline_save); polylineSave.setOnClickListener(v -> { - if (map.getPointsOfPoly(featureId).size() > 1) { + if (map.getPolyPoints(featureId).size() > 1) { polygonOrPolylineDialog.dismiss(); finishWithResult(); } else { @@ -311,7 +311,7 @@ public void initMap(MapFragment newMapFragment) { } private void finishWithResult() { - List points = map.getPointsOfPoly(featureId); + List points = map.getPolyPoints(featureId); setResult(RESULT_OK, new Intent().putExtra( FormEntryActivity.GEOTRACE_RESULTS, formatPoints(points))); finish(); @@ -397,7 +397,7 @@ private void buildDialogs() { zoomPointButton = zoomDialogView.findViewById(R.id.zoom_saved_location); zoomPointButton.setOnClickListener(v -> { - map.zoomToBoundingBox(map.getPointsOfPoly(featureId), 0.6, true); + map.zoomToBoundingBox(map.getPolyPoints(featureId), 0.6, true); zoomDialog.dismiss(); }); } @@ -512,7 +512,7 @@ private void clear() { } private void showClearDialog() { - if (!map.getPointsOfPoly(featureId).isEmpty()) { + if (!map.getPolyPoints(featureId).isEmpty()) { new AlertDialog.Builder(this) .setMessage(R.string.geo_clear_warning) .setPositiveButton(R.string.clear, (dialog, id) -> clear()) @@ -543,7 +543,7 @@ public void showZoomDialog() { zoomLocationButton.setBackgroundColor(Color.parseColor("#50e2e2e2")); zoomLocationButton.setTextColor(Color.parseColor("#FF979797")); } - if (!map.getPointsOfPoly(featureId).isEmpty()) { + if (!map.getPolyPoints(featureId).isEmpty()) { zoomPointButton.setEnabled(true); zoomPointButton.setBackgroundColor(Color.parseColor("#50cccccc")); zoomPointButton.setTextColor(themeUtils.getPrimaryTextColor()); diff --git a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java index 6576a12884a..9a616c40cea 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java @@ -209,21 +209,21 @@ protected void moveOrAnimateCamera(CameraUpdate movement, boolean animate) { @Override public int addDraggablePoly(@NonNull Iterable points, boolean closedPolygon) { int featureId = nextFeatureId++; - features.put(featureId, new DraggablePoly(map, points, closedPolygon)); + features.put(featureId, new PolyFeature(map, points, closedPolygon)); return featureId; } @Override public void appendPointToPoly(int featureId, @NonNull MapPoint point) { MapFeature feature = features.get(featureId); - if (feature instanceof DraggablePoly) { - ((DraggablePoly) feature).addPoint(point); + if (feature instanceof PolyFeature) { + ((PolyFeature) feature).addPoint(point); } } - @Override public @NonNull List getPointsOfPoly(int featureId) { + @Override public @NonNull List getPolyPoints(int featureId) { MapFeature feature = features.get(featureId); - if (feature instanceof DraggablePoly) { - return ((DraggablePoly) feature).getPoints(); + if (feature instanceof PolyFeature) { + return ((PolyFeature) feature).getPoints(); } return new ArrayList<>(); } @@ -401,13 +401,13 @@ interface MapFeature { } /** A polyline or polygon that can be manipulated by dragging markers at its vertices. */ - protected static class DraggablePoly implements MapFeature { + protected static class PolyFeature implements MapFeature { final GoogleMap map; final List markers = new ArrayList<>(); final boolean closedPolygon; Polyline polyline; - public DraggablePoly(GoogleMap map, Iterable points, boolean closedPolygon) { + public PolyFeature(GoogleMap map, Iterable points, boolean closedPolygon) { this.map = map; this.closedPolygon = closedPolygon; if (map == null) { // during Robolectric tests, map will be null diff --git a/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java index 3b2df9002d8..d16dd7e55b3 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java @@ -100,7 +100,7 @@ public interface MapFragment { * Returns the vertices of the polyline or polygon specified by featureId, or an * empty list if the featureId does not identify an existing polyline or polygon. */ - @NonNull List getPointsOfPoly(int featureId); + @NonNull List getPolyPoints(int featureId); /** Removes a specified map feature from the map. */ void removeFeature(int featureId); diff --git a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java index a7d62e06bf0..835c8d1fde6 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java @@ -187,21 +187,21 @@ public MapView getMapView() { @Override public int addDraggablePoly(@NonNull Iterable points, boolean closedPolygon) { int featureId = nextFeatureId++; - features.put(featureId, new DraggablePoly(map, points, closedPolygon)); + features.put(featureId, new PolyFeature(map, points, closedPolygon)); return featureId; } @Override public void appendPointToPoly(int featureId, @NonNull MapPoint point) { MapFeature feature = features.get(featureId); - if (feature != null && feature instanceof DraggablePoly) { - ((DraggablePoly) feature).addPoint(point); + if (feature != null && feature instanceof PolyFeature) { + ((PolyFeature) feature).addPoint(point); } } - @Override public @NonNull List getPointsOfPoly(int featureId) { + @Override public @NonNull List getPolyPoints(int featureId) { MapFeature feature = features.get(featureId); - if (feature instanceof DraggablePoly) { - return ((DraggablePoly) feature).getPoints(); + if (feature instanceof PolyFeature) { + return ((PolyFeature) feature).getPoints(); } return new ArrayList<>(); } @@ -367,14 +367,14 @@ interface MapFeature { } /** A polyline or polygon that can be manipulated by dragging markers at its vertices. */ - protected static class DraggablePoly implements MapFeature, Marker.OnMarkerClickListener, Marker.OnMarkerDragListener { + protected static class PolyFeature implements MapFeature, Marker.OnMarkerClickListener, Marker.OnMarkerDragListener { final MapView map; final List markers = new ArrayList<>(); final Polyline polyline; final boolean closedPolygon; public static final int STROKE_WIDTH = 5; - public DraggablePoly(MapView map, Iterable points, boolean closedPolygon) { + public PolyFeature(MapView map, Iterable points, boolean closedPolygon) { this.map = map; this.closedPolygon = closedPolygon; polyline = new Polyline(); From ac7c4450b00196e1499aeeaf1542725cba8c4d4d Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Wed, 9 Jan 2019 19:52:27 -0800 Subject: [PATCH 04/20] Add MapFragment support for marker (single-point) features. --- .../android/map/GoogleMapFragment.java | 77 ++++++--- .../odk/collect/android/map/MapFragment.java | 28 +++- .../collect/android/map/OsmMapFragment.java | 147 ++++++++++++------ 3 files changed, 173 insertions(+), 79 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java index 9a616c40cea..ba1b186ff4a 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java @@ -59,10 +59,10 @@ public class GoogleMapFragment extends SupportMapFragment implements public static final float POINT_ZOOM = 16; protected GoogleMap map; - protected List gpsLocationReadyListeners = new ArrayList<>(); - protected MapFragment.PointListener clickListener; - protected MapFragment.PointListener longPressListener; - protected MapFragment.PointListener gpsLocationListener; + protected List gpsLocationReadyListeners = new ArrayList<>(); + protected PointListener clickListener; + protected PointListener longPressListener; + protected PointListener gpsLocationListener; protected LocationClient locationClient; protected MapPoint lastLocationFix; protected int nextFeatureId = 1; @@ -207,6 +207,20 @@ protected void moveOrAnimateCamera(CameraUpdate movement, boolean animate) { } } + @Override public int addMarker(MapPoint point, boolean draggable) { + int featureId = nextFeatureId++; + features.put(featureId, new MarkerFeature(map, point, draggable)); + return featureId; + } + + @Override public @Nullable MapPoint getMarkerPoint(int featureId) { + MapFeature feature = features.get(featureId); + if (feature instanceof MarkerFeature) { + return ((MarkerFeature) feature).getPoint(); + } + return null; + } + @Override public int addDraggablePoly(@NonNull Iterable points, boolean closedPolygon) { int featureId = nextFeatureId++; features.put(featureId, new PolyFeature(map, points, closedPolygon)); @@ -386,6 +400,21 @@ protected void updateFeatures() { return new LatLng(point.lat, point.lon); } + protected static Marker createMarker(GoogleMap map, MapPoint point, boolean draggable) { + // A Marker's position is a LatLng with just latitude and longitude + // fields. We need to store the point's altitude and standard + // deviation values somewhere, so they go in the marker's snippet. + return map.addMarker(new MarkerOptions() + .position(toLatLng(point)) + .snippet(point.alt + ";" + point.sd) + .draggable(draggable) + ); + } + + @VisibleForTesting public boolean isGpsErrorDialogShowing() { + return gpsErrorDialog != null && gpsErrorDialog.isShowing(); + } + /** * A MapFeature is a physical feature on a map, such as a point, a road, * a building, a region, etc. It is presented to the user as one editable @@ -400,6 +429,27 @@ interface MapFeature { void dispose(); } + protected static class MarkerFeature implements MapFeature { + final GoogleMap map; + Marker marker; + + public MarkerFeature(GoogleMap map, MapPoint point, boolean draggable) { + this.map = map; + this.marker = createMarker(map, point, draggable); + } + + public MapPoint getPoint() { + return fromMarker(marker); + } + + public void update() { } + + public void dispose() { + marker.remove(); + marker = null; + } + } + /** A polyline or polygon that can be manipulated by dragging markers at its vertices. */ protected static class PolyFeature implements MapFeature { final GoogleMap map; @@ -414,7 +464,7 @@ public PolyFeature(GoogleMap map, Iterable points, boolean closedPolyg return; } for (MapPoint point : points) { - addMarker(point); + markers.add(createMarker(map, point, true)); } update(); } @@ -460,21 +510,10 @@ public void addPoint(MapPoint point) { if (map == null) { // during Robolectric tests, map will be null return; } - addMarker(point); + markers.add(createMarker(map, point, true)); update(); } - protected void addMarker(MapPoint point) { - // A Marker's position is a LatLng with just latitude and longitude - // fields. We need to store the point's altitude and standard - // deviation values somewhere, so they go in the marker's snippet. - markers.add(map.addMarker(new MarkerOptions() - .position(toLatLng(point)) - .snippet(point.alt + ";" + point.sd) - .draggable(true) - )); - } - protected void clearPolyline() { if (polyline != null) { polyline.remove(); @@ -482,8 +521,4 @@ protected void clearPolyline() { } } } - - @VisibleForTesting public boolean isGpsErrorDialogShowing() { - return gpsErrorDialog != null && gpsErrorDialog.isShowing(); - } } diff --git a/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java index d16dd7e55b3..36ccf814acf 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java @@ -86,6 +86,16 @@ public interface MapFragment { */ void zoomToBoundingBox(Iterable points, double scaleFactor, boolean animate); + /** + * Adds a marker to the map at the given location. If draggable is true, + * the user will be able to drag the marker to change its location. + * Returns a positive integer, the featureId for the newly added shape. + */ + int addMarker(MapPoint point, boolean draggable); + + /** Gets the location of an existing marker. */ + MapPoint getMarkerPoint(int featureId); + /** * Adds a polyline or polygon to the map with the given sequence of vertices. * The vertices will have handles that can be dragged by the user. @@ -102,12 +112,18 @@ public interface MapFragment { */ @NonNull List getPolyPoints(int featureId); - /** Removes a specified map feature from the map. */ + /** Removes a specified map feature from the map, leaving its featureId invalid. */ void removeFeature(int featureId); /** Removes all map features from the map. */ void clearFeatures(); + /** Sets or clears the callback for a click on the map. */ + void setClickListener(@Nullable PointListener listener); + + /** Sets or clears the callback for a long press on the map. */ + void setLongPressListener(@Nullable PointListener listener); + /** * Enables/disables GPS tracking. While enabled, the GPS location is shown * on the map, the first GPS fix will trigger any pending callbacks set by @@ -135,12 +151,6 @@ public interface MapFragment { */ void setGpsLocationListener(@Nullable PointListener listener); - /** Sets or clears the callback for a click on the map. */ - void setClickListener(@Nullable PointListener listener); - - /** Sets or clears the callback for a long press on the map. */ - void setLongPressListener(@Nullable PointListener listener); - interface ReadyListener { void onReady(@Nullable MapFragment mapFragment); } @@ -148,4 +158,8 @@ interface ReadyListener { interface PointListener { void onPoint(@NonNull MapPoint point); } + + interface FeatureListener { + void onFeature(@NonNull int featureId); + } } diff --git a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java index 835c8d1fde6..3e40ef5d981 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java @@ -65,9 +65,10 @@ public class OsmMapFragment extends Fragment implements MapFragment, protected MapView map; protected ReadyListener readyListener; - protected MapFragment.PointListener clickListener; - protected MapFragment.PointListener longPressListener; - protected MapFragment.PointListener gpsLocationListener; + protected PointListener clickListener; + protected PointListener longPressListener; + protected PointListener gpsLocationListener; + protected FeatureListener dragEndListener; protected MyLocationNewOverlay myLocationOverlay; protected LocationClient locationClient; protected int nextFeatureId = 1; @@ -185,6 +186,20 @@ public MapView getMapView() { } } + @Override public int addMarker(MapPoint point, boolean draggable) { + int featureId = nextFeatureId++; + features.put(featureId, new MarkerFeature(map, point, draggable)); + return featureId; + } + + @Override public @Nullable MapPoint getMarkerPoint(int featureId) { + MapFeature feature = features.get(featureId); + if (feature instanceof MarkerFeature) { + return ((MarkerFeature) feature).getPoint(); + } + return null; + } + @Override public int addDraggablePoly(@NonNull Iterable points, boolean closedPolygon) { int featureId = nextFeatureId++; features.put(featureId, new PolyFeature(map, points, closedPolygon)); @@ -333,7 +348,10 @@ protected void addMapLayoutChangeListener(MapView map) { if (geoPoint == null) { return null; } - return fromGeoPoint(geoPoint, overlay.getLastFix().getAccuracy()); + return new MapPoint( + geoPoint.getLatitude(), geoPoint.getLongitude(), + geoPoint.getAltitude(), overlay.getLastFix().getAccuracy() + ); } protected static @NonNull MapPoint fromGeoPoint(@NonNull IGeoPoint geoPoint) { @@ -344,14 +362,60 @@ protected void addMapLayoutChangeListener(MapView map) { return new MapPoint(geoPoint.getLatitude(), geoPoint.getLongitude(), geoPoint.getAltitude()); } - protected static @NonNull MapPoint fromGeoPoint(@NonNull GeoPoint geoPoint, double sd) { - return new MapPoint(geoPoint.getLatitude(), geoPoint.getLongitude(), geoPoint.getAltitude(), sd); + protected static @NonNull MapPoint fromMarker(@NonNull Marker marker) { + GeoPoint geoPoint = marker.getPosition(); + double sd = 0; + try { + sd = Double.parseDouble(marker.getSubDescription()); + } catch (NumberFormatException e) { /* ignore */ } + return new MapPoint( + geoPoint.getLatitude(), geoPoint.getLongitude(), + geoPoint.getAltitude(), sd + ); } protected static @NonNull GeoPoint toGeoPoint(@NonNull MapPoint point) { return new GeoPoint(point.lat, point.lon, point.alt); } + protected Marker createMarker(MapView map, MapPoint point, MapFeature feature) { + // A Marker's position is a GeoPoint with latitude, longitude, and + // altitude fields. We need to store the standard deviation value + // somewhere, so it goes in the marker's sub-description field. + Marker marker = new Marker(map); + marker.setPosition(toGeoPoint(point)); + marker.setSubDescription(Double.toString(point.sd)); + marker.setDraggable(feature != null); + marker.setIcon(ContextCompat.getDrawable(map.getContext(), R.drawable.ic_place_black)); + marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM); + + marker.setOnMarkerDragListener(new Marker.OnMarkerDragListener() { + @Override public void onMarkerDragStart(Marker marker) { } + + @Override public void onMarkerDrag(Marker marker) { + // When a marker is manually dragged, the position is no longer + // obtained from a GPS reading, so the standard deviation field + // is no longer meaningful; reset it to zero. + marker.setSubDescription("0"); + feature.update(); + } + + @Override public void onMarkerDragEnd(Marker marker) { + feature.update(); + } + }); + + // Prevent the text bubble from appearing when a marker is clicked. + marker.setOnMarkerClickListener((unusedMarker, unusedMap) -> false); + + map.getOverlays().add(marker); + return marker; + } + + @VisibleForTesting public boolean isGpsErrorDialogShowing() { + return gpsErrorDialog != null && gpsErrorDialog.isShowing(); + } + /** * A MapFeature is a physical feature on a map, such as a point, a road, * a building, a region, etc. It is presented to the user as one editable @@ -366,8 +430,30 @@ interface MapFeature { void dispose(); } + /** A marker that can optionally be dragged by the user. */ + protected class MarkerFeature implements MapFeature { + final MapView map; + Marker marker; + + public MarkerFeature(MapView map, MapPoint point, boolean draggable) { + this.map = map; + this.marker = createMarker(map, point, draggable ? this : null); + } + + public MapPoint getPoint() { + return fromMarker(marker); + } + + public void update() { } + + public void dispose() { + map.getOverlays().remove(marker); + marker = null; + } + } + /** A polyline or polygon that can be manipulated by dragging markers at its vertices. */ - protected static class PolyFeature implements MapFeature, Marker.OnMarkerClickListener, Marker.OnMarkerDragListener { + protected class PolyFeature implements MapFeature { final MapView map; final List markers = new ArrayList<>(); final Polyline polyline; @@ -383,7 +469,7 @@ public PolyFeature(MapView map, Iterable points, boolean closedPolygon paint.setStrokeWidth(STROKE_WIDTH); map.getOverlays().add(polyline); for (MapPoint point : points) { - addMarker(point); + markers.add(createMarker(map, point, this)); } update(); } @@ -411,55 +497,14 @@ public void dispose() { public List getPoints() { List points = new ArrayList<>(); for (Marker marker : markers) { - points.add(fromGeoPoint( - marker.getPosition(), Double.valueOf(marker.getSubDescription()))); + points.add(fromMarker(marker)); } return points; } public void addPoint(MapPoint point) { - addMarker(point); - update(); - } - - protected void addMarker(MapPoint point) { - // A Marker's position is a GeoPoint with latitude, longitude, and - // altitude fields. We need to store the standard deviation value - // somewhere, so it goes in the marker's sub-description field. - Marker marker = new Marker(map); - marker.setPosition(toGeoPoint(point)); - marker.setSubDescription(Double.toString(point.sd)); - marker.setDraggable(true); - marker.setIcon(ContextCompat.getDrawable(map.getContext(), R.drawable.ic_place_black)); - marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM); - marker.setOnMarkerClickListener(this); - marker.setOnMarkerDragListener(this); - map.getOverlays().add(marker); - markers.add(marker); - } - - @Override public void onMarkerDragStart(Marker marker) { - } - - @Override public void onMarkerDragEnd(Marker marker) { - update(); - } - - @Override public void onMarkerDrag(Marker marker) { - // When a marker is manually dragged, the position is no longer - // obtained from a GPS reading, so the standard deviation field - // is no longer meaningful; reset it to zero. - marker.setSubDescription("0"); + markers.add(createMarker(map, point, this)); update(); } - - @Override public boolean onMarkerClick(Marker marker, MapView map) { - // Prevent the text bubble from appearing when a marker is clicked. - return false; - } - } - - @VisibleForTesting public boolean isGpsErrorDialogShowing() { - return gpsErrorDialog != null && gpsErrorDialog.isShowing(); } } From 568823acbc2a6027863063aa09cb4b988bc38424 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Wed, 9 Jan 2019 19:54:19 -0800 Subject: [PATCH 05/20] Add MapFragment support for a drag-end listener. --- .../android/map/GoogleMapFragment.java | 43 ++++++++++++++++--- .../odk/collect/android/map/MapFragment.java | 3 ++ .../collect/android/map/OsmMapFragment.java | 40 ++++++++++++++++- 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java index ba1b186ff4a..f447c082ea6 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java @@ -63,6 +63,7 @@ public class GoogleMapFragment extends SupportMapFragment implements protected PointListener clickListener; protected PointListener longPressListener; protected PointListener gpsLocationListener; + protected FeatureListener dragEndListener; protected LocationClient locationClient; protected MapPoint lastLocationFix; protected int nextFeatureId = 1; @@ -264,6 +265,10 @@ protected void moveOrAnimateCamera(CameraUpdate movement, boolean animate) { longPressListener = listener; } + @Override public void setDragEndListener(@Nullable FeatureListener listener) { + dragEndListener = listener; + } + @Override public void setGpsLocationListener(@Nullable PointListener listener) { gpsLocationListener = listener; } @@ -318,20 +323,22 @@ protected void moveOrAnimateCamera(CameraUpdate movement, boolean animate) { } } - @Override public void onMarkerDragStart(Marker marker) { - updateFeatures(); - } + @Override public void onMarkerDragStart(Marker marker) { } @Override public void onMarkerDrag(Marker marker) { // When a marker is manually dragged, the position is no longer // obtained from a GPS reading, so the altitude and standard deviation // fields are no longer meaningful; reset them to zero. marker.setSnippet("0;0"); - updateFeatures(); + updateFeature(findFeature(marker)); } @Override public void onMarkerDragEnd(Marker marker) { - updateFeatures(); + int featureId = findFeature(marker); + updateFeature(featureId); + if (dragEndListener != null && featureId != -1) { + dragEndListener.onFeature(featureId); + } } @Override public void onClientStart() { @@ -361,8 +368,19 @@ protected void showGpsDisabledAlert() { gpsErrorDialog.show(); } - protected void updateFeatures() { - for (MapFeature feature : features.values()) { + /** Finds the feature to which the given marker belongs. */ + protected int findFeature(Marker marker) { + for (int featureId : features.keySet()) { + if (features.get(featureId).ownsMarker(marker)) { + return featureId; + } + } + return -1; // not found + } + + protected void updateFeature(int featureId) { + MapFeature feature = features.get(featureId); + if (feature != null) { feature.update(); } } @@ -422,6 +440,9 @@ protected static Marker createMarker(GoogleMap map, MapPoint point, boolean drag * (e.g. geometric elements, handles for manipulation, etc.). */ interface MapFeature { + /** Returns true if the given marker belongs to this feature. */ + boolean ownsMarker(Marker marker); + /** Updates the feature's geometry after any UI handles have moved. */ void update(); @@ -442,6 +463,10 @@ public MapPoint getPoint() { return fromMarker(marker); } + public boolean ownsMarker(Marker givenMarker) { + return marker.equals(givenMarker); + } + public void update() { } public void dispose() { @@ -469,6 +494,10 @@ public PolyFeature(GoogleMap map, Iterable points, boolean closedPolyg update(); } + public boolean ownsMarker(Marker givenMarker) { + return markers.contains(givenMarker); + } + public void update() { List latLngs = new ArrayList<>(); for (Marker marker : markers) { diff --git a/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java index 36ccf814acf..e0a1b919dd0 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java @@ -124,6 +124,9 @@ public interface MapFragment { /** Sets or clears the callback for a long press on the map. */ void setLongPressListener(@Nullable PointListener listener); + /** Sets or clears the callback for when a drag is completed. */ + void setDragEndListener(@Nullable FeatureListener listener); + /** * Enables/disables GPS tracking. While enabled, the GPS location is shown * on the map, the first GPS fix will trigger any pending callbacks set by diff --git a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java index 3e40ef5d981..c33306ec0f2 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java @@ -244,6 +244,10 @@ public MapView getMapView() { longPressListener = listener; } + @Override public void setDragEndListener(@Nullable FeatureListener listener) { + dragEndListener = listener; + } + @Override public void setGpsLocationListener(@Nullable PointListener listener) { gpsLocationListener = listener; } @@ -397,11 +401,15 @@ protected Marker createMarker(MapView map, MapPoint point, MapFeature feature) { // obtained from a GPS reading, so the standard deviation field // is no longer meaningful; reset it to zero. marker.setSubDescription("0"); - feature.update(); + updateFeature(findFeature(marker)); } @Override public void onMarkerDragEnd(Marker marker) { - feature.update(); + int featureId = findFeature(marker); + updateFeature(featureId); + if (dragEndListener != null && featureId != -1) { + dragEndListener.onFeature(featureId); + } } }); @@ -412,6 +420,23 @@ protected Marker createMarker(MapView map, MapPoint point, MapFeature feature) { return marker; } + /** Finds the feature to which the given marker belongs. */ + protected int findFeature(Marker marker) { + for (int featureId : features.keySet()) { + if (features.get(featureId).ownsMarker(marker)) { + return featureId; + } + } + return -1; // not found + } + + protected void updateFeature(int featureId) { + MapFeature feature = features.get(featureId); + if (feature != null) { + feature.update(); + } + } + @VisibleForTesting public boolean isGpsErrorDialogShowing() { return gpsErrorDialog != null && gpsErrorDialog.isShowing(); } @@ -423,6 +448,9 @@ protected Marker createMarker(MapView map, MapPoint point, MapFeature feature) { * (e.g. geometric elements, handles for manipulation, etc.). */ interface MapFeature { + /** Returns true if the given marker belongs to this feature. */ + boolean ownsMarker(Marker marker); + /** Updates the feature's geometry after any UI handles have moved. */ void update(); @@ -444,6 +472,10 @@ public MapPoint getPoint() { return fromMarker(marker); } + public boolean ownsMarker(Marker givenMarker) { + return marker.equals(givenMarker); + } + public void update() { } public void dispose() { @@ -474,6 +506,10 @@ public PolyFeature(MapView map, Iterable points, boolean closedPolygon update(); } + public boolean ownsMarker(Marker givenMarker) { + return markers.contains(givenMarker); + } + public void update() { List geoPoints = new ArrayList<>(); for (Marker marker : markers) { From 627a9df86a76ca5bdbd3dd1aa642aa6d19509ba8 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Wed, 9 Jan 2019 19:53:07 -0800 Subject: [PATCH 06/20] Add MapFragment support for getting the location provider. --- .../java/org/odk/collect/android/map/GoogleMapFragment.java | 6 ++++++ .../main/java/org/odk/collect/android/map/MapFragment.java | 3 +++ .../java/org/odk/collect/android/map/OsmMapFragment.java | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java index f447c082ea6..c389a35c787 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java @@ -66,6 +66,7 @@ public class GoogleMapFragment extends SupportMapFragment implements protected FeatureListener dragEndListener; protected LocationClient locationClient; protected MapPoint lastLocationFix; + protected String lastLocationProvider; protected int nextFeatureId = 1; protected Map features = new HashMap<>(); protected AlertDialog gpsErrorDialog; @@ -298,6 +299,7 @@ protected void moveOrAnimateCamera(CameraUpdate movement, boolean animate) { @Override public void onLocationChanged(Location location) { lastLocationFix = fromLocation(location); + lastLocationProvider = location.getProvider(); for (ReadyListener listener : gpsLocationReadyListeners) { listener.onReady(this); } @@ -311,6 +313,10 @@ protected void moveOrAnimateCamera(CameraUpdate movement, boolean animate) { return lastLocationFix; } + @Override public @Nullable String getLocationProvider() { + return lastLocationProvider; + } + @Override public void onMapClick(LatLng latLng) { if (clickListener != null) { clickListener.onPoint(fromLatLng(latLng)); diff --git a/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java index e0a1b919dd0..97414380290 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/MapFragment.java @@ -138,6 +138,9 @@ public interface MapFragment { /** Gets the last GPS location fix, or null if there hasn't been one. */ @Nullable MapPoint getGpsLocation(); + /** Gets the provider of the last fix, or null if there hasn't been one. */ + @Nullable String getLocationProvider(); + /** * Queues a callback to be invoked on the UI thread as soon as a GPS fix is * available. If there already is a location fix, the callback is invoked diff --git a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java index c33306ec0f2..186e45d1dcb 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java @@ -286,6 +286,11 @@ public MapView getMapView() { return fromLocation(myLocationOverlay); } + @Override public @Nullable String getLocationProvider() { + Location fix = myLocationOverlay.getLastFix(); + return fix != null ? fix.getProvider() : null; + } + @Override public void onLocationChanged(Location location) { if (gpsLocationListener != null) { MapPoint point = fromLocation(myLocationOverlay); From c69bf4413c08dc495bf209cfdaa327f016a5c588 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Wed, 9 Jan 2019 20:05:44 -0800 Subject: [PATCH 07/20] Migrate GeoPointMapActivity to use the MapFragment interface. --- .../activities/GeoPointMapActivity.java | 459 ++++++------------ .../android/activities/GeoShapeActivity.java | 4 +- .../src/main/res/layout/geopoint_layout.xml | 9 +- 3 files changed, 157 insertions(+), 315 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java index 0375a7fb980..8f5cec26b21 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java @@ -18,33 +18,24 @@ import android.app.AlertDialog; import android.content.Intent; import android.graphics.Color; -import android.location.Location; import android.os.Bundle; -import android.provider.Settings; import android.view.View; -import android.view.View.OnClickListener; import android.view.Window; import android.widget.Button; import android.widget.ImageButton; import android.widget.TextView; -import com.google.android.gms.location.LocationListener; -import com.google.android.gms.maps.CameraUpdateFactory; -import com.google.android.gms.maps.GoogleMap; -import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener; -import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener; -import com.google.android.gms.maps.SupportMapFragment; -import com.google.android.gms.maps.model.LatLng; -import com.google.android.gms.maps.model.Marker; -import com.google.android.gms.maps.model.MarkerOptions; - import org.odk.collect.android.R; -import org.odk.collect.android.location.client.LocationClient; -import org.odk.collect.android.location.client.LocationClients; +import org.odk.collect.android.map.GoogleMapFragment; +import org.odk.collect.android.map.MapFragment; +import org.odk.collect.android.map.MapPoint; +import org.odk.collect.android.map.OsmMapFragment; +import org.odk.collect.android.preferences.GeneralKeys; import org.odk.collect.android.spatial.MapHelper; import org.odk.collect.android.utilities.GeoPointUtils; import org.odk.collect.android.utilities.ToastUtils; import org.odk.collect.android.widgets.GeoPointWidget; +import org.osmdroid.tileprovider.IRegisterReceiver; import java.text.DecimalFormat; @@ -53,36 +44,27 @@ import static org.odk.collect.android.utilities.PermissionUtils.areLocationPermissionsGranted; /** - * Version of the GeoPointMapActivity that uses the new Maps v2 API and Fragments to enable - * specifying a location via placing a tracker on a map. - * - * @author guisalmon@gmail.com - * @author jonnordling@gmail.com + * Allow the user to indicate a location by placing a marker on a map, either + * by touching a point on the map or by tapping a button to place the marker + * at the current location (obtained from GPS or other location sensors). */ -public class GeoPointMapActivity extends BaseGeoMapActivity implements OnMarkerDragListener, OnMapLongClickListener, - LocationClient.LocationClientListener, LocationListener { +public class GeoPointMapActivity extends BaseGeoMapActivity implements IRegisterReceiver { - private static final String LOCATION_COUNT = "locationCount"; + public static final String PREF_VALUE_GOOGLE_MAPS = "google_maps"; - private GoogleMap map; - private MarkerOptions markerOptions; - private Marker marker; - private LatLng latLng; + private MapFragment map; + private int featureId = -1; // will be a positive featureId once map is ready private TextView locationStatus; private TextView locationInfo; - private LocationClient locationClient; - - private Location location; - private ImageButton reloadLocation; + private MapPoint location; + private ImageButton placeMarkerButton; private boolean isDragged; - private ImageButton showLocation; - - private int locationCount; + private ImageButton zoomButton; - //private KmlLayer kk; + private MapHelper helper; private AlertDialog errorDialog; @@ -91,17 +73,25 @@ public class GeoPointMapActivity extends BaseGeoMapActivity implements OnMarkerD private Button zoomPointButton; private Button zoomLocationButton; - private ImageButton clearPointButton; + private ImageButton clearButton; - private boolean setClear; private boolean captureLocation; private boolean foundFirstLocation; - private boolean readOnly; - private boolean draggable; - private boolean intentDraggable; + + /** + * True if a tap on the clear button removed an existing marker and + * no new marker has been placed. + */ + private boolean setClear; + + /** True if the current marker location came from the intent. */ private boolean locationFromIntent; - private boolean isMapReady; + /** True if the intent requested for the point to be read-only. */ + private boolean intentReadOnly; + + /** True if the intent requested for the marker to be draggable. */ + private boolean intentDraggable; @Override public void onCreate(Bundle savedInstanceState) { @@ -114,13 +104,8 @@ public void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); - if (savedInstanceState != null) { - locationCount = savedInstanceState.getInt(LOCATION_COUNT); - } - try { setContentView(R.layout.geopoint_layout); - } catch (NoClassDefFoundError e) { Timber.e(e, "Google maps not accessible due to: %s ", e.getMessage()); ToastUtils.showShortToast(R.string.google_play_services_error_occured); @@ -130,294 +115,210 @@ public void onCreate(Bundle savedInstanceState) { locationStatus = findViewById(R.id.location_status); locationInfo = findViewById(R.id.location_info); - reloadLocation = findViewById(R.id.reload_location); - showLocation = findViewById(R.id.show_location); + placeMarkerButton = findViewById(R.id.place_marker); + zoomButton = findViewById(R.id.zoom); - locationClient = LocationClients.clientForContext(this); + createMapFragment().addTo(this, R.id.map_container, this::initMap); + } - isMapReady = false; - ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMapAsync(googleMap -> { - setupMap(googleMap); - locationClient.setListener(this); - locationClient.start(); - }); + public MapFragment createMapFragment() { + String mapSdk = getIntent().getStringExtra(GeneralKeys.KEY_MAP_SDK); + return (mapSdk == null || mapSdk.equals(PREF_VALUE_GOOGLE_MAPS)) ? + new GoogleMapFragment() : new OsmMapFragment(); } - @Override - protected void onStop() { - locationClient.stop(); + @Override protected void onStop() { + map.setGpsLocationEnabled(false); super.onStop(); } + @Override public void destroy() { } + public void returnLocation() { - Intent i = new Intent(); - if (setClear || (readOnly && latLng == null)) { - i.putExtra(FormEntryActivity.LOCATION_RESULT, ""); - setResult(RESULT_OK, i); + String result = null; - } else if (isDragged || readOnly || locationFromIntent) { + if (setClear || (intentReadOnly && featureId == -1)) { + result = ""; + } else if (isDragged || intentReadOnly || locationFromIntent) { Timber.i("IsDragged !!!"); - i.putExtra( - FormEntryActivity.LOCATION_RESULT, - latLng.latitude + " " + latLng.longitude + " " - + 0 + " " + 0); - setResult(RESULT_OK, i); + MapPoint point = map.getMarkerPoint(featureId); + result = point.lat + " " + point.lon + " " + 0 + " " + 0; } else if (location != null) { Timber.i("IsNotDragged !!!"); + result = String.format("%s %s %s %s", location.lat, location.lon, location.alt, location.sd); + } - i.putExtra( - FormEntryActivity.LOCATION_RESULT, - getResultString(location) - ); - setResult(RESULT_OK, i); + if (result != null) { + setResult(RESULT_OK, new Intent().putExtra(FormEntryActivity.LOCATION_RESULT, result)); } finish(); } - public String getResultString(Location location) { - return String.format("%s %s %s %s", location.getLatitude(), location.getLongitude(), location.getAltitude(), location.getAccuracy()); - } - - private String truncateFloat(float f) { - return new DecimalFormat("#.##").format(f); - } - @SuppressLint("MissingPermission") // Permission handled in Constructor - private void setupMap(GoogleMap googleMap) { - map = googleMap; - if (map == null) { - ToastUtils.showShortToast(R.string.google_play_services_error_occured); + public void initMap(MapFragment newMapFragment) { + if (newMapFragment == null) { finish(); return; } - helper = new MapHelper(this, map, selectedLayer); - map.setMyLocationEnabled(true); - map.getUiSettings().setCompassEnabled(true); - map.getUiSettings().setMyLocationButtonEnabled(false); - map.getUiSettings().setZoomControlsEnabled(false); - markerOptions = new MarkerOptions(); - helper = new MapHelper(this, map, selectedLayer); + map = newMapFragment; + map.setDragEndListener(this::onDragEnd); + map.setLongPressListener(this::onLongPress); + + if (map instanceof GoogleMapFragment) { + helper = new MapHelper(this, ((GoogleMapFragment) map).getGoogleMap(), selectedLayer); + } else if (map instanceof OsmMapFragment) { + helper = new MapHelper(this, ((OsmMapFragment) map).getMapView(), this, selectedLayer); + } + helper.setBasemap(); ImageButton acceptLocation = findViewById(R.id.accept_location); + acceptLocation.setOnClickListener(v -> returnLocation()); - acceptLocation.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - returnLocation(); - } - }); - - reloadLocation.setEnabled(false); - reloadLocation.setOnClickListener(v -> { - removeMarker(); - latLng = new LatLng(location.getLatitude(), location.getLongitude()); - if (marker == null) { - addMarker(); - if (draggable && !readOnly) { - marker.setDraggable(true); - } - } - zoomToPoint(); + placeMarkerButton.setEnabled(false); + placeMarkerButton.setOnClickListener(v -> { + placeMarker(map.getGpsLocation(), intentDraggable && !intentReadOnly); + zoomToMarker(true); }); // Focuses on marked location - //showLocation.setClickable(false); - showLocation.setEnabled(false); - showLocation.setOnClickListener(v -> showZoomDialog()); + zoomButton.setEnabled(false); + zoomButton.setOnClickListener(v -> showZoomDialog()); // Menu Layer Toggle ImageButton layers = findViewById(R.id.layer_menu); layers.setOnClickListener(v -> helper.showLayersDialog()); + zoomDialogView = getLayoutInflater().inflate(R.layout.geo_zoom_dialog, null); zoomLocationButton = zoomDialogView.findViewById(R.id.zoom_location); zoomLocationButton.setOnClickListener(v -> { - zoomToLocation(); + map.zoomToPoint(map.getGpsLocation(), true); zoomDialog.dismiss(); }); zoomPointButton = zoomDialogView.findViewById(R.id.zoom_saved_location); zoomPointButton.setOnClickListener(v -> { - zoomToPoint(); + zoomToMarker(true); zoomDialog.dismiss(); }); - clearPointButton = findViewById(R.id.clear); - clearPointButton.setEnabled(false); - clearPointButton.setOnClickListener(v -> { - removeMarker(); - if (location != null) { - reloadLocation.setEnabled(true); + clearButton = findViewById(R.id.clear); + clearButton.setEnabled(false); + clearButton.setOnClickListener(v -> { + clear(); + if (map.getGpsLocation() != null) { + placeMarkerButton.setEnabled(true); // locationStatus.setVisibility(View.VISIBLE); } - // reloadLocation.setEnabled(true); + // placeMarkerButton.setEnabled(true); locationInfo.setVisibility(View.VISIBLE); locationStatus.setVisibility(View.VISIBLE); - draggable = intentDraggable; locationFromIntent = false; - overlayMyLocationLayers(); }); Intent intent = getIntent(); if (intent != null && intent.getExtras() != null) { - if (intent.hasExtra(GeoPointWidget.DRAGGABLE_ONLY)) { - draggable = intent.getBooleanExtra(GeoPointWidget.DRAGGABLE_ONLY, false); - intentDraggable = draggable; - if (!intentDraggable) { - // Not Draggable, set text for Map else leave as placement-map text - locationInfo.setText(getString(R.string.geopoint_no_draggable_instruction)); - } + intentDraggable = intent.getBooleanExtra(GeoPointWidget.DRAGGABLE_ONLY, false); + if (!intentDraggable) { + // Not Draggable, set text for Map else leave as placement-map text + locationInfo.setText(getString(R.string.geopoint_no_draggable_instruction)); } - if (intent.hasExtra(GeoPointWidget.READ_ONLY)) { - readOnly = intent.getBooleanExtra(GeoPointWidget.READ_ONLY, false); - if (readOnly) { - captureLocation = true; - clearPointButton.setEnabled(false); - } + intentReadOnly = intent.getBooleanExtra(GeoPointWidget.READ_ONLY, false); + if (intentReadOnly) { + captureLocation = true; + clearButton.setEnabled(false); } if (intent.hasExtra(GeoPointWidget.LOCATION)) { double[] location = intent.getDoubleArrayExtra(GeoPointWidget.LOCATION); - latLng = new LatLng(location[0], location[1]); + + // If the location is initially set from the intent, both dragging + // and the "place marker" button are initially disabled. To enable + // them, the user must clear the marker and add a new one. + placeMarker(new MapPoint(location[0], location[1]), false); + placeMarkerButton.setEnabled(false); + captureLocation = true; - reloadLocation.setEnabled(false); - draggable = false; // If data loaded, must clear first locationFromIntent = true; - + locationInfo.setVisibility(View.GONE); + locationStatus.setVisibility(View.GONE); + zoomButton.setEnabled(true); + foundFirstLocation = true; + zoomToMarker(false); } } - /*Zoom only if there's a previous location*/ - if (latLng != null) { - locationInfo.setVisibility(View.GONE); - locationStatus.setVisibility(View.GONE); - showLocation.setEnabled(true); - addMarker(); - foundFirstLocation = true; - zoomToPoint(); - } helper.setBasemap(); - isMapReady = true; - upMyLocationOverlayLayers(); + map.setGpsLocationListener(this::onLocationChanged); + map.setGpsLocationEnabled(true); } - private void upMyLocationOverlayLayers() { - if (!locationClient.isMonitoringLocation() || !isMapReady) { - return; - } - - // Make sure we can access Location: - if (!locationClient.isLocationAvailable()) { - showGPSDisabledAlertToUser(); - - } else { - overlayMyLocationLayers(); - } - } - - private void overlayMyLocationLayers() { - if (draggable && !readOnly) { - map.setOnMarkerDragListener(this); - map.setOnMapLongClickListener(this); - - if (marker != null) { - marker.setDraggable(true); - } - } - } - - @Override - public void onLocationChanged(Location location) { + public void onLocationChanged(MapPoint point) { if (setClear) { - reloadLocation.setEnabled(true); + placeMarkerButton.setEnabled(true); } - Location previousLocation = this.location; - this.location = location; + MapPoint previousLocation = this.location; + this.location = point; - if (location != null) { - Timber.i("onLocationChanged(%d) location: %s", locationCount, location); + if (point != null) { + Timber.i("onLocationChanged: location = %s", point); if (previousLocation != null) { enableShowLocation(true); if (!captureLocation && !setClear) { - latLng = new LatLng(location.getLatitude(), location.getLongitude()); - addMarker(); - reloadLocation.setEnabled(true); + placeMarker(point, intentDraggable && !intentReadOnly); + placeMarkerButton.setEnabled(true); } if (!foundFirstLocation) { - //zoomToPoint(); showZoomDialog(); foundFirstLocation = true; } - String locationString = getAccuracyStringForLocation(location); - locationStatus.setText(locationString); + locationStatus.setText(getString( + R.string.location_provider_accuracy, + GeoPointUtils.capitalizeGps(map.getLocationProvider()), + new DecimalFormat("#.##").format(point.sd) + )); } } else { - Timber.i("onLocationChanged(%d) null location", locationCount); + Timber.i("onLocationChanged: null location"); } } - @Override - public void onMarkerDrag(Marker arg0) { - - } - - @Override - public void onMarkerDragEnd(Marker marker) { - latLng = marker.getPosition(); - isDragged = true; - captureLocation = true; - setClear = false; - map.animateCamera( - CameraUpdateFactory.newLatLngZoom(latLng, map.getCameraPosition().zoom)); - - } - - @Override - public void onMarkerDragStart(Marker arg0) { - + public void onDragEnd(int draggedFeatureId) { + if (draggedFeatureId == featureId) { + isDragged = true; + captureLocation = true; + setClear = false; + map.setCenter(map.getMarkerPoint(featureId), true); + } } - @Override - public void onMapLongClick(LatLng latLng) { - this.latLng = latLng; - if (marker == null) { - addMarker(); - } else { - marker.setPosition(latLng); + public void onLongPress(MapPoint point) { + if (intentDraggable && !intentReadOnly) { + placeMarker(point, true); + enableShowLocation(true); + isDragged = true; } - enableShowLocation(true); - marker.setDraggable(true); - isDragged = true; } private void enableShowLocation(boolean shouldEnable) { - if (showLocation != null) { - showLocation.setEnabled(shouldEnable); + if (zoomButton != null) { + zoomButton.setEnabled(shouldEnable); } } - private void zoomToLocation() { - LatLng here = new LatLng(location.getLatitude(), location.getLongitude()); - map.animateCamera(CameraUpdateFactory.newLatLngZoom(here, 16)); - } - - private void zoomToPoint() { - if (latLng != null) { - map.animateCamera(CameraUpdateFactory.newLatLngZoom(marker.getPosition(), 16)); - } - + public void zoomToMarker(boolean animate) { + map.zoomToPoint(map.getMarkerPoint(featureId), animate); } public void showZoomDialog() { - if (zoomDialog == null) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.zoom_to_where)); @@ -431,7 +332,7 @@ public void showZoomDialog() { } //If feature enable zoom to button else disable if (zoomLocationButton != null) { - if (location != null) { + if (map.getGpsLocation() != null) { zoomLocationButton.setEnabled(true); zoomLocationButton.setBackgroundColor(Color.parseColor("#50cccccc")); zoomLocationButton.setTextColor(themeUtils.getPrimaryTextColor()); @@ -441,7 +342,7 @@ public void showZoomDialog() { zoomLocationButton.setTextColor(Color.parseColor("#FF979797")); } - if (latLng != null & !setClear) { + if (featureId != -1 && !setClear) { zoomPointButton.setEnabled(true); zoomPointButton.setBackgroundColor(Color.parseColor("#50cccccc")); zoomPointButton.setTextColor(themeUtils.getPrimaryTextColor()); @@ -455,75 +356,23 @@ public void showZoomDialog() { zoomDialog.show(); } - private void showGPSDisabledAlertToUser() { - AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); - - alertDialogBuilder.setMessage(getString(R.string.gps_enable_message)) - .setCancelable(false) - .setPositiveButton(getString(R.string.enable_gps), - (dialog, id) -> { - startActivityForResult( - new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 0); - errorDialog = null; - }); - - alertDialogBuilder.setNegativeButton(getString(R.string.cancel), - (dialog, id) -> { - dialog.cancel(); - errorDialog = null; - }); - - errorDialog = alertDialogBuilder.create(); - errorDialog.show(); - } - - // remove the marker and disable the trash button. - private void removeMarker() { - if (marker != null) { - marker.remove(); - latLng = null; - marker = null; - isDragged = false; - captureLocation = false; - clearPointButton.setEnabled(false); - setClear = true; - } - } + private void clear() { + map.clearFeatures(); + featureId = -1; + clearButton.setEnabled(false); - // add the marker and enable the trash button. - private void addMarker() { - if (marker == null) { - markerOptions.position(latLng); - marker = map.addMarker(markerOptions); - clearPointButton.setEnabled(true); - captureLocation = true; - setClear = false; - } + isDragged = false; + captureLocation = false; + setClear = true; } - @Override - public void onClientStart() { - locationClient.requestLocationUpdates(this); - upMyLocationOverlayLayers(); - } - - @Override - public void onClientStartFailure() { - - } - - @Override - public void onClientStop() { - - } - - /** - * For testing purposes only. - * - * @param mapReady Whether or not the Google Map is ready. - */ - public void setMapReady(boolean mapReady) { - isMapReady = mapReady; + /** Places the marker and enables the button to remove it. */ + private void placeMarker(MapPoint point, boolean draggable) { + map.clearFeatures(); + featureId = map.addMarker(point, draggable); + clearButton.setEnabled(true); + captureLocation = true; + setClear = false; } public void setCaptureLocation(boolean captureLocation) { @@ -538,13 +387,7 @@ public String getLocationStatus() { return locationStatus.getText().toString(); } - public String getAccuracyStringForLocation(Location location) { - return getString(R.string.location_provider_accuracy, GeoPointUtils.capitalizeGps(location.getProvider()), - truncateFloat(location.getAccuracy())); - } - public AlertDialog getZoomDialog() { return zoomDialog; } - -} \ No newline at end of file +} diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeActivity.java index ad397c49f1a..1dd9b3cb76e 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoShapeActivity.java @@ -124,7 +124,7 @@ private void initMap(MapFragment newMapFragment) { } map = newMapFragment; - map.setLongPressListener(this::addVertex); + map.setLongPressListener(this::onLongPress); if (map instanceof GoogleMapFragment) { helper = new MapHelper(this, ((GoogleMapFragment) map).getGoogleMap(), selectedLayer); @@ -190,7 +190,7 @@ private void onGpsLocationReady(MapFragment map) { } } - private void addVertex(MapPoint point) { + private void onLongPress(MapPoint point) { map.appendPointToPoly(featureId, point); clearButton.setEnabled(true); zoomButton.setEnabled(true); diff --git a/collect_app/src/main/res/layout/geopoint_layout.xml b/collect_app/src/main/res/layout/geopoint_layout.xml index b2b092c6ea0..54183885fef 100644 --- a/collect_app/src/main/res/layout/geopoint_layout.xml +++ b/collect_app/src/main/res/layout/geopoint_layout.xml @@ -31,9 +31,8 @@ - @@ -59,7 +58,7 @@ Date: Wed, 9 Jan 2019 20:07:10 -0800 Subject: [PATCH 08/20] Allow manual testers to use the volume buttons to switch between the old and new GeoPoint activity. --- .../collect/android/widgets/GeoPointWidget.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/GeoPointWidget.java b/collect_app/src/main/java/org/odk/collect/android/widgets/GeoPointWidget.java index 15b61dfaa5a..5c7db8e0a84 100644 --- a/collect_app/src/main/java/org/odk/collect/android/widgets/GeoPointWidget.java +++ b/collect_app/src/main/java/org/odk/collect/android/widgets/GeoPointWidget.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ConfigurationInfo; +import android.media.AudioManager; import android.preference.PreferenceManager; import android.view.View; import android.widget.Button; @@ -32,6 +33,7 @@ import org.javarosa.form.api.FormEntryPrompt; import org.odk.collect.android.R; import org.odk.collect.android.activities.GeoPointActivity; +import org.odk.collect.android.activities.GeoPointGoogleMapActivity; import org.odk.collect.android.activities.GeoPointMapActivity; import org.odk.collect.android.activities.GeoPointOsmMapActivity; import org.odk.collect.android.listeners.PermissionListener; @@ -72,6 +74,10 @@ public class GeoPointWidget extends QuestionWidget implements BinaryWidget { private String stringAnswer; + private boolean isRingerSilent() { + return ((AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE)).getRingerMode() == AudioManager.RINGER_MODE_SILENT; + } + public GeoPointWidget(Context context, FormEntryPrompt prompt) { super(context, prompt); @@ -291,9 +297,12 @@ public void denied() { private void startGeoPoint() { Intent i; if (useMapsV2 && useMaps) { - if (mapSDK.equals(GOOGLE_MAP_KEY)) { + if (isRingerSilent()) { + i = new Intent(getContext(), GeoPointMapActivity.class); + i.putExtra(GeneralKeys.KEY_MAP_SDK, mapSDK); + } else if (mapSDK.equals(GOOGLE_MAP_KEY)) { if (PlayServicesUtil.isGooglePlayServicesAvailable(getContext())) { - i = new Intent(getContext(), GeoPointMapActivity.class); + i = new Intent(getContext(), GeoPointGoogleMapActivity.class); } else { PlayServicesUtil.showGooglePlayServicesAvailabilityErrorDialog(getContext()); return; @@ -322,4 +331,4 @@ private void startGeoPoint() { ((Activity) getContext()).startActivityForResult(i, RequestCodes.LOCATION_CAPTURE); } -} \ No newline at end of file +} From 2ca77105546e930055978c46b2e802da51c31d97 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Mon, 14 Jan 2019 12:09:59 -0800 Subject: [PATCH 09/20] Fix GeoPointGoogleMapActivityTest. --- .../android/activities/GeoPointGoogleMapActivity.java | 2 +- ...ityTest.java => GeoPointGoogleMapActivityTest.java} | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename collect_app/src/test/java/org/odk/collect/android/location/activities/{GeoPointMapActivityTest.java => GeoPointGoogleMapActivityTest.java} (91%) diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointGoogleMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointGoogleMapActivity.java index fc14ce8fefe..e02f040d6e0 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointGoogleMapActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointGoogleMapActivity.java @@ -119,7 +119,7 @@ public void onCreate(Bundle savedInstanceState) { } try { - setContentView(R.layout.geopoint_layout); + setContentView(R.layout.geopoint_google_layout); } catch (NoClassDefFoundError e) { Timber.e(e, "Google maps not accessible due to: %s ", e.getMessage()); diff --git a/collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointMapActivityTest.java b/collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointGoogleMapActivityTest.java similarity index 91% rename from collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointMapActivityTest.java rename to collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointGoogleMapActivityTest.java index 409dd4e68ff..79ccefbd63a 100644 --- a/collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointMapActivityTest.java +++ b/collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointGoogleMapActivityTest.java @@ -11,7 +11,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.odk.collect.android.R; -import org.odk.collect.android.activities.GeoPointMapActivity; +import org.odk.collect.android.activities.GeoPointGoogleMapActivity; import org.odk.collect.android.location.client.LocationClient; import org.odk.collect.android.location.client.LocationClients; import org.robolectric.Robolectric; @@ -31,14 +31,14 @@ import static org.robolectric.Shadows.shadowOf; @RunWith(RobolectricTestRunner.class) -public class GeoPointMapActivityTest extends BaseGeoActivityTest { +public class GeoPointGoogleMapActivityTest extends BaseGeoActivityTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); - private ActivityController activityController; + private ActivityController activityController; - private GeoPointMapActivity activity; + private GeoPointGoogleMapActivity activity; private ShadowActivity shadowActivity; @Mock @@ -50,7 +50,7 @@ public class GeoPointMapActivityTest extends BaseGeoActivityTest { @Before public void setUp() throws Exception { super.setUp(); - activityController = Robolectric.buildActivity(GeoPointMapActivity.class); + activityController = Robolectric.buildActivity(GeoPointGoogleMapActivity.class); activity = activityController.get(); shadowActivity = shadowOf(activity); From 3b89c93c8b021779587f52acd78a11d53a1d8b2c Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Mon, 14 Jan 2019 12:21:14 -0800 Subject: [PATCH 10/20] Fix SpotBugs complaint. --- .../java/org/odk/collect/android/map/GoogleMapFragment.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java index c389a35c787..b74f69a1b02 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java @@ -457,11 +457,9 @@ interface MapFeature { } protected static class MarkerFeature implements MapFeature { - final GoogleMap map; Marker marker; public MarkerFeature(GoogleMap map, MapPoint point, boolean draggable) { - this.map = map; this.marker = createMarker(map, point, draggable); } From c0b6c30ab7aa7cef7d5387710336319d81734266 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Tue, 15 Jan 2019 13:43:26 -0800 Subject: [PATCH 11/20] Log warnings if Marker objects contain invalid data (should never happen). --- .../java/org/odk/collect/android/map/GoogleMapFragment.java | 5 ++++- .../java/org/odk/collect/android/map/OsmMapFragment.java | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java index b74f69a1b02..7771a6da1db 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java @@ -49,6 +49,7 @@ import java.util.Map; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import timber.log.Timber; public class GoogleMapFragment extends SupportMapFragment implements MapFragment, LocationListener, LocationClient.LocationClientListener, @@ -416,7 +417,9 @@ protected void updateFeature(int featureId) { if (parts.length >= 2) { sd = Double.parseDouble(parts[1]); } - } catch (NumberFormatException e) { /* ignore */ } + } catch (NumberFormatException e) { + Timber.w("Assertion violated: Marker.getSnippet() did not contain two numbers"); + } return new MapPoint(position.latitude, position.longitude, alt, sd); } diff --git a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java index 186e45d1dcb..e0ac36fb382 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java @@ -57,6 +57,8 @@ import java.util.List; import java.util.Map; +import timber.log.Timber; + public class OsmMapFragment extends Fragment implements MapFragment, MapEventsReceiver, LocationListener, LocationClient.LocationClientListener { public static final GeoPoint INITIAL_CENTER = new GeoPoint(0.0, -30.0); @@ -376,7 +378,9 @@ protected void addMapLayoutChangeListener(MapView map) { double sd = 0; try { sd = Double.parseDouble(marker.getSubDescription()); - } catch (NumberFormatException e) { /* ignore */ } + } catch (NumberFormatException e) { + Timber.w("Assertion violated: Marker.getSubDescription() did not contain a number"); + } return new MapPoint( geoPoint.getLatitude(), geoPoint.getLongitude(), geoPoint.getAltitude(), sd From 305d5c36d546394caed5e66cbda3352a2b2ae6d6 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Tue, 15 Jan 2019 13:46:22 -0800 Subject: [PATCH 12/20] Use ternary operators as suggested in code review. --- .../java/org/odk/collect/android/map/GoogleMapFragment.java | 5 +---- .../java/org/odk/collect/android/map/OsmMapFragment.java | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java index 7771a6da1db..e60f8c70119 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/GoogleMapFragment.java @@ -218,10 +218,7 @@ protected void moveOrAnimateCamera(CameraUpdate movement, boolean animate) { @Override public @Nullable MapPoint getMarkerPoint(int featureId) { MapFeature feature = features.get(featureId); - if (feature instanceof MarkerFeature) { - return ((MarkerFeature) feature).getPoint(); - } - return null; + return feature instanceof MarkerFeature ? ((MarkerFeature) feature).getPoint() : null; } @Override public int addDraggablePoly(@NonNull Iterable points, boolean closedPolygon) { diff --git a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java index e0ac36fb382..032c0a77853 100644 --- a/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java +++ b/collect_app/src/main/java/org/odk/collect/android/map/OsmMapFragment.java @@ -196,10 +196,7 @@ public MapView getMapView() { @Override public @Nullable MapPoint getMarkerPoint(int featureId) { MapFeature feature = features.get(featureId); - if (feature instanceof MarkerFeature) { - return ((MarkerFeature) feature).getPoint(); - } - return null; + return feature instanceof MarkerFeature ? ((MarkerFeature) feature).getPoint() : null; } @Override public int addDraggablePoly(@NonNull Iterable points, boolean closedPolygon) { From 330a306ae908e2b9c8843dec831405ddc44b9708 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Tue, 15 Jan 2019 23:18:08 -0800 Subject: [PATCH 13/20] Rename locationFromIntent to pointFromIntent (it is related to the recorded point and not the GPS location). --- .../android/activities/GeoPointMapActivity.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java index 8f5cec26b21..e11dee2dad0 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java @@ -84,8 +84,8 @@ public class GeoPointMapActivity extends BaseGeoMapActivity implements IRegister */ private boolean setClear; - /** True if the current marker location came from the intent. */ - private boolean locationFromIntent; + /** True if the current point came from the intent. */ + private boolean pointFromIntent; /** True if the intent requested for the point to be read-only. */ private boolean intentReadOnly; @@ -139,7 +139,7 @@ public void returnLocation() { if (setClear || (intentReadOnly && featureId == -1)) { result = ""; - } else if (isDragged || intentReadOnly || locationFromIntent) { + } else if (isDragged || intentReadOnly || pointFromIntent) { Timber.i("IsDragged !!!"); MapPoint point = map.getMarkerPoint(featureId); result = point.lat + " " + point.lon + " " + 0 + " " + 0; @@ -213,7 +213,7 @@ public void initMap(MapFragment newMapFragment) { // placeMarkerButton.setEnabled(true); locationInfo.setVisibility(View.VISIBLE); locationStatus.setVisibility(View.VISIBLE); - locationFromIntent = false; + pointFromIntent = false; }); Intent intent = getIntent(); @@ -231,16 +231,16 @@ public void initMap(MapFragment newMapFragment) { } if (intent.hasExtra(GeoPointWidget.LOCATION)) { - double[] location = intent.getDoubleArrayExtra(GeoPointWidget.LOCATION); + double[] point = intent.getDoubleArrayExtra(GeoPointWidget.LOCATION); - // If the location is initially set from the intent, both dragging + // If the point is initially set from the intent, both dragging // and the "place marker" button are initially disabled. To enable // them, the user must clear the marker and add a new one. - placeMarker(new MapPoint(location[0], location[1]), false); + placeMarker(new MapPoint(point[0], point[1]), false); placeMarkerButton.setEnabled(false); captureLocation = true; - locationFromIntent = true; + pointFromIntent = true; locationInfo.setVisibility(View.GONE); locationStatus.setVisibility(View.GONE); zoomButton.setEnabled(true); From 6eeb00a1929c91d6284b4e610c5666bad902e383 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Wed, 16 Jan 2019 02:28:57 -0800 Subject: [PATCH 14/20] Preserve the incoming point's alt and sd fields. --- .../odk/collect/android/activities/GeoPointMapActivity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java index e11dee2dad0..0a9596a5cf0 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java @@ -142,7 +142,7 @@ public void returnLocation() { } else if (isDragged || intentReadOnly || pointFromIntent) { Timber.i("IsDragged !!!"); MapPoint point = map.getMarkerPoint(featureId); - result = point.lat + " " + point.lon + " " + 0 + " " + 0; + result = String.format("%s %s %s %s", point.lat, point.lon, point.alt, point.sd); } else if (location != null) { Timber.i("IsNotDragged !!!"); result = String.format("%s %s %s %s", location.lat, location.lon, location.alt, location.sd); @@ -236,7 +236,7 @@ public void initMap(MapFragment newMapFragment) { // If the point is initially set from the intent, both dragging // and the "place marker" button are initially disabled. To enable // them, the user must clear the marker and add a new one. - placeMarker(new MapPoint(point[0], point[1]), false); + placeMarker(new MapPoint(point[0], point[1], point[2], point[3]), false); placeMarkerButton.setEnabled(false); captureLocation = true; From 93f30fd946402e9cb1ee366eb0a50e141d466ded Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Wed, 16 Jan 2019 02:13:20 -0800 Subject: [PATCH 15/20] Rename enableShowLocation to enableZoomButton because it enables the zoom button. --- .../odk/collect/android/activities/GeoPointMapActivity.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java index 0a9596a5cf0..b7c697947bd 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java @@ -267,7 +267,7 @@ public void onLocationChanged(MapPoint point) { Timber.i("onLocationChanged: location = %s", point); if (previousLocation != null) { - enableShowLocation(true); + enableZoomButton(true); if (!captureLocation && !setClear) { placeMarker(point, intentDraggable && !intentReadOnly); @@ -303,12 +303,12 @@ public void onDragEnd(int draggedFeatureId) { public void onLongPress(MapPoint point) { if (intentDraggable && !intentReadOnly) { placeMarker(point, true); - enableShowLocation(true); + enableZoomButton(true); isDragged = true; } } - private void enableShowLocation(boolean shouldEnable) { + private void enableZoomButton(boolean shouldEnable) { if (zoomButton != null) { zoomButton.setEnabled(shouldEnable); } From ee125327f940553d8d2ab793a829eb986e2b84ef Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Wed, 16 Jan 2019 02:15:35 -0800 Subject: [PATCH 16/20] Disable long-pressing while point is initially set from the intent. --- .../activities/GeoPointMapActivity.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java index b7c697947bd..3d1e6b9aa96 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java @@ -93,6 +93,9 @@ public class GeoPointMapActivity extends BaseGeoMapActivity implements IRegister /** True if the intent requested for the marker to be draggable. */ private boolean intentDraggable; + /** While true, the point cannot be moved by dragging or long-pressing. */ + private boolean isPointLocked; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -177,7 +180,7 @@ public void initMap(MapFragment newMapFragment) { placeMarkerButton.setEnabled(false); placeMarkerButton.setOnClickListener(v -> { - placeMarker(map.getGpsLocation(), intentDraggable && !intentReadOnly); + placeMarker(map.getGpsLocation()); zoomToMarker(true); }); @@ -233,10 +236,11 @@ public void initMap(MapFragment newMapFragment) { if (intent.hasExtra(GeoPointWidget.LOCATION)) { double[] point = intent.getDoubleArrayExtra(GeoPointWidget.LOCATION); - // If the point is initially set from the intent, both dragging - // and the "place marker" button are initially disabled. To enable - // them, the user must clear the marker and add a new one. - placeMarker(new MapPoint(point[0], point[1], point[2], point[3]), false); + // If the point is initially set from the intent, the "place marker" + // button, dragging, and long-pressing are all initially disabled. + // To enable them, the user must clear the marker and add a new one. + isPointLocked = true; + placeMarker(new MapPoint(point[0], point[1], point[2], point[3])); placeMarkerButton.setEnabled(false); captureLocation = true; @@ -270,7 +274,7 @@ public void onLocationChanged(MapPoint point) { enableZoomButton(true); if (!captureLocation && !setClear) { - placeMarker(point, intentDraggable && !intentReadOnly); + placeMarker(point); placeMarkerButton.setEnabled(true); } @@ -301,8 +305,8 @@ public void onDragEnd(int draggedFeatureId) { } public void onLongPress(MapPoint point) { - if (intentDraggable && !intentReadOnly) { - placeMarker(point, true); + if (intentDraggable && !intentReadOnly && !isPointLocked) { + placeMarker(point); enableZoomButton(true); isDragged = true; } @@ -361,15 +365,16 @@ private void clear() { featureId = -1; clearButton.setEnabled(false); + isPointLocked = false; isDragged = false; captureLocation = false; setClear = true; } /** Places the marker and enables the button to remove it. */ - private void placeMarker(MapPoint point, boolean draggable) { + private void placeMarker(MapPoint point) { map.clearFeatures(); - featureId = map.addMarker(point, draggable); + featureId = map.addMarker(point, intentDraggable && !intentReadOnly && !isPointLocked); clearButton.setEnabled(true); captureLocation = true; setClear = false; From 29d96fb7dc83d52b57845fcb72179803e6e3e91e Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Thu, 17 Jan 2019 00:48:02 -0800 Subject: [PATCH 17/20] Resume GPS tracking after GeoPointMapActivity is stopped and restarted. --- .../collect/android/activities/GeoPointMapActivity.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java index 3d1e6b9aa96..796d227977d 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java @@ -130,6 +130,13 @@ public MapFragment createMapFragment() { new GoogleMapFragment() : new OsmMapFragment(); } + @Override protected void onStart() { + super.onStart(); + if (map != null) { + map.setGpsLocationEnabled(true); + } + } + @Override protected void onStop() { map.setGpsLocationEnabled(false); super.onStop(); From ac36f6f26a2924af7025b1c3f2a5ae8623994706 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Thu, 17 Jan 2019 00:50:38 -0800 Subject: [PATCH 18/20] Prevent NPE on activity stop in BaseGeoMapActivity. --- .../odk/collect/android/activities/BaseGeoMapActivity.java | 4 +++- .../odk/collect/android/activities/GeoPointMapActivity.java | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/BaseGeoMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/BaseGeoMapActivity.java index 8ad6ea6e27a..b301b0e8292 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/BaseGeoMapActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/BaseGeoMapActivity.java @@ -36,7 +36,9 @@ protected void onCreate(Bundle savedInstanceState) { @Override protected void onSaveInstanceState(Bundle outState) { - outState.putInt(MAP_LAYER_KEY, helper.getSelectedLayer()); + if (helper != null) { + outState.putInt(MAP_LAYER_KEY, helper.getSelectedLayer()); + } super.onSaveInstanceState(outState); } } diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java index 796d227977d..493c63182a1 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java @@ -64,8 +64,6 @@ public class GeoPointMapActivity extends BaseGeoMapActivity implements IRegister private boolean isDragged; private ImageButton zoomButton; - private MapHelper helper; - private AlertDialog errorDialog; private AlertDialog zoomDialog; From a792aa1c14207537b6374b6005e6367481f458e9 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Thu, 17 Jan 2019 00:51:32 -0800 Subject: [PATCH 19/20] Remove unused errorDialog field. --- .../collect/android/activities/GeoPointMapActivity.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java index 493c63182a1..e83431818e0 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointMapActivity.java @@ -62,13 +62,10 @@ public class GeoPointMapActivity extends BaseGeoMapActivity implements IRegister private ImageButton placeMarkerButton; private boolean isDragged; - private ImageButton zoomButton; - - private AlertDialog errorDialog; + private ImageButton zoomButton; private AlertDialog zoomDialog; private View zoomDialogView; - private Button zoomPointButton; private Button zoomLocationButton; private ImageButton clearButton; @@ -389,10 +386,6 @@ public void setCaptureLocation(boolean captureLocation) { this.captureLocation = captureLocation; } - public AlertDialog getErrorDialog() { - return errorDialog; - } - public String getLocationStatus() { return locationStatus.getText().toString(); } From 4fc69686922e750347ecf50236b8eb9aa023ea90 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Thu, 24 Jan 2019 16:06:58 -0800 Subject: [PATCH 20/20] Remove old GeoPointGoogleMapActivity and GeoPointOsmMapActivity. --- collect_app/src/main/AndroidManifest.xml | 6 - .../activities/GeoPointGoogleMapActivity.java | 550 ---------------- .../activities/GeoPointOsmMapActivity.java | 612 ------------------ .../android/widgets/GeoPointWidget.java | 44 +- .../res/layout/geopoint_google_layout.xml | 106 --- .../main/res/layout/geopoint_osm_layout.xml | 99 --- .../GeoPointGoogleMapActivityTest.java | 120 ---- .../GeoPointOsmMapActivityTest.java | 104 --- 8 files changed, 14 insertions(+), 1627 deletions(-) delete mode 100644 collect_app/src/main/java/org/odk/collect/android/activities/GeoPointGoogleMapActivity.java delete mode 100644 collect_app/src/main/java/org/odk/collect/android/activities/GeoPointOsmMapActivity.java delete mode 100644 collect_app/src/main/res/layout/geopoint_google_layout.xml delete mode 100644 collect_app/src/main/res/layout/geopoint_osm_layout.xml delete mode 100644 collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointGoogleMapActivityTest.java delete mode 100644 collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointOsmMapActivityTest.java diff --git a/collect_app/src/main/AndroidManifest.xml b/collect_app/src/main/AndroidManifest.xml index 5b4c42e29e6..368d865d826 100644 --- a/collect_app/src/main/AndroidManifest.xml +++ b/collect_app/src/main/AndroidManifest.xml @@ -201,12 +201,6 @@ the specific language governing permissions and limitations under the License. - - diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointGoogleMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointGoogleMapActivity.java deleted file mode 100644 index e02f040d6e0..00000000000 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointGoogleMapActivity.java +++ /dev/null @@ -1,550 +0,0 @@ -/* - * Copyright (C) 2011 University of Washington - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.odk.collect.android.activities; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.content.Intent; -import android.graphics.Color; -import android.location.Location; -import android.os.Bundle; -import android.provider.Settings; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.Window; -import android.widget.Button; -import android.widget.ImageButton; -import android.widget.TextView; - -import com.google.android.gms.location.LocationListener; -import com.google.android.gms.maps.CameraUpdateFactory; -import com.google.android.gms.maps.GoogleMap; -import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener; -import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener; -import com.google.android.gms.maps.SupportMapFragment; -import com.google.android.gms.maps.model.LatLng; -import com.google.android.gms.maps.model.Marker; -import com.google.android.gms.maps.model.MarkerOptions; - -import org.odk.collect.android.R; -import org.odk.collect.android.location.client.LocationClient; -import org.odk.collect.android.location.client.LocationClients; -import org.odk.collect.android.spatial.MapHelper; -import org.odk.collect.android.utilities.GeoPointUtils; -import org.odk.collect.android.utilities.ToastUtils; -import org.odk.collect.android.widgets.GeoPointWidget; - -import java.text.DecimalFormat; - -import timber.log.Timber; - -import static org.odk.collect.android.utilities.PermissionUtils.areLocationPermissionsGranted; - -/** - * Version of the GeoPointMapActivity that uses the new Maps v2 API and Fragments to enable - * specifying a location via placing a tracker on a map. - * - * @author guisalmon@gmail.com - * @author jonnordling@gmail.com - */ -public class GeoPointGoogleMapActivity extends BaseGeoMapActivity implements OnMarkerDragListener, OnMapLongClickListener, - LocationClient.LocationClientListener, LocationListener { - - private static final String LOCATION_COUNT = "locationCount"; - - private GoogleMap map; - private MarkerOptions markerOptions; - private Marker marker; - private LatLng latLng; - - private TextView locationStatus; - private TextView locationInfo; - - private LocationClient locationClient; - - private Location location; - private ImageButton reloadLocation; - - private boolean isDragged; - private ImageButton showLocation; - - private int locationCount; - - //private KmlLayer kk; - - private AlertDialog errorDialog; - - private AlertDialog zoomDialog; - private View zoomDialogView; - - private Button zoomPointButton; - private Button zoomLocationButton; - private ImageButton clearPointButton; - - private boolean setClear; - private boolean captureLocation; - private boolean foundFirstLocation; - private boolean readOnly; - private boolean draggable; - private boolean intentDraggable; - private boolean locationFromIntent; - - private boolean isMapReady; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (!areLocationPermissionsGranted(this)) { - finish(); - return; - } - - requestWindowFeature(Window.FEATURE_NO_TITLE); - - if (savedInstanceState != null) { - locationCount = savedInstanceState.getInt(LOCATION_COUNT); - } - - try { - setContentView(R.layout.geopoint_google_layout); - - } catch (NoClassDefFoundError e) { - Timber.e(e, "Google maps not accessible due to: %s ", e.getMessage()); - ToastUtils.showShortToast(R.string.google_play_services_error_occured); - finish(); - return; - } - - locationStatus = findViewById(R.id.location_status); - locationInfo = findViewById(R.id.location_info); - reloadLocation = findViewById(R.id.reload_location); - showLocation = findViewById(R.id.show_location); - - locationClient = LocationClients.clientForContext(this); - - isMapReady = false; - ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMapAsync(googleMap -> { - setupMap(googleMap); - locationClient.setListener(this); - locationClient.start(); - }); - } - - @Override - protected void onStop() { - locationClient.stop(); - super.onStop(); - } - - public void returnLocation() { - Intent i = new Intent(); - if (setClear || (readOnly && latLng == null)) { - i.putExtra(FormEntryActivity.LOCATION_RESULT, ""); - setResult(RESULT_OK, i); - - } else if (isDragged || readOnly || locationFromIntent) { - Timber.i("IsDragged !!!"); - i.putExtra( - FormEntryActivity.LOCATION_RESULT, - latLng.latitude + " " + latLng.longitude + " " - + 0 + " " + 0); - setResult(RESULT_OK, i); - } else if (location != null) { - Timber.i("IsNotDragged !!!"); - - i.putExtra( - FormEntryActivity.LOCATION_RESULT, - getResultString(location) - ); - setResult(RESULT_OK, i); - } - finish(); - } - - public String getResultString(Location location) { - return String.format("%s %s %s %s", location.getLatitude(), location.getLongitude(), location.getAltitude(), location.getAccuracy()); - } - - private String truncateFloat(float f) { - return new DecimalFormat("#.##").format(f); - } - - @SuppressLint("MissingPermission") // Permission handled in Constructor - private void setupMap(GoogleMap googleMap) { - map = googleMap; - if (map == null) { - ToastUtils.showShortToast(R.string.google_play_services_error_occured); - finish(); - return; - } - helper = new MapHelper(this, map, selectedLayer); - map.setMyLocationEnabled(true); - map.getUiSettings().setCompassEnabled(true); - map.getUiSettings().setMyLocationButtonEnabled(false); - map.getUiSettings().setZoomControlsEnabled(false); - - markerOptions = new MarkerOptions(); - helper = new MapHelper(this, map, selectedLayer); - - ImageButton acceptLocation = findViewById(R.id.accept_location); - - acceptLocation.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - returnLocation(); - } - }); - - reloadLocation.setEnabled(false); - reloadLocation.setOnClickListener(v -> { - removeMarker(); - latLng = new LatLng(location.getLatitude(), location.getLongitude()); - if (marker == null) { - addMarker(); - if (draggable && !readOnly) { - marker.setDraggable(true); - } - } - zoomToPoint(); - }); - - // Focuses on marked location - //showLocation.setClickable(false); - showLocation.setEnabled(false); - showLocation.setOnClickListener(v -> showZoomDialog()); - - // Menu Layer Toggle - ImageButton layers = findViewById(R.id.layer_menu); - layers.setOnClickListener(v -> helper.showLayersDialog()); - zoomDialogView = getLayoutInflater().inflate(R.layout.geo_zoom_dialog, null); - zoomLocationButton = zoomDialogView.findViewById(R.id.zoom_location); - zoomLocationButton.setOnClickListener(v -> { - zoomToLocation(); - zoomDialog.dismiss(); - }); - - zoomPointButton = zoomDialogView.findViewById(R.id.zoom_saved_location); - zoomPointButton.setOnClickListener(v -> { - zoomToPoint(); - zoomDialog.dismiss(); - }); - - clearPointButton = findViewById(R.id.clear); - clearPointButton.setEnabled(false); - clearPointButton.setOnClickListener(v -> { - removeMarker(); - if (location != null) { - reloadLocation.setEnabled(true); - // locationStatus.setVisibility(View.VISIBLE); - } - // reloadLocation.setEnabled(true); - locationInfo.setVisibility(View.VISIBLE); - locationStatus.setVisibility(View.VISIBLE); - draggable = intentDraggable; - locationFromIntent = false; - overlayMyLocationLayers(); - }); - - Intent intent = getIntent(); - if (intent != null && intent.getExtras() != null) { - if (intent.hasExtra(GeoPointWidget.DRAGGABLE_ONLY)) { - draggable = intent.getBooleanExtra(GeoPointWidget.DRAGGABLE_ONLY, false); - intentDraggable = draggable; - if (!intentDraggable) { - // Not Draggable, set text for Map else leave as placement-map text - locationInfo.setText(getString(R.string.geopoint_no_draggable_instruction)); - } - } - - if (intent.hasExtra(GeoPointWidget.READ_ONLY)) { - readOnly = intent.getBooleanExtra(GeoPointWidget.READ_ONLY, false); - if (readOnly) { - captureLocation = true; - clearPointButton.setEnabled(false); - } - } - - if (intent.hasExtra(GeoPointWidget.LOCATION)) { - double[] location = intent.getDoubleArrayExtra(GeoPointWidget.LOCATION); - latLng = new LatLng(location[0], location[1]); - captureLocation = true; - reloadLocation.setEnabled(false); - draggable = false; // If data loaded, must clear first - locationFromIntent = true; - - } - } - /*Zoom only if there's a previous location*/ - if (latLng != null) { - locationInfo.setVisibility(View.GONE); - locationStatus.setVisibility(View.GONE); - showLocation.setEnabled(true); - addMarker(); - foundFirstLocation = true; - zoomToPoint(); - } - - helper.setBasemap(); - - isMapReady = true; - upMyLocationOverlayLayers(); - } - - private void upMyLocationOverlayLayers() { - if (!locationClient.isMonitoringLocation() || !isMapReady) { - return; - } - - // Make sure we can access Location: - if (!locationClient.isLocationAvailable()) { - showGPSDisabledAlertToUser(); - - } else { - overlayMyLocationLayers(); - } - } - - private void overlayMyLocationLayers() { - if (draggable && !readOnly) { - map.setOnMarkerDragListener(this); - map.setOnMapLongClickListener(this); - - if (marker != null) { - marker.setDraggable(true); - } - } - } - - @Override - public void onLocationChanged(Location location) { - if (setClear) { - reloadLocation.setEnabled(true); - } - - Location previousLocation = this.location; - this.location = location; - - if (location != null) { - Timber.i("onLocationChanged(%d) location: %s", locationCount, location); - - if (previousLocation != null) { - enableShowLocation(true); - - if (!captureLocation && !setClear) { - latLng = new LatLng(location.getLatitude(), location.getLongitude()); - addMarker(); - reloadLocation.setEnabled(true); - } - - if (!foundFirstLocation) { - //zoomToPoint(); - showZoomDialog(); - foundFirstLocation = true; - } - - String locationString = getAccuracyStringForLocation(location); - locationStatus.setText(locationString); - } - - } else { - Timber.i("onLocationChanged(%d) null location", locationCount); - } - } - - @Override - public void onMarkerDrag(Marker arg0) { - - } - - @Override - public void onMarkerDragEnd(Marker marker) { - latLng = marker.getPosition(); - isDragged = true; - captureLocation = true; - setClear = false; - map.animateCamera( - CameraUpdateFactory.newLatLngZoom(latLng, map.getCameraPosition().zoom)); - - } - - @Override - public void onMarkerDragStart(Marker arg0) { - - } - - @Override - public void onMapLongClick(LatLng latLng) { - this.latLng = latLng; - if (marker == null) { - addMarker(); - } else { - marker.setPosition(latLng); - } - enableShowLocation(true); - marker.setDraggable(true); - isDragged = true; - } - - private void enableShowLocation(boolean shouldEnable) { - if (showLocation != null) { - showLocation.setEnabled(shouldEnable); - } - } - - private void zoomToLocation() { - LatLng here = new LatLng(location.getLatitude(), location.getLongitude()); - map.animateCamera(CameraUpdateFactory.newLatLngZoom(here, 16)); - } - - private void zoomToPoint() { - if (latLng != null) { - map.animateCamera(CameraUpdateFactory.newLatLngZoom(marker.getPosition(), 16)); - } - - } - - public void showZoomDialog() { - - if (zoomDialog == null) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.zoom_to_where)); - builder.setView(zoomDialogView) - .setNegativeButton(R.string.cancel, (dialog, id) -> dialog.cancel()) - .setOnCancelListener(dialog -> { - dialog.cancel(); - zoomDialog.dismiss(); - }); - zoomDialog = builder.create(); - } - //If feature enable zoom to button else disable - if (zoomLocationButton != null) { - if (location != null) { - zoomLocationButton.setEnabled(true); - zoomLocationButton.setBackgroundColor(Color.parseColor("#50cccccc")); - zoomLocationButton.setTextColor(themeUtils.getPrimaryTextColor()); - } else { - zoomLocationButton.setEnabled(false); - zoomLocationButton.setBackgroundColor(Color.parseColor("#50e2e2e2")); - zoomLocationButton.setTextColor(Color.parseColor("#FF979797")); - } - - if (latLng != null & !setClear) { - zoomPointButton.setEnabled(true); - zoomPointButton.setBackgroundColor(Color.parseColor("#50cccccc")); - zoomPointButton.setTextColor(themeUtils.getPrimaryTextColor()); - } else { - zoomPointButton.setEnabled(false); - zoomPointButton.setBackgroundColor(Color.parseColor("#50e2e2e2")); - zoomPointButton.setTextColor(Color.parseColor("#FF979797")); - } - } - - zoomDialog.show(); - } - - private void showGPSDisabledAlertToUser() { - AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); - - alertDialogBuilder.setMessage(getString(R.string.gps_enable_message)) - .setCancelable(false) - .setPositiveButton(getString(R.string.enable_gps), - (dialog, id) -> { - startActivityForResult( - new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 0); - errorDialog = null; - }); - - alertDialogBuilder.setNegativeButton(getString(R.string.cancel), - (dialog, id) -> { - dialog.cancel(); - errorDialog = null; - }); - - errorDialog = alertDialogBuilder.create(); - errorDialog.show(); - } - - // remove the marker and disable the trash button. - private void removeMarker() { - if (marker != null) { - marker.remove(); - latLng = null; - marker = null; - isDragged = false; - captureLocation = false; - clearPointButton.setEnabled(false); - setClear = true; - } - } - - // add the marker and enable the trash button. - private void addMarker() { - if (marker == null) { - markerOptions.position(latLng); - marker = map.addMarker(markerOptions); - clearPointButton.setEnabled(true); - captureLocation = true; - setClear = false; - } - } - - @Override - public void onClientStart() { - locationClient.requestLocationUpdates(this); - upMyLocationOverlayLayers(); - } - - @Override - public void onClientStartFailure() { - - } - - @Override - public void onClientStop() { - - } - - /** - * For testing purposes only. - * - * @param mapReady Whether or not the Google Map is ready. - */ - public void setMapReady(boolean mapReady) { - isMapReady = mapReady; - } - - public void setCaptureLocation(boolean captureLocation) { - this.captureLocation = captureLocation; - } - - public AlertDialog getErrorDialog() { - return errorDialog; - } - - public String getLocationStatus() { - return locationStatus.getText().toString(); - } - - public String getAccuracyStringForLocation(Location location) { - return getString(R.string.location_provider_accuracy, GeoPointUtils.capitalizeGps(location.getProvider()), - truncateFloat(location.getAccuracy())); - } - - public AlertDialog getZoomDialog() { - return zoomDialog; - } - -} diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointOsmMapActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointOsmMapActivity.java deleted file mode 100644 index 923693709cb..00000000000 --- a/collect_app/src/main/java/org/odk/collect/android/activities/GeoPointOsmMapActivity.java +++ /dev/null @@ -1,612 +0,0 @@ -/* - * Copyright (C) 2016 GeoODK - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.odk.collect.android.activities; - -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.Color; -import android.location.Location; -import android.os.Bundle; -import android.os.Handler; -import android.provider.Settings; -import android.support.v4.content.ContextCompat; -import android.view.View; -import android.view.Window; -import android.widget.Button; -import android.widget.ImageButton; -import android.widget.TextView; - -import com.google.android.gms.location.LocationListener; - -import org.odk.collect.android.R; -import org.odk.collect.android.location.client.LocationClient; -import org.odk.collect.android.location.client.LocationClients; -import org.odk.collect.android.spatial.MapHelper; -import org.odk.collect.android.utilities.GeoPointUtils; -import org.odk.collect.android.utilities.ToastUtils; -import org.odk.collect.android.widgets.GeoPointWidget; -import org.osmdroid.events.MapEventsReceiver; -import org.osmdroid.tileprovider.IRegisterReceiver; -import org.osmdroid.util.GeoPoint; -import org.osmdroid.views.MapView; -import org.osmdroid.views.overlay.MapEventsOverlay; -import org.osmdroid.views.overlay.Marker; -import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay; - -import java.text.DecimalFormat; - -import timber.log.Timber; - -import static org.odk.collect.android.utilities.PermissionUtils.areLocationPermissionsGranted; - -/** - * Version of the GeoPointMapActivity that uses the new OSMDDroid - * - * @author jonnordling@gmail.com - */ -public class GeoPointOsmMapActivity extends BaseGeoMapActivity implements LocationListener, - Marker.OnMarkerDragListener, MapEventsReceiver, IRegisterReceiver, - LocationClient.LocationClientListener { - - private static final String LOCATION_COUNT = "locationCount"; - - //private GoogleMap map; - private MapView map; - - private final Handler handler = new Handler(); - private Marker marker; - - private GeoPoint latLng; - - private TextView locationStatus; - - private LocationClient locationClient; - - private Location location; - private ImageButton reloadLocationButton; - - private boolean captureLocation; - private boolean setClear; - private boolean isDragged; - private ImageButton showLocationButton; - private ImageButton clearPointButton; - - private int locationCount; - - private AlertDialog zoomDialog; - private View zoomDialogView; - - private Button zoomPointButton; - private Button zoomLocationButton; - - public MyLocationNewOverlay myLocationOverlay; - - private boolean readOnly; - private boolean draggable; - private boolean intentDraggable; - private boolean locationFromIntent; - private int locationCountNum; - private boolean foundFirstLocation; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (!areLocationPermissionsGranted(this)) { - finish(); - return; - } - - requestWindowFeature(Window.FEATURE_NO_TITLE); - - if (savedInstanceState != null) { - locationCount = savedInstanceState.getInt(LOCATION_COUNT); - } - - try { - setContentView(R.layout.geopoint_osm_layout); - } catch (NoClassDefFoundError e) { - ToastUtils.showShortToast(R.string.google_play_services_error_occured); - finish(); - return; - } - - map = findViewById(R.id.omap); - if (helper == null) { - // For testing: - helper = new MapHelper(this, map, this, selectedLayer); - - map.setMultiTouchControls(true); - map.setBuiltInZoomControls(true); - map.setTilesScaledToDpi(true); - } - - marker = new Marker(map); - marker.setIcon(ContextCompat.getDrawable(this, R.drawable.ic_place_black)); - myLocationOverlay = new MyLocationNewOverlay(map); - - handler.postDelayed(new Runnable() { - public void run() { - GeoPoint point = new GeoPoint(34.08145, -39.85007); - map.getController().setZoom(4); - map.getController().setCenter(point); - } - }, 100); - - locationStatus = findViewById(R.id.location_status); - TextView locationInfo = findViewById(R.id.location_info); - - locationClient = LocationClients.clientForContext(this); - locationClient.setListener(this); - - ImageButton saveLocationButton = findViewById(R.id.accept_location); - saveLocationButton.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - returnLocation(); - } - }); - - reloadLocationButton = findViewById(R.id.reload_location); - reloadLocationButton.setEnabled(false); - reloadLocationButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - addMarker(marker); - latLng = new GeoPoint(location.getLatitude(), location.getLongitude()); - marker.setPosition(latLng); - captureLocation = true; - isDragged = false; - zoomToPoint(); - } - - }); - - // Focuses on marked location - showLocationButton = findViewById(R.id.show_location); - showLocationButton.setVisibility(View.VISIBLE); - showLocationButton.setEnabled(false); - showLocationButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showZoomDialog(); - } - }); - - // not clickable until we have a marker set.... - showLocationButton.setClickable(false); - - // Menu Layer Toggle - ImageButton layersButton = findViewById(R.id.layer_menu); - layersButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - helper.showLayersDialog(); - - } - }); - - zoomDialogView = getLayoutInflater().inflate(R.layout.geo_zoom_dialog, null); - - zoomLocationButton = zoomDialogView.findViewById(R.id.zoom_location); - zoomLocationButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - zoomToLocation(); - map.invalidate(); - zoomDialog.dismiss(); - } - }); - - zoomPointButton = zoomDialogView.findViewById(R.id.zoom_saved_location); - zoomPointButton.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - zoomToPoint(); - map.invalidate(); - zoomDialog.dismiss(); - } - }); - - clearPointButton = findViewById(R.id.clear); - clearPointButton.setEnabled(false); - clearPointButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - map.getOverlays().remove(marker); - marker.remove(map); - if (location != null) { - reloadLocationButton.setEnabled(true); - //locationStatus.setVisibility(View.VISIBLE); - } - locationStatus.setVisibility(View.VISIBLE); - removeMarker(marker); - isDragged = false; - captureLocation = false; - draggable = intentDraggable; - locationFromIntent = false; - overlayMyLocationLayers(); - map.invalidate(); - } - }); - - Intent intent = getIntent(); - if (intent != null && intent.getExtras() != null) { - - if (intent.hasExtra(GeoPointWidget.DRAGGABLE_ONLY)) { - draggable = intent.getBooleanExtra(GeoPointWidget.DRAGGABLE_ONLY, false); - intentDraggable = draggable; - if (!intentDraggable) { - // Not Draggable, set text for Map else leave as placement-map text - locationInfo.setText(getString(R.string.geopoint_no_draggable_instruction)); - } - } - - if (intent.hasExtra(GeoPointWidget.READ_ONLY)) { - readOnly = intent.getBooleanExtra(GeoPointWidget.READ_ONLY, false); - if (readOnly) { - captureLocation = true; - clearPointButton.setEnabled(false); - } - } - - if (intent.hasExtra(GeoPointWidget.LOCATION)) { - double[] location = intent.getDoubleArrayExtra(GeoPointWidget.LOCATION); - latLng = new GeoPoint(location[0], location[1]); - reloadLocationButton.setEnabled(false); - captureLocation = true; - isDragged = true; - draggable = false; // If data loaded, must clear first - locationFromIntent = true; - } - } - - if (latLng != null) { - marker.setPosition(latLng); - addMarker(marker); - map.invalidate(); - captureLocation = true; - foundFirstLocation = true; - zoomToPoint(); - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(LOCATION_COUNT, locationCount); - } - - @Override - protected void onStart() { - super.onStart(); - locationClient.start(); - } - - @Override - protected void onResume() { - super.onResume(); - - if (map != null) { - helper.setBasemap(); - } - - } - - @Override - protected void onStop() { - locationClient.stop(); - super.onStop(); - } - - private void upMyLocationOverlayLayers() { - showLocationButton.setClickable(marker != null); - - // make sure we have a good location provider before continuing - locationClient.requestLocationUpdates(this); - - if (!locationClient.isLocationAvailable()) { - showGPSDisabledAlertToUser(); - - } else { - overlayMyLocationLayers(); - } - } - - private void overlayMyLocationLayers() { - map.getOverlays().add(myLocationOverlay); - if (draggable && !readOnly) { - if (marker != null) { - marker.setOnMarkerDragListener(this); - marker.setDraggable(true); - } - - MapEventsOverlay overlayEvents = new MapEventsOverlay(this); - map.getOverlays().add(overlayEvents); - } - - myLocationOverlay.setEnabled(true); - myLocationOverlay.enableMyLocation(); - } - - private void zoomToPoint() { - if (latLng != null) { - handler.postDelayed(new Runnable() { - public void run() { - map.getController().setZoom(16); - map.getController().setCenter(latLng); - map.invalidate(); - } - }, 200); - } - - } - - /** - * Sets up the look and actions for the progress dialog while the GPS is searching. - */ - public void returnLocation() { - Intent i = new Intent(); - if (setClear || (readOnly && latLng == null)) { - i.putExtra(FormEntryActivity.LOCATION_RESULT, ""); - setResult(RESULT_OK, i); - - } else if (isDragged || readOnly || locationFromIntent) { - i.putExtra( - FormEntryActivity.LOCATION_RESULT, - latLng.getLatitude() + " " + latLng.getLongitude() + " " - + 0 + " " + 0); - setResult(RESULT_OK, i); - } else if (location != null) { - i.putExtra( - FormEntryActivity.LOCATION_RESULT, - getResultString(location)); - setResult(RESULT_OK, i); - } else { - i.putExtra(FormEntryActivity.LOCATION_RESULT, ""); - setResult(RESULT_OK, i); - } - finish(); - } - - private String truncateFloat(float f) { - return new DecimalFormat("#.##").format(f); - } - - public String getResultString(Location location) { - return String.format("%s %s %s %s", location.getLatitude(), location.getLongitude(), location.getAltitude(), location.getAccuracy()); - } - - @Override - public void onLocationChanged(Location location) { - - this.location = location; - if (setClear) { - reloadLocationButton.setEnabled(true); - } - if (this.location != null) { - int locationCountFoundLimit = 1; - if (locationCountNum >= locationCountFoundLimit) { - showLocationButton.setEnabled(true); - if (!captureLocation & !setClear) { - latLng = new GeoPoint(this.location.getLatitude(), this.location.getLongitude()); - addMarker(marker); - marker.setPosition(latLng); - captureLocation = true; - reloadLocationButton.setEnabled(true); - } - if (!foundFirstLocation) { - // zoomToPoint(); - showZoomDialog(); - foundFirstLocation = true; - } - locationStatus.setText( - getString(R.string.location_provider_accuracy, GeoPointUtils.capitalizeGps(this.location.getProvider()), - truncateFloat(this.location.getAccuracy()))); - } else { - // Prevent from forever increasing - if (locationCountNum <= 100) { - locationCountNum++; - } - } - - //if (location.getLatitude() != marker.getPosition().getLatitude() & location - // .getLongitude() != marker.getPosition().getLongitude()) { - //reloadLocationButton.setEnabled(true); - //} - // - //If location is accurate enough, stop updating position and make the marker - // draggable - //if (location.getAccuracy() <= mLocationAccuracy) { - //stopGeolocating(); - //} - - } else { - Timber.i("onLocationChanged(%d) null location", locationCount); - } - } - - @Override - public void onMarkerDrag(Marker arg0) { - - } - - @Override - public void onMarkerDragEnd(Marker marker) { - latLng = marker.getPosition(); - isDragged = true; - captureLocation = true; - setClear = false; - map.getController().animateTo(latLng); - map.getController().setZoom(map.getZoomLevel()); - } - - @Override - public void onMarkerDragStart(Marker arg0) { - //stopGeolocating(); - } - - @Override - public boolean singleTapConfirmedHelper(GeoPoint geoPoint) { - return false; - } - - @Override - public boolean longPressHelper(GeoPoint geoPoint) { - if (marker == null) { - marker = new Marker(map); - - } - showLocationButton.setEnabled(true); - map.invalidate(); - marker.setPosition(geoPoint); - marker.setIcon(ContextCompat.getDrawable(this, R.drawable.ic_place_black)); - marker.setDraggable(true); - latLng = geoPoint; - isDragged = true; - captureLocation = true; - addMarker(marker); - return false; - } - - public void showZoomDialog() { - - if (zoomDialog == null) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.zoom_to_where)); - builder.setView(zoomDialogView) - .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }) - .setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - dialog.cancel(); - zoomDialog.dismiss(); - } - }); - zoomDialog = builder.create(); - } - //If feature enable zoom to button else disable - if (myLocationOverlay.getMyLocation() != null) { - zoomLocationButton.setEnabled(true); - zoomLocationButton.setBackgroundColor(Color.parseColor("#50cccccc")); - zoomLocationButton.setTextColor(themeUtils.getPrimaryTextColor()); - } else { - zoomLocationButton.setEnabled(false); - zoomLocationButton.setBackgroundColor(Color.parseColor("#50e2e2e2")); - zoomLocationButton.setTextColor(Color.parseColor("#FF979797")); - } - - if (latLng != null & !setClear) { - zoomPointButton.setEnabled(true); - zoomPointButton.setBackgroundColor(Color.parseColor("#50cccccc")); - zoomPointButton.setTextColor(themeUtils.getPrimaryTextColor()); - } else { - zoomPointButton.setEnabled(false); - zoomPointButton.setBackgroundColor(Color.parseColor("#50e2e2e2")); - zoomPointButton.setTextColor(Color.parseColor("#FF979797")); - } - zoomDialog.show(); - } - - private void zoomToLocation() { - if (myLocationOverlay.getMyLocation() != null) { - final GeoPoint location = new GeoPoint(this.location.getLatitude(), - this.location.getLongitude()); - handler.postDelayed(new Runnable() { - public void run() { - map.getController().setZoom(16); - map.getController().setCenter(location); - map.invalidate(); - } - }, 200); - } - } - - // add the marker and enable the trash button. - private void addMarker(Marker marker) { - map.getOverlays().add(marker); - clearPointButton.setEnabled(true); - setClear = false; - } - - // remove the marker and disable the trash button. - private void removeMarker(Marker marker) { - map.getOverlays().remove(marker); - marker.remove(map); - clearPointButton.setEnabled(false); - setClear = true; - } - - private void showGPSDisabledAlertToUser() { - AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); - alertDialogBuilder.setMessage(getString(R.string.gps_enable_message)) - .setCancelable(false) - .setPositiveButton(getString(R.string.enable_gps), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - startActivityForResult( - new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 0); - } - }); - alertDialogBuilder.setNegativeButton(getString(R.string.cancel), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - AlertDialog alert = alertDialogBuilder.create(); - alert.show(); - } - - @Override - public void destroy() { - - } - - @Override - public void onClientStart() { - upMyLocationOverlayLayers(); - } - - @Override - public void onClientStartFailure() { - showGPSDisabledAlertToUser(); - } - - @Override - public void onClientStop() { - - } - - public AlertDialog getZoomDialog() { - return zoomDialog; - } - - /** - * For testing purposes. - * - * @param helper The MapHelper to set. - */ - public void setHelper(MapHelper helper) { - this.helper = helper; - } -} diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/GeoPointWidget.java b/collect_app/src/main/java/org/odk/collect/android/widgets/GeoPointWidget.java index 5c7db8e0a84..36f26147cea 100644 --- a/collect_app/src/main/java/org/odk/collect/android/widgets/GeoPointWidget.java +++ b/collect_app/src/main/java/org/odk/collect/android/widgets/GeoPointWidget.java @@ -21,7 +21,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ConfigurationInfo; -import android.media.AudioManager; import android.preference.PreferenceManager; import android.view.View; import android.widget.Button; @@ -33,9 +32,7 @@ import org.javarosa.form.api.FormEntryPrompt; import org.odk.collect.android.R; import org.odk.collect.android.activities.GeoPointActivity; -import org.odk.collect.android.activities.GeoPointGoogleMapActivity; import org.odk.collect.android.activities.GeoPointMapActivity; -import org.odk.collect.android.activities.GeoPointOsmMapActivity; import org.odk.collect.android.listeners.PermissionListener; import org.odk.collect.android.preferences.GeneralKeys; import org.odk.collect.android.utilities.PlayServicesUtil; @@ -74,10 +71,6 @@ public class GeoPointWidget extends QuestionWidget implements BinaryWidget { private String stringAnswer; - private boolean isRingerSilent() { - return ((AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE)).getRingerMode() == AudioManager.RINGER_MODE_SILENT; - } - public GeoPointWidget(Context context, FormEntryPrompt prompt) { super(context, prompt); @@ -295,24 +288,16 @@ public void denied() { } private void startGeoPoint() { - Intent i; - if (useMapsV2 && useMaps) { - if (isRingerSilent()) { - i = new Intent(getContext(), GeoPointMapActivity.class); - i.putExtra(GeneralKeys.KEY_MAP_SDK, mapSDK); - } else if (mapSDK.equals(GOOGLE_MAP_KEY)) { - if (PlayServicesUtil.isGooglePlayServicesAvailable(getContext())) { - i = new Intent(getContext(), GeoPointGoogleMapActivity.class); - } else { - PlayServicesUtil.showGooglePlayServicesAvailabilityErrorDialog(getContext()); - return; - } - } else { - i = new Intent(getContext(), GeoPointOsmMapActivity.class); - } - } else { - i = new Intent(getContext(), GeoPointActivity.class); + Activity activity = (Activity) getContext(); + if (mapSDK.equals(GOOGLE_MAP_KEY) && + !PlayServicesUtil.isGooglePlayServicesAvailable(activity)) { + PlayServicesUtil.showGooglePlayServicesAvailabilityErrorDialog(activity); + return; } + Intent intent = (useMapsV2 && useMaps) ? + new Intent(activity, GeoPointMapActivity.class) + .putExtra(GeneralKeys.KEY_MAP_SDK, mapSDK) : + new Intent(activity, GeoPointActivity.class); if (stringAnswer != null && !stringAnswer.isEmpty()) { String[] sa = stringAnswer.split(" "); @@ -321,14 +306,13 @@ private void startGeoPoint() { gp[1] = Double.valueOf(sa[1]); gp[2] = Double.valueOf(sa[2]); gp[3] = Double.valueOf(sa[3]); - i.putExtra(LOCATION, gp); + intent.putExtra(LOCATION, gp); } - i.putExtra(READ_ONLY, readOnly); - i.putExtra(DRAGGABLE_ONLY, draggable); - i.putExtra(ACCURACY_THRESHOLD, accuracyThreshold); + intent.putExtra(READ_ONLY, readOnly); + intent.putExtra(DRAGGABLE_ONLY, draggable); + intent.putExtra(ACCURACY_THRESHOLD, accuracyThreshold); waitForData(); - - ((Activity) getContext()).startActivityForResult(i, RequestCodes.LOCATION_CAPTURE); + activity.startActivityForResult(intent, RequestCodes.LOCATION_CAPTURE); } } diff --git a/collect_app/src/main/res/layout/geopoint_google_layout.xml b/collect_app/src/main/res/layout/geopoint_google_layout.xml deleted file mode 100644 index b2b092c6ea0..00000000000 --- a/collect_app/src/main/res/layout/geopoint_google_layout.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/collect_app/src/main/res/layout/geopoint_osm_layout.xml b/collect_app/src/main/res/layout/geopoint_osm_layout.xml deleted file mode 100644 index 24904e9a3f9..00000000000 --- a/collect_app/src/main/res/layout/geopoint_osm_layout.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointGoogleMapActivityTest.java b/collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointGoogleMapActivityTest.java deleted file mode 100644 index 79ccefbd63a..00000000000 --- a/collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointGoogleMapActivityTest.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.odk.collect.android.location.activities; - -import android.content.Intent; -import android.location.Location; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; -import org.odk.collect.android.R; -import org.odk.collect.android.activities.GeoPointGoogleMapActivity; -import org.odk.collect.android.location.client.LocationClient; -import org.odk.collect.android.location.client.LocationClients; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.android.controller.ActivityController; -import org.robolectric.shadows.ShadowActivity; - -import static android.app.Activity.RESULT_OK; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.odk.collect.android.activities.FormEntryActivity.LOCATION_RESULT; -import static org.odk.collect.android.location.activities.GeoPointActivityTest.newMockLocation; -import static org.robolectric.Shadows.shadowOf; - -@RunWith(RobolectricTestRunner.class) -public class GeoPointGoogleMapActivityTest extends BaseGeoActivityTest { - - @Rule - public MockitoRule rule = MockitoJUnit.rule(); - - private ActivityController activityController; - - private GeoPointGoogleMapActivity activity; - private ShadowActivity shadowActivity; - - @Mock - LocationClient locationClient; - - /** - * Runs {@link Before} each test. - */ - @Before - public void setUp() throws Exception { - super.setUp(); - activityController = Robolectric.buildActivity(GeoPointGoogleMapActivity.class); - activity = activityController.get(); - shadowActivity = shadowOf(activity); - - LocationClients.setTestClient(locationClient); - } - - @Test - public void testLocationClientLifecycle() { - activityController.create(); - - // Create should prepare map async; onClientStartWill start location monitoring. - // Whichever happens second will pass forward to upMyLocationOverlayLayers. - when(locationClient.isMonitoringLocation()).thenReturn(true); - activity.setMapReady(true); - activity.setCaptureLocation(true); - - when(locationClient.isLocationAvailable()).thenReturn(true); - - activity.onClientStart(); - verify(locationClient).requestLocationUpdates(activity); - - assertNull(activity.getErrorDialog()); - - activity.onLocationChanged(newMockLocation()); - assertEquals(activity.getLocationStatus(), activity.getString(R.string.please_wait_long)); - assertNull(activity.getZoomDialog()); - - Location location = newMockLocation(); - when(location.getProvider()).thenReturn("GPS"); - when(location.getAccuracy()).thenReturn(1.0f); - when(location.getLatitude()).thenReturn(2.0); - when(location.getLongitude()).thenReturn(3.0); - when(location.getAltitude()).thenReturn(4.0); - - activity.onLocationChanged(location); - - assertEquals(activity.getLocationStatus(), activity.getAccuracyStringForLocation(location)); - assertNotNull(activity.getZoomDialog()); - - activity.getZoomDialog().dismiss(); - - activity.returnLocation(); - assertTrue(shadowActivity.isFinishing()); - - assertEquals(shadowActivity.getResultCode(), RESULT_OK); - - Intent resultIntent = shadowActivity.getResultIntent(); - assertEquals(resultIntent.getStringExtra(LOCATION_RESULT), activity.getResultString(location)); - } - - @Test - public void activityShouldOpenErrorDialogIfLocationIsUnavailable() { - activityController.create(); - - // Create should prepare map async; onClientStartWill start location monitoring. - // Whichever happens second will pass forward to upMyLocationOverlayLayers. - when(locationClient.isMonitoringLocation()).thenReturn(true); - activity.setMapReady(true); - - when(locationClient.isLocationAvailable()).thenReturn(false); - - activity.onClientStart(); - verify(locationClient).requestLocationUpdates(activity); - - assertNotNull(activity.getErrorDialog()); - } -} diff --git a/collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointOsmMapActivityTest.java b/collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointOsmMapActivityTest.java deleted file mode 100644 index 29cc49f78e0..00000000000 --- a/collect_app/src/test/java/org/odk/collect/android/location/activities/GeoPointOsmMapActivityTest.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.odk.collect.android.location.activities; - -import android.content.Intent; -import android.location.Location; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; -import org.odk.collect.android.activities.GeoPointOsmMapActivity; -import org.odk.collect.android.location.client.LocationClient; -import org.odk.collect.android.location.client.LocationClients; -import org.odk.collect.android.spatial.MapHelper; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.android.controller.ActivityController; -import org.robolectric.shadows.ShadowActivity; - -import static android.app.Activity.RESULT_OK; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.odk.collect.android.activities.FormEntryActivity.LOCATION_RESULT; -import static org.odk.collect.android.location.activities.GeoPointActivityTest.newMockLocation; -import static org.robolectric.Shadows.shadowOf; - -@RunWith(RobolectricTestRunner.class) -public class GeoPointOsmMapActivityTest extends BaseGeoActivityTest { - - @Rule - public MockitoRule rule = MockitoJUnit.rule(); - - private ActivityController activityController; - - private GeoPointOsmMapActivity activity; - private ShadowActivity shadowActivity; - - @Mock - LocationClient locationClient; - - @Mock - MapHelper mapHelper; - - /** - * Runs {@link Before} each test. - */ - @Before - public void setUp() throws Exception { - super.setUp(); - activityController = Robolectric.buildActivity(GeoPointOsmMapActivity.class); - activity = activityController.get(); - shadowActivity = shadowOf(activity); - - LocationClients.setTestClient(locationClient); - } - - @Test - public void testLocationClientLifecycle() { - activity.setHelper(mapHelper); - - activityController.create(); - activityController.start(); - - verify(locationClient).start(); - - when(locationClient.isLocationAvailable()).thenReturn(true); - - activity.onClientStart(); - - verify(locationClient).requestLocationUpdates(activity); - - Location location = newMockLocation(); - activity.onLocationChanged(location); - - assertNull(activity.getZoomDialog()); - - // Second location should do something: - Location secondLocation = newMockLocation(); - when(secondLocation.getProvider()).thenReturn("GPS"); - when(secondLocation.getAccuracy()).thenReturn(1.0f); - when(secondLocation.getLatitude()).thenReturn(2.0); - when(secondLocation.getLongitude()).thenReturn(3.0); - when(secondLocation.getAltitude()).thenReturn(4.0); - - activity.onLocationChanged(secondLocation); - - assertNotNull(activity.getZoomDialog()); - activity.getZoomDialog().dismiss(); - - activity.returnLocation(); - - assertEquals(shadowActivity.getResultCode(), RESULT_OK); - Intent resultIntent = shadowActivity.getResultIntent(); - - assertEquals(resultIntent.getStringExtra(LOCATION_RESULT), - activity.getResultString(secondLocation)); - - } -} \ No newline at end of file