package com.xtremelabs.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 com.xtremelabs.robolectric.Robolectric; import com.xtremelabs.robolectric.internal.Implementation; import com.xtremelabs.robolectric.internal.Implements; import java.util.*; /** * Shadow of {@code LocationManager} that provides for the simulation of different location providers being enabled and * disabled. */ @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 gpsStatusListeners = new ArrayList(); private Criteria lastBestProviderCriteria; private boolean lastBestProviderEnabled; private String bestEnabledProvider, bestDisabledProvider; private final Map> requestLocationUpdateListenersMap = new LinkedHashMap>(); @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(requestLocationUpdateListenersMap.keySet()); 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); Robolectric.getShadowApplication().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 : providersEnabled.keySet()) { if (!enabledOnly || providersEnabled.get(provider).getKey()) { 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); } /** * Returns the best provider with respect to the passed criteria (if any) and its status. If no criteria are passed * * NB: Gps is considered the best provider for fine accuracy and high power consumption, network is considered the * best provider for coarse accuracy and low power consumption. * * @param criteria * @param enabled * @return */ @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); } private void addLocationListener(String provider, LocationListener listener) { if (!requestLocationUpdateListenersMap.containsKey(listener)) { requestLocationUpdateListenersMap.put(listener, new HashSet()); } requestLocationUpdateListenersMap.get(listener).add(provider); } @Implementation public void requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener, Looper looper) { addLocationListener(provider, listener); } @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) { requestLocationUpdateListenersMap.remove(listener); } @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); } /** * 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 * @throws Exception * */ 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() { return new ArrayList(requestLocationUpdateListenersMap.keySet()); } public Map getRequestLocationUdpateCriteriaPendingIntents() { return requestLocationUdpateCriteriaPendingIntents; } public Map getRequestLocationUdpateProviderPendingIntents() { return requestLocationUdpateProviderPendingIntents; } public Collection getProvidersForListener(LocationListener listener) { Set providers = requestLocationUpdateListenersMap.get(listener); return providers == null ? Collections.emptyList() : new ArrayList(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; } } }