
org.robolectric.shadows.ShadowLocationManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of framework Show documentation
Show all versions of framework Show documentation
An alternative Android testing framework.
The newest version!
package org.robolectric.shadows;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Intent;
import android.location.Criteria;
import android.location.GpsStatus.Listener;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Looper;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Implements(LocationManager.class)
public class ShadowLocationManager {
private final Map providersEnabled = new LinkedHashMap<>();
private final Map lastKnownLocations = new HashMap<>();
private final Map requestLocationUdpateCriteriaPendingIntents = new HashMap<>();
private final Map requestLocationUdpateProviderPendingIntents = new HashMap<>();
private final ArrayList removedLocationListeners = new ArrayList<>();
private final ArrayList gpsStatusListeners = new ArrayList<>();
private Criteria lastBestProviderCriteria;
private boolean lastBestProviderEnabled;
private String bestEnabledProvider, bestDisabledProvider;
/** Location listeners along with metadata on when they should be fired. */
private static final class ListenerRegistration {
final long minTime;
final float minDistance;
final LocationListener listener;
final String provider;
Location lastSeenLocation;
long lastSeenTime;
ListenerRegistration(String provider, long minTime, float minDistance, Location locationAtCreation,
LocationListener listener) {
this.provider = provider;
this.minTime = minTime;
this.minDistance = minDistance;
this.lastSeenTime = locationAtCreation == null ? 0 : locationAtCreation.getTime();
this.lastSeenLocation = locationAtCreation;
this.listener = listener;
}
}
/** Mapped by provider. */
private final Map> locationListeners =
new HashMap<>();
@Implementation
public boolean isProviderEnabled(String provider) {
LocationProviderEntry map = providersEnabled.get(provider);
if (map != null) {
Boolean isEnabled = map.getKey();
return isEnabled == null ? true : isEnabled;
}
return false;
}
@Implementation
public List getAllProviders() {
Set allKnownProviders = new LinkedHashSet<>(providersEnabled.keySet());
allKnownProviders.add(LocationManager.GPS_PROVIDER);
allKnownProviders.add(LocationManager.NETWORK_PROVIDER);
allKnownProviders.add(LocationManager.PASSIVE_PROVIDER);
return new ArrayList<>(allKnownProviders);
}
/**
* Sets the value to return from {@link #isProviderEnabled(String)} for the given {@code provider}
*
* @param provider
* name of the provider whose status to set
* @param isEnabled
* whether that provider should appear enabled
*/
public void setProviderEnabled(String provider, boolean isEnabled) {
setProviderEnabled(provider, isEnabled, null);
}
public void setProviderEnabled(String provider, boolean isEnabled, List criteria) {
LocationProviderEntry providerEntry = providersEnabled.get(provider);
if (providerEntry == null) {
providerEntry = new LocationProviderEntry();
}
providerEntry.enabled = isEnabled;
providerEntry.criteria = criteria;
providersEnabled.put(provider, providerEntry);
List locationUpdateListeners = new ArrayList<>(getRequestLocationUpdateListeners());
for (LocationListener locationUpdateListener : locationUpdateListeners) {
if (isEnabled) {
locationUpdateListener.onProviderEnabled(provider);
} else {
locationUpdateListener.onProviderDisabled(provider);
}
}
// Send intent to notify about provider status
final Intent intent = new Intent();
intent.putExtra(LocationManager.KEY_PROVIDER_ENABLED, isEnabled);
ShadowApplication.getInstance().sendBroadcast(intent);
Set requestLocationUdpatePendingIntentSet = requestLocationUdpateCriteriaPendingIntents
.keySet();
for (PendingIntent requestLocationUdpatePendingIntent : requestLocationUdpatePendingIntentSet) {
try {
requestLocationUdpatePendingIntent.send();
} catch (CanceledException e) {
requestLocationUdpateCriteriaPendingIntents
.remove(requestLocationUdpatePendingIntent);
}
}
// if this provider gets disabled and it was the best active provider, then it's not anymore
if (provider.equals(bestEnabledProvider) && !isEnabled) {
bestEnabledProvider = null;
}
}
@Implementation
public List getProviders(boolean enabledOnly) {
ArrayList enabledProviders = new ArrayList<>();
for (String provider : getAllProviders()) {
if (!enabledOnly || providersEnabled.get(provider) != null) {
enabledProviders.add(provider);
}
}
return enabledProviders;
}
@Implementation
public Location getLastKnownLocation(String provider) {
return lastKnownLocations.get(provider);
}
@Implementation
public boolean addGpsStatusListener(Listener listener) {
if (!gpsStatusListeners.contains(listener)) {
gpsStatusListeners.add(listener);
}
return true;
}
@Implementation
public void removeGpsStatusListener(Listener listener) {
gpsStatusListeners.remove(listener);
}
@Implementation
public String getBestProvider(Criteria criteria, boolean enabled) {
lastBestProviderCriteria = criteria;
lastBestProviderEnabled = enabled;
if (criteria == null) {
return getBestProviderWithNoCriteria(enabled);
}
return getBestProviderWithCriteria(criteria, enabled);
}
private String getBestProviderWithCriteria(Criteria criteria, boolean enabled) {
List providers = getProviders(enabled);
int powerRequirement = criteria.getPowerRequirement();
int accuracy = criteria.getAccuracy();
for (String provider : providers) {
LocationProviderEntry locationProviderEntry = providersEnabled.get(provider);
if (locationProviderEntry == null) {
continue;
}
List criteriaList = locationProviderEntry.getValue();
if (criteriaList == null) {
continue;
}
for (Criteria criteriaListItem : criteriaList) {
if (criteria.equals(criteriaListItem)) {
return provider;
} else if (criteriaListItem.getAccuracy() == accuracy) {
return provider;
} else if (criteriaListItem.getPowerRequirement() == powerRequirement) {
return provider;
}
}
}
// TODO: these conditions are incomplete
for (String provider : providers) {
if (provider.equals(LocationManager.NETWORK_PROVIDER) && (accuracy == Criteria.ACCURACY_COARSE || powerRequirement == Criteria.POWER_LOW)) {
return provider;
} else if (provider.equals(LocationManager.GPS_PROVIDER) && accuracy == Criteria.ACCURACY_FINE && powerRequirement != Criteria.POWER_LOW) {
return provider;
}
}
// No enabled provider found with the desired criteria, then return the the first registered provider(?)
return providers.isEmpty()? null : providers.get(0);
}
private String getBestProviderWithNoCriteria(boolean enabled) {
List providers = getProviders(enabled);
if (enabled && bestEnabledProvider != null) {
return bestEnabledProvider;
} else if (bestDisabledProvider != null) {
return bestDisabledProvider;
} else if (providers.contains(LocationManager.GPS_PROVIDER)) {
return LocationManager.GPS_PROVIDER;
} else if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
return LocationManager.NETWORK_PROVIDER;
}
return null;
}
@Implementation
public void requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener) {
addLocationListener(provider, listener, minTime, minDistance);
}
private void addLocationListener(String provider, LocationListener listener, long minTime, float minDistance) {
List providerListeners = locationListeners.get(provider);
if (providerListeners == null) {
providerListeners = new ArrayList<>();
locationListeners.put(provider, providerListeners);
}
removeDuplicates(listener, providerListeners);
providerListeners.add(new ListenerRegistration(provider,
minTime, minDistance, copyOf(getLastKnownLocation(provider)), listener));
}
private void removeDuplicates(LocationListener listener,
List providerListeners) {
final Iterator iterator = providerListeners.iterator();
while (iterator.hasNext()) {
if (iterator.next().listener.equals(listener)) {
iterator.remove();
}
}
}
@Implementation
public void requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener,
Looper looper) {
addLocationListener(provider, listener, minTime, minDistance);
}
@Implementation
public void requestLocationUpdates(long minTime, float minDistance, Criteria criteria, PendingIntent pendingIntent) {
if (pendingIntent == null) {
throw new IllegalStateException("Intent must not be null");
}
if (getBestProvider(criteria, true) == null) {
throw new IllegalArgumentException("no providers found for criteria");
}
requestLocationUdpateCriteriaPendingIntents.put(pendingIntent, criteria);
}
@Implementation
public void requestLocationUpdates(String provider, long minTime, float minDistance,
PendingIntent pendingIntent) {
if (pendingIntent == null) {
throw new IllegalStateException("Intent must not be null");
}
if (!providersEnabled.containsKey(provider)) {
throw new IllegalArgumentException("no providers found");
}
requestLocationUdpateProviderPendingIntents.put(pendingIntent, provider);
}
@Implementation
public void removeUpdates(LocationListener listener) {
removedLocationListeners.add(listener);
}
private void cleanupRemovedLocationListeners() {
for (Map.Entry> entry : locationListeners.entrySet()) {
List 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.remove(pendingIntent) != null);
while (requestLocationUdpateProviderPendingIntents.remove(pendingIntent) != null);
}
public boolean hasGpsStatusListener(Listener listener) {
return gpsStatusListeners.contains(listener);
}
/**
* 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;
}
/**
* 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);
}
/**
* @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() - location1.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;
}
final private 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;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy