Commit 1f7459c5 authored by Chris Scott's avatar Chris Scott

Merge pull request #1 from christocracy/android

Android
parents 3f569226 2038b1c0
.DS_Store
......@@ -18,13 +18,32 @@
<!-- android -->
<platform name="android">
<source-file src="src/android/BackgroundGpsPlugin.java" target-dir="src/com/tenforwardconsulting/cordova/bgloc" />
<source-file src="src/android/LocationUpdateService.java" target-dir="src/com/tenforwardconsulting/cordova/bgloc" />
<source-file src="src/android/data/DAOFactory.java" target-dir="src/com/tenforwardconsulting/cordova/bgloc/data" />
<source-file src="src/android/data/Location.java" target-dir="src/com/tenforwardconsulting/cordova/bgloc/data" />
<source-file src="src/android/data/LocationDAO.java" target-dir="src/com/tenforwardconsulting/cordova/bgloc/data" />
<source-file src="src/android/data/sqlite/LocationOpenHelper.java" target-dir="src/com/tenforwardconsulting/cordova/bgloc/data/sqlite" />
<source-file src="src/android/data/sqlite/SQLiteLocationDAO.java" target-dir="src/com/tenforwardconsulting/cordova/bgloc/data/sqlite" />
<source-file src="src/android/notification.png" target-dir="res/drawable" />
<source-file src="src/android/android-support-v4.jar" target-dir="libs" />
<config-file target="AndroidManifest.xml" parent="/manifest/application">
<service android:enabled="true" android:name="com.tenforwardconsulting.cordova.bgloc.LocationUpdateService" android:process=":remote" />
</config-file>
<config-file target="AndroidManifest.xml" parent="/manifest">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.INTERNET" />
</config-file>
<config-file target="res/xml/config.xml" parent="/*">
<feature name="BackgroundGeoLocation">
<param name="android-package" value="CDVBackgroundGeolocation"/>
<param name="android-package" value="com.tenforwardconsulting.cordova.bgloc.BackgroundGpsPlugin"/>
</feature>
</config-file>
<source-file src="src/android/CDVBackgroundGeoLocation.java" target-dir="src/org/transistorsoft/background-geolocation" />
</platform>
<platform name="ios">
......
package com.tenforwardconsulting.cordova.bgloc;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;
import android.app.Activity;
import android.content.Intent;
public class BackgroundGpsPlugin extends CordovaPlugin {
public static final String ACTION_START = "start";
public static final String ACTION_STOP = "stop";
public static final String ACTION_CONFIGURE = "configure";
private String authToken;
private String url;
@Override
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) {
Activity activity = this.cordova.getActivity();
Intent updateServiceIntent = new Intent(activity, LocationUpdateService.class);
if (ACTION_START.equalsIgnoreCase(action)) {
if (authToken == null || url == null) {
callbackContext.error("Call configure before calling start");
return false;
}
updateServiceIntent.putExtra("authToken", authToken);
updateServiceIntent.putExtra("url", url);
activity.startService(updateServiceIntent);
} else if (ACTION_STOP.equalsIgnoreCase(action)) {
activity.stopService(updateServiceIntent);
} else if (ACTION_CONFIGURE.equalsIgnoreCase(action)) {
try {
this.authToken = data.getString(0);
this.url = data.getString(1);
} catch (JSONException e) {
callbackContext.error("authToken/url required as parameters: " + e.getMessage());
return false;
}
}
return true;
}
}
package com.tenforwardconsulting.cordova.bgloc;
import java.util.Arrays;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONObject;
import com.tenforwardconsulting.cordova.bgloc.data.DAOFactory;
import com.tenforwardconsulting.cordova.bgloc.data.LocationDAO;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationManager;
import android.support.v4.app.NotificationCompat;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.SystemClock;
import android.util.Log;
import android.widget.Toast;
public class LocationUpdateService extends Service implements LocationListener {
private static final String TAG = "LocationService";
public static final int NOTIFICATION_ID = 555;
private PowerManager.WakeLock wakeLock;
private Location lastLocation;
private long lastUpdateTime = 0l;
private BusyTask looper;
private String authToken = "HypVBMmDxbh76pHpwots";
private String url = "http://192.168.2.15:3000/users/current_location.json";
private Notification notification;
private NotificationManager notificationManager;
private LocationManager locationManager;
private ConnectivityManager connectivityManager;
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
Log.i(TAG, "OnBind" + intent);
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "OnCreate");
notificationManager = (NotificationManager)this.getSystemService(NOTIFICATION_SERVICE);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
locationManager = (LocationManager)this.getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1*60*1000, 0, this);
connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
wakeLock.acquire();
// looper = new BusyTask();
// looper.execute("go");
//scheudlePostLocation(locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER));
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Received start id " + startId + ": " + intent);
if (intent != null) {
this.authToken = intent.getStringExtra("authToken");
this.url = intent.getStringExtra("url");
}
Toast.makeText(this, "Background location tracking started", Toast.LENGTH_SHORT).show();
//We want this service to continue running until it is explicitly
// stopped, so return sticky.
notification = buildNotification();
notificationManager.notify(NOTIFICATION_ID, notification);
return START_STICKY;
}
@Override
public boolean stopService(Intent intent) {
Log.i(TAG, "Received stop: " + intent);
cleanUp();
Toast.makeText(this, "Background location tracking stopped", Toast.LENGTH_SHORT).show();
return super.stopService(intent);
}
@Override
public void onDestroy() {
Log.w(TAG, "Destroyed Location update Service");
cleanUp();
super.onDestroy();
}
private void cleanUp() {
locationManager.removeUpdates(this);
notificationManager.cancel(NOTIFICATION_ID);
wakeLock.release();
if (looper != null) {
looper.stop = true;
}
}
private Notification buildNotification() {
PackageManager pm = this.getPackageManager();
Intent notificationIntent = pm.getLaunchIntentForPackage(this.getPackageName());
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
Application application = this.getApplication();
int backgroundIconId = 0;
for (String s: Arrays.asList("ic_launcher", "icon", "notification") ) {
backgroundIconId = application.getResources().getIdentifier(s, "drawable", application.getPackageName());
if (backgroundIconId != 0) {
break;
}
}
int appNameId = application.getResources().getIdentifier("app_name", "string", application.getPackageName());
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setSmallIcon(backgroundIconId)
.setContentTitle(this.getString(appNameId))
.setOngoing(true)
.setContentIntent(contentIntent)
.setWhen(System.currentTimeMillis());
if (lastLocation != null) {
builder.setContentText("Last location: " + lastLocation.getLatitude() + ", " + lastLocation.getLongitude());
} else {
builder.setContentText("Tracking your GPS position");
}
return builder.build();
}
public void onLocationChanged(Location location) {
Log.d(TAG, "Location22Change: " +location.getLatitude() + ", " + location.getLongitude());
lastLocation = location;
Log.d(TAG, "Location33Change:");
persistLocation(location);
if (this.isNetworkConnected()) {
Log.d(TAG, "Scheduling location network post");
schedulePostLocations();
} else {
Log.d(TAG, "Network unavailable, waiting for now");
}
}
public void onProviderDisabled(String provider) {
// TODO Auto-generated method stub
}
public void onProviderEnabled(String provider) {
// TODO Auto-generated method stub
}
public void onStatusChanged(String provider, int status, Bundle extras) {
// TODO Auto-generated method stub
}
private void schedulePostLocations() {
PostLocationTask task = new LocationUpdateService.PostLocationTask();
Log.d(TAG, "beforeexecute " + task.getStatus());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
task.execute();
Log.d(TAG, "afterexecute " + task.getStatus());
}
private boolean postLocation(com.tenforwardconsulting.cordova.bgloc.data.Location l) {
if (l == null) {
Log.w(TAG, "postLocation: null location");
return false;
}
try {
lastUpdateTime = SystemClock.elapsedRealtime();
Log.i(TAG, "Posting native location update: " + l);
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpPost request = new HttpPost(url);
JSONObject params = new JSONObject();
params.put("auth_token", authToken);
JSONObject location = new JSONObject();
location.put("latitude", l.getLatitude());
location.put("longitude", l.getLongitude());
location.put("recorded_at", l.getRecordedAt());
params.put("location", location);
StringEntity se = new StringEntity(params.toString());
request.setEntity(se);
request.setHeader("Accept", "application/json");
request.setHeader("Content-type", "application/json");
Log.d(TAG, "Posting to " + request.getURI().toString());
HttpResponse response = httpClient.execute(request);
Log.i(TAG, "Response received: " + response.getStatusLine());
if (response.getStatusLine().getStatusCode() == 200) {
return true;
} else {
return false;
}
} catch (Throwable e) {
Log.w(TAG, "Exception posting location: " + e);
e.printStackTrace();
return false;
}
}
private void persistLocation(Location location) {
LocationDAO dao = DAOFactory.createLocationDAO(this.getApplicationContext());
com.tenforwardconsulting.cordova.bgloc.data.Location savedLocation = com.tenforwardconsulting.cordova.bgloc.data.Location.fromAndroidLocation(location);
if (dao.persistLocation(savedLocation)) {
Log.d(TAG, "Persisted Location: " + savedLocation);
} else {
Log.w(TAG, "Failed to persist location");
}
}
private boolean isNetworkConnected() {
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo != null) {
Log.d(TAG, "Network found, type = " + networkInfo.getTypeName());
return networkInfo.isConnected();
} else {
Log.d(TAG, "No active network info");
return false;
}
}
@Override
public void onTaskRemoved(Intent rootIntent) {
this.stopSelf();
super.onTaskRemoved(rootIntent);
}
private class BusyTask extends AsyncTask<String, Integer, Boolean>{
public boolean stop = false;
@Override
protected Boolean doInBackground(String...params) {
while(!stop) {
Log.d(TAG, "#timestamp " + System.currentTimeMillis());
if (lastUpdateTime + 5*60*1000 < SystemClock.elapsedRealtime()) {
Log.d(TAG, "5 minutes, forcing update with last location");
postLocation(com.tenforwardconsulting.cordova.bgloc.data.Location.fromAndroidLocation(
locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)));
}
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return true;
}
}
private class PostLocationTask extends AsyncTask<Object, Integer, Boolean> {
@Override
protected Boolean doInBackground(Object...objects) {
Log.d(TAG, "Executing PostLocationTask#doInBackground");
LocationDAO locationDAO = DAOFactory.createLocationDAO(LocationUpdateService.this.getApplicationContext());
for (com.tenforwardconsulting.cordova.bgloc.data.Location savedLocation : locationDAO.getAllLocations()) {
Log.d(TAG, "Posting saved location");
if (postLocation(savedLocation)) {
locationDAO.deleteLocation(savedLocation);
}
}
return true;
}
@Override
protected void onPostExecute(Boolean result) {
Log.d(TAG, "PostLocationTask#onPostExecture");
notification = buildNotification();
notificationManager.notify(NOTIFICATION_ID, notification);
}
}
}
package com.tenforwardconsulting.cordova.bgloc.data;
import android.content.Context;
import com.tenforwardconsulting.cordova.bgloc.data.sqlite.SQLiteLocationDAO;
public abstract class DAOFactory {
public static LocationDAO createLocationDAO(Context context) {
//Very basic for now
return new SQLiteLocationDAO(context);
}
}
package com.tenforwardconsulting.cordova.bgloc.data;
import java.util.Date;
import android.os.SystemClock;
public class Location {
private String latitude;
private String longitude;
private Date recordedAt;
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLatitude() {
return latitude;
}
public void setLatitude(String latitude) {
this.latitude = latitude;
}
public String getLongitude() {
return longitude;
}
public void setLongitude(String longitude) {
this.longitude = longitude;
}
public Date getRecordedAt() {
return recordedAt;
}
public void setRecordedAt(Date recordedAt) {
this.recordedAt = recordedAt;
}
public static Location fromAndroidLocation(android.location.Location originalLocation) {
Location location = new Location();
location.setRecordedAt(new Date(originalLocation.getTime()));
location.setLongitude(String.valueOf(originalLocation.getLongitude()));
location.setLatitude(String.valueOf(originalLocation.getLatitude()));
return location;
}
}
package com.tenforwardconsulting.cordova.bgloc.data;
public interface LocationDAO {
public Location[] getAllLocations();
public boolean persistLocation(Location l);
public void deleteLocation(Location l);
}
package com.tenforwardconsulting.cordova.bgloc.data.sqlite;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class LocationOpenHelper extends SQLiteOpenHelper {
private static final String SQLITE_DATABASE_NAME = "cordova_bg_locations";
private static final int DATABASE_VERSION = 1;
public static final String LOCATION_TABLE_NAME = "location";
private static final String LOCATION_TABLE_COLUMNS =
" id INTEGER PRIMARY KEY AUTOINCREMENT," +
" recordedAt TEXT," +
" latitude TEXT," +
" longitude TEXT";
private static final String LOCATION_TABLE_CREATE =
"CREATE TABLE " + LOCATION_TABLE_NAME + " (" +
LOCATION_TABLE_COLUMNS +
");";
LocationOpenHelper(Context context) {
super(context, SQLITE_DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(LOCATION_TABLE_CREATE);
Log.d(this.getClass().getName(), LOCATION_TABLE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
}
}
\ No newline at end of file
package com.tenforwardconsulting.cordova.bgloc.data.sqlite;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.tenforwardconsulting.cordova.bgloc.data.Location;
import com.tenforwardconsulting.cordova.bgloc.data.LocationDAO;
public class SQLiteLocationDAO implements LocationDAO {
public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
private static final String TAG = "SQLiteLocationDAO";
private Context context;
public SQLiteLocationDAO(Context context) {
this.context = context;
}
public Location[] getAllLocations() {
SQLiteDatabase db = null;
Cursor c = null;
List<Location> all = new ArrayList<Location>();
try {
db = new LocationOpenHelper(context).getReadableDatabase();
c = db.query(LocationOpenHelper.LOCATION_TABLE_NAME, null, null, null, null, null, null);
while (c.moveToNext()) {
all.add(hydrate(c));
}
} finally {
if (c != null) {
c.close();
}
if (db != null) {
db.close();
}
}
return all.toArray(new Location[all.size()]);
}
public boolean persistLocation(Location location) {
SQLiteDatabase db = new LocationOpenHelper(context).getWritableDatabase();
db.beginTransaction();
ContentValues values = getContentValues(location);
long rowId = db.insert(LocationOpenHelper.LOCATION_TABLE_NAME, null, values);
Log.d(TAG, "After insert, rowId = " + rowId);
db.setTransactionSuccessful();
db.endTransaction();
db.close();
if (rowId > -1) {
location.setId(rowId);
return true;
} else {
return false;
}
}
public void deleteLocation(Location location) {
SQLiteDatabase db = new LocationOpenHelper(context).getWritableDatabase();
db.beginTransaction();
db.delete(LocationOpenHelper.LOCATION_TABLE_NAME, "id = ?", new String[]{location.getId().toString()});
db.setTransactionSuccessful();
db.endTransaction();
db.close();
}
private Location hydrate(Cursor c) {
Location l = new Location();
l.setId(c.getLong(c.getColumnIndex("id")));
l.setRecordedAt(stringToDate(c.getString(c.getColumnIndex("recordedAt"))));
l.setLatitude(c.getString(c.getColumnIndex("latitude")));
l.setLongitude(c.getString(c.getColumnIndex("longitude")));
return l;
}
private ContentValues getContentValues(Location location) {
ContentValues values = new ContentValues();
values.put("latitude", location.getLatitude());
values.put("longitude", location.getLongitude());
values.put("recordedAt", dateToString(location.getRecordedAt()));
return values;
}
public Date stringToDate(String dateTime) {
SimpleDateFormat iso8601Format = new SimpleDateFormat(DATE_FORMAT);
Date date = null;
try {
date = iso8601Format.parse(dateTime);
} catch (ParseException e) {
Log.e("DBUtil", "Parsing ISO8601 datetime ("+ dateTime +") failed", e);
}
return date;
}
public String dateToString(Date date) {
SimpleDateFormat iso8601Format = new SimpleDateFormat(DATE_FORMAT);
return iso8601Format.format(date);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment