package org.mortbay.ijetty.console;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import android.content.ContentResolver;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
public class FinderServlet extends HttpServlet
{
private static final String TAG = "IJetty.Cnsl";
public static final String LAST_NETWORK_LOCATION = "org.mortbay.ijetty.console.lastNetworkLocation";
public static final String LAST_GPS_LOCATION = "org.mortbay.ijetty.console.lastGPSLocation";
public static final String NETWORK_LISTENER = "org.mortbay.ijetty.console.networkListener";
public static final String GPS_LISTENER = "org.mortbay.ijetty.console.gpsListener";
public static final String LAST_TRACKER_REQUEST_TIME = "org.mortbay.ijetty.console.lastTrackerRequestTime";
public static final long CONTINUATION_TIMEOUT = 5000L; //wait up to 5sec to get a location when enabling tracking
public static final long INTER_REQUEST_TIMEOUT = 1000L * 60 * 5; //10 mins without requests
public static final int DEFAULT_RING_SEC = 10;
android.content.Context androidContext;
ContentResolver resolver;
LocationManager locationManager;
public Map<String,Location> providerLocations = Collections.synchronizedMap(new HashMap<String,Location>());
public Object sync = new Object();
LooperThread gpsLooper;
LooperThread networkLooper;
Thread controlThread;
AtomicBoolean tracking = new AtomicBoolean();
MediaListener mediaListener;
MediaPlayer mediaPlayer;
class StopperThread extends Thread
{
public void run ()
{
try
{
Thread.currentThread().sleep(10000);
if (mediaPlayer != null)
mediaPlayer.stop();
}
catch (InterruptedException e)
{
Log.i(TAG, "Interruped waiting to stop mediaplayer ringtone");
}
finally
{
//release the resources
if (mediaPlayer != null)
{
mediaPlayer.release();
mediaPlayer = null;
}
}
}
}
class MediaListener implements MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener
{
public void onPrepared(MediaPlayer mp)
{
mp.start();
StopperThread stopper = new StopperThread();
stopper.start();
}
public void onCompletion(MediaPlayer mp)
{
mp.stop();
}
public boolean onError(MediaPlayer mp, int what, int extra)
{
mp.stop();
return false;
}
}
class LooperThread extends Thread
{
String provider;
Looper looper;
AsyncLocationListener listener;
public LooperThread (String provider)
{
super();
this.provider = provider;
setDaemon(true);
}
public void run()
{
Looper.prepare();
looper = Looper.myLooper();
listener = new AsyncLocationListener(provider);
locationManager.requestLocationUpdates(provider, 60000L, 0F, listener, looper); //Get an update every 60 secs
Log.d(TAG, "Requested location updates for "+provider);
Looper.loop();
}
public void quit ()
{
locationManager.removeUpdates(listener);
looper.quit();
}
}
class ControlThread extends Thread
{
public ControlThread()
{
super("Looper-Control");
setDaemon(true);
}
public void run()
{
try
{
while (true)
{
checkInterRequestGap();
Thread.currentThread().sleep(60000);
}
}
catch (InterruptedException e)
{
//Finish loop
return;
}
}
}
public class AsyncLocationListener implements LocationListener
{
String provider = null;
public AsyncLocationListener (String provider)
{
this.provider = provider;
}
public void onLocationChanged(Location location)
{
Log.d(TAG, "Provider: "+provider+" location change: "+location);
synchronized (sync)
{
try
{
providerLocations.put(provider, location);
Log.d(TAG, "put location");
sync.notifyAll();
Log.d(TAG, "notified All");
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
public void onStatusChanged(String provider, int status, Bundle extras)
{
// TODO Auto-generated method stub
}
public void onProviderEnabled(String provider)
{
// TODO Auto-generated method stub
}
public void onProviderDisabled(String provider)
{
//unregister the listener?
}
}
public ContentResolver getContentResolver()
{
return resolver;
}
@Override
public void init(ServletConfig config) throws ServletException
{
super.init(config);
resolver = (ContentResolver)getServletContext().getAttribute("org.mortbay.ijetty.contentResolver");
androidContext = (android.content.Context)config.getServletContext().getAttribute("org.mortbay.ijetty.context");
locationManager = (LocationManager)androidContext.getSystemService(Context.LOCATION_SERVICE);
controlThread = new ControlThread();
controlThread.start();
}
public void startTrackers()
{
synchronized (sync)
{
tracking.set(true);
if (gpsLooper == null)
{
gpsLooper = new LooperThread(LocationManager.GPS_PROVIDER);
}
if (networkLooper == null)
{
networkLooper = new LooperThread(LocationManager.NETWORK_PROVIDER);
}
}
if (!gpsLooper.isAlive())
{
Log.i(TAG, "Starting gps looper thread");
gpsLooper.start();
Log.i(TAG, "Started gps looper thread");
}
if (!networkLooper.isAlive())
{
Log.i(TAG, "Starting network looper thread");
networkLooper.start();
Log.i(TAG, "Started network looper thread");
}
}
public void stopTrackers()
{
Log.d(TAG, "Stopping trackers");
synchronized (sync)
{
tracking.set(false);
if (networkLooper != null)
{
Log.d(TAG, "Stopping network looper");
((LooperThread)networkLooper).quit();
networkLooper.interrupt();
networkLooper = null;
}
if (gpsLooper != null)
{
Log.d(TAG, "Stopping gps looper");
((LooperThread)gpsLooper).quit();
gpsLooper.interrupt();
gpsLooper = null;
}
getServletContext().removeAttribute(LAST_TRACKER_REQUEST_TIME); //reset last request time
providerLocations.clear(); //empty the known locations
}
}
public void checkInterRequestGap ()
{
synchronized (sync)
{
Long lastTrackerRequestTime = (Long)getServletContext().getAttribute(LAST_TRACKER_REQUEST_TIME);
if (lastTrackerRequestTime == null)
return;
long gap = System.currentTimeMillis() - lastTrackerRequestTime.longValue();
if (gap > INTER_REQUEST_TIMEOUT)
{
Log.d(TAG, "ControlThread stopping trackers: request gap = "+gap);
stopTrackers();
}
else
Log.d(TAG, "ControlThread, gap too small: "+gap);
}
}
@Override
public void destroy()
{
stopTrackers();
controlThread.interrupt();
super.destroy();
if (mediaPlayer != null)
{
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
}
public Location lastLocation ()
{
Location lastGps = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
Location lastNetwork = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
synchronized (sync)
{
Location gps = providerLocations.get(LocationManager.GPS_PROVIDER);
Location network = providerLocations.get(LocationManager.NETWORK_PROVIDER);
//if no existing gps location, or if last known location is more recent
if (gps == null || (lastGps != null && lastGps.getTime() > gps.getTime()))
providerLocations.put(LocationManager.GPS_PROVIDER, lastGps);
if (network == null || (lastNetwork != null && lastNetwork.getTime() > network.getTime()))
providerLocations.put(LocationManager.NETWORK_PROVIDER, lastNetwork);
return getLocation();
}
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String action = request.getParameter("action");
if (action == null)
{
response.setStatus(HttpServletResponse.SC_OK);
return;
}
if (action.equalsIgnoreCase("last"))
{
Location l = lastLocation();
sendLocation(response,l);
return;
}
if (action.equalsIgnoreCase("update"))
{
Location l = null;
synchronized (sync)
{
if (!tracking.get())
{
//not tracking
sendError(response, "Not tracking");
return;
}
//update time of request
getServletContext().setAttribute(LAST_TRACKER_REQUEST_TIME, new Long(System.currentTimeMillis()));
l = getLocation();
}
sendLocation (response, l);
return;
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String action = request.getParameter("action");
if (action == null)
{
response.setStatus(HttpServletResponse.SC_OK);
return;
}
if (action.equalsIgnoreCase("ring"))
{
ring(request.getParameter("sec"));
response.setStatus(HttpServletResponse.SC_OK);
return;
}
if (action.equalsIgnoreCase("track"))
{
Location l = null;
synchronized (sync)
{
//start tracking if not already started
startTrackers();
//update time of last request
getServletContext().setAttribute(LAST_TRACKER_REQUEST_TIME, new Long(System.currentTimeMillis()));
//if no location, wait a while to see if the tracker has got one
l = getLocation();
if (l == null)
{
try
{
sync.wait(CONTINUATION_TIMEOUT);
}
catch (InterruptedException e)
{
Log.e(TAG, "Interrupted waiting for Location", e);
}
l = getLocation();
}
}
sendLocation (response, l);
return;
}
if (action.equalsIgnoreCase("stopTrack"))
{
Log.i(TAG, "Stopping trackers");
stopTrackers(); //stop the trackers
sendEmpty(response);
return;
}
}
public void ring (String sec)
{
int seconds = DEFAULT_RING_SEC;
if (sec != null)
{
sec = sec.trim();
if (!"".equals(sec))
seconds = Integer.valueOf(sec);
}
if (mediaPlayer == null)
{
Uri uri = RingtoneManager.getActualDefaultRingtoneUri(androidContext, RingtoneManager.TYPE_RINGTONE);
mediaListener = new MediaListener();
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
try
{
mediaPlayer.setDataSource(androidContext, uri);
mediaPlayer.setOnPreparedListener(mediaListener);
mediaPlayer.setOnCompletionListener(mediaListener);
mediaPlayer.setOnErrorListener(mediaListener);
}
catch (Exception e)
{
Log.i(TAG, "Error preparing ring", e);
}
}
if (!mediaPlayer.isPlaying())
{
mediaPlayer.prepareAsync();
}
else
Log.i(TAG, "Ring already playing");
}
public Location getLocation ()
{
//Go for finest grained location via GPS first
Location l;
synchronized (sync)
{
Location gps = providerLocations.get(LocationManager.GPS_PROVIDER);
Location network = providerLocations.get(LocationManager.NETWORK_PROVIDER);
if (gps != null)
{
if (network == null)
{
l = gps;
}
else
{
if (network.getTime() > gps.getTime())
l = network; //most recent
else
l = gps;
}
}
else
l = network;
}
return l;
}
public void sendLocation (HttpServletResponse response, Location location) throws IOException
{
response.setContentType("text/json");
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = response.getWriter();
StringBuffer buff = new StringBuffer();
asJson(buff, location);
writer.println(buff.toString());
}
public void sendError (HttpServletResponse response, String error) throws IOException
{
response.setContentType("text/json");
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = response.getWriter();
StringBuffer buff = new StringBuffer();
jsonError(buff, error);
writer.println(buff.toString());
}
public void sendEmpty (HttpServletResponse response) throws IOException
{
response.setContentType("text/json");
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = response.getWriter();
writer.println("{}");
}
private void asJson (StringBuffer buff, Location location)
{
if (buff == null)
return;
if (location == null)
jsonError(buff, "No location");
else
{
buff.append("{ \"location\": { ");
buff.append(" \"lat\": "+location.getLatitude()+",");
buff.append(" \"long\": "+location.getLongitude()+",");
buff.append(" \"time\": "+location.getTime());
buff.append(" }");
buff.append("}");
}
}
private void jsonError(StringBuffer buff, String err)
{
if (buff == null)
return;
buff.setLength(0);
buff.append("{ \"error\": \""+err+"\"}");
}
}