/*
* 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;
/**
* All of the {@link LocationProvider} implementations in OpenLAPI share common code, here
* is the place where we store it. This also makes creating new implementations somewhat
* easier as there is not so much boilerplate to worry about.
* <p>
* This class should not be used directly by applications.
*
* @author Samuel Halliday, ThinkTank Maths Limited
*/
public abstract class LocationProviderSimplified extends LocationProvider
implements LocationBackendListener {
// this is a count of the number of Threads calling the getLocation method
// equivalent to the count within a semaphore
private int countGetLocation = 0;
private volatile boolean interrupt;
private LocationListener listener = null;
private volatile LocationDaemon locDaemon = null;
private volatile int state = TEMPORARILY_UNAVAILABLE;
public final Location getLocation(int timeout) throws LocationException,
InterruptedException, SecurityException, IllegalArgumentException {
interrupt = false;
OpenLAPICommon.testPermission("javax.microedition.location.Location");
if (timeout <= 0)
throw new IllegalArgumentException();
if (state == OUT_OF_SERVICE)
throw new LocationException("Out of service");
/*
* Get the last known location and if it is no older than timeout, return it
* immediately. Note that this is OpenLAPI being liberal with the definition in
* JSR-179, which does not explicitly state that the timestamp must be newer than
* when this method is called.
*/
long time = System.currentTimeMillis();
Location location = getLastKnownLocation();
long last = location.getTimestamp();
// returning a valid location is not required by the spec, but let's be nice...
if ((last + 1000L * timeout) >= time && location.isValid())
return location;
try {
synchronized (this) {
countGetLocation++;
// start up the backend if it's not already running
startBackend();
}
// set the time when we should timeout
long timeoutMillis = 1000L * timeout + time;
// set the period between checks to allow for 10 checks in total
// note that Thread.sleep takes milliseconds, but timeout is in seconds
long period = 100L * timeout;
// loop until we timeout or we get a valid fix on our location
while (System.currentTimeMillis() <= timeoutMillis) {
if (interrupt)
throw new InterruptedException(
"getLocation() was interrupted by reset()");
// now periodically poll the last known location,
// which will be set by the background worker thread
location = getLastKnownLocation();
// ensure Location is newer than what was known before calling this method
if (!location.isValid() || (location.getTimestamp() <= last)) {
Thread.sleep(period);
continue;
}
return location;
}
// fail
throw new LocationException("Timed out");
} finally {
synchronized (this) {
countGetLocation--;
maybeStopBackend();
}
}
}
public final int getState() {
return state;
}
public final synchronized void reset() {
interrupt = true;
stopBackend();
}
// synchronised to avoid all kinds of nasty race conditions with starting and
// stopping various daemons
public final synchronized void setLocationListener(
LocationListener listener, int interval, int timeout, int maxAge) {
this.listener = listener;
if (listener != null)
try {
startBackend();
} catch (LocationException e) {
// provider temporarily failed to start
}
else
maybeStopBackend();
alertProximityListeners((listener == null) ? false : true);
if (locDaemon != null) {
// tell the current listen thread to change
locDaemon.update(listener, interval, timeout, maxAge);
return;
} else if (listener != null) {
// first listener registered, start a new thread
locDaemon =
new LocationDaemon(listener, interval, timeout, maxAge,
this);
new Thread(locDaemon).start();
}
}
public final void updateLocation(Location newLocation) {
setLastKnownLocation(newLocation);
}
public final synchronized void updateState(int newState) {
if (state == newState)
return;
state = newState;
if (listener != null)
listener.providerStateChanged(this, newState);
}
/**
* Stop the backend if there are no threads calling {@link #getLocation(int)} and
* there are no {@link LocationListener} instances registered with this provider. This
* method must be called within a block synchronized on this instance, as should all
* methods dealing with the state of the backend.
*/
private void maybeStopBackend() {
if ((listener != null) || (countGetLocation > 0))
return;
stopBackend();
}
/**
* Start a new backend in a background thread. Ignore if a backend is currently
* running. Always called from a block that is synchronized on this.
*
* @throws LocationException
* if the backend could not be started
*/
abstract protected void startBackend() throws LocationException;
/**
* Stop the backend that is currently running. Ignore if no backend is currently
* running. Always called from a block that is synchronized on this.
*
* @throws LocationException
* if the backend could not be stopped
*/
protected abstract void stopBackend();
}