/*
*
*
* Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program 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
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.j2me.location;
import com.sun.j2me.log.Logging;
import java.util.Vector;
import javax.microedition.location.Coordinates;
import javax.microedition.location.Location;
import javax.microedition.location.LocationException;
import javax.microedition.location.LocationProvider;
import javax.microedition.location.ProximityListener;
import javax.microedition.location.QualifiedCoordinates;
/**
* Asynchronous thread for periodic location
* event handling.
*/
public class ProximityNotifier {
/**
* Instance of the proximity notifier. If the application does not
* use proximity notification, this class will not be instantiated.
*/
static ProximityNotifier Instance = null;
/** Array of registered listeners. */
Vector proximityListeners = new Vector();
/** Thread for proximity notifications. */
ProximityThread proximityThread = null;
/** Current thread performing monitoring state updating. */
StateMonitorThread stateThread = null;
/** Dedicated provider for delivering proximity data. */
LocationProviderImpl proximityProvider = null;
/**
* Gets a handle to proximity notifier.
* @return current proximity notifier handle
*/
public static ProximityNotifier getInstance() {
if (Instance == null) {
Instance = new ProximityNotifier();
}
return Instance;
}
/**
* Constructor.
*/
private ProximityNotifier() {
}
/**
* This class wraps proximity listeners and adds additional information.
*/
static class ProximityListenerDecorator {
/** Location proximity listener. */
private ProximityListener listener;
/** Coordinates to monitor. */
private Coordinates coordinates;
/** Radius for proximity check. */
private float proximityRadius;
// JAVADOC COMMENT ELIDED
ProximityListenerDecorator(ProximityListener listener,
Coordinates coordinates,
float proximityRadius) {
this.listener = listener;
this.coordinates = coordinates;
this.proximityRadius = proximityRadius;
}
// JAVADOC COMMENT ELIDED
public void proximityEvent(Location location) {
// Nothing to notify if an invalid location
if (!location.isValid()) {
return;
}
QualifiedCoordinates qCoord = location.getQualifiedCoordinates();
float distance = coordinates.distance(qCoord);
float hAccuracy = qCoord.getHorizontalAccuracy();
if (Float.isNaN(hAccuracy)) {
hAccuracy = 0.0F;
}
/*
* Perform a stricter test for proximity. The looser test for
* proximity would be distance + hAccuracy but that would
* mean that we might be notifying for a location that's not
* within the requested radius.
*/
if (distance - hAccuracy <= proximityRadius) {
// listener should be removed *before* notifying,
// because it can be re-registered in the user code
Instance.removeProximityListener(listener);
listener.proximityEvent(coordinates, location);
}
}
/**
* Monitor state change dispatcher.
*
* @param isMonitoringActive is trur if monitoring is enabled
*/
public void monitoringStateChanged(boolean isMonitoringActive) {
listener.monitoringStateChanged(isMonitoringActive);
}
}
// JAVADOC COMMENT ELIDED
public void addProximityListener(ProximityListener listener,
Coordinates coordinates,
float proximityRadius) {
synchronized (proximityListeners) {
proximityListeners.addElement(
new ProximityListenerDecorator(listener, coordinates,
proximityRadius));
}
synchronized (this) {
if (proximityProvider == null) {
try {
proximityProvider =
LocationProviderImpl.getInstanceImpl(null);
} catch (LocationException e) {
// nothing to do
}
}
if (proximityThread == null) {
proximityThread = new ProximityThread();
proximityThread.start();
} else {
synchronized (proximityThread) {
proximityThread.notify();
}
}
if (stateThread == null) {
stateThread = new StateMonitorThread();
stateThread.start();
} else {
synchronized (stateThread) {
stateThread.notify();
}
}
}
}
// JAVADOC COMMENT ELIDED
public void removeProximityListener(ProximityListener listener) {
ProximityListenerDecorator[] listeners;
synchronized (proximityListeners) {
listeners =
new ProximityListenerDecorator[proximityListeners.size()];
proximityListeners.copyInto(listeners);
}
for (int i = 0; i < listeners.length; i++) {
if (listeners[i].listener == listener) {
synchronized (proximityListeners) {
proximityListeners.removeElement(listeners[i]);
}
}
}
// check if it was the last listener
if (proximityListeners.isEmpty()) {
synchronized (this) {
if (proximityThread != null) {
proximityThread.terminate();
if (Thread.currentThread() != proximityThread) {
try { // wait for thread to die
proximityThread.join();
} catch (InterruptedException e) { // do nothing
if (Logging.TRACE_ENABLED) {
Logging.trace(e, "Wrong thread exception.");
}
}
}
proximityThread = null;
}
if (stateThread != null) {
stateThread.terminate();
try { // wait for thread to die
stateThread.join();
} catch (InterruptedException e) { // do nothing
if (Logging.TRACE_ENABLED) {
Logging.trace(e, "Wrong thread exception.");
}
}
stateThread = null;
}
// dedicated provider is no longer needed
proximityProvider = null;
}
}
}
/**
* Dispatches a proximity event.
*
* @param location location that triggered the proximity event
*/
void fireProximityEvent(Location location) {
// prevents concurent modification in which the event code modifies
// the vector by invoking remove/add during event execution
ProximityListenerDecorator[] listeners;
synchronized (proximityListeners) {
listeners =
new ProximityListenerDecorator[proximityListeners.size()];
proximityListeners.copyInto(listeners);
}
if (listeners.length > 0) {
for (int i = 0; i < listeners.length; i++) {
listeners[i].proximityEvent(location);
}
}
}
/**
* Dispatches a monitor stat changed event.
*
* @param isMonitoringActive is true if monitoring is enabled
*/
void fireMonitoringStateChanged(boolean isMonitoringActive) {
// prevents concurent modification in which the event code modifies
// the vector by invoking remove/add during event execution
ProximityListenerDecorator[] listeners;
synchronized (proximityListeners) {
listeners =
new ProximityListenerDecorator[proximityListeners.size()];
proximityListeners.copyInto(listeners);
}
for (int i = 0; i < listeners.length; i++) {
listeners[i].monitoringStateChanged(isMonitoringActive);
}
}
/**
* Returns a vector of proximity listeners.
*
* @return the vector of registered proximity listeners
*/
Vector getListeners() {
return proximityListeners;
}
/**
* The recommended time interval for proximity update events.
*
* @return the default interval of the dedicated provider
*/
int getProximityInterval() {
if (proximityProvider != null) {
return proximityProvider.getDefaultInterval();
}
return 10;
}
/**
* The recommended time interval for querying monitoring state.
*
* @return time interval in seconds
*/
int getStateInterval() {
if (proximityProvider != null) {
return proximityProvider.getStateInterval();
}
return 10;
}
/**
* Retrieves location from the dedicated provider.
*
* @return location to be used for proximity detection.
*/
Location getLocation() {
Location location = null;
try {
if (proximityProvider == null) {
proximityProvider = LocationProviderImpl.getInstanceImpl(null);
}
location = proximityProvider.getLocationImpl(-1);
} catch (LocationException e) {
//nothing to do
} catch (InterruptedException e) {
//nothing to do
}
return location;
}
/**
* Checks if the monitoring is active.
*
* @return true if monitoring is active, false otherwise
*/
boolean getMonitoringState() {
return proximityProvider != null &&
proximityProvider.getState() == LocationProvider.AVAILABLE;
}
}
/**
* Class ProximityThread sends proximity notifications
* when the proximity is detected.
*/
class ProximityThread extends Thread {
/** Flag indicating if the thread should terminate. */
private boolean terminated = false;
/**
* Constructor.
*/
ProximityThread() {
}
/**
* Terminates the thread.
*/
void terminate() {
terminated = true;
synchronized (this) {
notify();
}
}
/**
* Runs the proximity notifier logic.
*/
public void run() {
ProximityNotifier notifier = ProximityNotifier.getInstance();
// time interval for updating location
int interval = notifier.getProximityInterval();
Location l = notifier.getLocation();
try {
while (!terminated) {
if (l != null) {
notifier.fireProximityEvent(l);
}
if (terminated) { // the thread was stopped
break;
}
long startWait = System.currentTimeMillis();
synchronized (this) {
wait((long)interval * 1000);
}
l = LocationProviderImpl.getLastKnownLocation();
if (l == null || (l.getTimestamp() < startWait)) {
l = notifier.getLocation();
}
}
} catch (InterruptedException e) {
if (Logging.TRACE_ENABLED) {
Logging.trace(e, "Wrong thread exception.");
}
}
}
}
/**
* Class StateMonitorThread sends notifications when
* the state of monitor is changed.
*/
class StateMonitorThread extends Thread {
/** Flag indicating if the thread should terminate. */
private boolean terminated = false;
/**
* Constructor.
*/
StateMonitorThread() {
}
/**
* Terminates the thread.
*/
void terminate() {
terminated = true;
synchronized (this) {
notify();
}
}
/**
* Runs the proximity notifier logic.
*/
public void run() {
boolean state = true;
ProximityNotifier notifier = ProximityNotifier.getInstance();
// time interval for checking the state in seconds
int interval = notifier.getStateInterval();
try {
while (!terminated) {
boolean newState = notifier.getMonitoringState();
if (newState != state) {
state = newState;
notifier.fireMonitoringStateChanged(state);
}
if (terminated) { // the thread was stopped
break;
}
synchronized (this) {
wait((long)interval * 1000);
}
}
} catch (InterruptedException e) {
if (Logging.TRACE_ENABLED) {
Logging.trace(e, "Wrong thread exception.");
}
}
}
}