init commit

This commit is contained in:
2011-12-17 15:43:48 +00:00
parent eeb14e8bb7
commit 4a0469abf7
190 changed files with 21930 additions and 0 deletions

View 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}

View 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));
}
};
}
}

View 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;
}

View File

@@ -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;
}

View 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.
*/
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
};
}
}

View File

@@ -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);
}

View 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;
}
}

View File

@@ -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);
}

View File

@@ -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
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}