[android] Revise handling of tracking modes and interactions

with touch gestures.

This fixes issues #6549 and #6567. Also pertinent to #6557.
Additional code has been added to the test app (Activity "User Tracking
Mode") to test.
A potential race condition in the MapboxMap#easeCamera() methods where
a camera change generated by tracking which could have reset the tracking
modes has been eliminated by factoring out a new method
MapboxMap#easeCameraInternal().
This commit is contained in:
clydebarrow
2016-10-04 18:13:02 +11:00
committed by Tobrun
parent 214ec4e96a
commit a4b0e2f30c
9 changed files with 236 additions and 161 deletions

View File

@@ -107,6 +107,8 @@ public class MapboxConstants {
public static final String STATE_MY_LOCATION_ENABLED = "myLocationEnabled";
public static final String STATE_MY_LOCATION_TRACKING_MODE = "myLocationTracking";
public static final String STATE_MY_BEARING_TRACKING_MODE = "myBearingTracking";
public static final String STATE_MY_LOCATION_TRACKING_DISMISS = "myLocationTrackingDismiss";
public static final String STATE_MY_BEARING_TRACKING_DISMISS = "myBearingTrackingDismiss";
public static final String STATE_COMPASS_ENABLED = "compassEnabled";
public static final String STATE_COMPASS_GRAVITY = "compassGravity";
public static final String STATE_COMPASS_MARGIN_LEFT = "compassMarginLeft";

View File

@@ -444,7 +444,11 @@ public class MapView extends FrameLayout {
savedInstanceState.getInt(MapboxConstants.STATE_MY_LOCATION_TRACKING_MODE, MyLocationTracking.TRACKING_NONE));
//noinspection ResourceType
trackingSettings.setMyBearingTrackingMode(
savedInstanceState.getInt(MapboxConstants.STATE_MY_BEARING_TRACKING_MODE, MyBearingTracking.NONE));
savedInstanceState.getInt(MapboxConstants.STATE_MY_BEARING_TRACKING_MODE, MyBearingTracking.NONE));
trackingSettings.setDismissLocationTrackingOnGesture(
savedInstanceState.getBoolean(MapboxConstants.STATE_MY_LOCATION_TRACKING_DISMISS, true));
trackingSettings.setDismissBearingTrackingOnGesture(
savedInstanceState.getBoolean(MapboxConstants.STATE_MY_BEARING_TRACKING_DISMISS, true));
} else if (savedInstanceState == null) {
// Start Telemetry (authorization determined in initial MapboxEventManager constructor)
Log.i(MapView.class.getCanonicalName(), "MapView start Telemetry...");
@@ -517,6 +521,8 @@ public class MapView extends FrameLayout {
TrackingSettings trackingSettings = mapboxMap.getTrackingSettings();
outState.putInt(MapboxConstants.STATE_MY_LOCATION_TRACKING_MODE, trackingSettings.getMyLocationTrackingMode());
outState.putInt(MapboxConstants.STATE_MY_BEARING_TRACKING_MODE, trackingSettings.getMyBearingTrackingMode());
outState.putBoolean(MapboxConstants.STATE_MY_LOCATION_TRACKING_DISMISS, trackingSettings.isDismissLocationTrackingOnGesture());
outState.putBoolean(MapboxConstants.STATE_MY_BEARING_TRACKING_DISMISS, trackingSettings.isDismissBearingTrackingOnGesture());
// UiSettings
UiSettings uiSettings = mapboxMap.getUiSettings();
@@ -1822,7 +1828,7 @@ public class MapView extends FrameLayout {
(tapPoint.y - averageIconHeight / 2 - toleranceTopBottom) / screenDensity,
(tapPoint.x + averageIconWidth / 2 + toleranceSides) / screenDensity,
(tapPoint.y + averageIconHeight / 2 + toleranceTopBottom) / screenDensity);
List<Marker> nearbyMarkers = getMarkersInRect(tapRect);
long newSelectedMarkerId = -1;
@@ -1890,12 +1896,11 @@ public class MapView extends FrameLayout {
// Called for flings
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (destroyed || !mapboxMap.getUiSettings().isScrollGesturesEnabled()) {
if (destroyed || !mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) {
return false;
}
// reset tracking modes if gesture occurs
resetTrackingModesIfRequired();
resetTrackingModesIfRequired(true, false);
// Fling the map
float ease = 0.25f;
@@ -1927,7 +1932,7 @@ public class MapView extends FrameLayout {
if (!scrollInProgress) {
scrollInProgress = true;
}
if (destroyed || !mapboxMap.getUiSettings().isScrollGesturesEnabled()) {
if (destroyed || !mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) {
return false;
}
@@ -1937,9 +1942,8 @@ public class MapView extends FrameLayout {
requestDisallowInterceptTouchEvent(true);
// reset tracking modes if gesture occurs
resetTrackingModesIfRequired();
// reset tracking if needed
resetTrackingModesIfRequired(true, false);
// Cancel any animation
nativeMapView.cancelTransitions();
@@ -1967,9 +1971,6 @@ public class MapView extends FrameLayout {
return false;
}
// reset tracking modes if gesture occurs
resetTrackingModesIfRequired();
beginTime = detector.getEventTime();
trackGestureEvent(MapboxEvent.GESTURE_PINCH_START, detector.getFocusX(), detector.getFocusY());
return true;
@@ -2020,6 +2021,11 @@ public class MapView extends FrameLayout {
// Gesture is a quickzoom if there aren't two fingers
quickZoom = !twoTap;
// make an assumption here; if the zoom center is specified by the gesture, it's NOT going
// to be in the center of the map. Therefore the zoom will translate the map center, so tracking
// should be disabled.
resetTrackingModesIfRequired(!quickZoom, false);
// Scale the map
if (focalPoint != null) {
// arround user provided focal point
@@ -2046,12 +2052,8 @@ public class MapView extends FrameLayout {
// Called when two fingers first touch the screen
@Override
public boolean onRotateBegin(RotateGestureDetector detector) {
if (destroyed || !mapboxMap.getUiSettings().isRotateGesturesEnabled()) {
if (destroyed || !mapboxMap.getTrackingSettings().isRotateGestureCurrentlyEnabled())
return false;
}
// reset tracking modes if gesture occurs
resetTrackingModesIfRequired();
beginTime = detector.getEventTime();
trackGestureEvent(MapboxEvent.GESTURE_ROTATION_START, detector.getFocusX(), detector.getFocusY());
@@ -2070,9 +2072,8 @@ public class MapView extends FrameLayout {
// Called for rotation
@Override
public boolean onRotate(RotateGestureDetector detector) {
if (destroyed || !mapboxMap.getUiSettings().isRotateGesturesEnabled()) {
if (destroyed || !mapboxMap.getTrackingSettings().isRotateGestureCurrentlyEnabled())
return false;
}
if (dragStarted) {
return false;
@@ -2100,6 +2101,11 @@ public class MapView extends FrameLayout {
// Cancel any animation
nativeMapView.cancelTransitions();
// rotation constitutes translation of anything except the center of
// rotation, so cancel both location and bearing tracking if required
resetTrackingModesIfRequired(true, true);
// Get rotate value
double bearing = nativeMapView.getBearing();
bearing += detector.getRotationDegreesDelta();
@@ -2130,9 +2136,6 @@ public class MapView extends FrameLayout {
return false;
}
// reset tracking modes if gesture occurs
resetTrackingModesIfRequired();
beginTime = detector.getEventTime();
trackGestureEvent(MapboxEvent.GESTURE_PITCH_START, detector.getFocusX(), detector.getFocusY());
return true;
@@ -2234,7 +2237,7 @@ public class MapView extends FrameLayout {
return true;
case KeyEvent.KEYCODE_DPAD_LEFT:
if (!mapboxMap.getUiSettings().isScrollGesturesEnabled()) {
if (!mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) {
return false;
}
@@ -2246,7 +2249,7 @@ public class MapView extends FrameLayout {
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (!mapboxMap.getUiSettings().isScrollGesturesEnabled()) {
if (!mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) {
return false;
}
@@ -2258,7 +2261,7 @@ public class MapView extends FrameLayout {
return true;
case KeyEvent.KEYCODE_DPAD_UP:
if (!mapboxMap.getUiSettings().isScrollGesturesEnabled()) {
if (!mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) {
return false;
}
@@ -2270,7 +2273,7 @@ public class MapView extends FrameLayout {
return true;
case KeyEvent.KEYCODE_DPAD_DOWN:
if (!mapboxMap.getUiSettings().isScrollGesturesEnabled()) {
if (!mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) {
return false;
}
@@ -2350,7 +2353,7 @@ public class MapView extends FrameLayout {
switch (event.getActionMasked()) {
// The trackball was rotated
case MotionEvent.ACTION_MOVE:
if (!mapboxMap.getUiSettings().isScrollGesturesEnabled()) {
if (!mapboxMap.getTrackingSettings().isScrollGestureCurrentlyEnabled()) {
return false;
}
@@ -2634,14 +2637,26 @@ public class MapView extends FrameLayout {
ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
void resetTrackingModesIfRequired() {
/**
* Reset the tracking modes as necessary. Location tracking is reset if the map center is changed,
* bearing tracking if there is a rotation.
*
* @param translate
* @param rotate
*/
void resetTrackingModesIfRequired(boolean translate, boolean rotate) {
TrackingSettings trackingSettings = mapboxMap.getTrackingSettings();
if (trackingSettings.isDismissLocationTrackingOnGesture()) {
// if tracking is on, and we should dismiss tracking with gestures, and this is a scroll action, turn tracking off
if (translate && !trackingSettings.isLocationTrackingDisabled() && trackingSettings.isDismissLocationTrackingOnGesture())
resetLocationTrackingMode();
}
if (trackingSettings.isDismissBearingTrackingOnGesture()) {
// reset bearing tracking only on rotate
if (rotate && !trackingSettings.isBearingTrackingDisabled() && trackingSettings.isDismissBearingTrackingOnGesture())
resetBearingTrackingMode();
}
}
void resetTrackingModesIfRequired(CameraPosition cameraPosition ) {
resetTrackingModesIfRequired(cameraPosition.target != null, cameraPosition.bearing != -1);
}
private void resetLocationTrackingMode() {

View File

@@ -390,11 +390,9 @@ public class MapboxMap {
@UiThread
public final void moveCamera(CameraUpdate update, MapboxMap.CancelableCallback callback) {
// dismiss tracking, moving camera is equal to a gesture
if (!trackingSettings.isLocationTrackingDisabled()) {
mapView.resetTrackingModesIfRequired();
}
cameraPosition = update.getCameraPosition(this);
mapView.resetTrackingModesIfRequired(cameraPosition);
mapView.jumpTo(cameraPosition.bearing, cameraPosition.target, cameraPosition.tilt, cameraPosition.zoom);
if (callback != null) {
callback.onFinish();
@@ -435,6 +433,8 @@ public class MapboxMap {
* unless specified within {@link CameraUpdate}. A callback can be used to be notified when
* easing the camera stops. If {@link #getCameraPosition()} is called during the animation, it
* will return the current location of the camera in flight.
* <p>
* Note that this will cancel location tracking mode if enabled.
*
* @param update The change that should be applied to the camera.
* @param durationMs The duration of the animation in milliseconds. This must be strictly
@@ -458,31 +458,38 @@ public class MapboxMap {
@UiThread
public final void easeCamera(
CameraUpdate update, int durationMs, boolean easingInterpolator, final MapboxMap.CancelableCallback callback) {
CameraUpdate update, int durationMs, boolean easingInterpolator, final MapboxMap.CancelableCallback callback) {
// dismiss tracking, moving camera is equal to a gesture
if (!trackingSettings.isLocationTrackingDisabled()) {
mapView.resetTrackingModesIfRequired();
}
CameraPosition cameraPosition = update.getCameraPosition(this);
mapView.resetTrackingModesIfRequired(cameraPosition);
easeCameraInternal(cameraPosition, durationMs, easingInterpolator, callback);
}
cameraPosition = update.getCameraPosition(this);
/**
* Internal use only.
* Used by tracking actions.
*/
@UiThread
public final void easeCameraInternal(
CameraPosition cameraPosition, int durationMs, boolean easingInterpolator, final MapboxMap.CancelableCallback callback) {
mapView.easeTo(cameraPosition.bearing, cameraPosition.target, getDurationNano(durationMs), cameraPosition.tilt,
cameraPosition.zoom, easingInterpolator, new CancelableCallback() {
@Override
public void onCancel() {
if (callback != null) {
callback.onCancel();
}
invalidateCameraPosition();
}
cameraPosition.zoom, easingInterpolator, new CancelableCallback() {
@Override
public void onCancel() {
if (callback != null) {
callback.onCancel();
}
invalidateCameraPosition();
}
@Override
public void onFinish() {
if (callback != null) {
callback.onFinish();
}
invalidateCameraPosition();
}
});
@Override
public void onFinish() {
if (callback != null) {
callback.onFinish();
}
invalidateCameraPosition();
}
});
}
/**
@@ -553,33 +560,31 @@ public class MapboxMap {
@UiThread
public final void animateCamera(CameraUpdate update, int durationMs, final MapboxMap.CancelableCallback callback) {
// dismiss tracking, moving camera is equal to a gesture
if (!trackingSettings.isLocationTrackingDisabled() && trackingSettings.isDismissLocationTrackingOnGesture()) {
mapView.resetTrackingModesIfRequired();
}
cameraPosition = update.getCameraPosition(this);
mapView.resetTrackingModesIfRequired(cameraPosition);
mapView.flyTo(cameraPosition.bearing, cameraPosition.target, getDurationNano(durationMs), cameraPosition.tilt,
cameraPosition.zoom, new CancelableCallback() {
@Override
public void onCancel() {
if (callback != null) {
callback.onCancel();
}
invalidateCameraPosition();
}
cameraPosition.zoom, new CancelableCallback() {
@Override
public void onCancel() {
if (callback != null) {
callback.onCancel();
}
invalidateCameraPosition();
}
@Override
public void onFinish() {
if (onCameraChangeListener != null) {
onCameraChangeListener.onCameraChange(cameraPosition);
}
@Override
public void onFinish() {
if (onCameraChangeListener != null) {
onCameraChangeListener.onCameraChange(cameraPosition);
}
if (callback != null) {
callback.onFinish();
}
invalidateCameraPosition();
}
});
if (callback != null) {
callback.onFinish();
}
invalidateCameraPosition();
}
});
}
/**
@@ -1706,7 +1711,7 @@ public class MapboxMap {
public void setMyLocationEnabled(boolean enabled) {
if (!mapView.isPermissionsAccepted()) {
Log.e(MapboxConstants.TAG, "Could not activate user location tracking: "
+ "user did not accept the permission or permissions were not requested.");
+ "user did not accept the permission or permissions were not requested.");
return;
}
myLocationEnabled = enabled;
@@ -1838,7 +1843,6 @@ public class MapboxMap {
return mapView.getNativeMapView().queryRenderedFeatures(coordinates, layerIds);
}
//
// Interfaces
//

View File

@@ -45,7 +45,6 @@ public class TrackingSettings {
public void setMyLocationTrackingMode(@MyLocationTracking.Mode int myLocationTrackingMode) {
this.myLocationTrackingMode = myLocationTrackingMode;
mapView.setMyLocationTrackingMode(myLocationTrackingMode);
validateGesturesForLocationTrackingMode();
}
/**
@@ -80,7 +79,6 @@ public class TrackingSettings {
public void setMyBearingTrackingMode(@MyBearingTracking.Mode int myBearingTrackingMode) {
this.myBearingTrackingMode = myBearingTrackingMode;
mapView.setMyBearingTrackingMode(myBearingTrackingMode);
validateGesturesForBearingTrackingMode();
}
/**
@@ -135,7 +133,6 @@ public class TrackingSettings {
public void setDismissAllTrackingOnGesture(boolean dismissTrackingOnGesture) {
dismissLocationTrackingOnGesture = dismissTrackingOnGesture;
dismissBearingTrackingOnGesture = dismissTrackingOnGesture;
validateAllGesturesForTrackingModes();
}
/**
@@ -145,7 +142,6 @@ public class TrackingSettings {
*/
public void setDismissLocationTrackingOnGesture(boolean dismissLocationTrackingOnGesture) {
this.dismissLocationTrackingOnGesture = dismissLocationTrackingOnGesture;
validateGesturesForLocationTrackingMode();
}
/**
@@ -164,7 +160,6 @@ public class TrackingSettings {
*/
public void setDismissBearingTrackingOnGesture(boolean dismissBearingTrackingOnGesture) {
this.dismissBearingTrackingOnGesture = dismissBearingTrackingOnGesture;
validateGesturesForBearingTrackingMode();
}
/**
@@ -186,42 +181,37 @@ public class TrackingSettings {
}
/**
* Retyrns uf bearing tracking is disabled
* Is bearing tracking disabled?
*
* @return True if bearing tracking will be disabled.
* @return True if bearing tracking is disabled.
*/
public boolean isBearingTrackingDisabled() {
return myBearingTrackingMode == MyBearingTracking.NONE;
}
private void validateAllGesturesForTrackingModes() {
validateGesturesForBearingTrackingMode();
validateGesturesForLocationTrackingMode();
/**
* Is the map currently in a state where rotate gestures are recognised?
* This requires both that the user interface has such gestures enabled,
* and that they are not currently blocked by a bearing tracking mode.
*/
public boolean isRotateGestureCurrentlyEnabled() {
// rotate gestures are recognised if:
// The user settings are enabled AND;
// EITHER bearing tracking is dismissed on gesture OR there is no bearing tracking
return uiSettings.isRotateGesturesEnabled() &&
(dismissBearingTrackingOnGesture || myBearingTrackingMode == MyBearingTracking.NONE);
}
private void validateGesturesForLocationTrackingMode() {
int myLocationTrackingMode = getMyLocationTrackingMode();
if (!dismissLocationTrackingOnGesture) {
if (myLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
uiSettings.setScrollGesturesEnabled(true);
} else {
uiSettings.setScrollGesturesEnabled(false);
}
} else {
uiSettings.setScrollGesturesEnabled(true);
}
}
/**
* Is the map currently in a state where scroll gestures are recognised?
* This requires both that the user interface has such gestures enabled,
* and that they are not currently blocked by a location tracking mode.
*/
private void validateGesturesForBearingTrackingMode() {
int myBearingTrackingMode = getMyBearingTrackingMode();
if (!dismissBearingTrackingOnGesture) {
if (myBearingTrackingMode == MyBearingTracking.NONE || myLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
uiSettings.setRotateGesturesEnabled(true);
} else {
uiSettings.setRotateGesturesEnabled(false);
}
} else {
uiSettings.setRotateGesturesEnabled(true);
}
public boolean isScrollGestureCurrentlyEnabled() {
return uiSettings.isScrollGesturesEnabled() &&
(dismissLocationTrackingOnGesture || myLocationTrackingMode == MyLocationTracking.TRACKING_NONE);
}
}

View File

@@ -457,16 +457,16 @@ public class MyLocationView extends View {
}
public void setMyLocationTrackingMode(@MyLocationTracking.Mode int myLocationTrackingMode) {
this.myLocationTrackingMode = myLocationTrackingMode;
MyLocationBehaviorFactory factory = new MyLocationBehaviorFactory();
myLocationBehavior = factory.getBehavioralModel(myLocationTrackingMode);
if (myLocationTrackingMode != MyLocationTracking.TRACKING_NONE && location != null) {
if (myLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW && location != null) {
// center map directly if we have a location fix
myLocationBehavior.updateLatLng(location);
mapboxMap.moveCamera(CameraUpdateFactory.newLatLng(new LatLng(location)));
mapboxMap.easeCameraInternal(CameraUpdateFactory.newLatLng(new LatLng(location)).getCameraPosition(mapboxMap), 0, false, null);
}
this.myLocationTrackingMode = myLocationTrackingMode;
invalidate();
update();
}
@@ -586,7 +586,7 @@ public class MyLocationView extends View {
private void rotateCamera() {
CameraPosition.Builder builder = new CameraPosition.Builder();
builder.bearing(currentDegree);
mapboxMap.easeCamera(CameraUpdateFactory.newCameraPosition(builder.build()), COMPASS_UPDATE_RATE_MS, false /*linear interpolator*/);
mapboxMap.easeCameraInternal(CameraUpdateFactory.newCameraPosition(builder.build()).getCameraPosition(mapboxMap), COMPASS_UPDATE_RATE_MS, false /*linear interpolator*/, null);
}
@Override
@@ -702,7 +702,7 @@ public class MyLocationView extends View {
updateAccuracy(location);
// ease to new camera position with a linear interpolator
mapboxMap.easeCamera(CameraUpdateFactory.newCameraPosition(builder.build()), (int) locationUpdateDuration, false /*linear interpolator*/);
mapboxMap.easeCameraInternal(CameraUpdateFactory.newCameraPosition(builder.build()).getCameraPosition(mapboxMap), (int) locationUpdateDuration, false /*linear interpolator*/, null);
}
@Override
@@ -765,7 +765,8 @@ public class MyLocationView extends View {
@Override
void invalidate() {
screenLocation = projection.toScreenLocation(latLng);
if(latLng != null)
screenLocation = projection.toScreenLocation(latLng);
MyLocationView.this.invalidate();
}
}