listenerRegistrations = entry.getValue();
+ for (int i = listenerRegistrations.size() - 1; i >= 0; i--) {
+ LocationListener listener = listenerRegistrations.get(i).listener;
+ if (removedLocationListeners.contains(listener)) {
+ listenerRegistrations.remove(i);
+ }
+ }
+ }
+ }
+
+ @Implementation
+ public void removeUpdates(PendingIntent pendingIntent) {
+ while (requestLocationUdpateCriteriaPendingIntents.containsKey(pendingIntent)) {
+ requestLocationUdpateCriteriaPendingIntents.remove(pendingIntent);
+ }
+ while (requestLocationUdpateProviderPendingIntents.containsKey(pendingIntent)) {
+ requestLocationUdpateProviderPendingIntents.remove(pendingIntent);
+ }
+ }
+
+ public boolean hasGpsStatusListener(GpsStatus.Listener listener) {
+ return gpsStatusListeners.contains(listener);
+ }
+
+ /**
+ * Non-Android accessor.
+ *
+ *
+ * Gets the criteria value used in the last call to {@link #getBestProvider(
+ * android.location.Criteria, boolean)}
+ *
+ * @return the criteria used to find the best provider
+ */
+ public Criteria getLastBestProviderCriteria() {
+ return lastBestProviderCriteria;
+ }
+
+ /**
+ * Non-Android accessor.
+ *
+ *
+ * Gets the enabled value used in the last call to {@link #getBestProvider(
+ * android.location.Criteria, boolean)}
+ *
+ * @return the enabled value used to find the best provider
+ */
+ public boolean getLastBestProviderEnabledOnly() {
+ return lastBestProviderEnabled;
+ }
+
+ /**
+ * Sets the value to return from {@link #getBestProvider(android.location.Criteria, boolean)}
+ * for the given
+ * {@code provider}
+ *
+ * @param provider name of the provider who should be considered best
+ * @param enabled Enabled
+ * @param criteria List of criteria
+ * @throws Exception if provider is not known
+ * @return false If provider is not enabled but it is supposed to be set as the best enabled
+ * provider don't set it, otherwise true
+ */
+ public boolean setBestProvider(String provider, boolean enabled, List criteria)
+ throws Exception {
+ if (!getAllProviders().contains(provider)) {
+ throw new IllegalStateException("Best provider is not a known provider");
+ }
+ // If provider is not enabled but it is supposed to be set as the best enabled provider
+ // don't set it.
+ for (String prvdr : providersEnabled.keySet()) {
+ if (provider.equals(prvdr) && providersEnabled.get(prvdr).enabled != enabled) {
+ return false;
+ }
+ }
+
+ if (enabled) {
+ bestEnabledProvider = provider;
+ if (provider.equals(bestDisabledProvider)) {
+ bestDisabledProvider = null;
+ }
+ } else {
+ bestDisabledProvider = provider;
+ if (provider.equals(bestEnabledProvider)) {
+ bestEnabledProvider = null;
+ }
+ }
+ if (criteria == null) {
+ return true;
+ }
+ LocationProviderEntry entry;
+ if (!providersEnabled.containsKey(provider)) {
+ entry = new LocationProviderEntry();
+ entry.enabled = enabled;
+ entry.criteria = criteria;
+ } else {
+ entry = providersEnabled.get(provider);
+ }
+ providersEnabled.put(provider, entry);
+
+ return true;
+ }
+
+ public boolean setBestProvider(String provider, boolean enabled) throws Exception {
+ return setBestProvider(provider, enabled, null);
+ }
+
+ /**
+ * Sets the value to return from {@link #getLastKnownLocation(String)} for the given {@code
+ * provider}
+ *
+ * @param provider
+ * name of the provider whose location to set
+ * @param location
+ * the last known location for the provider
+ */
+ public void setLastKnownLocation(String provider, Location location) {
+ lastKnownLocations.put(provider, location);
+ }
+
+ /**
+ * Non-Android accessor.
+ *
+ * @return lastRequestedLocationUpdatesLocationListener
+ */
+ public List getRequestLocationUpdateListeners() {
+ cleanupRemovedLocationListeners();
+ List all = new ArrayList<>();
+ for (Map.Entry> entry : locationListeners.entrySet()) {
+ for (ListenerRegistration reg : entry.getValue()) {
+ all.add(reg.listener);
+ }
+ }
+
+ return all;
+ }
+
+ public void simulateLocation(Location location) {
+ cleanupRemovedLocationListeners();
+ setLastKnownLocation(location.getProvider(), location);
+
+ List providerListeners = locationListeners.get(
+ location.getProvider());
+ if (providerListeners == null) {
+ return;
+ }
+
+ for (ListenerRegistration listenerReg : providerListeners) {
+ if (listenerReg.lastSeenLocation != null && location != null) {
+ float distanceChange = distanceBetween(location, listenerReg.lastSeenLocation);
+ boolean withinMinDistance = distanceChange < listenerReg.minDistance;
+ boolean exceededMinTime = location.getTime() - listenerReg.lastSeenTime >
+ listenerReg.minTime;
+ if (withinMinDistance || !exceededMinTime) {
+ continue;
+ }
+ }
+ listenerReg.lastSeenLocation = copyOf(location);
+ listenerReg.lastSeenTime = location == null ? 0 : location.getTime();
+ listenerReg.listener.onLocationChanged(copyOf(location));
+ }
+ cleanupRemovedLocationListeners();
+ }
+
+ private Location copyOf(Location location) {
+ if (location == null) {
+ return null;
+ }
+ Location copy = new Location(location);
+ copy.setAccuracy(location.getAccuracy());
+ copy.setAltitude(location.getAltitude());
+ copy.setBearing(location.getBearing());
+ copy.setExtras(location.getExtras());
+ copy.setLatitude(location.getLatitude());
+ copy.setLongitude(location.getLongitude());
+ copy.setProvider(location.getProvider());
+ copy.setSpeed(location.getSpeed());
+ copy.setTime(location.getTime());
+ return copy;
+ }
+
+ /**
+ * Returns the distance between the two locations in meters.
+ * Adapted from: http://stackoverflow.com/questions/837872/calculate-distance-in-meters-when-
+ * you-know-longitude-and-latitude-in-java
+ */
+ private static float distanceBetween(Location location1, Location location2) {
+ double earthRadius = 3958.75;
+ double latDifference = Math.toRadians(location2.getLatitude() - location1.getLatitude());
+ double lonDifference = Math.toRadians(location2.getLongitude() - location2.getLongitude());
+ double a = Math.sin(latDifference / 2) * Math.sin(latDifference / 2) +
+ Math.cos(Math.toRadians(location1.getLatitude())) * Math.cos(Math.toRadians(
+ location2.getLatitude())) *
+ Math.sin(lonDifference / 2) * Math.sin(lonDifference / 2);
+ double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ double dist = Math.abs(earthRadius * c);
+
+ int meterConversion = 1609;
+
+ return new Float(dist * meterConversion);
+ }
+
+ public Map getRequestLocationUdpateCriteriaPendingIntents() {
+ return requestLocationUdpateCriteriaPendingIntents;
+ }
+
+ public Map getRequestLocationUdpateProviderPendingIntents() {
+ return requestLocationUdpateProviderPendingIntents;
+ }
+
+ public Collection getProvidersForListener(LocationListener listener) {
+ cleanupRemovedLocationListeners();
+ Set providers = new HashSet<>();
+ for (List listenerRegistrations : locationListeners.values()) {
+ for (ListenerRegistration listenerRegistration : listenerRegistrations) {
+ if (listenerRegistration.listener == listener) {
+ providers.add(listenerRegistration.provider);
+ }
+ }
+ }
+ return providers;
+ }
+
+ @Implementation
+ public void requestSingleUpdate(String provider, LocationListener listener, Looper looper) {
+ }
+
+ private final class LocationProviderEntry implements Map.Entry> {
+ private Boolean enabled;
+ private List criteria;
+
+ @Override
+ public Boolean getKey() {
+ return enabled;
+ }
+
+ @Override
+ public List getValue() {
+ return criteria;
+ }
+
+ @Override
+ public List setValue(List criteria) {
+ List oldCriteria = this.criteria;
+ this.criteria = criteria;
+ return oldCriteria;
+ }
+ }
+
+}