Skip to content
This repository has been archived by the owner on Apr 23, 2023. It is now read-only.

Commit

Permalink
Throw IllegalStateException (#210)
Browse files Browse the repository at this point in the history
* throw IllegalStateException

* move service callback code into separate class & add tests

* add back final

* add documentation
  • Loading branch information
sarahsnow1 committed May 24, 2017
1 parent ce4bf79 commit f2757a6
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.mapzen.android.lost.internal.DwellServiceIntentFactory;
import com.mapzen.android.lost.internal.FusedLocationProviderApiImpl;
import com.mapzen.android.lost.internal.FusedLocationServiceCallbackManager;
import com.mapzen.android.lost.internal.FusedLocationServiceConnectionManager;
import com.mapzen.android.lost.internal.GeofencingApiImpl;
import com.mapzen.android.lost.internal.GeofencingServiceIntentFactory;
Expand All @@ -17,7 +18,8 @@ public class LocationServices {
* Entry point for APIs concerning location updates.
*/
public static final FusedLocationProviderApi FusedLocationApi =
new FusedLocationProviderApiImpl(new FusedLocationServiceConnectionManager());
new FusedLocationProviderApiImpl(new FusedLocationServiceConnectionManager(),
new FusedLocationServiceCallbackManager());

/**
* Entry point for APIs concerning geofences.
Expand Down
36 changes: 36 additions & 0 deletions lost/src/main/java/com/mapzen/android/lost/api/LostApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,63 @@ interface ConnectionCallbacks {
void onConnectionSuspended();
}

/**
* Connects the client so that it will be ready for use. This must be done before any of the
* {@link LocationServices} APIs can be interacted with. When the client is connected, any
* registered {@link ConnectionCallbacks} will receive a call to
* {@link ConnectionCallbacks#onConnected()} and the client can then be used.
*/
void connect();

/**
* Disconnects the client. To avoid {@link IllegalStateException}s, be sure to unregister any
* location updates requested through the {@link FusedLocationProviderApi}.
*/
void disconnect();

/**
* Returns whether or not the client is connected and ready to be used.
* @return
*/
boolean isConnected();

/**
* Unregisters callbacks added in {@link LostApiClient.Builder#addConnectionCallbacks(
* ConnectionCallbacks)}. Use this method to avoid leaking resources.
* @param callbacks
*/
void unregisterConnectionCallbacks(ConnectionCallbacks callbacks);

/**
* {@link LostApiClient} builder class for creating and configuring new instances.
*/
final class Builder {
private final Context context;
private WeakReference<ConnectionCallbacks> connectionCallbacks;

/**
* Creates a new object using the {@link Context}'s application context.
* @param context
*/
public Builder(Context context) {
this.context = context.getApplicationContext();
}

/**
* Adds {@link ConnectionCallbacks} to the client. It is strongly recommended that these
* callbacks are used to determine when the client is connected and ready for use.
* @param callbacks
* @return
*/
public Builder addConnectionCallbacks(ConnectionCallbacks callbacks) {
this.connectionCallbacks = new WeakReference(callbacks);
return this;
}

/**
* Builds a new client given the properties currently configured on the builder.
* @return
*/
public LostApiClient build() {
ConnectionCallbacks callbacks = null;
if (connectionCallbacks != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import com.mapzen.android.lost.api.LocationCallback;
import com.mapzen.android.lost.api.LocationListener;
import com.mapzen.android.lost.api.LocationRequest;
import com.mapzen.android.lost.api.LocationResult;
import com.mapzen.android.lost.api.LostApiClient;
import com.mapzen.android.lost.api.PendingResult;
import com.mapzen.android.lost.api.Status;
Expand All @@ -22,7 +21,6 @@
import android.os.Looper;
import android.os.RemoteException;

import java.util.ArrayList;
import java.util.Map;
import java.util.Set;

Expand All @@ -34,6 +32,7 @@ public class FusedLocationProviderApiImpl extends ApiImpl

private Context context;
private FusedLocationServiceConnectionManager serviceConnectionManager;
private FusedLocationServiceCallbackManager serviceCallbackManager;
private boolean isBound;

IFusedLocationProviderService service;
Expand All @@ -44,33 +43,15 @@ public void onLocationChanged(final Location location) throws RemoteException {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override public void run() {
final LostClientManager clientManager = LostClientManager.shared();
ReportedChanges changes = clientManager.reportLocationChanged(location);

LocationAvailability availability;
try {
availability = service.getLocationAvailability();
} catch (RemoteException e) {
throw new RuntimeException(e);
}

ArrayList<Location> locations = new ArrayList<>();
locations.add(location);
final LocationResult result = LocationResult.create(locations);
ReportedChanges pendingIntentChanges = clientManager.sendPendingIntent(
context, location, availability, result);

ReportedChanges callbackChanges = clientManager.reportLocationResult(location, result);

changes.putAll(pendingIntentChanges);
changes.putAll(callbackChanges);
clientManager.updateReportedValues(changes);
serviceCallbackManager.onLocationChanged(context, location, clientManager, service);
}
});
}

@Override public void onLocationAvailabilityChanged(LocationAvailability locationAvailability)
throws RemoteException {
LostClientManager.shared().notifyLocationAvailability(locationAvailability);
LostClientManager clientManager = LostClientManager.shared();
serviceCallbackManager.onLocationAvailabilityChanged(locationAvailability, clientManager);
}
};

Expand Down Expand Up @@ -106,8 +87,10 @@ public void onLocationChanged(final Location location) throws RemoteException {
isBound = false;
}

public FusedLocationProviderApiImpl(FusedLocationServiceConnectionManager connectionManager) {
public FusedLocationProviderApiImpl(FusedLocationServiceConnectionManager connectionManager,
FusedLocationServiceCallbackManager callbackManager) {
serviceConnectionManager = connectionManager;
serviceCallbackManager = callbackManager;
serviceConnectionManager.setEventCallbacks(this);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.mapzen.android.lost.internal;

import com.mapzen.android.lost.api.LocationAvailability;
import com.mapzen.android.lost.api.LocationResult;

import android.content.Context;
import android.location.Location;
import android.os.RemoteException;

import java.util.ArrayList;

/**
* Handles callbacks received in {@link FusedLocationProviderApiImpl} from
* {@link FusedLocationProviderService}.
*/
public class FusedLocationServiceCallbackManager {

/**
* Called when a new location has been received. This method handles dispatching changes to all
* {@link com.mapzen.android.lost.api.LocationListener}s, {@link android.app.PendingIntent}s, and
* {@link com.mapzen.android.lost.api.LocationCallback}s which are registered. If the
* {@link IFusedLocationProviderService} is null, an {@link IllegalStateException} will be thrown.
* @param context
* @param location
* @param clientManager
* @param service
*/
void onLocationChanged(Context context, Location location, LostClientManager clientManager,
IFusedLocationProviderService service) {
if (service == null) {
throw new IllegalStateException("Location update received after client was "
+ "disconnected. Did you forget to unregister location updates before "
+ "disconnecting?");
}

ReportedChanges changes = clientManager.reportLocationChanged(location);

LocationAvailability availability;
try {
availability = service.getLocationAvailability();
} catch (RemoteException e) {
throw new RuntimeException(e);
}

ArrayList<Location> locations = new ArrayList<>();
locations.add(location);
final LocationResult result = LocationResult.create(locations);
ReportedChanges pendingIntentChanges = clientManager.sendPendingIntent(
context, location, availability, result);

ReportedChanges callbackChanges = clientManager.reportLocationResult(location, result);

changes.putAll(pendingIntentChanges);
changes.putAll(callbackChanges);
clientManager.updateReportedValues(changes);
}

/**
* Handles notifying all registered {@link LocationCallback}s that {@link LocationAvailability}
* has changed.
* @param locationAvailability
* @param clientManager
*/
void onLocationAvailabilityChanged(LocationAvailability locationAvailability,
LostClientManager clientManager) {
clientManager.notifyLocationAvailability(locationAvailability);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ public class FusedLocationProviderApiImplTest extends BaseRobolectricTest {
FusedLocationServiceConnectionManager.EventCallbacks.class));
Mockito.doCallRealMethod().when(connectionManager).connect(any(Context.class), any(
LostApiClient.ConnectionCallbacks.class));
api = new FusedLocationProviderApiImpl(connectionManager);
api = new FusedLocationProviderApiImpl(connectionManager,
new FusedLocationServiceCallbackManager());
api.connect(application, null);
api.service = service;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.mapzen.android.lost.internal;

import com.mapzen.android.lost.api.LocationAvailability;
import com.mapzen.android.lost.api.LocationResult;

import org.junit.Test;

import android.content.Context;
import android.location.Location;
import android.os.RemoteException;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class FusedLocationServiceCallbackManagerTest {

FusedLocationServiceCallbackManager callbackManager =
new FusedLocationServiceCallbackManager();

@Test(expected = IllegalStateException.class)
public void onLocationChanged_shouldThrowIfServiceDisconnected() {
callbackManager.onLocationChanged(mock(Context.class), mock(Location.class),
mock(LostClientManager.class), null);
}

@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);
}

@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 {
when(service.getLocationAvailability()).thenReturn(locationAvailability);
} catch (RemoteException e) {
e.printStackTrace();
}
Context context = mock(Context.class);
Location location = mock(Location.class);
callbackManager.onLocationChanged(context, location, clientManager, service);
verify(clientManager).sendPendingIntent(eq(context), eq(location), eq(locationAvailability),
any(LocationResult.class));
}

@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);
callbackManager.onLocationAvailabilityChanged(locationAvailability, clientManager);
verify(clientManager).notifyLocationAvailability(locationAvailability);
}
}

0 comments on commit f2757a6

Please sign in to comment.