package io.nlopez.smartlocation.location.providers;
import android.content.Context;
import android.location.Location;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Queue;
import io.nlopez.smartlocation.OnLocationUpdatedListener;
import io.nlopez.smartlocation.location.LocationProvider;
import io.nlopez.smartlocation.location.ServiceLocationProvider;
import io.nlopez.smartlocation.location.config.LocationParams;
import io.nlopez.smartlocation.utils.Logger;
/**
* A {@link LocationProvider} that allows multiple location services to be used. <br/><br/> New
* instances of <code>MultiFallbackProvider</code> must be initialized via the Builder class:
* <pre>
* LocationProvider provider = new MultiLocationProvider.Builder()
* .withGooglePlayServicesProvider()
* .withDefaultProvider()
* .build();
* </pre>
* <code>MultiFallbackProvider</code> will attempt to use the location services in the order they
* were added to the builder. If the provider fails to connect to the underlying service, the next
* provider in the list is used. <br/><br/> If no providers are added to the builder, the {@link
* LocationManagerProvider} is used by default.
*
* @author abkaplan07
*/
public class MultiFallbackProvider implements LocationProvider {
private Queue<LocationProvider> providers;
private LocationProvider currentProvider;
private Context context;
private Logger logger;
private OnLocationUpdatedListener locationListener;
private LocationParams locationParams;
private boolean singleUpdate;
private boolean shouldStart;
MultiFallbackProvider() {
this.providers = new LinkedList<>();
}
@Override
public void init(Context context, Logger logger) {
this.context = context;
this.logger = logger;
LocationProvider current = getCurrentProvider();
if (current != null) {
current.init(context, logger);
}
}
@Override
public void start(OnLocationUpdatedListener listener, LocationParams params, boolean
singleUpdate) {
this.shouldStart = true;
this.locationListener = listener;
this.locationParams = params;
this.singleUpdate = singleUpdate;
LocationProvider current = getCurrentProvider();
if (current != null) {
current.start(listener, params, singleUpdate);
}
}
@Override
public void stop() {
LocationProvider current = getCurrentProvider();
if (current != null) {
current.stop();
}
}
@Override
public Location getLastLocation() {
LocationProvider current = getCurrentProvider();
if (current == null) {
return null;
}
return current.getLastLocation();
}
boolean addProvider(LocationProvider provider) {
return providers.add(provider);
}
Collection<LocationProvider> getProviders() {
return providers;
}
/**
* Gets the current <code>LocationProvider</code> instance in use.
*
* @return the underlying <code>LocationProvider</code> used for location services.
*/
LocationProvider getCurrentProvider() {
if (currentProvider == null && !providers.isEmpty()) {
currentProvider = providers.poll();
}
return currentProvider;
}
/**
* Fetches the next location provider in the fallback list, and initializes it. If location
* updates have already been started, this restarts location updates.<br/><br/>If there are no
* location providers left, no action occurs.
*/
void fallbackProvider() {
if (!providers.isEmpty()) {
// Stop the current provider if it is running
currentProvider.stop();
// Fetch the next provider in the list.
currentProvider = providers.poll();
currentProvider.init(context, logger);
if (shouldStart) {
currentProvider.start(locationListener, locationParams, singleUpdate);
}
}
}
/**
* Builder class for the {@link MultiFallbackProvider}.
*/
public static class Builder {
private MultiFallbackProvider builtProvider;
public Builder() {
this.builtProvider = new MultiFallbackProvider();
}
/**
* Adds Google Location Services as a provider.
*/
public Builder withGooglePlayServicesProvider() {
return withServiceProvider(new LocationGooglePlayServicesProvider());
}
/**
* Adds the built-in Android Location Manager as a provider.
*/
public Builder withDefaultProvider() {
return withProvider(new LocationManagerProvider());
}
/**
* Adds the given {@link ServiceLocationProvider} as a location provider. If the given
* location provider detects that its underlying service is not available, the built
* <code>MultiFallbackProvider</code> will fall back to the next location provider in the
* list.
*
* @param provider a <code>ServiceLocationProvider</code> that can detect if the underlying
* location service is not available.
*/
public Builder withServiceProvider(ServiceLocationProvider provider) {
FallbackListenerWrapper fallbackListener = new FallbackListenerWrapper(builtProvider,
provider);
provider.setServiceListener(fallbackListener);
return withProvider(provider);
}
/**
* Adds the given {@link LocationProvider} as a provider. Note that these providers
* <strong>DO NOT</strong> support fallback behavior.
*
* @param provider a <code>LocationProvider</code> instance.
*/
public Builder withProvider(LocationProvider provider) {
builtProvider.addProvider(provider);
return this;
}
/**
* Builds a {@link MultiFallbackProvider} instance. If no providers were added to the
* builder, the built-in Android Location Manager is used.
*/
public MultiFallbackProvider build() {
// Always ensure we have the default provider
if (builtProvider.providers.isEmpty()) {
withDefaultProvider();
}
return builtProvider;
}
}
}