1package com.xtremelabs.robolectric.shadows; 2 3import android.app.PendingIntent; 4import android.app.PendingIntent.CanceledException; 5import android.content.Intent; 6import android.location.Criteria; 7import android.location.GpsStatus.Listener; 8import android.location.Location; 9import android.location.LocationListener; 10import android.location.LocationManager; 11import android.os.Looper; 12import com.xtremelabs.robolectric.Robolectric; 13import com.xtremelabs.robolectric.internal.Implementation; 14import com.xtremelabs.robolectric.internal.Implements; 15 16import 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) 23public 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