diff --git a/build.gradle b/build.gradle index a4feb6c..0b6ed93 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' } } diff --git a/lost-sample/build.gradle b/lost-sample/build.gradle index f97f958..343e6f6 100644 --- a/lost-sample/build.gradle +++ b/lost-sample/build.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:2.3.3' } } diff --git a/lost/build.gradle b/lost/build.gradle index fd09dd0..e641147 100644 --- a/lost/build.gradle +++ b/lost/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' classpath 'net.researchgate:gradle-release:2.4.0' } diff --git a/lost/src/main/java/com/mapzen/android/lost/internal/AndroidHandlerFactory.java b/lost/src/main/java/com/mapzen/android/lost/internal/AndroidHandlerFactory.java new file mode 100644 index 0000000..27b9953 --- /dev/null +++ b/lost/src/main/java/com/mapzen/android/lost/internal/AndroidHandlerFactory.java @@ -0,0 +1,15 @@ +package com.mapzen.android.lost.internal; + +import android.os.Handler; +import android.os.Looper; + +/** + * Concrete implementation of {@link HandlerFactory} used by {@link LostClientManager}. + */ +class AndroidHandlerFactory implements HandlerFactory { + + @Override public void run(Looper looper, Runnable runnable) { + Handler handler = new Handler(looper); + handler.post(runnable); + } +} diff --git a/lost/src/main/java/com/mapzen/android/lost/internal/ClientManager.java b/lost/src/main/java/com/mapzen/android/lost/internal/ClientManager.java index 4fe73b5..2950c02 100644 --- a/lost/src/main/java/com/mapzen/android/lost/internal/ClientManager.java +++ b/lost/src/main/java/com/mapzen/android/lost/internal/ClientManager.java @@ -34,14 +34,12 @@ void addLocationCallback(LostApiClient client, LocationRequest request, boolean removeListener(LostApiClient client, LocationListener listener); boolean removePendingIntent(LostApiClient client, PendingIntent callbackIntent); boolean removeLocationCallback(LostApiClient client, LocationCallback callback); - ReportedChanges reportLocationChanged(Location location); - ReportedChanges sendPendingIntent(Context context, Location location, + void reportLocationChanged(Location location); + void sendPendingIntent(Context context, Location location, LocationAvailability availability, LocationResult result); - ReportedChanges reportLocationResult(Location location, final LocationResult result); - void updateReportedValues(ReportedChanges changes); + void reportLocationResult(Location location, final LocationResult result); void notifyLocationAvailability(final LocationAvailability availability); boolean hasNoListeners(); - void shutdown(); Map> getLocationListeners(); Map> getPendingIntents(); Map> getLocationCallbacks(); diff --git a/lost/src/main/java/com/mapzen/android/lost/internal/Clock.java b/lost/src/main/java/com/mapzen/android/lost/internal/Clock.java index fefeb8b..f5aaf94 100644 --- a/lost/src/main/java/com/mapzen/android/lost/internal/Clock.java +++ b/lost/src/main/java/com/mapzen/android/lost/internal/Clock.java @@ -1,5 +1,10 @@ package com.mapzen.android.lost.internal; +import android.location.Location; + public interface Clock { - long getCurrentTimeInMillis(); + long MS_TO_NS = 1000000; + + long getSystemElapsedTimeInNanos(); + long getElapsedTimeInNanos(Location location); } diff --git a/lost/src/main/java/com/mapzen/android/lost/internal/FusedLocationProviderApiImpl.java b/lost/src/main/java/com/mapzen/android/lost/internal/FusedLocationProviderApiImpl.java index 9fc42db..21bf675 100644 --- a/lost/src/main/java/com/mapzen/android/lost/internal/FusedLocationProviderApiImpl.java +++ b/lost/src/main/java/com/mapzen/android/lost/internal/FusedLocationProviderApiImpl.java @@ -87,7 +87,6 @@ public void onLocationChanged(final Location location) throws RemoteException { context.unbindService(this); isBound = false; } - clientManager.shutdown(); service = null; } diff --git a/lost/src/main/java/com/mapzen/android/lost/internal/FusedLocationServiceCallbackManager.java b/lost/src/main/java/com/mapzen/android/lost/internal/FusedLocationServiceCallbackManager.java index a519f5d..d7df02e 100644 --- a/lost/src/main/java/com/mapzen/android/lost/internal/FusedLocationServiceCallbackManager.java +++ b/lost/src/main/java/com/mapzen/android/lost/internal/FusedLocationServiceCallbackManager.java @@ -30,7 +30,7 @@ public class FusedLocationServiceCallbackManager { void onLocationChanged(Context context, Location location, ClientManager clientManager, IFusedLocationProviderService service) { - ReportedChanges changes = clientManager.reportLocationChanged(location); + clientManager.reportLocationChanged(location); LocationAvailability availability = null; try { @@ -42,14 +42,9 @@ void onLocationChanged(Context context, Location location, ClientManager clientM ArrayList locations = new ArrayList<>(); locations.add(location); final LocationResult result = LocationResult.create(locations); - ReportedChanges pendingIntentChanges = clientManager.sendPendingIntent( - context, location, availability, result); + clientManager.sendPendingIntent(context, location, availability, result); - ReportedChanges callbackChanges = clientManager.reportLocationResult(location, result); - - changes.putAll(pendingIntentChanges); - changes.putAll(callbackChanges); - clientManager.updateReportedValues(changes); + clientManager.reportLocationResult(location, result); } /** diff --git a/lost/src/main/java/com/mapzen/android/lost/internal/FusionEngine.java b/lost/src/main/java/com/mapzen/android/lost/internal/FusionEngine.java index e5172eb..de46cea 100644 --- a/lost/src/main/java/com/mapzen/android/lost/internal/FusionEngine.java +++ b/lost/src/main/java/com/mapzen/android/lost/internal/FusionEngine.java @@ -40,7 +40,7 @@ public FusionEngine(Context context, Callback callback) { @Override public Location getLastLocation() { final List providers = locationManager.getAllProviders(); - final long minTime = clock.getCurrentTimeInMillis() - RECENT_UPDATE_THRESHOLD_IN_MILLIS; + final long minTime = clock.getSystemElapsedTimeInNanos() - RECENT_UPDATE_THRESHOLD_IN_NANOS; Location bestLocation = null; float bestAccuracy = Float.MAX_VALUE; @@ -51,7 +51,7 @@ public FusionEngine(Context context, Callback callback) { final Location location = locationManager.getLastKnownLocation(provider); if (location != null) { final float accuracy = location.getAccuracy(); - final long time = location.getTime(); + final long time = clock.getElapsedTimeInNanos(location); if (time > minTime && accuracy < bestAccuracy) { bestLocation = location; bestAccuracy = accuracy; @@ -224,8 +224,8 @@ public static boolean isBetterThan(Location locationA, Location locationB) { return true; } - if (SystemClock.getTimeInNanos(locationA) - > SystemClock.getTimeInNanos(locationB) + RECENT_UPDATE_THRESHOLD_IN_NANOS) { + if (clock.getElapsedTimeInNanos(locationA) + > clock.getElapsedTimeInNanos(locationB) + RECENT_UPDATE_THRESHOLD_IN_NANOS) { return true; } diff --git a/lost/src/main/java/com/mapzen/android/lost/internal/HandlerFactory.java b/lost/src/main/java/com/mapzen/android/lost/internal/HandlerFactory.java new file mode 100644 index 0000000..b952cf5 --- /dev/null +++ b/lost/src/main/java/com/mapzen/android/lost/internal/HandlerFactory.java @@ -0,0 +1,11 @@ +package com.mapzen.android.lost.internal; + +import android.os.Looper; + +/** + * Generic interface for {@link AndroidHandlerFactory} which can be seamlessly replaced for test + * class when running test suite. + */ +interface HandlerFactory { + void run(Looper looper, Runnable runnable); +} diff --git a/lost/src/main/java/com/mapzen/android/lost/internal/LocationRequestReport.java b/lost/src/main/java/com/mapzen/android/lost/internal/LocationRequestReport.java new file mode 100644 index 0000000..65581c7 --- /dev/null +++ b/lost/src/main/java/com/mapzen/android/lost/internal/LocationRequestReport.java @@ -0,0 +1,21 @@ +package com.mapzen.android.lost.internal; + +import com.mapzen.android.lost.api.LocationRequest; + +import android.location.Location; + +/** + * Wrapper object used to store info about when a {@link Location} was last reported for a given + * {@link LocationRequest}. Used by {@link LostClientManager} to determine if a new location should + * should be reported to a given client listener. + */ +class LocationRequestReport { + + Location location; + final LocationRequest locationRequest; + + LocationRequestReport(LocationRequest request) { + this.locationRequest = request; + } + +} diff --git a/lost/src/main/java/com/mapzen/android/lost/internal/LostClientManager.java b/lost/src/main/java/com/mapzen/android/lost/internal/LostClientManager.java index 2a679b5..884efff 100644 --- a/lost/src/main/java/com/mapzen/android/lost/internal/LostClientManager.java +++ b/lost/src/main/java/com/mapzen/android/lost/internal/LostClientManager.java @@ -21,6 +21,7 @@ import java.util.Set; import static com.mapzen.android.lost.api.FusedLocationProviderApi.KEY_LOCATION_CHANGED; +import static com.mapzen.android.lost.internal.Clock.MS_TO_NS; /** * Used by {@link LostApiClientImpl} to manage connected clients and by @@ -31,27 +32,27 @@ public class LostClientManager implements ClientManager { private static final String TAG = ClientManager.class.getSimpleName(); private static LostClientManager instance; + private final Clock clock; + private final HandlerFactory handlerFactory; + private HashMap clients; + private Map listenerToReports; + private Map intentToReports; + private Map callbackToReports; - private Map listenerToLocationRequests; - private Map intentToLocationRequests; - private Map callbackToLocationRequests; - private ReportedChanges reportedChanges; + LostClientManager(Clock clock, HandlerFactory handlerFactory) { + this.clock = clock; + this.handlerFactory = handlerFactory; - LostClientManager() { clients = new HashMap<>(); - - listenerToLocationRequests = new HashMap<>(); - intentToLocationRequests = new HashMap<>(); - callbackToLocationRequests = new HashMap<>(); - - reportedChanges = new ReportedChanges(new HashMap(), - new HashMap()); + listenerToReports = new HashMap<>(); + intentToReports = new HashMap<>(); + callbackToReports = new HashMap<>(); } public static LostClientManager shared() { if (instance == null) { - instance = new LostClientManager(); + instance = new LostClientManager(new SystemClock(), new AndroidHandlerFactory()); } return instance; } @@ -76,14 +77,14 @@ public static LostClientManager shared() { LocationListener listener) { throwIfClientNotAdded(client); clients.get(client).locationListeners().add(listener); - listenerToLocationRequests.put(listener, request); + listenerToReports.put(listener, new LocationRequestReport(request)); } @Override public void addPendingIntent(LostApiClient client, LocationRequest request, PendingIntent callbackIntent) { throwIfClientNotAdded(client); clients.get(client).pendingIntents().add(callbackIntent); - intentToLocationRequests.put(callbackIntent, request); + intentToReports.put(callbackIntent, new LocationRequestReport(request)); } @Override public void addLocationCallback(LostApiClient client, LocationRequest request, @@ -91,7 +92,7 @@ public static LostClientManager shared() { throwIfClientNotAdded(client); clients.get(client).locationCallbacks().add(callback); clients.get(client).looperMap().put(callback, looper); - callbackToLocationRequests.put(callback, request); + callbackToReports.put(callback, new LocationRequestReport(request)); } private void throwIfClientNotAdded(LostApiClient client) { @@ -109,7 +110,7 @@ private void throwIfClientNotAdded(LostApiClient client) { removed = true; } - listenerToLocationRequests.remove(listener); + listenerToReports.remove(listener); return removed; } @@ -122,7 +123,7 @@ private void throwIfClientNotAdded(LostApiClient client) { removed = true; } - intentToLocationRequests.remove(callbackIntent); + intentToReports.remove(callbackIntent); return removed; } @@ -136,7 +137,7 @@ private void throwIfClientNotAdded(LostApiClient client) { } clients.get(client).looperMap().remove(callback); - callbackToLocationRequests.remove(callback); + callbackToReports.remove(callback); return removed; } @@ -149,8 +150,8 @@ private void throwIfClientNotAdded(LostApiClient client) { * @param location * @return */ - @Override public ReportedChanges reportLocationChanged(final Location location) { - return iterateAndNotify(location, getLocationListeners(), listenerToLocationRequests, + @Override public void reportLocationChanged(final Location location) { + iterateAndNotify(location, getLocationListeners(), listenerToReports, new Notifier() { @Override public void notify(LostApiClient client, LocationListener listener) { listener.onLocationChanged(location); @@ -167,31 +168,27 @@ private void throwIfClientNotAdded(LostApiClient client) { * @param location * @return */ - @Override public ReportedChanges sendPendingIntent(final Context context, + @Override public void sendPendingIntent(final Context context, final Location location, final LocationAvailability availability, final LocationResult result) { - return iterateAndNotify(location, - getPendingIntents(), intentToLocationRequests, new Notifier() { + iterateAndNotify(location, + getPendingIntents(), intentToReports, new Notifier() { @Override public void notify(LostApiClient client, PendingIntent pendingIntent) { fireIntent(context, pendingIntent, location, availability, result); } }); } - @Override public ReportedChanges reportLocationResult(Location location, + @Override public void reportLocationResult(Location location, final LocationResult result) { - return iterateAndNotify(location, - getLocationCallbacks(), callbackToLocationRequests, new Notifier() { + iterateAndNotify(location, + getLocationCallbacks(), callbackToReports, new Notifier() { @Override public void notify(LostApiClient client, LocationCallback callback) { notifyCallback(client, callback, result); } }); } - @Override public void updateReportedValues(ReportedChanges changes) { - reportedChanges.putAll(changes); - } - @Override public void notifyLocationAvailability(final LocationAvailability availability) { for (LostClientWrapper wrapper : clients.values()) { for (LocationCallback callback : wrapper.locationCallbacks()) { @@ -212,10 +209,6 @@ private void throwIfClientNotAdded(LostApiClient client) { return true; } - @Override public void shutdown() { - reportedChanges.clearAll(); - } - @VisibleForTesting void clearClients() { clients.clear(); } @@ -261,8 +254,7 @@ private void fireIntent(Context context, PendingIntent intent, Location location private void notifyCallback(LostApiClient client, final LocationCallback callback, final LocationResult result) { final Looper looper = clients.get(client).looperMap().get(callback); - final Handler handler = new Handler(looper); - handler.post(new Runnable() { + handlerFactory.run(looper, new Runnable() { @Override public void run() { callback.onLocationResult(result); } @@ -280,37 +272,34 @@ private void notifyAvailability(LostApiClient client, final LocationCallback cal }); } - private ReportedChanges iterateAndNotify(Location location, - Map> clientToObj, Map objToLocationRequest, + private void iterateAndNotify(Location location, + Map> clientToObj, Map objToLocationRequest, Notifier notifier) { - Map updatedRequestToReportedTime = new HashMap<>(); - Map updatedRequestToReportedLocation = new HashMap<>(); for (LostApiClient client : clientToObj.keySet()) { if (clientToObj.get(client) != null) { for (final T obj : clientToObj.get(client)) { - LocationRequest request = objToLocationRequest.get(obj); - Long lastReportedTime = reportedChanges.timeChanges().get(request); - Location lastReportedLocation = reportedChanges.locationChanges().get(request); - if (lastReportedTime == null && lastReportedLocation == null) { - updatedRequestToReportedTime.put(request, location.getTime()); - updatedRequestToReportedLocation.put(request, location); + LocationRequestReport report = objToLocationRequest.get(obj); + LocationRequest request = report.locationRequest; + Location lastReportedLocation = report.location; + if (lastReportedLocation == null) { + report.location = location; notifier.notify(client, obj); } else { - long intervalSinceLastReport = location.getTime() - lastReportedTime; + long lastReportedTime = clock.getElapsedTimeInNanos(lastReportedLocation); + long locationTime = clock.getElapsedTimeInNanos(location); + long intervalSinceLastReport = (locationTime - lastReportedTime) / MS_TO_NS; long fastestInterval = request.getFastestInterval(); float smallestDisplacement = request.getSmallestDisplacement(); float displacementSinceLastReport = location.distanceTo(lastReportedLocation); if (intervalSinceLastReport >= fastestInterval && displacementSinceLastReport >= smallestDisplacement) { - updatedRequestToReportedTime.put(request, location.getTime()); - updatedRequestToReportedLocation.put(request, location); + report.location = location; notifier.notify(client, obj); } } } } } - return new ReportedChanges(updatedRequestToReportedTime, updatedRequestToReportedLocation); } interface Notifier { diff --git a/lost/src/main/java/com/mapzen/android/lost/internal/ReportedChanges.java b/lost/src/main/java/com/mapzen/android/lost/internal/ReportedChanges.java deleted file mode 100644 index 2889073..0000000 --- a/lost/src/main/java/com/mapzen/android/lost/internal/ReportedChanges.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.mapzen.android.lost.internal; - -import com.mapzen.android.lost.api.LocationRequest; - -import android.location.Location; - -import java.util.Map; - -/** - * Represents changes to reported locations and times at which locations were reported for - * different modes of notification (listeners, pending intents, and callbacks). After all types of - * callbacks are invoked, these changes will be committed so that the next time a location is - * reported, the {@link FusedLocationProviderServiceDelegate} can properly determine if it should be - * reported to listeners, pending intents, and callbacks. - */ -public class ReportedChanges { - - private Map updatedRequestToReportedTime; - private Map updatedRequestToReportedLocation; - - public ReportedChanges(Map timeChanges, - Map locationChanges) { - updatedRequestToReportedTime = timeChanges; - updatedRequestToReportedLocation = locationChanges; - } - - public Map timeChanges() { - return updatedRequestToReportedTime; - } - - public Map locationChanges() { - return updatedRequestToReportedLocation; - } - - public void putAll(ReportedChanges changes) { - updatedRequestToReportedTime.putAll(changes.timeChanges()); - updatedRequestToReportedLocation.putAll(changes.locationChanges()); - } - - public void clearAll() { - updatedRequestToReportedTime.clear(); - updatedRequestToReportedLocation.clear(); - } -} diff --git a/lost/src/main/java/com/mapzen/android/lost/internal/SystemClock.java b/lost/src/main/java/com/mapzen/android/lost/internal/SystemClock.java index 7414ac7..a50c171 100644 --- a/lost/src/main/java/com/mapzen/android/lost/internal/SystemClock.java +++ b/lost/src/main/java/com/mapzen/android/lost/internal/SystemClock.java @@ -4,13 +4,15 @@ import android.os.Build; public class SystemClock implements Clock { - public static final long MS_TO_NS = 1000000; - @Override public long getCurrentTimeInMillis() { - return System.currentTimeMillis(); + @Override public long getSystemElapsedTimeInNanos() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return android.os.SystemClock.elapsedRealtimeNanos(); + } + return android.os.SystemClock.elapsedRealtime() * MS_TO_NS; } - public static long getTimeInNanos(Location location) { + @Override public long getElapsedTimeInNanos(Location location) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { return location.getElapsedRealtimeNanos(); } diff --git a/lost/src/test/java/com/mapzen/android/lost/internal/FusedLocationProviderApiImplTest.java b/lost/src/test/java/com/mapzen/android/lost/internal/FusedLocationProviderApiImplTest.java index 8f9d03b..4a8ed26 100644 --- a/lost/src/test/java/com/mapzen/android/lost/internal/FusedLocationProviderApiImplTest.java +++ b/lost/src/test/java/com/mapzen/android/lost/internal/FusedLocationProviderApiImplTest.java @@ -51,7 +51,8 @@ public class FusedLocationProviderApiImplTest extends BaseRobolectricTest { private FusedLocationProviderApiImpl api; private IFusedLocationProviderService service = mock(IFusedLocationProviderService.class); private FusedLocationServiceConnectionManager connectionManager; - private ClientManager clientManager = new LostClientManager(); + private ClientManager clientManager = new LostClientManager(new SystemClock(), + mock(HandlerFactory.class)); @Before public void setUp() throws Exception { connectedClient = new LostApiClientImpl(RuntimeEnvironment.application, null, @@ -319,16 +320,6 @@ public void setMockTrace_shouldThrowIfNotConnected() throws Exception { verify(service).remove(api.remoteCallback); } - @Test public void onDisconnect_shouldShutdownClientManager() throws Exception { - Context context = mock(Context.class); - clientManager = mock(LostClientManager.class); - FusedLocationProviderApiImpl apiImpl = new FusedLocationProviderApiImpl(connectionManager, - new FusedLocationServiceCallbackManager(), requestManager, clientManager); - apiImpl.onConnect(context); - apiImpl.onDisconnect(); - verify(clientManager).shutdown(); - } - @Test public void removeLocationUpdates_shouldReturnStatusSuccessIfListenerRemoved() { TestResultCallback callback = new TestResultCallback(); TestLocationListener listener = new TestLocationListener(); diff --git a/lost/src/test/java/com/mapzen/android/lost/internal/FusedLocationServiceCallbackManagerTest.java b/lost/src/test/java/com/mapzen/android/lost/internal/FusedLocationServiceCallbackManagerTest.java index 157383f..1fbb023 100644 --- a/lost/src/test/java/com/mapzen/android/lost/internal/FusedLocationServiceCallbackManagerTest.java +++ b/lost/src/test/java/com/mapzen/android/lost/internal/FusedLocationServiceCallbackManagerTest.java @@ -23,8 +23,6 @@ public class FusedLocationServiceCallbackManagerTest { @Test public void onLocationChanged_shouldReportLocationChanged() { LostClientManager clientManager = mock(LostClientManager.class); Location location = mock(Location.class); - when(clientManager.reportLocationChanged(any(Location.class))). - thenReturn(mock(ReportedChanges.class)); callbackManager.onLocationChanged(mock(Context.class), location, clientManager, mock(IFusedLocationProviderService.class)); verify(clientManager).reportLocationChanged(location); @@ -32,8 +30,6 @@ public class FusedLocationServiceCallbackManagerTest { @Test public void onLocationChanged_shouldSendPendingIntent() { LostClientManager clientManager = mock(LostClientManager.class); - when(clientManager.reportLocationChanged(any(Location.class))). - thenReturn(mock(ReportedChanges.class)); IFusedLocationProviderService service = mock(IFusedLocationProviderService.class); LocationAvailability locationAvailability = mock(LocationAvailability.class); try { @@ -50,32 +46,12 @@ public class FusedLocationServiceCallbackManagerTest { @Test public void onLocationChanged_shouldReportLocationResult() { LostClientManager clientManager = mock(LostClientManager.class); - when(clientManager.reportLocationChanged(any(Location.class))). - thenReturn(mock(ReportedChanges.class)); IFusedLocationProviderService service = mock(IFusedLocationProviderService.class); Location location = mock(Location.class); callbackManager.onLocationChanged(mock(Context.class), location, clientManager, service); verify(clientManager).reportLocationResult(eq(location), any(LocationResult.class)); } - @Test public void onLocationChanged_shouldUpdateReportedValues() { - LostClientManager clientManager = mock(LostClientManager.class); - ReportedChanges changes = mock(ReportedChanges.class); - when(clientManager.reportLocationChanged(any(Location.class))).thenReturn(changes); - ReportedChanges pendingIntentChanges = mock(ReportedChanges.class); - when(clientManager.sendPendingIntent(any(Context.class), any(Location.class), - any(LocationAvailability.class), any(LocationResult.class))).thenReturn( - pendingIntentChanges); - ReportedChanges callbackChanges = mock(ReportedChanges.class); - when(clientManager.reportLocationResult(any(Location.class), any(LocationResult.class))). - thenReturn(callbackChanges); - callbackManager.onLocationChanged(mock(Context.class), mock(Location.class), clientManager, - mock(IFusedLocationProviderService.class)); - verify(changes).putAll(pendingIntentChanges); - verify(changes).putAll(callbackChanges); - verify(clientManager).updateReportedValues(changes); - } - @Test public void onLocationAvailabilityChanged_shouldNotifyLocationAvailability() { LostClientManager clientManager = mock(LostClientManager.class); LocationAvailability locationAvailability = mock(LocationAvailability.class); diff --git a/lost/src/test/java/com/mapzen/android/lost/internal/FusionEngineTest.java b/lost/src/test/java/com/mapzen/android/lost/internal/FusionEngineTest.java index e25d213..ea994c8 100644 --- a/lost/src/test/java/com/mapzen/android/lost/internal/FusionEngineTest.java +++ b/lost/src/test/java/com/mapzen/android/lost/internal/FusionEngineTest.java @@ -94,35 +94,37 @@ public class FusionEngineTest extends BaseRobolectricTest { assertThat(fusionEngine.getLastLocation()).isEqualTo(passiveLocation); } - @Test public void getLastLocation_shouldIgnoreStaleLocations() throws Exception { - long time = System.currentTimeMillis(); + @Test @Config(sdk = 17) + public void getLastLocation_shouldIgnoreStaleLocations() throws Exception { + long time = android.os.SystemClock.elapsedRealtime() * MS_TO_NS; initTestClock(time); Location gpsLocation = new Location(GPS_PROVIDER); gpsLocation.setAccuracy(100); - gpsLocation.setTime(time); + gpsLocation.setElapsedRealtimeNanos(time); shadowLocationManager.setLastKnownLocation(GPS_PROVIDER, gpsLocation); Location networkLocation = new Location(NETWORK_PROVIDER); networkLocation.setAccuracy(100); - networkLocation.setTime(time - (2 * RECENT_UPDATE_THRESHOLD_IN_MILLIS)); + networkLocation.setElapsedRealtimeNanos(time - (2 * RECENT_UPDATE_THRESHOLD_IN_MILLIS)); shadowLocationManager.setLastKnownLocation(NETWORK_PROVIDER, networkLocation); assertThat(fusionEngine.getLastLocation()).isEqualTo(gpsLocation); } - @Test public void getLastLocation_ifNoFreshLocationsShouldReturnMostRecent() throws Exception { - long time = System.currentTimeMillis(); + @Test @Config(sdk = 17) + public void getLastLocation_ifNoFreshLocationsShouldReturnMostRecent() throws Exception { + long time = android.os.SystemClock.elapsedRealtime() * MS_TO_NS; initTestClock(time); Location gpsLocation = new Location(GPS_PROVIDER); gpsLocation.setAccuracy(100); - gpsLocation.setTime(time - (2 * RECENT_UPDATE_THRESHOLD_IN_MILLIS)); + gpsLocation.setElapsedRealtimeNanos(time - (2 * RECENT_UPDATE_THRESHOLD_IN_MILLIS)); shadowLocationManager.setLastKnownLocation(GPS_PROVIDER, gpsLocation); Location networkLocation = new Location(NETWORK_PROVIDER); networkLocation.setAccuracy(100); - networkLocation.setTime(time - (3 * RECENT_UPDATE_THRESHOLD_IN_MILLIS)); + networkLocation.setElapsedRealtimeNanos(time - (3 * RECENT_UPDATE_THRESHOLD_IN_MILLIS)); shadowLocationManager.setLastKnownLocation(NETWORK_PROVIDER, networkLocation); assertThat(fusionEngine.getLastLocation()).isEqualTo(gpsLocation); @@ -404,9 +406,9 @@ public void isBetterThan_shouldReturnTrueIfLocationBIsStale_Api17() throws Excep assertThat(FusionEngine.isBetterThan(locationA, locationB)).isFalse(); } - private static void initTestClock(long time) { + private static void initTestClock(long nanos) { TestClock testClock = new TestClock(); - testClock.setCurrentTimeInMillis(time); + testClock.currentTimeInNanos = nanos; FusionEngine.clock = testClock; } diff --git a/lost/src/test/java/com/mapzen/android/lost/internal/LostApiClientImplTest.java b/lost/src/test/java/com/mapzen/android/lost/internal/LostApiClientImplTest.java index bb28dcb..81c61ec 100644 --- a/lost/src/test/java/com/mapzen/android/lost/internal/LostApiClientImplTest.java +++ b/lost/src/test/java/com/mapzen/android/lost/internal/LostApiClientImplTest.java @@ -28,6 +28,8 @@ public class LostApiClientImplTest extends BaseRobolectricTest { private LostApiClient client; private TestConnectionCallbacks callbacks; + private Clock clock = mock(Clock.class); + private HandlerFactory handlerFactory = mock(HandlerFactory.class); @Before public void setUp() throws Exception { LostClientManager.shared().clearClients(); @@ -73,7 +75,7 @@ public class LostApiClientImplTest extends BaseRobolectricTest { client.connect(); TestConnectionCallbacks callbacks = new TestConnectionCallbacks(); LostApiClient anotherClient = - new LostApiClientImpl(application, callbacks, new LostClientManager()); + new LostApiClientImpl(application, callbacks, new LostClientManager(clock, handlerFactory)); callbacks.setLostClient(anotherClient); anotherClient.connect(); assertThat(callbacks.isClientConnectedOnConnect()).isTrue(); @@ -85,12 +87,12 @@ public class LostApiClientImplTest extends BaseRobolectricTest { @Override public void onConnected() { // Connect second Lost client with new connection callbacks once the service has connected. new LostApiClientImpl(application, new TestConnectionCallbacks(), - new LostClientManager()).connect(); + new LostClientManager(clock, handlerFactory)).connect(); } @Override public void onConnectionSuspended() { } - }, new LostClientManager()).connect(); + }, new LostClientManager(clock, handlerFactory)).connect(); FusedLocationProviderApiImpl api = (FusedLocationProviderApiImpl) LocationServices.FusedLocationApi; @@ -157,7 +159,7 @@ public class LostApiClientImplTest extends BaseRobolectricTest { @Test public void disconnect_multipleClients_shouldNotRemoveFusedLocationProviderApiImpl() throws Exception { LostApiClient anotherClient = - new LostApiClientImpl(application, callbacks, new LostClientManager()); + new LostApiClientImpl(application, callbacks, new LostClientManager(clock, handlerFactory)); anotherClient.connect(); client.connect(); client.disconnect(); @@ -166,7 +168,7 @@ public class LostApiClientImplTest extends BaseRobolectricTest { @Test public void disconnect_multipleClients_shouldNotRemoveGeofencingApiImpl() throws Exception { LostApiClient anotherClient = - new LostApiClientImpl(application, callbacks, new LostClientManager()); + new LostApiClientImpl(application, callbacks, new LostClientManager(clock, handlerFactory)); anotherClient.connect(); client.connect(); client.disconnect(); @@ -175,7 +177,7 @@ public class LostApiClientImplTest extends BaseRobolectricTest { @Test public void disconnect_multipleClients_shouldNotRemoveSettingsApiImpl() throws Exception { LostApiClient anotherClient = - new LostApiClientImpl(application, callbacks, new LostClientManager()); + new LostApiClientImpl(application, callbacks, new LostClientManager(clock, handlerFactory)); anotherClient.connect(); client.connect(); client.disconnect(); @@ -210,7 +212,7 @@ public class LostApiClientImplTest extends BaseRobolectricTest { @Test public void isConnected_multipleClients_shouldReturnFalseAfterDisconnected() throws Exception { LostApiClient anotherClient = - new LostApiClientImpl(application, callbacks, new LostClientManager()); + new LostApiClientImpl(application, callbacks, new LostClientManager(clock, handlerFactory)); anotherClient.connect(); client.connect(); client.disconnect(); diff --git a/lost/src/test/java/com/mapzen/android/lost/internal/LostClientManagerTest.java b/lost/src/test/java/com/mapzen/android/lost/internal/LostClientManagerTest.java index a9d0e4f..7930ab3 100644 --- a/lost/src/test/java/com/mapzen/android/lost/internal/LostClientManagerTest.java +++ b/lost/src/test/java/com/mapzen/android/lost/internal/LostClientManagerTest.java @@ -2,6 +2,7 @@ import com.mapzen.android.lost.BaseRobolectricTest; import com.mapzen.android.lost.api.LocationAvailability; +import com.mapzen.android.lost.api.LocationCallback; import com.mapzen.android.lost.api.LocationRequest; import com.mapzen.android.lost.api.LocationResult; import com.mapzen.android.lost.api.LostApiClient; @@ -15,27 +16,38 @@ import org.robolectric.shadows.ShadowApplication; import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; import android.location.Location; import android.os.Looper; import java.util.ArrayList; +import java.util.List; import static android.location.LocationManager.GPS_PROVIDER; import static com.mapzen.android.lost.api.LocationAvailability.EXTRA_LOCATION_AVAILABILITY; import static com.mapzen.android.lost.api.LocationResult.EXTRA_LOCATION_RESULT; import static org.fest.assertions.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.robolectric.RuntimeEnvironment.application; @RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21, manifest = Config.NONE) public class LostClientManagerTest extends BaseRobolectricTest { - LostClientManager manager = new LostClientManager(); + Clock clock; + HandlerFactory handlerFactory; + LostClientManager manager; LostApiClient client; @Before public void setup() throws Exception { + clock = mock(Clock.class); + handlerFactory = new TestHandlerFactory(); + manager = new LostClientManager(clock, handlerFactory); client = new LostApiClient.Builder(application).build(); } @@ -109,6 +121,19 @@ public void addLocationCallback_shouldThrowExceptionIfClientWasNotAdded() throws assertThat(manager.getLocationListeners().get(client)).isEmpty(); } + @Test public void removeListener_shouldSendUpdateImmediatelyOnReAdd() throws Exception { + manager.addClient(client); + LocationRequest request = LocationRequest.create(); + TestLocationListener listener = new TestLocationListener(); + manager.addListener(client, request, listener); + manager.reportLocationChanged(mock(Location.class)); + assertThat(listener.getAllLocations().size()).isEqualTo(1); + manager.removeListener(client, listener); + manager.addListener(client, request, listener); + manager.reportLocationChanged(mock(Location.class)); + assertThat(listener.getAllLocations().size()).isEqualTo(2); + } + @Test public void removePendingIntent_shouldRemovePendingIntentForClient() { manager.addClient(client); LocationRequest request = LocationRequest.create(); @@ -118,6 +143,22 @@ public void addLocationCallback_shouldThrowExceptionIfClientWasNotAdded() throws assertThat(manager.getPendingIntents().get(client)).isEmpty(); } + @Test public void removePendingIntent_shouldSendUpdateImmediatelyOnReAdd() throws Exception { + manager.addClient(client); + LocationRequest request = LocationRequest.create(); + PendingIntent pendingIntent = mock(PendingIntent.class); + manager.addPendingIntent(client, request, pendingIntent); + List locations = new ArrayList(); + manager.sendPendingIntent(mock(Context.class), mock(Location.class), + mock(LocationAvailability.class), LocationResult.create(locations)); + verify(pendingIntent).send(any(Context.class), anyInt(), any(Intent.class)); + manager.removePendingIntent(client, pendingIntent); + manager.addPendingIntent(client, request, pendingIntent); + manager.sendPendingIntent(mock(Context.class), mock(Location.class), + mock(LocationAvailability.class), LocationResult.create(locations)); + verify(pendingIntent, times(2)).send(any(Context.class), anyInt(), any(Intent.class)); + } + @Test public void removeLocationCallback_shouldRemoveLocationCallbackForClient() { manager.addClient(client); LocationRequest request = LocationRequest.create(); @@ -128,6 +169,21 @@ public void addLocationCallback_shouldThrowExceptionIfClientWasNotAdded() throws assertThat(manager.getLocationCallbacks().get(client)).isEmpty(); } + @Test public void removeLocationCallback_shouldSendUpdateImmediatelyOnReAdd() throws Exception { + manager.addClient(client); + LocationRequest request = LocationRequest.create(); + LocationCallback callback = mock(LocationCallback.class); + Looper looper = mock(Looper.class); + manager.addLocationCallback(client, request, callback, looper); + List locations = new ArrayList(); + manager.reportLocationResult(mock(Location.class), LocationResult.create(locations)); + verify(callback).onLocationResult(any(LocationResult.class)); + manager.removeLocationCallback(client, callback); + manager.addLocationCallback(client, request, callback, looper); + manager.reportLocationResult(mock(Location.class), LocationResult.create(locations)); + verify(callback, times(2)).onLocationResult(any(LocationResult.class)); + } + @Test public void reportLocationChanged_shouldNotifyListener() { manager.addClient(client); LocationRequest request = LocationRequest.create(); @@ -254,8 +310,7 @@ public void addLocationCallback_shouldThrowExceptionIfClientWasNotAdded() throws Location location1 = getTestLocation("test_provider", 0, 0, 0); Location location2 = getTestLocation("test_provider", 1, 1, 1000); - ReportedChanges reportedChanges = manager.reportLocationChanged(location1); - manager.updateReportedValues(reportedChanges); + manager.reportLocationChanged(location1); manager.reportLocationChanged(location2); assertThat(listener.getMostRecentLocation()).isEqualTo(location1); } @@ -272,8 +327,7 @@ public void addLocationCallback_shouldThrowExceptionIfClientWasNotAdded() throws Location location1 = getTestLocation("test_provider", 0, 0, 0); Location location2 = getTestLocation("test_provider", 1, 1, 1); - ReportedChanges reportedChanges = manager.reportLocationChanged(location1); - manager.updateReportedValues(reportedChanges); + manager.reportLocationChanged(location1); manager.reportLocationChanged(location2); assertThat(listener.getMostRecentLocation()).isEqualTo(location1); } diff --git a/lost/src/test/java/com/mapzen/android/lost/internal/ReportedChangesTest.java b/lost/src/test/java/com/mapzen/android/lost/internal/ReportedChangesTest.java deleted file mode 100644 index d9b8838..0000000 --- a/lost/src/test/java/com/mapzen/android/lost/internal/ReportedChangesTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.mapzen.android.lost.internal; - -import com.mapzen.android.lost.api.LocationRequest; - -import org.junit.Before; -import org.junit.Test; - -import android.location.Location; - -import java.util.HashMap; -import java.util.Map; - -import static org.fest.assertions.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -public class ReportedChangesTest { - - Map timeChanges = new HashMap<>(); - Map locationChanges = new HashMap<>(); - ReportedChanges changes; - - @Before public void setup() { - changes = new ReportedChanges(timeChanges, locationChanges); - } - - @Test public void putAll_shouldUpdateTimeChanges() { - Map otherTimeChanges = new HashMap<>(); - LocationRequest locRequest = LocationRequest.create(new TestPidReader()); - otherTimeChanges.put(locRequest, 1234L); - Map otherLocationChanges = new HashMap<>(); - ReportedChanges otherChanges = new ReportedChanges(otherTimeChanges, otherLocationChanges); - changes.putAll(otherChanges); - assertThat(changes.timeChanges().get(locRequest)).isEqualTo(1234L); - } - - @Test public void putAll_shouldUpdateLocationChanges() { - Map otherTimeChanges = new HashMap<>(); - Map otherLocationChanges = new HashMap<>(); - LocationRequest locRequest = LocationRequest.create(new TestPidReader()); - Location loc = mock(Location.class); - otherLocationChanges.put(locRequest, loc); - ReportedChanges otherChanges = new ReportedChanges(otherTimeChanges, otherLocationChanges); - changes.putAll(otherChanges); - assertThat(changes.locationChanges().get(locRequest)).isEqualTo(loc); - } - - @Test public void clearAll_shouldClearTimeChanges() { - Map otherTimeChanges = new HashMap<>(); - LocationRequest locRequest = LocationRequest.create(new TestPidReader()); - otherTimeChanges.put(locRequest, 1234L); - Map otherLocationChanges = new HashMap<>(); - Location loc = mock(Location.class); - otherLocationChanges.put(locRequest, loc); - ReportedChanges otherChanges = new ReportedChanges(otherTimeChanges, otherLocationChanges); - changes.putAll(otherChanges); - changes.clearAll(); - assertThat(changes.timeChanges()).isEmpty(); - } - - @Test public void clearAll_shouldClearLocationChanges() { - Map otherTimeChanges = new HashMap<>(); - LocationRequest locRequest = LocationRequest.create(new TestPidReader()); - otherTimeChanges.put(locRequest, 1234L); - Map otherLocationChanges = new HashMap<>(); - Location loc = mock(Location.class); - otherLocationChanges.put(locRequest, loc); - ReportedChanges otherChanges = new ReportedChanges(otherTimeChanges, otherLocationChanges); - changes.putAll(otherChanges); - changes.clearAll(); - assertThat(changes.locationChanges()).isEmpty(); - } - -} diff --git a/lost/src/test/java/com/mapzen/android/lost/internal/SystemClockTest.java b/lost/src/test/java/com/mapzen/android/lost/internal/SystemClockTest.java index 23ab48d..9db8a3a 100644 --- a/lost/src/test/java/com/mapzen/android/lost/internal/SystemClockTest.java +++ b/lost/src/test/java/com/mapzen/android/lost/internal/SystemClockTest.java @@ -15,12 +15,15 @@ @RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21, manifest = Config.NONE) public class SystemClockTest { + + SystemClock clock = new SystemClock(); + @Test @Config(sdk = 17) public void getTimeInNanos_shouldReturnElapsedRealtimeNanosForSdk17AndUp() throws Exception { final long nanos = 1000000; final Location location = new Location("mock"); location.setElapsedRealtimeNanos(nanos); - assertThat(SystemClock.getTimeInNanos(location)).isEqualTo(nanos); + assertThat(clock.getElapsedTimeInNanos(location)).isEqualTo(nanos); } @Test @Config(sdk = 16) public void getTimeInNanos_shouldUseUtcTimeInMillisForSdk16AndLower() @@ -28,6 +31,6 @@ public class SystemClockTest { final long millis = 1000; final Location location = new Location("mock"); location.setTime(millis); - assertThat(SystemClock.getTimeInNanos(location)).isEqualTo(millis * MS_TO_NS); + assertThat(clock.getElapsedTimeInNanos(location)).isEqualTo(millis * MS_TO_NS); } } diff --git a/lost/src/test/java/com/mapzen/android/lost/internal/TestClock.java b/lost/src/test/java/com/mapzen/android/lost/internal/TestClock.java index a529b5d..44d0335 100644 --- a/lost/src/test/java/com/mapzen/android/lost/internal/TestClock.java +++ b/lost/src/test/java/com/mapzen/android/lost/internal/TestClock.java @@ -1,13 +1,16 @@ package com.mapzen.android.lost.internal; +import android.location.Location; + public class TestClock implements Clock { - private long currentTimeInMillis; + long currentTimeInNanos; + long elapsedTimeInNanos; - @Override public long getCurrentTimeInMillis() { - return currentTimeInMillis; + @Override public long getSystemElapsedTimeInNanos() { + return currentTimeInNanos; } - public void setCurrentTimeInMillis(long currentTimeInMillis) { - this.currentTimeInMillis = currentTimeInMillis; + @Override public long getElapsedTimeInNanos(Location location) { + return elapsedTimeInNanos; } } diff --git a/lost/src/test/java/com/mapzen/android/lost/internal/TestHandlerFactory.java b/lost/src/test/java/com/mapzen/android/lost/internal/TestHandlerFactory.java new file mode 100644 index 0000000..3c3fb00 --- /dev/null +++ b/lost/src/test/java/com/mapzen/android/lost/internal/TestHandlerFactory.java @@ -0,0 +1,10 @@ +package com.mapzen.android.lost.internal; + +import android.os.Looper; + +public class TestHandlerFactory implements HandlerFactory { + + @Override public void run(Looper looper, Runnable runnable) { + runnable.run(); + } +}