/*
* Copyright ThinkTank Maths Limited 2006 - 2008
*
* This file is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this file. If not, see <http://www.gnu.org/licenses/>.
*/
package com.openlapi;
/**
* Periodically polls the instantiating {@link LocationProvider#getLocation(int)} to allow
* {@link LocationListener}s to receive updates.
* <p>
* Listening to LocationProvider state changes is not the responsibility of this class.
*/
class LocationDaemon implements Runnable {
/**
* Runs until it obtains a valid {@link Location}, or the {@link LocationListener} is
* de-registered.
*/
private class FirstValidLocation implements Runnable {
public void run() {
while (!end) {
try {
Location location = provider.getLocation(Integer.MAX_VALUE);
if (!location.isValid())
continue;
updateListener(location);
break;
} catch (Exception e) {
}
}
}
}
// If -1 is passed as the interval, this is used.
private static final int DEFAULT_LISTEN_INTERVAL = 60000;
// If -1 is passed as the maxAge, this is used.
private static final int DEFAULT_MAXAGE = 20000;
// If -1 is passed as the timeout, this is used.
private static final int DEFAULT_TIMEOUT = 10;
// Flag to alert the listener if the parameters have been changed recently.
private volatile boolean changed = false;
// Set true when the thread is to finish
private volatile boolean end = false;
private int interval;
private volatile LocationListener listener;
private int maxAge;
private final LocationProvider provider;
private int timeout;
/**
* @param listener
* @param interval
* @param timeout
* @param maxAge
* @param provider
*/
protected LocationDaemon(LocationListener listener, int interval,
int timeout, int maxAge, LocationProvider provider) {
this.provider = provider;
update(listener, interval, timeout, maxAge);
changed = false;
}
public void run() {
if (listener == null)
return;
// only spawn FirstValidLocation if lastKnownLocation isn't valid
Location firstLocation = getLocation(0);
if (firstLocation.isValid()) {
updateListener(firstLocation);
} else {
FirstValidLocation firstLocationThread = new FirstValidLocation();
new Thread(firstLocationThread).start();
}
// thereafter loop the wait and capture process
while (!end) {
synchronized (this) {
if (changed) {
changed = false;
continue;
}
}
Location location = getLocation(timeout);
if (end)
break;
updateListener(location);
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
}
}
}
// Helper method to return the Location, but will grab the last known location if it's
// new enough. If there were any problems, an invalid Location object is returned.
// synchronised to encourage contention here, increasing the likelihood of
// getting a "free" recent lookup.
private synchronized Location getLocation(int timeout) {
Location location = LocationProvider.getLastKnownLocation();
if (location.isValid()) {
int age =
(int) (System.currentTimeMillis() - location.getTimestamp());
if (age <= maxAge)
return location;
}
try {
return provider.getLocation(timeout);
} catch (Exception e) {
return Location.getInvalid();
}
}
// ensures that this operation is always atomic
private synchronized void updateListener(Location location) {
if (listener != null)
listener.locationUpdated(provider, location);
}
/**
* End all running daemons cleanly.
*/
protected void end() {
end = true;
}
/**
* Register a new LocationListener and parameters.
*
* @param listener
* @param interval
* @param timeout
* @param maxAge
*/
protected synchronized void update(LocationListener listener, int interval,
int timeout, int maxAge) {
// convert to milliseconds
maxAge = 1000 * maxAge;
interval = 1000 * interval;
if (interval == -1000) {
interval = DEFAULT_LISTEN_INTERVAL;
}
if (maxAge == -1000) {
maxAge = DEFAULT_MAXAGE;
}
if (timeout == -1) {
// special case: timeout is shorter than interval
if (DEFAULT_TIMEOUT > (interval / 1000)) {
timeout = interval / 1000;
} else {
timeout = DEFAULT_TIMEOUT;
}
}
this.listener = listener;
this.interval = interval;
this.timeout = timeout;
this.maxAge = maxAge;
// alert the loop of a change
changed = true;
if (listener == null)
end();
}
}