Skip to content

Commit 7e11bad

Browse files
rigdernfacebook-github-bot
authored andcommitted
Android: Improve getCurrentPosition API
Summary: We ran into a couple of problems with the implementation of `getCurrentPosition` on Android: - It sometimes returns an inaccurate location - It times out when `enableHighAccuracy` is `true` (#7495) This change improves `getCurrentPosition` for both of the above problems. Instead of calling `requestSingleUpdate` it now calls `requestLocationUpdates` so it can receive multiple locations giving it an opportunity to pick a better one. Unlike `requestSingleUpdate`, this approach doesn't seem to timeout when `enableHighAccuracy` is `true`. **Test plan (required)** Verified in a test app that `getCurrentPosition` returns a good location and doesn't timeout when `enableHighAccuracy` is `true`. Also, my team has been using this change in our app in production. Adam Comella Microsoft Corp. Closes #15094 Differential Revision: D5632100 Pulled By: hramos fbshipit-source-id: 86e40b01d941a13820cb775bccad7e19dba3d692
1 parent 4000202 commit 7e11bad

File tree

2 files changed

+75
-6
lines changed

2 files changed

+75
-6
lines changed

ReactAndroid/src/main/java/com/facebook/react/modules/location/BUCK

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ android_library(
77
"PUBLIC",
88
],
99
deps = [
10+
react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"),
1011
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
1112
react_native_dep("third-party/java/jsr-305:jsr-305"),
1213
react_native_target("java/com/facebook/react/bridge:bridge"),

ReactAndroid/src/main/java/com/facebook/react/modules/location/LocationModule.java

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
import com.facebook.react.module.annotations.ReactModule;
2929
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
3030

31+
import com.facebook.react.common.ReactConstants;
32+
import com.facebook.common.logging.FLog;
33+
3134
import javax.annotation.Nullable;
3235

3336
/**
@@ -129,13 +132,13 @@ public void getCurrentPosition(
129132
return;
130133
}
131134
Location location = locationManager.getLastKnownLocation(provider);
132-
if (location != null &&
133-
SystemClock.currentTimeMillis() - location.getTime() < locationOptions.maximumAge) {
135+
if (location != null && (SystemClock.currentTimeMillis() - location.getTime()) < locationOptions.maximumAge) {
134136
success.invoke(locationToMap(location));
135137
return;
136138
}
139+
137140
new SingleUpdateRequest(locationManager, provider, locationOptions.timeout, success, error)
138-
.invoke();
141+
.invoke(location);
139142
} catch (SecurityException e) {
140143
throwLocationPermissionMissing(e);
141144
}
@@ -246,6 +249,7 @@ private static class SingleUpdateRequest {
246249
private final LocationManager mLocationManager;
247250
private final String mProvider;
248251
private final long mTimeout;
252+
private Location mOldLocation;
249253
private final Handler mHandler = new Handler();
250254
private final Runnable mTimeoutRunnable = new Runnable() {
251255
@Override
@@ -254,6 +258,7 @@ public void run() {
254258
if (!mTriggered) {
255259
mError.invoke(PositionError.buildError(PositionError.TIMEOUT, "Location request timed out"));
256260
mLocationManager.removeUpdates(mLocationListener);
261+
FLog.i(ReactConstants.TAG, "LocationModule: Location request timed out");
257262
mTriggered = true;
258263
}
259264
}
@@ -263,11 +268,14 @@ public void run() {
263268
@Override
264269
public void onLocationChanged(Location location) {
265270
synchronized (SingleUpdateRequest.this) {
266-
if (!mTriggered) {
271+
if (!mTriggered && isBetterLocation(location, mOldLocation)) {
267272
mSuccess.invoke(locationToMap(location));
268273
mHandler.removeCallbacks(mTimeoutRunnable);
269274
mTriggered = true;
275+
mLocationManager.removeUpdates(mLocationListener);
270276
}
277+
278+
mOldLocation = location;
271279
}
272280
}
273281

@@ -295,9 +303,69 @@ private SingleUpdateRequest(
295303
mError = error;
296304
}
297305

298-
public void invoke() {
299-
mLocationManager.requestSingleUpdate(mProvider, mLocationListener, null);
306+
public void invoke(Location location) {
307+
mOldLocation = location;
308+
mLocationManager.requestLocationUpdates(mProvider, 100, 1, mLocationListener);
300309
mHandler.postDelayed(mTimeoutRunnable, mTimeout);
301310
}
311+
312+
private static final int TWO_MINUTES = 1000 * 60 * 2;
313+
314+
/** Determines whether one Location reading is better than the current Location fix
315+
* taken from Android Examples https://developer.android.com/guide/topics/location/strategies.html
316+
*
317+
* @param location The new Location that you want to evaluate
318+
* @param currentBestLocation The current Location fix, to which you want to compare the new one
319+
*/
320+
private boolean isBetterLocation(Location location, Location currentBestLocation) {
321+
if (currentBestLocation == null) {
322+
// A new location is always better than no location
323+
return true;
324+
}
325+
326+
// Check whether the new location fix is newer or older
327+
long timeDelta = location.getTime() - currentBestLocation.getTime();
328+
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
329+
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
330+
boolean isNewer = timeDelta > 0;
331+
332+
// If it's been more than two minutes since the current location, use the new location
333+
// because the user has likely moved
334+
if (isSignificantlyNewer) {
335+
return true;
336+
// If the new location is more than two minutes older, it must be worse
337+
} else if (isSignificantlyOlder) {
338+
return false;
339+
}
340+
341+
// Check whether the new location fix is more or less accurate
342+
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
343+
boolean isLessAccurate = accuracyDelta > 0;
344+
boolean isMoreAccurate = accuracyDelta < 0;
345+
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
346+
347+
// Check if the old and new location are from the same provider
348+
boolean isFromSameProvider = isSameProvider(location.getProvider(),
349+
currentBestLocation.getProvider());
350+
351+
// Determine location quality using a combination of timeliness and accuracy
352+
if (isMoreAccurate) {
353+
return true;
354+
} else if (isNewer && !isLessAccurate) {
355+
return true;
356+
} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
357+
return true;
358+
}
359+
360+
return false;
361+
}
362+
363+
/** Checks whether two providers are the same */
364+
private boolean isSameProvider(String provider1, String provider2) {
365+
if (provider1 == null) {
366+
return provider2 == null;
367+
}
368+
return provider1.equals(provider2);
369+
}
302370
}
303371
}

0 commit comments

Comments
 (0)