Home | History | Annotate | Download | only in shadows
      1 package com.xtremelabs.robolectric.shadows;
      2 
      3 import android.app.PendingIntent;
      4 import android.app.PendingIntent.CanceledException;
      5 import android.content.Intent;
      6 import android.location.Criteria;
      7 import android.location.GpsStatus.Listener;
      8 import android.location.Location;
      9 import android.location.LocationListener;
     10 import android.location.LocationManager;
     11 import android.os.Looper;
     12 import com.xtremelabs.robolectric.Robolectric;
     13 import com.xtremelabs.robolectric.internal.Implementation;
     14 import com.xtremelabs.robolectric.internal.Implements;
     15 
     16 import java.util.*;
     17 
     18 /**
     19  * Shadow of {@code LocationManager} that provides for the simulation of different location providers being enabled and
     20  * disabled.
     21  */
     22 @Implements(LocationManager.class)
     23 public class ShadowLocationManager {
     24     private final Map<String, LocationProviderEntry> providersEnabled = new LinkedHashMap<String, LocationProviderEntry>();
     25     private final Map<String, Location> lastKnownLocations = new HashMap<String, Location>();
     26     private final Map<PendingIntent, Criteria> requestLocationUdpateCriteriaPendingIntents = new HashMap<PendingIntent, Criteria>();
     27     private final Map<PendingIntent, String> requestLocationUdpateProviderPendingIntents = new HashMap<PendingIntent, String>();
     28 
     29     private final ArrayList<Listener> gpsStatusListeners = new ArrayList<Listener>();
     30     private Criteria lastBestProviderCriteria;
     31     private boolean lastBestProviderEnabled;
     32     private String bestEnabledProvider, bestDisabledProvider;
     33     private final Map<LocationListener, Set<String>> requestLocationUpdateListenersMap = new LinkedHashMap<LocationListener, Set<String>>();
     34 
     35     @Implementation
     36     public boolean isProviderEnabled(String provider) {
     37         LocationProviderEntry map = providersEnabled.get(provider);
     38         if (map != null) {
     39             Boolean isEnabled = map.getKey();
     40             return isEnabled == null ? true : isEnabled;
     41         }
     42         return false;
     43     }
     44 
     45     @Implementation
     46     public List<String> getAllProviders() {
     47         Set<String> allKnownProviders = new LinkedHashSet<String>(providersEnabled.keySet());
     48         allKnownProviders.add(LocationManager.GPS_PROVIDER);
     49         allKnownProviders.add(LocationManager.NETWORK_PROVIDER);
     50         allKnownProviders.add(LocationManager.PASSIVE_PROVIDER);
     51 
     52         return new ArrayList<String>(allKnownProviders);
     53     }
     54 
     55     /**
     56      * Sets the value to return from {@link #isProviderEnabled(String)} for the given {@code provider}
     57      *
     58      * @param provider
     59      *            name of the provider whose status to set
     60      * @param isEnabled
     61      *            whether that provider should appear enabled
     62      */
     63     public void setProviderEnabled(String provider, boolean isEnabled) {
     64         setProviderEnabled(provider, isEnabled, null);
     65     }
     66 
     67     public void setProviderEnabled(String provider, boolean isEnabled, List<Criteria> criteria) {
     68         LocationProviderEntry providerEntry = providersEnabled.get(provider);
     69         if (providerEntry == null) {
     70             providerEntry = new LocationProviderEntry();
     71         }
     72         providerEntry.enabled = isEnabled;
     73         providerEntry.criteria = criteria;
     74         providersEnabled.put(provider, providerEntry);
     75         List<LocationListener> locationUpdateListeners = new ArrayList<LocationListener>(requestLocationUpdateListenersMap.keySet());
     76         for (LocationListener locationUpdateListener : locationUpdateListeners) {
     77             if (isEnabled) {
     78                 locationUpdateListener.onProviderEnabled(provider);
     79             } else {
     80                 locationUpdateListener.onProviderDisabled(provider);
     81             }
     82         }
     83         // Send intent to notify about provider status
     84         final Intent intent = new Intent();
     85         intent.putExtra(LocationManager.KEY_PROVIDER_ENABLED, isEnabled);
     86         Robolectric.getShadowApplication().sendBroadcast(intent);
     87         Set<PendingIntent> requestLocationUdpatePendingIntentSet = requestLocationUdpateCriteriaPendingIntents
     88                 .keySet();
     89         for (PendingIntent requestLocationUdpatePendingIntent : requestLocationUdpatePendingIntentSet) {
     90             try {
     91                 requestLocationUdpatePendingIntent.send();
     92             } catch (CanceledException e) {
     93                 requestLocationUdpateCriteriaPendingIntents
     94                         .remove(requestLocationUdpatePendingIntent);
     95             }
     96         }
     97         // if this provider gets disabled and it was the best active provider, then it's not anymore
     98         if (provider.equals(bestEnabledProvider) && !isEnabled) {
     99             bestEnabledProvider = null;
    100         }
    101     }
    102 
    103     @Implementation
    104     public List<String> getProviders(boolean enabledOnly) {
    105         ArrayList<String> enabledProviders = new ArrayList<String>();
    106         for (String provider : providersEnabled.keySet()) {
    107             if (!enabledOnly || providersEnabled.get(provider).getKey()) {
    108                 enabledProviders.add(provider);
    109             }
    110         }
    111         return enabledProviders;
    112     }
    113 
    114     @Implementation
    115     public Location getLastKnownLocation(String provider) {
    116         return lastKnownLocations.get(provider);
    117     }
    118 
    119     @Implementation
    120     public boolean addGpsStatusListener(Listener listener) {
    121         if (!gpsStatusListeners.contains(listener)) {
    122             gpsStatusListeners.add(listener);
    123         }
    124         return true;
    125     }
    126 
    127     @Implementation
    128     public void removeGpsStatusListener(Listener listener) {
    129         gpsStatusListeners.remove(listener);
    130     }
    131 
    132     /**
    133      * Returns the best provider with respect to the passed criteria (if any) and its status. If no criteria are passed
    134      *
    135      * NB: Gps is considered the best provider for fine accuracy and high power consumption, network is considered the
    136      * best provider for coarse accuracy and low power consumption.
    137      *
    138      * @param criteria
    139      * @param enabled
    140      * @return
    141      */
    142     @Implementation
    143     public String getBestProvider(Criteria criteria, boolean enabled) {
    144         lastBestProviderCriteria = criteria;
    145         lastBestProviderEnabled = enabled;
    146 
    147         if (criteria == null) {
    148             return getBestProviderWithNoCriteria(enabled);
    149         }
    150 
    151         return getBestProviderWithCriteria(criteria, enabled);
    152     }
    153 
    154     private String getBestProviderWithCriteria(Criteria criteria, boolean enabled) {
    155         List<String> providers = getProviders(enabled);
    156         int powerRequirement = criteria.getPowerRequirement();
    157         int accuracy = criteria.getAccuracy();
    158         for (String provider : providers) {
    159             LocationProviderEntry locationProviderEntry = providersEnabled.get(provider);
    160             if (locationProviderEntry == null) {
    161                 continue;
    162             }
    163             List<Criteria> criteriaList = locationProviderEntry.getValue();
    164             if (criteriaList == null) {
    165                 continue;
    166             }
    167             for (Criteria criteriaListItem : criteriaList) {
    168                 if (criteria.equals(criteriaListItem)) {
    169                     return provider;
    170                 } else if (criteriaListItem.getAccuracy() == accuracy) {
    171                     return provider;
    172                 } else if (criteriaListItem.getPowerRequirement() == powerRequirement) {
    173                     return provider;
    174                 }
    175             }
    176         }
    177         // TODO: these conditions are incomplete
    178         for (String provider : providers) {
    179             if (provider.equals(LocationManager.NETWORK_PROVIDER) && (accuracy == Criteria.ACCURACY_COARSE || powerRequirement == Criteria.POWER_LOW)) {
    180                 return provider;
    181             } else if (provider.equals(LocationManager.GPS_PROVIDER) && accuracy == Criteria.ACCURACY_FINE && powerRequirement != Criteria.POWER_LOW) {
    182                 return provider;
    183             }
    184         }
    185 
    186         // No enabled provider found with the desired criteria, then return the the first registered provider(?)
    187         return providers.isEmpty()? null : providers.get(0);
    188     }
    189 
    190     private String getBestProviderWithNoCriteria(boolean enabled) {
    191         List<String> providers = getProviders(enabled);
    192 
    193         if (enabled && bestEnabledProvider != null) {
    194             return bestEnabledProvider;
    195         } else if (bestDisabledProvider != null) {
    196             return bestDisabledProvider;
    197         } else if (providers.contains(LocationManager.GPS_PROVIDER)) {
    198             return LocationManager.GPS_PROVIDER;
    199         } else if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
    200             return LocationManager.NETWORK_PROVIDER;
    201         }
    202         return null;
    203     }
    204 
    205     @Implementation
    206     public void requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener) {
    207         addLocationListener(provider, listener);
    208     }
    209 
    210     private void addLocationListener(String provider, LocationListener listener) {
    211         if (!requestLocationUpdateListenersMap.containsKey(listener)) {
    212             requestLocationUpdateListenersMap.put(listener, new HashSet<String>());
    213         }
    214         requestLocationUpdateListenersMap.get(listener).add(provider);
    215     }
    216 
    217     @Implementation
    218     public void requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener,
    219             Looper looper) {
    220         addLocationListener(provider, listener);
    221     }
    222 
    223     @Implementation
    224     public void requestLocationUpdates(long minTime, float minDistance, Criteria criteria, PendingIntent pendingIntent) {
    225         if (pendingIntent == null) {
    226             throw new IllegalStateException("Intent must not be null");
    227         }
    228         if (getBestProvider(criteria, true) == null) {
    229             throw new IllegalArgumentException("no providers found for criteria");
    230         }
    231         requestLocationUdpateCriteriaPendingIntents.put(pendingIntent, criteria);
    232     }
    233 
    234     @Implementation
    235     public void requestLocationUpdates(String provider, long minTime, float minDistance,
    236             PendingIntent pendingIntent) {
    237         if (pendingIntent == null) {
    238             throw new IllegalStateException("Intent must not be null");
    239         }
    240         if (!providersEnabled.containsKey(provider)) {
    241             throw new IllegalArgumentException("no providers found");
    242         }
    243 
    244         requestLocationUdpateProviderPendingIntents.put(pendingIntent, provider);
    245     }
    246 
    247     @Implementation
    248     public void removeUpdates(LocationListener listener) {
    249         requestLocationUpdateListenersMap.remove(listener);
    250     }
    251 
    252     @Implementation
    253     public void removeUpdates(PendingIntent pendingIntent) {
    254         while (requestLocationUdpateCriteriaPendingIntents.remove(pendingIntent) != null);
    255         while (requestLocationUdpateProviderPendingIntents.remove(pendingIntent) != null);
    256     }
    257 
    258     public boolean hasGpsStatusListener(Listener listener) {
    259         return gpsStatusListeners.contains(listener);
    260     }
    261 
    262     /**
    263      * Non-Android accessor.
    264      * <p/>
    265      * Gets the criteria value used in the last call to {@link #getBestProvider(android.location.Criteria, boolean)}
    266      *
    267      * @return the criteria used to find the best provider
    268      */
    269     public Criteria getLastBestProviderCriteria() {
    270         return lastBestProviderCriteria;
    271     }
    272 
    273     /**
    274      * Non-Android accessor.
    275      * <p/>
    276      * Gets the enabled value used in the last call to {@link #getBestProvider(android.location.Criteria, boolean)}
    277      *
    278      * @return the enabled value used to find the best provider
    279      */
    280     public boolean getLastBestProviderEnabledOnly() {
    281         return lastBestProviderEnabled;
    282     }
    283 
    284     /**
    285      * Sets the value to return from {@link #getBestProvider(android.location.Criteria, boolean)} for the given
    286      * {@code provider}
    287      *
    288      * @param provider
    289      *            name of the provider who should be considered best
    290      * @throws Exception
    291      *
    292      */
    293     public boolean setBestProvider(String provider, boolean enabled, List<Criteria> criteria) throws Exception {
    294         if (!getAllProviders().contains(provider)) {
    295             throw new IllegalStateException("Best provider is not a known provider");
    296         }
    297         // If provider is not enabled but it is supposed to be set as the best enabled provider don't set it.
    298         for (String prvdr : providersEnabled.keySet()) {
    299             if (provider.equals(prvdr) && providersEnabled.get(prvdr).enabled != enabled) {
    300                 return false;
    301             }
    302         }
    303 
    304         if (enabled) {
    305             bestEnabledProvider = provider;
    306             if (provider.equals(bestDisabledProvider)) {
    307                 bestDisabledProvider = null;
    308             }
    309         } else {
    310             bestDisabledProvider = provider;
    311             if (provider.equals(bestEnabledProvider)) {
    312                 bestEnabledProvider = null;
    313             }
    314         }
    315         if (criteria == null) {
    316             return true;
    317         }
    318         LocationProviderEntry entry;
    319         if (!providersEnabled.containsKey(provider)) {
    320             entry = new LocationProviderEntry();
    321             entry.enabled = enabled;
    322             entry.criteria = criteria;
    323         } else {
    324             entry = providersEnabled.get(provider);
    325         }
    326         providersEnabled.put(provider, entry);
    327 
    328         return true;
    329     }
    330 
    331     public boolean setBestProvider(String provider, boolean enabled) throws Exception {
    332         return setBestProvider(provider, enabled, null);
    333     }
    334 
    335     /**
    336      * Sets the value to return from {@link #getLastKnownLocation(String)} for the given {@code provider}
    337      *
    338      * @param provider
    339      *            name of the provider whose location to set
    340      * @param location
    341      *            the last known location for the provider
    342      */
    343     public void setLastKnownLocation(String provider, Location location) {
    344         lastKnownLocations.put(provider, location);
    345     }
    346 
    347     /**
    348      * Non-Android accessor.
    349      *
    350      * @return lastRequestedLocationUpdatesLocationListener
    351      */
    352     public List<LocationListener> getRequestLocationUpdateListeners() {
    353         return new ArrayList<LocationListener>(requestLocationUpdateListenersMap.keySet());
    354     }
    355 
    356     public Map<PendingIntent, Criteria> getRequestLocationUdpateCriteriaPendingIntents() {
    357         return requestLocationUdpateCriteriaPendingIntents;
    358     }
    359 
    360     public Map<PendingIntent, String> getRequestLocationUdpateProviderPendingIntents() {
    361         return requestLocationUdpateProviderPendingIntents;
    362     }
    363 
    364     public Collection<String> getProvidersForListener(LocationListener listener) {
    365         Set<String> providers = requestLocationUpdateListenersMap.get(listener);
    366         return providers == null ? Collections.<String>emptyList() : new ArrayList<String>(providers);
    367     }
    368 
    369     final private class LocationProviderEntry implements Map.Entry<Boolean, List<Criteria>> {
    370         private Boolean enabled;
    371         private List<Criteria> criteria;
    372 
    373         @Override
    374         public Boolean getKey() {
    375             return enabled;
    376         }
    377 
    378         @Override
    379         public List<Criteria> getValue() {
    380             return criteria;
    381         }
    382 
    383         @Override
    384         public List<Criteria> setValue(List<Criteria> criteria) {
    385             List<Criteria> oldCriteria = this.criteria;
    386             this.criteria = criteria;
    387             return oldCriteria;
    388         }
    389     }
    390 
    391 }
    392