FileDocCategorySizeDatePackage
ProximityNotifier.javaAPI DocphoneME MR2 API (J2ME)11749Wed May 02 18:00:40 BST 2007com.sun.j2me.location

ProximityNotifier.java

/*
 *
 *
 * Copyright  1990-2007 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 java.util.*;
import javax.microedition.location.*;
import com.sun.midp.log.*;

/**
 * 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) {
		}
	    }
	    if (proximityThread == null) {
		proximityThread = new ProximityThread();
		proximityThread.start();
	    }
	    if (stateThread == null) {
		stateThread = new StateMonitorThread();
		stateThread.start();
	    }
	}
    }

    // 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);
        }
        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) {
	} catch (InterruptedException e) {
	}
	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();
	try {
	    while (!terminated) {
		Location location = notifier.getLocation();
		if (location != null) {
		    notifier.fireProximityEvent(location);
		}
		if (terminated) { // the thread was stopped
		    break;
		}
	        synchronized (this) {
                    wait(interval * 1000);
		}
            }
        } 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(interval * 1000);
		}
            }
        } catch (InterruptedException e) {
            if (Logging.TRACE_ENABLED) {
	        Logging.trace(e, "Wrong thread exception.");
            }
        }
    }
}