init commit
This commit is contained in:
292
LocationLib/src/com/TwentyCodes/android/SkyHook/SkyHook.java
Normal file
292
LocationLib/src/com/TwentyCodes/android/SkyHook/SkyHook.java
Normal file
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* @author Twenty Codes, LLC
|
||||
* @author ricky barrette
|
||||
* @date Oct 2, 2010
|
||||
*/
|
||||
package com.TwentyCodes.android.SkyHook;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
import com.TwentyCodes.android.debug.Debug;
|
||||
import com.TwentyCodes.android.location.AndroidGPS;
|
||||
import com.TwentyCodes.android.location.GeoPointLocationListener;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.skyhookwireless.wps.WPSAuthentication;
|
||||
import com.skyhookwireless.wps.WPSContinuation;
|
||||
import com.skyhookwireless.wps.WPSLocation;
|
||||
import com.skyhookwireless.wps.WPSPeriodicLocationCallback;
|
||||
import com.skyhookwireless.wps.WPSReturnCode;
|
||||
import com.skyhookwireless.wps.XPS;
|
||||
|
||||
/**
|
||||
* this calls will be used to create skyhook object that uses an listener interface to interact with the rest of location ringer
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public class SkyHook implements GeoPointLocationListener{
|
||||
|
||||
public static final String TAG = "Skyhook";
|
||||
public static final String USERNAME = "cjyh95q32gsc";
|
||||
public static final String USERNAME_FOR_TESTING = "twentycodes";
|
||||
public static final String REALM = "TwentyCodes";
|
||||
public static final int LOCATION_MESSAGE = 1;
|
||||
public static final int ERROR_MESSAGE = 2;
|
||||
public static final int DONE_MESSAGE = 3;
|
||||
private GeoPointLocationListener mListener;
|
||||
private long mPeriod = 0l; //period is in milliseconds for periodic updates
|
||||
private int mIterations = 0;
|
||||
private XPS mXps;
|
||||
private WPSAuthentication mWPSAuthentication;
|
||||
private Handler mHandler;
|
||||
private final XPScallback mXPScallback = new XPScallback();
|
||||
private boolean isPeriodicEnabled;
|
||||
private Context mContext;
|
||||
private boolean hasLocation;
|
||||
protected AndroidGPS mSkyHookFallback = null;
|
||||
protected long mFallBackDelay = 5000l;
|
||||
private boolean isFallBackScheduled = false;
|
||||
private boolean isEnabled = false;
|
||||
private boolean isUnauthorized = false;
|
||||
|
||||
/*
|
||||
* this runnable will be used to check if we have location from skyhook,
|
||||
* if we dont, then we will us android's location services to fall back on.
|
||||
*/
|
||||
private final Runnable mFallBack = new Runnable() {
|
||||
public void run() {
|
||||
mHandler.removeCallbacks(mFallBack);
|
||||
Log.d(TAG,"skyhook, "+ (hasLocation ? "is" : "isn't") +" working!");
|
||||
|
||||
if((! hasLocation) && (mSkyHookFallback == null) && isEnabled){
|
||||
Log.d(TAG,"falling back on android");
|
||||
mSkyHookFallback = new AndroidGPS(mContext);
|
||||
mSkyHookFallback.enableLocationUpdates(SkyHook.this);
|
||||
/*
|
||||
* Schedule another check, if skyhook is still enabled
|
||||
*/
|
||||
if(mXps != null)
|
||||
mHandler.postDelayed(mFallBack, mFallBackDelay );
|
||||
|
||||
} else {
|
||||
Log.d(TAG,"already fell back on android");
|
||||
if(mSkyHookFallback != null) {
|
||||
Log.d(TAG,"got location, picking up the slack");
|
||||
mSkyHookFallback.disableLocationUpdates();
|
||||
mSkyHookFallback = null;
|
||||
}
|
||||
isFallBackScheduled = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* this runnable keeps skyhook working!
|
||||
*/
|
||||
private final Runnable mPeriodicUpdates = new Runnable() {
|
||||
public void run() {
|
||||
if(Debug.DEBUG)
|
||||
Log.d(TAG,"geting location");
|
||||
mXps.getXPSLocation(mWPSAuthentication, mIterations, XPS.EXACT_ACCURACY, mXPScallback);
|
||||
}
|
||||
};
|
||||
|
||||
private class XPScallback implements WPSPeriodicLocationCallback {
|
||||
@Override
|
||||
public void done() {
|
||||
mHandler.sendMessage(mHandler.obtainMessage(DONE_MESSAGE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public WPSContinuation handleError(WPSReturnCode error) {
|
||||
mHandler.sendMessage(mHandler.obtainMessage(ERROR_MESSAGE, error));
|
||||
return WPSContinuation.WPS_CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WPSContinuation handleWPSPeriodicLocation(WPSLocation location) {
|
||||
mHandler.sendMessage(mHandler.obtainMessage(LOCATION_MESSAGE, location));
|
||||
return WPSContinuation.WPS_CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructors a new skyhook object
|
||||
* @param context
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public SkyHook(Context context) {
|
||||
mXps = new XPS(context);
|
||||
mContext = context;
|
||||
// initialize the Handler which will display location data
|
||||
// in the text view. we use a Handler because UI updates
|
||||
// must occur in the UI thread
|
||||
setUIHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructors a new skyhook object
|
||||
* @param context
|
||||
* @param period between location updates in milliseconds
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public SkyHook(Context context, long period) {
|
||||
this(context);
|
||||
mPeriod = period;
|
||||
}
|
||||
|
||||
/**
|
||||
* request current user location, note that the listeners onLocationChanged() will be call multiple times.
|
||||
* updates will stop once an accurate location is determined.
|
||||
* @author Ricky Barrette
|
||||
*/
|
||||
public void getLoctaion(){
|
||||
Log.d(TAG,"getLocation()");
|
||||
if (mListener != null){
|
||||
mWPSAuthentication = new WPSAuthentication(SkyHookRegistration.getUserName(mContext), REALM);
|
||||
mHandler.post(mPeriodicUpdates);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to register the the listener for periodic updates
|
||||
* @author Ricky Barrette
|
||||
*/
|
||||
public void getUpdates(){
|
||||
Log.d(TAG,"getUpdates()");
|
||||
if (mListener != null) {
|
||||
|
||||
if(Debug.DEBUG)
|
||||
Log.i(TAG, "username: " + SkyHookRegistration.getUserName(mContext));
|
||||
|
||||
mWPSAuthentication = new WPSAuthentication(SkyHookRegistration.getUserName(mContext), REALM);
|
||||
isPeriodicEnabled = true;
|
||||
mHandler.post(mPeriodicUpdates);
|
||||
isEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true is skyhook is enabled
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public boolean isEnabled(){
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any current registration for location updates of the current activity
|
||||
* with the given LocationListener. Following this call, updates will no longer
|
||||
* occur for this listener.
|
||||
* @param listener
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void removeUpdates() {
|
||||
Log.d(TAG,"removeUpdates()");
|
||||
mHandler.removeCallbacks(mFallBack);
|
||||
mListener = null;
|
||||
isPeriodicEnabled = false;
|
||||
if(mXps != null)
|
||||
mXps.abort();
|
||||
if(mSkyHookFallback != null) {
|
||||
Log.d(TAG,"disabling fallback");
|
||||
mSkyHookFallback.disableLocationUpdates();
|
||||
mSkyHookFallback = null;
|
||||
isEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for receiving notifications from SkyHook when
|
||||
* the location has changed. These methods are called if the
|
||||
* LocationListener has been registered with the location manager service using the method.
|
||||
* @param listener
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void setLocationListener(GeoPointLocationListener listener){
|
||||
Log.d(TAG,"setLocationListener()");
|
||||
if (mListener == null) {
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
private void setUIHandler() {
|
||||
mHandler = new Handler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(final Message msg) {
|
||||
switch (msg.what) {
|
||||
case LOCATION_MESSAGE:
|
||||
if (msg.obj instanceof WPSLocation) {
|
||||
WPSLocation location = (WPSLocation) msg.obj;
|
||||
if (mListener != null && location != null) {
|
||||
|
||||
if(Debug.DEBUG)
|
||||
Log.d(TAG,"got location "+ location.getLatitude() +", "+ location.getLongitude()+" +- "+ location.getHPE() +"m");
|
||||
|
||||
mListener.onLocationChanged(new GeoPoint((int) (location.getLatitude() * 1e6), (int) (location.getLongitude() * 1e6)), location.getHPE());
|
||||
hasLocation = true;
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
case ERROR_MESSAGE:
|
||||
if( msg.obj instanceof WPSReturnCode) {
|
||||
WPSReturnCode code = (WPSReturnCode) msg.obj;
|
||||
if ( code != null){
|
||||
Log.w(TAG, code.toString());
|
||||
}
|
||||
hasLocation = false;
|
||||
|
||||
/*
|
||||
* check to see if the error returned is an WPS_ERROR_UNAUTHORIZED
|
||||
* then check to see if this is the second occurrence of WPS_ERROR_UNAUTHORIZED,
|
||||
* if so we will stop skyhook's services to cut down the work load
|
||||
*/
|
||||
if(code == WPSReturnCode.WPS_ERROR_UNAUTHORIZED){
|
||||
if (isUnauthorized){
|
||||
isPeriodicEnabled = false;
|
||||
mXps.abort();
|
||||
mXps = null;
|
||||
}
|
||||
isUnauthorized = true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* check to see if we already have a fall back Scheduled
|
||||
* if we dont, and there is not fallback already in place, then schedule one
|
||||
*/
|
||||
if((! isFallBackScheduled) && ( mSkyHookFallback == null) && isEnabled) {
|
||||
Log.d(TAG, "scheduling fallback");
|
||||
mHandler.postDelayed(mFallBack, mFallBackDelay);
|
||||
isFallBackScheduled = true;
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
case DONE_MESSAGE:
|
||||
if (isPeriodicEnabled) {
|
||||
mHandler.postDelayed(mPeriodicUpdates, mPeriod);
|
||||
Log.d(TAG,"done getting location");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* called from our skyhook to android fall back class
|
||||
* (non-Javadoc)
|
||||
* @see com.TwentyCodes.android.location.GeoPointLocationListener#onLocationChanged(com.google.android.maps.GeoPoint, int)
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onLocationChanged(GeoPoint point, int accuracy) {
|
||||
if(! hasLocation)
|
||||
if(mListener != null)
|
||||
mListener.onLocationChanged(point, accuracy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @author Twenty Codes, LLC
|
||||
* @author ricky barrette
|
||||
* @date Oct 26, 2010
|
||||
*/
|
||||
package com.TwentyCodes.android.SkyHook;
|
||||
|
||||
import android.content.Context;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.TwentyCodes.android.debug.Debug;
|
||||
import com.skyhookwireless.wps.RegistrationCallback;
|
||||
import com.skyhookwireless.wps.WPSAuthentication;
|
||||
import com.skyhookwireless.wps.XPS;
|
||||
|
||||
/**
|
||||
* this class will be used to register new users with skyhook
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public class SkyHookRegistration{
|
||||
|
||||
private XPS mXps;
|
||||
private Context mContext;
|
||||
|
||||
public SkyHookRegistration(Context context){
|
||||
mContext = context;
|
||||
mXps = new XPS(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* attempts to register the user by their cell #
|
||||
*
|
||||
* TODO hash cell number for privacy
|
||||
* @param listener for call back methods
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void registerNewUser(RegistrationCallback listener){
|
||||
if(mXps != null){
|
||||
TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
if(tm == null)
|
||||
Log.v(SkyHook.TAG, "TelephonyManager is null");
|
||||
String newUser = tm.getLine1Number();
|
||||
|
||||
if(Debug.DEBUG)
|
||||
Log.v(SkyHook.TAG, "newUser = " + newUser);
|
||||
|
||||
if(newUser == null) {
|
||||
Log.e(SkyHook.TAG,"users number is null");
|
||||
}
|
||||
mXps.registerUser(new WPSAuthentication(SkyHook.USERNAME, SkyHook.REALM), new WPSAuthentication(newUser, SkyHook.REALM), listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the users username
|
||||
* @param context
|
||||
* @return
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static String getUserName(Context context){
|
||||
|
||||
switch(Debug.DEFAULT_REGISTRATION_BEHAVIOR){
|
||||
case NORMAL:
|
||||
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
if(tm == null)
|
||||
Log.v(SkyHook.TAG, "TelephonyManager is null");
|
||||
return tm.getLine1Number();
|
||||
|
||||
case RETURN_NULL:
|
||||
return null;
|
||||
|
||||
case USE_TESTING_USERNAME:
|
||||
return SkyHook.USERNAME_FOR_TESTING;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* @author Twenty Codes, LLC
|
||||
* @author Ricky Barrette barrette
|
||||
* @date Oct 6, 2010
|
||||
*/
|
||||
package com.TwentyCodes.android.SkyHook;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import com.TwentyCodes.android.debug.Debug;
|
||||
import com.TwentyCodes.android.location.GeoPointLocationListener;
|
||||
import com.TwentyCodes.android.location.LocationReceiver;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.skyhookwireless.wps.RegistrationCallback;
|
||||
import com.skyhookwireless.wps.WPSContinuation;
|
||||
import com.skyhookwireless.wps.WPSReturnCode;
|
||||
|
||||
/**
|
||||
* This service class will be used broadcast the users location either one time, or periodically.
|
||||
* To use as a one shot location service:
|
||||
* <blockquote><pre>PendingIntent pendingIntent = PendingIntent.getService(context, 0, SkyHookService.startService(context), 0);
|
||||
* or
|
||||
* Intent service = new Intent(context, SkyHookService.class);
|
||||
* context.startService(service);<pre></bloackquote>
|
||||
* To use as a recurring service:
|
||||
* <blockquote>SkyHookService.startService(this, (60000 * Integer.parseInt(ringer.getString(UPDATE_INTVERVAL , "5")))).run();</bloackquote>
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public class SkyHookService extends Service implements GeoPointLocationListener, RegistrationCallback{
|
||||
|
||||
/**
|
||||
* Used to tell the service how frequently it needs to run. This is required if you want a multishot service
|
||||
*/
|
||||
public static final String INTENT_EXTRA_PERIOD_BETWEEN_UPDATES = "period_beween_updates";
|
||||
|
||||
/**
|
||||
* Used to tell the service how accurate of a location you want reported
|
||||
*/
|
||||
public static final String INTENT_EXTRA_REQUIRED_ACCURACY = "required_accuracy";
|
||||
|
||||
/**
|
||||
* Used to tell the service the update action to broadcast. If this is not supplied, {@link LocationReceiver.INTENT_EXTRA_ACTION_UPDATE } will be used.
|
||||
* @see LocationReceiver.INTENT_EXTRA_ACTION_UPDATE
|
||||
*/
|
||||
public static final String INTENT_EXTRA_ACTION_UPDATE = "action_update";
|
||||
|
||||
public static final String TAG = "SkyHookService";
|
||||
public static final int REQUEST_CODE = 32741942;
|
||||
private SkyHook mSkyhook;
|
||||
protected long mPeriod = -1;
|
||||
private GeoPoint mLocation;
|
||||
private int mStartID;
|
||||
private int mRequiredAccuracy;
|
||||
private Intent mIntent;
|
||||
private int mAccuracy;
|
||||
|
||||
/**
|
||||
* broadcasts location to anything listening for updates
|
||||
*
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private void braodcastLocation() {
|
||||
if (mLocation != null) {
|
||||
Intent locationUpdate = new Intent();
|
||||
if(mIntent.getAction() != null)
|
||||
locationUpdate.setAction(mIntent.getAction());
|
||||
else
|
||||
locationUpdate.setAction(LocationReceiver.INTENT_EXTRA_ACTION_UPDATE);
|
||||
locationUpdate.putExtra(LocationReceiver.INTENT_EXTRA_LOCATION_PARCEL, convertLocation());
|
||||
sendBroadcast(locationUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* converts skyhook's location object into android's location object
|
||||
* @return converted location
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public Location convertLocation(){
|
||||
Location location = new Location("location");
|
||||
location.setLatitude(this.mLocation.getLatitudeE6() /1e6);
|
||||
location.setLongitude(this.mLocation.getLongitudeE6() /1e6);
|
||||
location.setAccuracy(this.mAccuracy);
|
||||
return location;
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-Javadoc)
|
||||
* @see android.app.Service#onBind(android.content.Intent)
|
||||
* @param arg0
|
||||
* @return
|
||||
* @author Ricky Barrette barrette
|
||||
*/
|
||||
@Override
|
||||
public IBinder onBind(Intent arg0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(){
|
||||
super.onCreate();
|
||||
this.mSkyhook = new SkyHook(this);
|
||||
this.mSkyhook.setLocationListener(this);
|
||||
|
||||
/*
|
||||
* fail safe
|
||||
* this will stop the service after the maximum running time, if location has not been reported
|
||||
*/
|
||||
new Handler().postDelayed(new Runnable(){
|
||||
@Override
|
||||
public void run(){
|
||||
stopSelfResult(mStartID);
|
||||
}
|
||||
}, Debug.MAX_LOCATION_SERVICE_RUN_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* aborts location services
|
||||
* (non-Javadoc)
|
||||
* @see android.app.Service#onDestroy()
|
||||
* @author Ricky Barrette
|
||||
*/
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
mSkyhook.removeUpdates();
|
||||
braodcastLocation();
|
||||
//ask android to restart service if mPeriod is set
|
||||
if(mPeriod > -1)
|
||||
registerWakeUp();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/*
|
||||
* I believe that this method is no longer needed as we are not supporting pre 2.1
|
||||
*/
|
||||
// /**
|
||||
// * To keep backwards compatibility we override onStart which is the equivalent of onStartCommand in pre android 2.x
|
||||
// * @author ricky barrette
|
||||
// */
|
||||
// @Override
|
||||
// public void onStart(Intent intent, int startId) {
|
||||
// Log.i(SkyHook.TAG, "onStart.Service started with start id of: " + startId);
|
||||
// parseIntent(intent);
|
||||
// this.mSkyhook.getUpdates();
|
||||
// }
|
||||
|
||||
/**
|
||||
* This method is called when startService is called. only used in 2.x android.
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.i(SkyHook.TAG , "onStartCommand.Service started with start id of: " + startId);
|
||||
mStartID = startId;
|
||||
parseIntent(intent);
|
||||
this.mSkyhook.getUpdates();
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the incoming intent for the service options
|
||||
*
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private void parseIntent(Intent intent){
|
||||
|
||||
this.mIntent = intent;
|
||||
|
||||
if (intent.hasExtra(INTENT_EXTRA_PERIOD_BETWEEN_UPDATES))
|
||||
mPeriod = intent.getLongExtra(INTENT_EXTRA_PERIOD_BETWEEN_UPDATES, 60000L);
|
||||
|
||||
if (intent.hasExtra(INTENT_EXTRA_REQUIRED_ACCURACY))
|
||||
mRequiredAccuracy = intent.getIntExtra(INTENT_EXTRA_REQUIRED_ACCURACY, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* registers our Receiver the starts the service with the alarm manager
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private void registerWakeUp(){
|
||||
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||
am.set(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis() + this.mPeriod, PendingIntent.getService(this, REQUEST_CODE, this.mIntent, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* a convince method for getting an intent to start the service
|
||||
* @param context
|
||||
* @return a intent that will be used to start the service
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static Intent getStartServiceIntent(final Context context){
|
||||
return new Intent(context, SkyHookService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* a convince method for stopping the service and removing its que from the alarm manager
|
||||
* @param context
|
||||
* @return a runnable that will stop the service
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static Runnable stopService(final Context context){
|
||||
return new Runnable(){
|
||||
@Override
|
||||
public void run(){
|
||||
context.stopService(new Intent(context, SkyHookService.class));
|
||||
((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).cancel(PendingIntent.getService(context, REQUEST_CODE, new Intent(context, SkyHookService.class), 0));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(GeoPoint point, int accuracy) {
|
||||
this.mLocation = point;
|
||||
this.mAccuracy = accuracy;
|
||||
/*
|
||||
* fail safe
|
||||
* if the accuracy is greater than the minimum required accuracy
|
||||
* then continue
|
||||
* else stop to report location
|
||||
*/
|
||||
if(accuracy < (this.mRequiredAccuracy > -1 ? this.mRequiredAccuracy : Debug.MINIMUM_REQUIRED_ACCURACY) || Debug.REPORT_FIRST_LOCATION)
|
||||
this.stopSelf(this.mStartID);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void done() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public WPSContinuation handleError(WPSReturnCode arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
/**
|
||||
* @author Twenty Codes, LLC
|
||||
* @author ricky barrette
|
||||
* @date Oct 2, 2010
|
||||
*/
|
||||
package com.TwentyCodes.android.SkyHook;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.Point;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import com.TwentyCodes.android.debug.Debug;
|
||||
import com.TwentyCodes.android.location.CompasOverlay;
|
||||
import com.TwentyCodes.android.location.CompassListener;
|
||||
import com.TwentyCodes.android.location.GeoPointLocationListener;
|
||||
import com.TwentyCodes.android.location.GeoUtils;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.google.android.maps.MapView;
|
||||
import com.google.android.maps.Overlay;
|
||||
import com.google.android.maps.Projection;
|
||||
|
||||
/**
|
||||
* this class will be used to display the users location on the map using skyhook's call back methods
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public class SkyHookUserOverlay extends Overlay implements GeoPointLocationListener, CompassListener{
|
||||
|
||||
private float mBearing = 0;
|
||||
private int mAccuracy;
|
||||
private GeoPoint mPoint;
|
||||
private Context mContext;
|
||||
private SkyHook mSkyHook;
|
||||
private boolean isEnabled;
|
||||
private MapView mMapView;
|
||||
private ProgressDialog mGPSprogress;
|
||||
private boolean isFistFix = true;
|
||||
private GeoPointLocationListener mListener;
|
||||
public boolean isFollowingUser = true;
|
||||
private final String TAG = "SkyHookUserOverlay";
|
||||
private CompasOverlay mCompass;
|
||||
private boolean isCompassEnabled;
|
||||
private int mUserArrow = R.drawable.user_arrow_animation_1;
|
||||
private volatile Thread mAnimationThread;
|
||||
|
||||
/**
|
||||
* Construct a new SkyHookUserOverlaymFollowUser
|
||||
* @param mapView
|
||||
* @param context
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public SkyHookUserOverlay(MapView mapView, Context context) {
|
||||
mContext = context;
|
||||
mMapView = mapView;
|
||||
mSkyHook = new SkyHook(context);
|
||||
mCompass = new CompasOverlay(context);
|
||||
mUserArrow = R.drawable.user_arrow_animation_1;
|
||||
mAnimationThread = new Thread(new Runnable(){
|
||||
@Override
|
||||
public void run(){
|
||||
int index = 0;
|
||||
boolean isCountingUp = true;
|
||||
while(! isEnabled){
|
||||
switch(index){
|
||||
case 1:
|
||||
mUserArrow = R.drawable.user_arrow_animation_2;
|
||||
break;
|
||||
case 2:
|
||||
mUserArrow = R.drawable.user_arrow_animation_3;
|
||||
break;
|
||||
default:
|
||||
mUserArrow = R.drawable.user_arrow_animation_1;
|
||||
try {
|
||||
Thread.sleep(300l);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(200l);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if(isCountingUp){
|
||||
if(index++ == 2)
|
||||
isCountingUp = false;
|
||||
} else if(index-- == 0)
|
||||
isCountingUp = true;
|
||||
|
||||
if(! isEnabled)
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new SkyHookUserOverlay
|
||||
* @param mapView
|
||||
* @param context
|
||||
* @param followUser
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public SkyHookUserOverlay(MapView mapView, Context context, boolean followUser) {
|
||||
this(mapView, context);
|
||||
isFollowingUser = followUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the compass
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void disableCompass(){
|
||||
isCompassEnabled = false;
|
||||
mMapView.getOverlays().remove(mCompass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops location updates and removes the overlay from view
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public synchronized void disableMyLocation(){
|
||||
// mAnimationThread.stop();
|
||||
Log.d(TAG,"disableMyLocation()");
|
||||
mSkyHook.removeUpdates();
|
||||
isEnabled = false;
|
||||
mCompass.disable();
|
||||
mGPSprogress.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the Acquiring GPS dialog
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void disableGPSDialog(){
|
||||
if(mGPSprogress != null)
|
||||
mGPSprogress.dismiss();
|
||||
}
|
||||
|
||||
/**
|
||||
* we override this methods so we can provide a drawable and a location to draw on the canvas.
|
||||
* (non-Javadoc)
|
||||
* @see com.google.android.maps.Overlay#draw(android.graphics.Canvas, com.google.android.maps.MapView, boolean)
|
||||
* @param canvas
|
||||
* @param mapView
|
||||
* @param shadow
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void draw(Canvas canvas, MapView mapView, boolean shadow){
|
||||
if (isEnabled && mPoint != null) {
|
||||
Point center = new Point();
|
||||
Point left = new Point();
|
||||
Projection projection = mapView.getProjection();
|
||||
GeoPoint leftGeo = GeoUtils.distanceFrom(mPoint, mAccuracy);
|
||||
projection.toPixels(leftGeo, left);
|
||||
projection.toPixels(mPoint, center);
|
||||
canvas = drawAccuracyCircle(center, left, canvas);
|
||||
canvas = drawUser(center, mBearing, canvas);
|
||||
/*
|
||||
* the following log is used to demonstrate if the leftGeo point is the correct
|
||||
*/
|
||||
// Log.d(SkyHook.TAG, (GeoUtils.distanceKm(mPoint, leftGeo) * 1000)+"m");
|
||||
}
|
||||
super.draw(canvas, mapView, shadow);
|
||||
}
|
||||
|
||||
/**
|
||||
* draws an accuracy circle onto the canvas supplied
|
||||
* @param center point of the circle
|
||||
* @param left point of the circle
|
||||
* @param canvas to be drawn on
|
||||
* @return modified canvas
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private Canvas drawAccuracyCircle(Point center, Point left, Canvas canvas) {
|
||||
Paint paint = new Paint();
|
||||
|
||||
/*
|
||||
* get radius of the circle being drawn by
|
||||
*/
|
||||
int circleRadius = center.x - left.x;
|
||||
if(circleRadius <= 0){
|
||||
circleRadius = left.x - center.x;
|
||||
}
|
||||
/*
|
||||
* paint a blue circle on the map
|
||||
*/
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStrokeWidth(2.0f);
|
||||
paint.setColor(Color.BLUE);
|
||||
paint.setStyle(Style.STROKE);
|
||||
canvas.drawCircle(center.x, center.y, circleRadius, paint);
|
||||
/*
|
||||
* fill the radius with a alpha blue
|
||||
*/
|
||||
paint.setAlpha(30);
|
||||
paint.setStyle(Style.FILL);
|
||||
canvas.drawCircle(center.x, center.y, circleRadius, paint);
|
||||
|
||||
/*
|
||||
* for testing
|
||||
* draw a dot over the left geopoint
|
||||
*/
|
||||
// paint.setColor(Color.RED);
|
||||
// RectF oval = new RectF(left.x - 1, left.y - 1, left.x + 1, left.y + 1);
|
||||
// canvas.drawOval(oval, paint);
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* draws user arrow that points north based on bearing onto the supplied canvas
|
||||
* @param point to draw user arrow on
|
||||
* @param bearing of the device
|
||||
* @param canvas to draw on
|
||||
* @return modified canvas
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private Canvas drawUser(Point point, float bearing, Canvas canvas){
|
||||
Bitmap user = BitmapFactory.decodeResource(mContext.getResources(), mUserArrow);
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postRotate(bearing);
|
||||
Bitmap rotatedBmp = Bitmap.createBitmap(
|
||||
user,
|
||||
0, 0,
|
||||
user.getWidth(),
|
||||
user.getHeight(),
|
||||
matrix,
|
||||
true
|
||||
);
|
||||
canvas.drawBitmap(
|
||||
rotatedBmp,
|
||||
point.x - (rotatedBmp.getWidth() / 2),
|
||||
point.y - (rotatedBmp.getHeight() / 2),
|
||||
null
|
||||
);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the compass
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public synchronized void enableCompass(){
|
||||
if(! this.isCompassEnabled){
|
||||
this.mMapView.getOverlays().add(this.mCompass);
|
||||
this.isCompassEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the Acquiring GPS dialog if the location has not been acquired
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void enableGPSDialog(){
|
||||
if(isFistFix)
|
||||
if(mGPSprogress != null){
|
||||
if(! mGPSprogress.isShowing())
|
||||
mGPSprogress = ProgressDialog.show(mContext, "", mContext.getText(R.string.gps_fix), true, true);
|
||||
} else
|
||||
mGPSprogress = ProgressDialog.show(mContext, "", mContext.getText(R.string.gps_fix), true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to enable MyLocation, registering for updates from sky hook
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void enableMyLocation(){
|
||||
Log.d(TAG,"enableMyLocation()");
|
||||
if (! isEnabled) {
|
||||
// this.mAnimationThread.start();
|
||||
mSkyHook.setLocationListener(this);
|
||||
mSkyHook.getUpdates();
|
||||
isEnabled = true;
|
||||
mCompass.enable(this);
|
||||
isFistFix = true;
|
||||
enableGPSDialog();
|
||||
|
||||
/**
|
||||
* this is a message that tells the user that we are having trouble getting an GPS signal
|
||||
*/
|
||||
new Handler().postAtTime(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mGPSprogress.isShowing()) {
|
||||
mGPSprogress.cancel();
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
|
||||
builder.setMessage(
|
||||
mContext.getText(R.string.sorry_theres_trouble))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(mContext.getText(android.R.string.ok),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick( DialogInterface dialog, int id) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
}
|
||||
}, SystemClock.uptimeMillis()+90000L);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the map to follow the user
|
||||
* @param followUser
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void followUser(boolean followUser){
|
||||
Log.d(TAG,"followUser()");
|
||||
isFollowingUser = followUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the users current bearing
|
||||
* @return
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public float getUserBearing(){
|
||||
return mBearing;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the users current location
|
||||
* @return
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public GeoPoint getUserLocation(){
|
||||
return mPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompassUpdate(float bearing) {
|
||||
if(Debug.DEBUG)
|
||||
Log.v(TAG, "onCompassUpdate()");
|
||||
mBearing = bearing;
|
||||
mMapView.invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* called when the SkyHook location changes, this mthod is resposiable for updating the overlay location and accuracy circle.
|
||||
* (non-Javadoc)
|
||||
* @see com.TwentyCodes.android.SkyHook.GeoPointLocationListener.location.LocationListener#onLocationChanged(com.google.android.maps.GeoPoint, float)
|
||||
* @param point
|
||||
* @param accuracy
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onLocationChanged(GeoPoint point, int accuracy) {
|
||||
|
||||
if(mCompass != null)
|
||||
mCompass.setLocation(point);
|
||||
|
||||
/*
|
||||
* if this is the first fix
|
||||
* set map center the users location, and zoom to the max zoom level
|
||||
*/
|
||||
if(point != null && isFistFix){
|
||||
mMapView.getController().setCenter(point);
|
||||
mMapView.getController().setZoom( (mMapView.getMaxZoomLevel() - 2) );
|
||||
mGPSprogress.dismiss();
|
||||
isFistFix = false;
|
||||
}
|
||||
|
||||
//update the users point, and accuracy for the UI
|
||||
mPoint = point;
|
||||
mAccuracy = accuracy;
|
||||
mMapView.invalidate();
|
||||
if(mListener != null){
|
||||
mListener.onLocationChanged(point, accuracy);
|
||||
}
|
||||
|
||||
if (isFollowingUser) {
|
||||
panToUserIfOffMap(point);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pans the map view if the user is off screen.
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private void panToUserIfOffMap(GeoPoint user) {
|
||||
GeoPoint center = mMapView.getMapCenter();
|
||||
double distance = GeoUtils.distanceKm(center, user);
|
||||
double distanceLat = GeoUtils.distanceKm(center, new GeoPoint((center.getLatitudeE6() + (int) (mMapView.getLatitudeSpan() / 2)), center.getLongitudeE6()));
|
||||
double distanceLon = GeoUtils.distanceKm(center, new GeoPoint(center.getLatitudeE6(), (center.getLongitudeE6() + (int) (mMapView.getLongitudeSpan() / 2))));
|
||||
|
||||
double whichIsGreater = (distanceLat > distanceLon) ? distanceLat : distanceLon;
|
||||
|
||||
/**
|
||||
* if the user is one the map, keep them their
|
||||
* else don't pan to user unless they pan pack to them
|
||||
*/
|
||||
if( ! (distance > whichIsGreater) )
|
||||
if (distance > distanceLat || distance > distanceLon){
|
||||
mMapView.getController().animateTo(user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to register the listener for location updates
|
||||
* @param listener
|
||||
* @author Ricky Barrette
|
||||
*/
|
||||
public void registerListener(GeoPointLocationListener listener){
|
||||
Log.d(TAG,"registerListener()");
|
||||
if (mListener == null){
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the destination for the compass
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void setDestination(GeoPoint destination){
|
||||
if(mCompass != null)
|
||||
mCompass.setDestination(destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* UnResgisters the listener. after this call you will no longer get location updates
|
||||
* @author Ricky Barrette
|
||||
*/
|
||||
public void unRegisterListener(){
|
||||
Log.d(TAG,"unRegisterListener()");
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the compass drawables and location
|
||||
* @param needleResId
|
||||
* @param backgroundResId
|
||||
* @param x
|
||||
* @param y
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void setCompassDrawables(int needleResId, int backgroundResId, int x, int y) {
|
||||
mCompass.setDrawables(needleResId, backgroundResId, x, y);
|
||||
}
|
||||
}
|
||||
48
LocationLib/src/com/TwentyCodes/android/SkyHook/Splash.java
Normal file
48
LocationLib/src/com/TwentyCodes/android/SkyHook/Splash.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @author Twenty Codes, LLC
|
||||
* @author ricky barrette
|
||||
* @date Nov 3, 2010
|
||||
*/
|
||||
package com.TwentyCodes.android.SkyHook;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
||||
/**
|
||||
* this activity will be used to display the Twenty Codes, LLC and Skyhook Wireless Splash Screen
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public class Splash extends Activity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
setContentView(R.layout.powered_by_skyhook);
|
||||
|
||||
new Handler().postDelayed( new Runnable() {
|
||||
@Override
|
||||
public void run(){
|
||||
finish();
|
||||
}
|
||||
} , 1500L);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
/**
|
||||
* a convince method for starting the splash screen activity
|
||||
* @param context
|
||||
* @return a runnable that will start the splash screen
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static Runnable showSpashScreen(final Context context){
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run(){
|
||||
context.startActivity(new Intent(context, com.TwentyCodes.android.SkyHook.Splash.class));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
52
LocationLib/src/com/TwentyCodes/android/debug/Debug.java
Normal file
52
LocationLib/src/com/TwentyCodes/android/debug/Debug.java
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Debug.java
|
||||
* @date Mar 1, 2011
|
||||
* @author ricky barrette
|
||||
* @author Twenty Codes, LLC
|
||||
*/
|
||||
package com.TwentyCodes.android.debug;
|
||||
|
||||
import android.hardware.SensorManager;
|
||||
|
||||
/**
|
||||
* This class will be used to enable and disable debugging features
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public final class Debug {
|
||||
|
||||
/**
|
||||
* Sets the logging level for this library
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* Sets the default SkyHook Registration Behavior used by SkyHookRegistration.getUserName()
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static final SkyHookRegistrationBehavior DEFAULT_REGISTRATION_BEHAVIOR = SkyHookRegistrationBehavior.NORMAL;
|
||||
|
||||
/**
|
||||
* Sets the default compass sensor update interval
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static final int COMPASS_UPDATE_INTERVAL = SensorManager.SENSOR_DELAY_UI;
|
||||
|
||||
/**
|
||||
* The maximum running time for a single shot location service
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static final long MAX_LOCATION_SERVICE_RUN_TIME = 60000l;
|
||||
|
||||
/**
|
||||
* Forces single shot location services to return the first location
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static final boolean REPORT_FIRST_LOCATION = false;
|
||||
|
||||
/**
|
||||
* Minimum Required accuracy to report
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static final int MINIMUM_REQUIRED_ACCURACY = 50;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* SkyHookTesting.java
|
||||
* @date Mar 1, 2011
|
||||
* @author ricky barrette
|
||||
* @author Twenty Codes, LLC
|
||||
*/
|
||||
package com.TwentyCodes.android.debug;
|
||||
|
||||
/**
|
||||
* This enum will be used to select the testing level
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public enum SkyHookRegistrationBehavior {
|
||||
|
||||
/**
|
||||
* Used to force SkyHookRegistration.getUserName to behave normally
|
||||
*/
|
||||
NORMAL,
|
||||
|
||||
/**
|
||||
* Used to force SkyHookRegistration.getUserName to return the testing user name
|
||||
*/
|
||||
USE_TESTING_USERNAME,
|
||||
|
||||
/**
|
||||
* Used to force SkyHookRegistration.getUserName to return null
|
||||
*/
|
||||
RETURN_NULL;
|
||||
|
||||
}
|
||||
146
LocationLib/src/com/TwentyCodes/android/location/AndroidGPS.java
Normal file
146
LocationLib/src/com/TwentyCodes/android/location/AndroidGPS.java
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* AndroidGPS.java
|
||||
* @date Feb 3, 2011
|
||||
* @author ricky barrette
|
||||
* @author Twenty Codes, LLC
|
||||
*/
|
||||
package com.TwentyCodes.android.location;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.TwentyCodes.android.SkyHook.SkyHook;
|
||||
import com.TwentyCodes.android.debug.Debug;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
|
||||
/**
|
||||
* This class will be used for gathering location using android's location services
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public class AndroidGPS implements LocationListener {
|
||||
|
||||
private static final String TAG = "AndroidGPS";
|
||||
private LocationManager mLocationManager;
|
||||
private GeoPointLocationListener mListener;
|
||||
private LocationListener mLocationListener;
|
||||
|
||||
/**
|
||||
* Creates a new SkyHookFallback
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public AndroidGPS(Context context) {
|
||||
mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove updates from androids location services
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void disableLocationUpdates(){
|
||||
if(Debug.DEBUG)
|
||||
Log.d(TAG, "disableLocationUpdates()");
|
||||
mListener = null;
|
||||
mLocationManager.removeUpdates(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to enable periodic location updates
|
||||
* @param listener
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void enableLocationUpdates(LocationListener listener) {
|
||||
if(Debug.DEBUG)
|
||||
Log.d(SkyHook.TAG, "enableLocationUpdates()");
|
||||
if(mLocationListener == null){
|
||||
mLocationListener = listener;
|
||||
requestUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* request periodic location updates from androids location services
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void enableLocationUpdates(GeoPointLocationListener listener) {
|
||||
if(Debug.DEBUG)
|
||||
Log.d(SkyHook.TAG, "enableLocationUpdates()");
|
||||
if (mListener == null) {
|
||||
mListener = listener;
|
||||
requestUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-Javadoc)
|
||||
* @see android.location.LocationListener#onLocationChanged(android.location.Location)
|
||||
* @param location
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
if(mListener != null)
|
||||
mListener.onLocationChanged(new GeoPoint( (int) (location.getLatitude() * 1e6), (int) (location.getLongitude() * 1e6)), (int) location.getAccuracy());
|
||||
|
||||
if(mLocationListener != null){
|
||||
mLocationListener.onLocationChanged(location);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-Javadoc)
|
||||
* @see android.location.LocationListener#onProviderDisabled(java.lang.String)
|
||||
* @param arg0
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onProviderDisabled(String arg0) {
|
||||
// UNUSED
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-Javadoc)
|
||||
* @see android.location.LocationListener#onProviderEnabled(java.lang.String)
|
||||
* @param arg0
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onProviderEnabled(String arg0) {
|
||||
// UNUSED
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-Javadoc)
|
||||
* @see android.location.LocationListener#onStatusChanged(java.lang.String, int, android.os.Bundle)
|
||||
* @param arg0
|
||||
* @param arg1
|
||||
* @param arg2
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onStatusChanged(String arg0, int arg1, Bundle arg2) {
|
||||
// UNUSED
|
||||
}
|
||||
|
||||
/**
|
||||
* Request updates from android location services
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private void requestUpdates() {
|
||||
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
|
||||
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
|
||||
try {
|
||||
mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this);
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
/* We do no handle this exception as it is caused if the android version is < 1.6. since the PASSIVE_PROVIDER call is not required
|
||||
* to function we can ignore it.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* CompasOverlay.java
|
||||
* @date Mar 9, 2011
|
||||
* @author ricky barrette
|
||||
* @author Twenty Codes, LLC
|
||||
*/
|
||||
package com.TwentyCodes.android.location;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Point;
|
||||
|
||||
import com.TwentyCodes.android.SkyHook.R;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.google.android.maps.MapView;
|
||||
import com.google.android.maps.Overlay;
|
||||
|
||||
/**
|
||||
* A Simple compass overlay that will be used to point towards a destination or north
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public class CompasOverlay extends Overlay implements CompassListener {
|
||||
|
||||
private float mBearing;
|
||||
private Context mContext;
|
||||
private GeoPoint mDestination;
|
||||
private GeoPoint mLocation;
|
||||
private boolean isEnabled;
|
||||
private CompassSensor mCompassSensor;
|
||||
private int mNeedleResId = R.drawable.needle;
|
||||
private int mBackgroundResId = R.drawable.compass;
|
||||
private int mX = 100;
|
||||
private int mY = 100;
|
||||
private CompassListener mListener;
|
||||
|
||||
/**
|
||||
* Creates a new CompasOverlay
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public CompasOverlay(Context context) {
|
||||
mContext = context;
|
||||
mCompassSensor = new CompassSensor(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CompasOverlay
|
||||
* @param context
|
||||
* @param destination
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public CompasOverlay(Context context, GeoPoint destination){
|
||||
this(context);
|
||||
mDestination = destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CompasOverlay
|
||||
* @param context
|
||||
* @param needleResId
|
||||
* @param backgroundResId
|
||||
* @param x
|
||||
* @param y
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public CompasOverlay(Context context, int needleResId, int backgroundResId, int x, int y){
|
||||
this(context, null, needleResId, backgroundResId, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CompasOverlay
|
||||
* @param context
|
||||
* @param destination
|
||||
* @param needleResId
|
||||
* @param backgroundResId
|
||||
* @param x
|
||||
* @param y
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public CompasOverlay(Context context, GeoPoint destination, int needleResId, int backgroundResId, int x, int y){
|
||||
this(context, destination);
|
||||
mX = x;
|
||||
mY = y;
|
||||
mNeedleResId = needleResId;
|
||||
mBackgroundResId = backgroundResId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculated the bearing from the current location to the current destination, or returns the bearing for north if there is no destination
|
||||
* @return bearing
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private float calculateBearing() {
|
||||
if( (mLocation == null) || (mDestination == null) )
|
||||
return mBearing;
|
||||
|
||||
float bearing = mBearing - GeoUtils.bearing(mLocation, mDestination).floatValue();
|
||||
if (bearing != 0)
|
||||
bearing = 360 - bearing;
|
||||
|
||||
return bearing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the compass overlay
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void disable(){
|
||||
isEnabled = false;
|
||||
mCompassSensor.disable();
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-Javadoc)
|
||||
* @see com.google.android.maps.Overlay#draw(android.graphics.Canvas, com.google.android.maps.MapView, boolean)
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
|
||||
|
||||
if(isEnabled){
|
||||
//set the center of the compass in the top left corner of the screen
|
||||
Point point = new Point();
|
||||
point.set(mX, mY);
|
||||
|
||||
//draw compass background
|
||||
Bitmap compass = BitmapFactory.decodeResource( mContext.getResources(), mBackgroundResId);
|
||||
canvas.drawBitmap(compass,
|
||||
point.x - (compass.getWidth() / 2),
|
||||
point.y - (compass.getHeight() / 2),
|
||||
null
|
||||
);
|
||||
|
||||
//draw the compass needle
|
||||
Bitmap arrowBitmap = BitmapFactory.decodeResource( mContext.getResources(), mNeedleResId);
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postRotate(calculateBearing());
|
||||
Bitmap rotatedBmp = Bitmap.createBitmap(
|
||||
arrowBitmap,
|
||||
0, 0,
|
||||
arrowBitmap.getWidth(),
|
||||
arrowBitmap.getHeight(),
|
||||
matrix,
|
||||
true
|
||||
);
|
||||
canvas.drawBitmap(
|
||||
rotatedBmp,
|
||||
point.x - (rotatedBmp.getWidth() / 2),
|
||||
point.y - (rotatedBmp.getHeight() / 2),
|
||||
null
|
||||
);
|
||||
mapView.invalidate();
|
||||
}
|
||||
super.draw(canvas, mapView, shadow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the compass overlay
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void enable(){
|
||||
if(! isEnabled){
|
||||
isEnabled = true;
|
||||
mCompassSensor.enable(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the compass overlay
|
||||
* @param listener
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void enable(CompassListener listener){
|
||||
mListener = listener;
|
||||
enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current bearing
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public float getBearing(){
|
||||
return mBearing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the compass Sensor to update the current bearing
|
||||
* (non-Javadoc)
|
||||
* @see com.TwentyCodes.android.location.CompassListener#onCompassUpdate(float)
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onCompassUpdate(float bearing) {
|
||||
mBearing = bearing;
|
||||
|
||||
/*
|
||||
* pass it down the chain
|
||||
*/
|
||||
if(mListener != null)
|
||||
mListener.onCompassUpdate(bearing);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param destination
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void setDestination(GeoPoint destination){
|
||||
mDestination = destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param needleResId
|
||||
* @param backgroundResId
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void setDrawables(int needleResId, int backgroundResId, int x, int y){
|
||||
mX = x;
|
||||
mY = y;
|
||||
mNeedleResId = needleResId;
|
||||
mBackgroundResId = backgroundResId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param location
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void setLocation(GeoPoint location){
|
||||
mLocation = location;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* CompassListener.java
|
||||
* @date Mar 2, 2011
|
||||
* @author ricky barrette
|
||||
* @author Twenty Codes, LLC
|
||||
*/
|
||||
package com.TwentyCodes.android.location;
|
||||
|
||||
/**
|
||||
* A simple listener interface to get updates from CompassSensor
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public interface CompassListener {
|
||||
|
||||
/**
|
||||
* Called when there is an update from the Compass Sensor
|
||||
* @param bearing
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void onCompassUpdate(float bearing);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* CompassSensor.java
|
||||
* @date Mar 2, 2011
|
||||
* @author ricky barrette
|
||||
* @author Twenty Codes, LLC
|
||||
*/
|
||||
package com.TwentyCodes.android.location;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
|
||||
import com.TwentyCodes.android.debug.Debug;
|
||||
|
||||
/**
|
||||
* A simple convince class that accesses the compass sensor on another thread
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public class CompassSensor{
|
||||
|
||||
private static final int BEARING = 0;
|
||||
private SensorManager mSensorManager;
|
||||
private Context mContext;
|
||||
private CompassListener mListener;
|
||||
private Handler mHandler;
|
||||
private SensorCallBack mCallBack;
|
||||
|
||||
/**
|
||||
* A convince callback class for the compass sensor
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private class SensorCallBack implements SensorEventListener {
|
||||
|
||||
/**
|
||||
* (non-Javadoc)
|
||||
* @see android.hardware.SensorEventListener#onAccuracyChanged(android.hardware.Sensor, int)
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
// NOT USED
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-Javadoc)
|
||||
* @see android.hardware.SensorEventListener#onSensorChanged(android.hardware.SensorEvent)
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
float myAzimuth = event.values[0];
|
||||
// myPitch = event.values[1];
|
||||
float roll = event.values[2];
|
||||
|
||||
if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
|
||||
boolean isNormal = false;
|
||||
if (roll <= -25)
|
||||
isNormal = false;
|
||||
|
||||
if (roll >= 25)
|
||||
isNormal = true;
|
||||
|
||||
if (isNormal)
|
||||
myAzimuth = myAzimuth + 90;
|
||||
else
|
||||
myAzimuth = myAzimuth - 90;
|
||||
}
|
||||
|
||||
mHandler.sendMessage(mHandler.obtainMessage(BEARING, myAzimuth));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CompassSensor
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public CompassSensor(Context context) {
|
||||
mContext = context;
|
||||
setUiHandler();
|
||||
|
||||
//start getting information from the compass sensor
|
||||
new Thread(new Runnable(){
|
||||
@Override
|
||||
public void run() {
|
||||
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
|
||||
}
|
||||
}).start();
|
||||
mCallBack = new SensorCallBack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables compass updates
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void disable(){
|
||||
mListener = null;
|
||||
mSensorManager.unregisterListener(mCallBack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to register the listener for compass updates
|
||||
* @param listener
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void enable(CompassListener listener){
|
||||
if(mListener == null) {
|
||||
mListener = listener;
|
||||
if(mSensorManager == null)
|
||||
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
|
||||
mSensorManager.registerListener(mCallBack, mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION), Debug.COMPASS_UPDATE_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the UI handler
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private void setUiHandler() {
|
||||
mHandler = new Handler(){
|
||||
@Override
|
||||
public void handleMessage(Message msg){
|
||||
System.out.print((Float) msg.obj);
|
||||
if(mListener != null)
|
||||
if(msg.what == BEARING)
|
||||
mListener.onCompassUpdate((Float) msg.obj);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @author Twenty Codes, LLC
|
||||
* @author ricky barrette
|
||||
* @date Oct 2, 2010
|
||||
*/
|
||||
package com.TwentyCodes.android.location;
|
||||
|
||||
import com.google.android.maps.GeoPoint;
|
||||
|
||||
/**
|
||||
* this interface will be used to interface with skyhook sdk with the rest of the application
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public interface GeoPointLocationListener {
|
||||
|
||||
/**
|
||||
* Called when the location has changed
|
||||
* @param point
|
||||
* @param accuracy
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void onLocationChanged(GeoPoint point, int accuracy);
|
||||
}
|
||||
232
LocationLib/src/com/TwentyCodes/android/location/GeoUtils.java
Normal file
232
LocationLib/src/com/TwentyCodes/android/location/GeoUtils.java
Normal file
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* @author Twenty Codes, LLC
|
||||
* @author Google Inc.
|
||||
* @author ricky barrette
|
||||
* @date Oct 2, 2010
|
||||
*
|
||||
* Some Code here is Copyright (C) 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.TwentyCodes.android.location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.graphics.Point;
|
||||
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.google.android.maps.MapView;
|
||||
|
||||
/**
|
||||
* This class contains common tools for computing common geological problems
|
||||
* @author ricky barrette
|
||||
* @author Google Inc.
|
||||
*/
|
||||
public class GeoUtils {
|
||||
|
||||
public static final int EARTH_RADIUS_KM = 6371;
|
||||
public static final double MILLION = 1000000;
|
||||
|
||||
/**
|
||||
* computes the bearing of lat2/lon2 in relationship from lat1/lon1 in degrees East
|
||||
* @param lat1 source lat
|
||||
* @param lon1 source lon
|
||||
* @param lat2 destination lat
|
||||
* @param lon2 destination lon
|
||||
* @return the bearing of lat2/lon2 in relationship from lat1/lon1 in degrees East
|
||||
* @author Google Inc.
|
||||
*/
|
||||
public static double bearing(double lat1, double lon1, double lat2, double lon2) {
|
||||
double lat1Rad = Math.toRadians(lat1);
|
||||
double lat2Rad = Math.toRadians(lat2);
|
||||
double deltaLonRad = Math.toRadians(lon2 - lon1);
|
||||
double y = Math.sin(deltaLonRad) * Math.cos(lat2Rad);
|
||||
double x = Math.cos(lat1Rad) * Math.sin(lat2Rad) - Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(deltaLonRad);
|
||||
return radToBearing(Math.atan2(y, x));
|
||||
}
|
||||
|
||||
/**
|
||||
* computes the bearing of lat2/lon2 in relationship from lat1/lon1 in degrees East
|
||||
* @param p1 source geopoint
|
||||
* @param p2 destination geopoint
|
||||
* @return the bearing of p2 in relationship from p1 in degrees East
|
||||
* @author Google Inc.
|
||||
*/
|
||||
public static Double bearing(GeoPoint p1, GeoPoint p2) {
|
||||
double lat1 = p1.getLatitudeE6() / MILLION;
|
||||
double lon1 = p1.getLongitudeE6() / MILLION;
|
||||
double lat2 = p2.getLatitudeE6() / MILLION;
|
||||
double lon2 = p2.getLongitudeE6() / MILLION;
|
||||
return bearing(lat1, lon1, lat2, lon2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a geopoint x meters away of the geopoint supplied. The new geopoint
|
||||
* shares the same latitude as geopoint point, this way they are on the same latitude arc.
|
||||
*
|
||||
* @param point central geopoint
|
||||
* @param distance in meters from the geopoint
|
||||
* @return geopoint that is x meters away from the geopoint supplied
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static GeoPoint distanceFrom(GeoPoint point, double distance){
|
||||
//convert meters into kilometers
|
||||
distance = distance / 1000;
|
||||
|
||||
// convert lat and lon of geopoint to radians
|
||||
double lat1Rad = Math.toRadians((point.getLatitudeE6() / 1e6));
|
||||
double lon1Rad = Math.toRadians((point.getLongitudeE6() / 1e6));
|
||||
|
||||
/*
|
||||
* kilometers = acos(sin(lat1Rad)sin(lat2Rad)+cos(lat1Rad)cos(lat2Rad)cos(lon2Rad-lon1Rad)6371
|
||||
*
|
||||
* we are solving this equation for lon2Rad
|
||||
*
|
||||
* lon2Rad = lon1Rad+acos(cos(meters/6371)sec(lat1Rad)sec(lat2Rad)-tan(lat1Rad)tan(lat2Rad))
|
||||
*
|
||||
* NOTE: sec(x) = 1/cos(x)
|
||||
*
|
||||
* NOTE: that lat2Rad is = lat1Rad because we want to keep the new geopoint on the same lat arc
|
||||
* therefore i saw no need to create a new variable for lat2Rad,
|
||||
* and simply inputed lat1Rad in place of lat2Rad in the equation
|
||||
*
|
||||
* NOTE: this equation has be tested in the field against another gps device, and the distanceKm() from google
|
||||
* and has been proven to be damn close
|
||||
*/
|
||||
double lon2Rad = lon1Rad + Math.acos( Math.cos((distance/6371)) * (1 / Math.cos(lat1Rad))
|
||||
* (1 / Math.cos(lat1Rad)) - Math.tan(lat1Rad) * Math.tan(lat1Rad));
|
||||
|
||||
//return a geopoint that is x meters away from the geopoint supplied
|
||||
return new GeoPoint(point.getLatitudeE6(), (int) (Math.toDegrees(lon2Rad) * 1e6));
|
||||
}
|
||||
|
||||
/**
|
||||
* computes the distance between to lat1/lon1 and lat2/lon2 based on the curve of the earth
|
||||
* @param lat1 source lat
|
||||
* @param lon1 source lon
|
||||
* @param lat2 destination lat
|
||||
* @param lon2 destination lon
|
||||
* @return the distance between to lat1/lon1 and lat2/lon2
|
||||
* @author Google Inc.
|
||||
*/
|
||||
public static double distanceKm(double lat1, double lon1, double lat2, double lon2) {
|
||||
double lat1Rad = Math.toRadians(lat1);
|
||||
double lat2Rad = Math.toRadians(lat2);
|
||||
double deltaLonRad = Math.toRadians(lon2 - lon1);
|
||||
return Math.acos(Math.sin(lat1Rad) * Math.sin(lat2Rad) + Math.cos(lat1Rad) * Math.cos(lat2Rad) * Math.cos(deltaLonRad)) * EARTH_RADIUS_KM;
|
||||
}
|
||||
|
||||
/**
|
||||
* a convince method for testing if 2 circles on the the surface of the earth intersect.
|
||||
* we will use this method to test if the users accuracy circle intersects a marked locaton's radius
|
||||
* if ( (accuracyCircleRadius + locationRadius) - fudgeFactor) > acos(sin(lat1Rad)sin(lat2Rad)+cos(lat1Rad)cos(lat2Rad)cos(lon2Rad-lon1Rad)6371
|
||||
* @param userPoint
|
||||
* @param accuracyRadius in KM
|
||||
* @param locationPoint
|
||||
* @param locationRadius in KM
|
||||
* @param fudgeFactor how many KM the circles have to intersect
|
||||
* @return true if the circles intersect
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static boolean isIntersecting(GeoPoint userPoint, float accuracyRadius, GeoPoint locationPoint, float locationRadius, float fudgeFactor){
|
||||
if(((accuracyRadius + locationRadius) - fudgeFactor) > distanceKm(locationPoint, userPoint))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* computes the distance between to p1 and p2 based on the curve of the earth
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @return the distance between to p1 and p2
|
||||
* @author Google Inc.
|
||||
*/
|
||||
public static double distanceKm(GeoPoint p1, GeoPoint p2) {
|
||||
//if we are handed a null, return -1 so we don't break
|
||||
if(p1 == null || p2 == null)
|
||||
return -1;
|
||||
|
||||
double lat1 = p1.getLatitudeE6() / MILLION;
|
||||
double lon1 = p1.getLongitudeE6() / MILLION;
|
||||
double lat2 = p2.getLatitudeE6() / MILLION;
|
||||
double lon2 = p2.getLongitudeE6() / MILLION;
|
||||
return distanceKm(lat1, lon1, lat2, lon2);
|
||||
}
|
||||
|
||||
/**
|
||||
* determines when the specified point is off the map
|
||||
* @param point
|
||||
* @return true is the point is off the map
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static boolean isPointOffMap(MapView map , GeoPoint point){
|
||||
if(map == null)
|
||||
return false;
|
||||
if (point == null)
|
||||
return false;
|
||||
GeoPoint center = map.getMapCenter();
|
||||
double distance = GeoUtils.distanceKm(center, point);
|
||||
double distanceLat = GeoUtils.distanceKm(center, new GeoPoint((center.getLatitudeE6() + (int) (map.getLatitudeSpan() / 2)), center.getLongitudeE6()));
|
||||
double distanceLon = GeoUtils.distanceKm(center, new GeoPoint(center.getLatitudeE6(), (center.getLongitudeE6() + (int) (map.getLongitudeSpan() / 2))));
|
||||
if (distance > distanceLat || distance > distanceLon){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* computes a geopoint the is the central geopoint between p1 and p1
|
||||
* @param p1 first geopoint
|
||||
* @param p2 second geopoint
|
||||
* @return a MidPoint object
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static MidPoint midPoint(GeoPoint p1, GeoPoint p2) {
|
||||
int minLatitude = (int)(+81 * 1E6);
|
||||
int maxLatitude = (int)(-81 * 1E6);
|
||||
int minLongitude = (int)(+181 * 1E6);
|
||||
int maxLongitude = (int)(-181 * 1E6);
|
||||
List<Point> mPoints = new ArrayList<Point>();
|
||||
int latitude = p1.getLatitudeE6();
|
||||
int longitude = p1.getLongitudeE6();
|
||||
if (latitude != 0 && longitude !=0) {
|
||||
minLatitude = (minLatitude > latitude) ? latitude : minLatitude;
|
||||
maxLatitude = (maxLatitude < latitude) ? latitude : maxLatitude;
|
||||
minLongitude = (minLongitude > longitude) ? longitude : minLongitude;
|
||||
maxLongitude = (maxLongitude < longitude) ? longitude : maxLongitude;
|
||||
mPoints.add(new Point(latitude, longitude));
|
||||
}
|
||||
|
||||
latitude = p2.getLatitudeE6();
|
||||
longitude = p2.getLongitudeE6();
|
||||
if (latitude != 0 && longitude !=0) {
|
||||
minLatitude = (minLatitude > latitude) ? latitude : minLatitude;
|
||||
maxLatitude = (maxLatitude < latitude) ? latitude : maxLatitude;
|
||||
minLongitude = (minLongitude > longitude) ? longitude : minLongitude;
|
||||
maxLongitude = (maxLongitude < longitude) ? longitude : maxLongitude;
|
||||
mPoints.add(new Point(latitude, longitude));
|
||||
}
|
||||
return new MidPoint(new GeoPoint((maxLatitude + minLatitude)/2, (maxLongitude + minLongitude)/2 ), minLatitude, minLongitude, maxLatitude, maxLongitude);
|
||||
}
|
||||
|
||||
/**
|
||||
* converts radians to bearing
|
||||
* @param rad
|
||||
* @return bearing
|
||||
* @author Google Inc.
|
||||
*/
|
||||
public static double radToBearing(double rad) {
|
||||
return (Math.toDegrees(rad) + 360) % 360;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @author Twenty Codes, LLC
|
||||
* @author ricky barrette
|
||||
* @date Oct 18, 2010
|
||||
*/
|
||||
package com.TwentyCodes.android.location;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
|
||||
/**
|
||||
* this abstract class will be used as a for classes wishing to be a receiver of location updates from the location services
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public abstract class LocationReceiver extends BroadcastReceiver {
|
||||
|
||||
public static final String INTENT_EXTRA_ACTION_UPDATE = "TwentyCodes.intent.action.LocationUpdate";
|
||||
public static final String INTENT_EXTRA_LOCATION_PARCEL = "location_parcel";
|
||||
public Context mContext;
|
||||
|
||||
/**
|
||||
* (non-Javadoc)
|
||||
* @see android.content.BroadcastReceiver#onReceive(android.content.Context, android.content.Intent)
|
||||
* @param contextonBind
|
||||
* @param intent
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
mContext = context;
|
||||
if(intent.getParcelableExtra(INTENT_EXTRA_LOCATION_PARCEL) != null){
|
||||
Location location = intent.getParcelableExtra(INTENT_EXTRA_LOCATION_PARCEL);
|
||||
onLocationUpdate(location);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* called when a location update is received
|
||||
* @param parcelableExtra
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public abstract void onLocationUpdate(Location location);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* @author Twenty Codes, LLC
|
||||
* @author ricky barrette
|
||||
* @date Oct 28, 2010
|
||||
*/
|
||||
package com.TwentyCodes.android.location;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
|
||||
import com.TwentyCodes.android.debug.Debug;
|
||||
|
||||
/**
|
||||
* This service class will be used broadcast the users location either one time, or periodically.
|
||||
* To use as a one shot location service:
|
||||
* <blockquote><pre>PendingIntent pendingIntent = PendingIntent.getService(context, 0, LocationService.startService(context), 0);
|
||||
* or
|
||||
* Intent service = new Intent(context, LocationService.class);
|
||||
* context.startService(service);<pre></bloackquote>
|
||||
* To use as a recurring service:
|
||||
* <blockquote>LocationService.startService(this, (60000 * Integer.parseInt(ringer.getString(UPDATE_INTVERVAL , "5")))).run();</bloackquote>
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public class LocationService extends Service implements LocationListener {
|
||||
|
||||
/**
|
||||
* Used to tell the service how frequently it needs to run. This is required if you want a multishot service
|
||||
*/
|
||||
public static final String INTENT_EXTRA_PERIOD_BETWEEN_UPDATES = "period_beween_updates";
|
||||
|
||||
/**
|
||||
* Used to tell the service how accurate of a location you want reported
|
||||
*/
|
||||
public static final String INTENT_EXTRA_REQUIRED_ACCURACY = "required_accuracy";
|
||||
|
||||
/**
|
||||
* Used to tell the service the update action to broadcast. If this is not supplied, {@link LocationReceiver.INTENT_EXTRA_ACTION_UPDATE } will be used.
|
||||
* @see LocationReceiver.INTENT_EXTRA_ACTION_UPDATE
|
||||
*/
|
||||
public static final String INTENT_EXTRA_ACTION_UPDATE = "action_update";
|
||||
|
||||
public static final String TAG = "LocationService";
|
||||
private static final int REQUEST_CODE = 7893749;
|
||||
private WakeLock mWakeLock;
|
||||
private long mPeriod = -1;
|
||||
private Location mLocation;
|
||||
private int mStartId;
|
||||
private AndroidGPS mLocationManager;
|
||||
private int mRequiredAccuracy;
|
||||
private Intent mIntent;
|
||||
/*
|
||||
* this runnable will be qued when the service is created. this will be used as a fail safe
|
||||
*/
|
||||
private Runnable failSafe = new Runnable() {
|
||||
@Override
|
||||
public void run(){
|
||||
stopSelf(mStartId);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* registers this service to be waken up by android's alarm manager
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private void registerwakeUp(){
|
||||
Log.d(TAG, "registerwakeUp()");
|
||||
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||
am.set(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis() + this.mPeriod, PendingIntent.getService(this, REQUEST_CODE, this.mIntent, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* broadcasts location to anything listening for updates,
|
||||
* since this is the last function of the service, we call finish()u
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private void broadcastLocation() {
|
||||
Log.d(TAG, "broadcastLocation()");
|
||||
if (mLocation != null) {
|
||||
Intent locationUpdate = new Intent();
|
||||
if(mIntent.getAction() != null)
|
||||
locationUpdate.setAction(mIntent.getAction());
|
||||
else
|
||||
locationUpdate.setAction(LocationReceiver.INTENT_EXTRA_ACTION_UPDATE);
|
||||
locationUpdate.putExtra(LocationReceiver.INTENT_EXTRA_LOCATION_PARCEL, mLocation);
|
||||
sendBroadcast(locationUpdate);
|
||||
stopSelf(mStartId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* called when the service is created. this will initialize the location manager, and acquire a wakelock
|
||||
* (non-Javadoc)
|
||||
* @see android.app.Service#onCreate()
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(){
|
||||
mLocationManager = new AndroidGPS(this);
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
|
||||
mWakeLock.acquire();
|
||||
|
||||
/*
|
||||
* que the fail safe runnable to kill the report location and kill it self after the MAX_RUN_TIME has been meet
|
||||
*/
|
||||
new Handler().postDelayed(failSafe, Debug.MAX_LOCATION_SERVICE_RUN_TIME);
|
||||
super.onCreate();
|
||||
}
|
||||
|
||||
/**
|
||||
* called when the service is destroyed.
|
||||
* this will remove any wakelock or location service running, and register to be waken back up
|
||||
* (non-Javadoc)
|
||||
* @see android.app.Service#onDestroy()
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
broadcastLocation();
|
||||
mLocationManager.disableLocationUpdates();
|
||||
if(mWakeLock.isHeld())
|
||||
mWakeLock.release();
|
||||
if(mPeriod > -1)
|
||||
registerwakeUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* To keep backwards compatibility we override onStart which is the equivalent of onStartCommand in pre android 2.x
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onStart(Intent intent, int startId) {
|
||||
if(Debug.DEBUG)
|
||||
Log.i(TAG, "onStart.Service started with start id of: " + startId);
|
||||
mStartId = startId;
|
||||
|
||||
parseIntent(intent);
|
||||
|
||||
mLocationManager.enableLocationUpdates(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when startService is called. only used in 2.x android.
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if(Debug.DEBUG)
|
||||
Log.i(TAG , "onStartCommand.Service started with start id of: " + startId);
|
||||
mStartId = startId;
|
||||
|
||||
parseIntent(intent);
|
||||
|
||||
mLocationManager.enableLocationUpdates(this);
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the incoming intent for the service options
|
||||
*
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private void parseIntent(Intent intent){
|
||||
|
||||
this.mIntent = intent;
|
||||
|
||||
if (intent.hasExtra(INTENT_EXTRA_PERIOD_BETWEEN_UPDATES))
|
||||
mPeriod = intent.getLongExtra(INTENT_EXTRA_PERIOD_BETWEEN_UPDATES, 60000L);
|
||||
|
||||
if (intent.hasExtra(INTENT_EXTRA_REQUIRED_ACCURACY))
|
||||
mRequiredAccuracy = intent.getIntExtra(INTENT_EXTRA_REQUIRED_ACCURACY, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-Javadoc)
|
||||
* @see android.app.Service#onBind(android.content.Intent)
|
||||
* @param arg0
|
||||
* @return
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public IBinder onBind(Intent arg0) {
|
||||
// UNUSED
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*a convince method for getting an intent to start the service
|
||||
* @param context
|
||||
* @return a intent that will start the service
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static Intent getStartServiceIntent(final Context context){
|
||||
return new Intent(context, LocationService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* a convince method for stopping the service and removing it's alarm
|
||||
* @param context
|
||||
* @return a runnable that will stop the service
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static Runnable stopService(final Context context){
|
||||
return new Runnable(){
|
||||
@Override
|
||||
public void run(){
|
||||
context.stopService(new Intent(context, LocationService.class));
|
||||
((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).cancel(PendingIntent.getService(context, REQUEST_CODE, new Intent(context, LocationService.class), 0));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
if(Debug.DEBUG)
|
||||
Log.d(TAG, "got location +- "+ location.getAccuracy() +"m");
|
||||
mLocation = location;
|
||||
if(location.getAccuracy() <= (mRequiredAccuracy > -1 ? mRequiredAccuracy : Debug.MINIMUM_REQUIRED_ACCURACY) || Debug.REPORT_FIRST_LOCATION){
|
||||
stopSelf(mStartId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(String provider) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(String provider) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
110
LocationLib/src/com/TwentyCodes/android/location/MapView.java
Normal file
110
LocationLib/src/com/TwentyCodes/android/location/MapView.java
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @author Twenty Codes, LLC
|
||||
* @author ricky barrette
|
||||
* @date Oct 10, 2010
|
||||
*/
|
||||
package com.TwentyCodes.android.location;
|
||||
|
||||
import com.TwentyCodes.android.debug.Debug;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
/**
|
||||
* We use this MapView Because it has double tap zoom capability and exception handling
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public class MapView extends com.google.android.maps.MapView {
|
||||
|
||||
private static final String TAG = "MapView";
|
||||
private long mLastTouchTime;
|
||||
private boolean mDoubleTapZoonEnabled = true;
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @param apiKey
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public MapView(Context context, String apiKey) {
|
||||
super(context, apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @param attrs
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public MapView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @param attrs
|
||||
* @param defStyle
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public MapView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
|
||||
long thisTime = System.currentTimeMillis();
|
||||
if (this.mDoubleTapZoonEnabled && thisTime - mLastTouchTime < 250) {
|
||||
// Double tap
|
||||
this.getController().zoomInFixing((int) ev.getX(), (int) ev.getY());
|
||||
mLastTouchTime = -1;
|
||||
} else {
|
||||
// Too slow
|
||||
mLastTouchTime = thisTime;
|
||||
}
|
||||
}
|
||||
|
||||
return super.onInterceptTouchEvent(ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* We will override the draw method to help prevent issues
|
||||
* (non-Javadoc)
|
||||
* @see android.view.View#draw(android.graphics.Canvas)
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
try {
|
||||
if(this.getZoomLevel() >= 21) {
|
||||
this.getController().setZoom(20);
|
||||
}
|
||||
super.draw(canvas);
|
||||
}
|
||||
catch(Exception ex) {
|
||||
// getController().setCenter(this.getMapCenter());
|
||||
// getController().setZoom(this.getZoomLevel() - 2);
|
||||
if(Debug.DEBUG)
|
||||
Log.d(TAG, "Internal error in MapView:" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isDoubleTapZoonEnabled the isDoubleTapZoonEnabled to set
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void setDoubleTapZoonEnabled(boolean isDoubleTapZoonEnabled) {
|
||||
this.mDoubleTapZoonEnabled = isDoubleTapZoonEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the isDoubleTapZoonEnabled
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public boolean getDoubleTapZoonEnabled() {
|
||||
return mDoubleTapZoonEnabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @author Twenty Codes, LLC
|
||||
* @author ricky barrette
|
||||
* @date Nov 30, 2010
|
||||
*/
|
||||
package com.TwentyCodes.android.location;
|
||||
|
||||
import com.google.android.maps.GeoPoint;
|
||||
|
||||
/**
|
||||
* This MidPoint object will hold the information form the calculations performed by GeoUtils.midPoint().
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public class MidPoint {
|
||||
|
||||
private int mMinLatitude;
|
||||
private int mMaxLatitude;
|
||||
private int mMinLongitude;
|
||||
private int mMaxLongitude;
|
||||
private GeoPoint mMidPoint;
|
||||
|
||||
/**
|
||||
* Creates a new MidPoint
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public MidPoint(GeoPoint midPoint, int minLatitude, int minLongitude, int maxLatitude, int maxLongitude) {
|
||||
mMinLatitude = minLatitude;
|
||||
mMaxLatitude = maxLatitude;
|
||||
mMinLongitude = minLongitude;
|
||||
mMaxLongitude = maxLongitude;
|
||||
mMidPoint = midPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* zooms the provided map view to the span of this mid point
|
||||
* @param mMapView
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void zoomToSpan(com.google.android.maps.MapView mMapView){
|
||||
mMapView.getController().zoomToSpan((mMaxLatitude - mMinLatitude), (mMaxLongitude - mMinLongitude));
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the calculated midpoint
|
||||
* @return
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public GeoPoint getMidPoint(){
|
||||
return mMidPoint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* ReverseGeocoder.java
|
||||
* @date Jan 31, 2011
|
||||
* @author ricky barrette
|
||||
* @author Twenty Codes, LLC
|
||||
*/
|
||||
|
||||
package com.TwentyCodes.android.location;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import com.TwentyCodes.android.debug.Debug;
|
||||
|
||||
/**
|
||||
* Due to this bug http://code.google.com/p/android/issues/detail?id=8816 google's Geocoder class does not function in android 2.2+.
|
||||
* I found this source in one of the comments mentioning that it is a work around.
|
||||
*
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public class ReverseGeocoder {
|
||||
|
||||
private static final String TAG = "ReverseGeocoder";
|
||||
|
||||
/**
|
||||
* Performs a google maps search for the address
|
||||
* @param location
|
||||
* @return JSON Array on google place marks nearby
|
||||
* @author ricky barrette
|
||||
* @throws IOException
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static JSONArray getFromLocation(Location location) throws IOException, JSONException {
|
||||
String urlStr = "http://maps.google.com/maps/geo?q=" + location.getLatitude() + "," + location.getLongitude() + "&output=json&sensor=false";
|
||||
StringBuffer response = new StringBuffer();
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
|
||||
if(Debug.DEBUG)
|
||||
Log.d(TAG, urlStr);
|
||||
HttpResponse hr = client.execute(new HttpGet(urlStr));
|
||||
HttpEntity entity = hr.getEntity();
|
||||
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(entity.getContent()));
|
||||
|
||||
String buff = null;
|
||||
while ((buff = br.readLine()) != null)
|
||||
response.append(buff);
|
||||
|
||||
if(Debug.DEBUG)
|
||||
Log.d(TAG, response.toString());
|
||||
|
||||
return new JSONObject(response.toString()).getJSONArray("Placemark");
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a google maps search for the closest address to the location
|
||||
* @param lat
|
||||
* @param lon
|
||||
* @return string address, or lat, lon if search fails
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static String getAddressFromLocation(Location location) {
|
||||
String urlStr = "http://maps.google.com/maps/geo?q=" + location.getLatitude() + "," + location.getLongitude() + "&output=json&sensor=false";
|
||||
StringBuffer response = new StringBuffer();
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
|
||||
if(Debug.DEBUG)
|
||||
Log.d(TAG, urlStr);
|
||||
try {
|
||||
HttpResponse hr = client.execute(new HttpGet(urlStr));
|
||||
HttpEntity entity = hr.getEntity();
|
||||
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(entity.getContent()));
|
||||
|
||||
String buff = null;
|
||||
while ((buff = br.readLine()) != null)
|
||||
response.append(buff);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if(Debug.DEBUG)
|
||||
Log.d(TAG, response.toString());
|
||||
|
||||
|
||||
JSONArray responseArray = null;
|
||||
try {
|
||||
responseArray = new JSONObject(response.toString()).getJSONArray("Placemark");
|
||||
} catch (JSONException e) {
|
||||
return location.getLatitude() +", "+ location.getLongitude() +" ± "+ location.getAccuracy()+"m";
|
||||
}
|
||||
|
||||
if(Debug.DEBUG)
|
||||
Log.d(TAG,responseArray.length() + " result(s)");
|
||||
|
||||
try {
|
||||
JSONObject jsl = responseArray.getJSONObject(0);
|
||||
return jsl.getString("address");
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return location.getLatitude() +", "+ location.getLongitude() +" ± "+ location.getAccuracy()+"m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a google maps search for the address
|
||||
* @param address to search
|
||||
* @return JSON Array of google place marks
|
||||
* @throws IOException
|
||||
* @throws JSONException
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public static JSONArray addressSearch(String address) throws IOException, JSONException {
|
||||
String urlStr = "http://maps.google.com/maps/geo?q=" + address + "&output=json&sensor=false";
|
||||
urlStr = urlStr.replace(' ', '+');
|
||||
StringBuffer response = new StringBuffer();
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
|
||||
if(Debug.DEBUG)
|
||||
Log.d(TAG, urlStr);
|
||||
HttpResponse hr = client.execute(new HttpGet(urlStr));
|
||||
HttpEntity entity = hr.getEntity();
|
||||
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(entity.getContent()));
|
||||
|
||||
String buff = null;
|
||||
while ((buff = br.readLine()) != null)
|
||||
response.append(buff);
|
||||
|
||||
if(Debug.DEBUG)
|
||||
Log.d(TAG, response.toString());
|
||||
|
||||
return new JSONObject(response.toString()).getJSONArray("Placemark");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
/**
|
||||
* @author Twenty Codes, LLC
|
||||
* @author ricky barrette
|
||||
* @date Dec 28, 2010
|
||||
*/
|
||||
package com.TwentyCodes.android.location;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.drawable.AnimationDrawable;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.location.Location;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.TwentyCodes.android.SkyHook.R;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.google.android.maps.MapView;
|
||||
import com.google.android.maps.MyLocationOverlay;
|
||||
import com.google.android.maps.Projection;
|
||||
|
||||
/**
|
||||
* This is the standard version of the UserOverlay.
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public class UserOverlay extends MyLocationOverlay {
|
||||
|
||||
private Context mContext;
|
||||
private MapView mMapView;
|
||||
private ProgressDialog mGPSprogress;
|
||||
private boolean isFirstFix = true;
|
||||
private GeoPointLocationListener mListener;
|
||||
private boolean isFollowingUser = true;
|
||||
private float myAzimuth;
|
||||
private GeoPoint mUser;
|
||||
private GeoPoint mDest;
|
||||
private boolean isShowingCompass;
|
||||
private AnimationDrawable mUserArrow;
|
||||
|
||||
/**
|
||||
* Creates a new UserOverlay
|
||||
* @param context
|
||||
* @param mapView
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public UserOverlay(Context context, MapView mapView) {
|
||||
super(context, mapView);
|
||||
mMapView = mapView;
|
||||
mContext = context;
|
||||
mUserArrow = (AnimationDrawable) mContext.getResources().getAnimation(R.drawable.userarrow);
|
||||
mUserArrow.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* disables the compass view
|
||||
* (non-Javadoc)
|
||||
* @see com.google.android.maps.MyLocationOverlay#disableCompass()
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void disableCompass(){
|
||||
isShowingCompass = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* called when the overlay is disabled. this will disable all progress dialogs, and location based servicess
|
||||
* (non-Javadoc)
|
||||
* @see com.google.android.maps.MyLocationOverlay#disableMyLocation()
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void disableMyLocation(){
|
||||
super.disableCompass();
|
||||
super.disableMyLocation();
|
||||
mGPSprogress.dismiss();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* draws an accuracy circle onto the canvas supplied
|
||||
* @param center point of the circle
|
||||
* @param left point of the circle
|
||||
* @param canvas to be drawn on
|
||||
* @return modified canvas
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private Canvas drawAccuracyCircle(Point center, Point left, Canvas canvas) {
|
||||
Paint paint = new Paint();
|
||||
|
||||
/*
|
||||
* get radius of the circle being drawn by
|
||||
*/
|
||||
int circleRadius = center.x - left.x;
|
||||
if(circleRadius <= 0){
|
||||
circleRadius = left.x - center.x;
|
||||
}
|
||||
/*
|
||||
* paint a blue circle on the map
|
||||
*/
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStrokeWidth(2.0f);
|
||||
paint.setColor(Color.BLUE);
|
||||
paint.setStyle(Style.STROKE);
|
||||
canvas.drawCircle(center.x, center.y, circleRadius, paint);
|
||||
/*
|
||||
* fill the radius with a alpha blue
|
||||
*/
|
||||
paint.setAlpha(30);
|
||||
paint.setStyle(Style.FILL);
|
||||
canvas.drawCircle(center.x, center.y, circleRadius, paint);
|
||||
|
||||
/*
|
||||
* for testing
|
||||
* draw a dot over the left geopoint
|
||||
*/
|
||||
// paint.setColor(Color.RED);
|
||||
// RectF oval = new RectF(left.x - 1, left.y - 1, left.x + 1, left.y + 1);
|
||||
// canvas.drawOval(oval, paint);
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* computes bearing to geopoint based on device oriantaion and draws the compass of what you want really really badly on screen
|
||||
* @param - canvas - the canvas to draw on
|
||||
* @param - bearing - bearing of user based on magnetic compass
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
protected void drawCompass(Canvas canvas, float bearing){
|
||||
myAzimuth = bearing;
|
||||
|
||||
mMapView.invalidate();
|
||||
|
||||
if (isShowingCompass) {
|
||||
/*
|
||||
* if the dest and user geopoint are not null, then draw the compass point to the dest geopoint
|
||||
*
|
||||
* else draw the compass to point north
|
||||
*/
|
||||
if (mUser != null && mDest != null){
|
||||
Double d = GeoUtils.bearing(mUser, mDest);
|
||||
bearing = bearing - d.floatValue();
|
||||
} else if (bearing != 0){
|
||||
bearing = 360 - bearing;
|
||||
}
|
||||
|
||||
super.drawCompass(canvas, bearing);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* we override this methods so we can provide a drawable and a location to draw on the canvas.
|
||||
* (non-Javadoc)
|
||||
* @see com.google.android.maps.Overlay#draw(android.graphics.Canvas, com.google.android.maps.MapView, boolean)
|
||||
* @param canvas
|
||||
* @param mapView
|
||||
* @param shadow
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
protected void drawMyLocation(Canvas canvas, MapView mapView, Location lastFix, GeoPoint point, long when){
|
||||
if (point != null) {
|
||||
|
||||
Point center = new Point();
|
||||
Point left = new Point();
|
||||
Projection projection = mapView.getProjection();
|
||||
GeoPoint leftGeo = GeoUtils.distanceFrom(point, lastFix.getAccuracy());
|
||||
projection.toPixels(leftGeo, left);
|
||||
projection.toPixels(point, center);
|
||||
canvas = drawAccuracyCircle(center, left, canvas);
|
||||
canvas = drawUser(center, myAzimuth, canvas);
|
||||
/*
|
||||
* the following log is used to demonstrate if the leftGeo point is the correct
|
||||
*/
|
||||
// Log.d(SkyHook.TAG, (GeoUtils.distanceKm(mPoint, leftGeo) * 1000)+"m");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* draws user arrow that points north based on bearing onto the supplied canvas
|
||||
* @param point to draw user arrow on
|
||||
* @param bearing of the device
|
||||
* @param canvas to draw on
|
||||
* @return modified canvas
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private Canvas drawUser(Point point, float bearing, Canvas canvas){
|
||||
Bitmap arrowBitmap = ((BitmapDrawable)mUserArrow.getCurrent()).getBitmap();
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postRotate(bearing);
|
||||
Bitmap rotatedBmp = Bitmap.createBitmap(
|
||||
arrowBitmap,
|
||||
0, 0,
|
||||
arrowBitmap.getWidth(),
|
||||
arrowBitmap.getHeight(),
|
||||
matrix,
|
||||
true
|
||||
);
|
||||
canvas.drawBitmap(
|
||||
rotatedBmp,
|
||||
point.x - (rotatedBmp.getWidth() / 2),
|
||||
point.y - (rotatedBmp.getHeight() / 2),
|
||||
null
|
||||
);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* enables the compass view
|
||||
* (non-Javadoc)
|
||||
* @see com.google.android.maps.MyLocationOverlay#enableCompass()
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public boolean enableCompass(){
|
||||
isShowingCompass = true;
|
||||
return isShowingCompass;
|
||||
}
|
||||
|
||||
/**
|
||||
* called when the user overlay is enabled, this will display the progress dialog
|
||||
* (non-Javadoc)
|
||||
* @see com.google.android.maps.MyLocationOverlay#enableMyLocation()
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public boolean enableMyLocation(){
|
||||
mGPSprogress = ProgressDialog.show(mContext, "", mContext.getText(R.string.gps_fix), true, true);
|
||||
isFirstFix = true;
|
||||
super.enableCompass();
|
||||
|
||||
/**
|
||||
* this is a message that tells the user that we are having trouble getting an GPS signal
|
||||
*/
|
||||
new Handler().postAtTime(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mGPSprogress.isShowing()) {
|
||||
mGPSprogress.cancel();
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
|
||||
builder.setMessage(
|
||||
mContext.getText(R.string.sorry_theres_trouble))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(mContext.getText(android.R.string.ok),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick( DialogInterface dialog, int id) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
}
|
||||
}, SystemClock.uptimeMillis()+90000L);
|
||||
|
||||
return super.enableMyLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the map to follow the user
|
||||
* @param followUser
|
||||
* @author ricky barrette
|
||||
*/
|
||||
public void followUser(boolean followUser){
|
||||
isFollowingUser = followUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* called when the SkyHook location changes, this method is resposiable for updating the overlay location and accuracy circle.
|
||||
* (non-Javadoc)
|
||||
* @see com.TwentyCodes.android.SkyHook.GeoPointLocationListener.location.LocationListener#onLocationChanged(com.google.android.maps.GeoPoint, float)
|
||||
* @param point
|
||||
* @param accuracy
|
||||
* @author ricky barrette
|
||||
*/
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
|
||||
GeoPoint point = new GeoPoint((int) (location.getLatitude() *1e6), (int) (location.getLongitude() *1e6));
|
||||
|
||||
/*
|
||||
* if this is the first fix
|
||||
* set map center the users location, and zoom to the max zoom level
|
||||
*/
|
||||
if(point != null && isFirstFix){
|
||||
mMapView.getController().setCenter(point);
|
||||
mMapView.getController().setZoom(mMapView.getMaxZoomLevel()+3);
|
||||
mGPSprogress.dismiss();
|
||||
isFirstFix = false;
|
||||
}
|
||||
|
||||
//pan to user if off map
|
||||
if (isFollowingUser) {
|
||||
panToUserIfOffMap(point);
|
||||
}
|
||||
|
||||
mListener.onLocationChanged(point, (int) location.getAccuracy());
|
||||
super.onLocationChanged(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* pans the map view if the user is off screen.
|
||||
* @author ricky barrette
|
||||
*/
|
||||
private void panToUserIfOffMap(GeoPoint user) {
|
||||
GeoPoint center = mMapView.getMapCenter();
|
||||
double distance = GeoUtils.distanceKm(center, user);
|
||||
double distanceLat = GeoUtils.distanceKm(center, new GeoPoint((center.getLatitudeE6() + (int) (mMapView.getLatitudeSpan() / 2)), center.getLongitudeE6()));
|
||||
double distanceLon = GeoUtils.distanceKm(center, new GeoPoint(center.getLatitudeE6(), (center.getLongitudeE6() + (int) (mMapView.getLongitudeSpan() / 2))));
|
||||
|
||||
double whichIsGreater = (distanceLat > distanceLon) ? distanceLat : distanceLon;
|
||||
|
||||
/**
|
||||
* if the user is one the map, keep them their
|
||||
* else don't pan to user unless they pan pack to them
|
||||
*/
|
||||
if( ! (distance > whichIsGreater) )
|
||||
if (distance > distanceLat || distance > distanceLon){
|
||||
mMapView.getController().animateTo(user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to register the listener for location updates
|
||||
* @param listener
|
||||
* @author Ricky Barrette
|
||||
*/
|
||||
public void registerListener(GeoPointLocationListener listener){
|
||||
if (mListener == null){
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UnResgisters the listener. after this call you will no longer get location updates
|
||||
* @author Ricky Barrette
|
||||
*/
|
||||
public void unRegisterListener(){
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user