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 isProviderEnabled(String provider)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 getAllProviders()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 */ setProviderEnabled(String provider, boolean isEnabled)63 public void setProviderEnabled(String provider, boolean isEnabled) { 64 setProviderEnabled(provider, isEnabled, null); 65 } 66 setProviderEnabled(String provider, boolean isEnabled, List<Criteria> criteria)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 getProviders(boolean enabledOnly)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 getLastKnownLocation(String provider)115 public Location getLastKnownLocation(String provider) { 116 return lastKnownLocations.get(provider); 117 } 118 119 @Implementation addGpsStatusListener(Listener listener)120 public boolean addGpsStatusListener(Listener listener) { 121 if (!gpsStatusListeners.contains(listener)) { 122 gpsStatusListeners.add(listener); 123 } 124 return true; 125 } 126 127 @Implementation removeGpsStatusListener(Listener listener)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 getBestProvider(Criteria criteria, boolean enabled)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 getBestProviderWithCriteria(Criteria criteria, boolean enabled)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 getBestProviderWithNoCriteria(boolean enabled)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 requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener)206 public void requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener) { 207 addLocationListener(provider, listener); 208 } 209 addLocationListener(String provider, LocationListener listener)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 requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener, Looper looper)218 public void requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener, 219 Looper looper) { 220 addLocationListener(provider, listener); 221 } 222 223 @Implementation requestLocationUpdates(long minTime, float minDistance, Criteria criteria, PendingIntent pendingIntent)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 requestLocationUpdates(String provider, long minTime, float minDistance, PendingIntent pendingIntent)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 removeUpdates(LocationListener listener)248 public void removeUpdates(LocationListener listener) { 249 requestLocationUpdateListenersMap.remove(listener); 250 } 251 252 @Implementation removeUpdates(PendingIntent pendingIntent)253 public void removeUpdates(PendingIntent pendingIntent) { 254 while (requestLocationUdpateCriteriaPendingIntents.remove(pendingIntent) != null); 255 while (requestLocationUdpateProviderPendingIntents.remove(pendingIntent) != null); 256 } 257 hasGpsStatusListener(Listener listener)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 */ getLastBestProviderCriteria()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 */ getLastBestProviderEnabledOnly()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 */ setBestProvider(String provider, boolean enabled, List<Criteria> criteria)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 setBestProvider(String provider, boolean enabled)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 */ setLastKnownLocation(String provider, Location location)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 */ getRequestLocationUpdateListeners()352 public List<LocationListener> getRequestLocationUpdateListeners() { 353 return new ArrayList<LocationListener>(requestLocationUpdateListenersMap.keySet()); 354 } 355 getRequestLocationUdpateCriteriaPendingIntents()356 public Map<PendingIntent, Criteria> getRequestLocationUdpateCriteriaPendingIntents() { 357 return requestLocationUdpateCriteriaPendingIntents; 358 } 359 getRequestLocationUdpateProviderPendingIntents()360 public Map<PendingIntent, String> getRequestLocationUdpateProviderPendingIntents() { 361 return requestLocationUdpateProviderPendingIntents; 362 } 363 getProvidersForListener(LocationListener listener)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 getKey()374 public Boolean getKey() { 375 return enabled; 376 } 377 378 @Override getValue()379 public List<Criteria> getValue() { 380 return criteria; 381 } 382 383 @Override setValue(List<Criteria> criteria)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