TRACKING APP: Refactoring of data model, implementation of discussed sending algorithm

This commit is contained in:
Lukas Zielinski committed 2014-11-27 18:41:30 +01:00
1 parent a6b1c4326d
commit fc04aac862
7 files changed
+283 -220

No files matched your search

@@ -34,7 +34,6 @@ public class AnalyticsContract {
String GPS_ALTITUDE = "gps_altitude";
String GPS_BEARING = "gps_bearing";
String GPS_DEVICE = "gps_device";
String GPS_ELAPSED_REALTIME = "gps_elapsedrealtime";
String GPS_LATITUDE = "gps_latitude";
String GPS_LONGITUDE = "gps_longitude";
String GPS_PROVIDER = "gps_provider";
@@ -32,7 +32,7 @@ public class AnalyticsDatabase extends SQLiteOpenHelper {
String EVENTS_JOIN_LEADERBOARDS_JOIN_COMPETITORS = Tables.LEADERBOARDS +
" INNER JOIN " + Tables.EVENTS + " ON (" + Tables.LEADERBOARDS + "." + BaseColumns._ID + " = " + Tables.EVENTS + ".leaderboard_id ) " +
" INNER JOIN " + Tables.COMPETITORS + " ON (" + Tables.LEADERBOARDS + "."+ BaseColumns._ID + " = " + Tables.COMPETITORS + ".leaderboard_id ) ";
String GPS_FIXES_JOIN_EVENTS = Tables.EVENTS + " INNER JOIN " + Tables.SENSOR_GPS;
String GPS_FIXES_JOIN_EVENTS = "sensor_gps LEFT JOIN events ON sensor_gps.event_id = events._id ";
}
public AnalyticsDatabase(Context context) {
@@ -71,17 +71,16 @@ public class AnalyticsDatabase extends SQLiteOpenHelper {
db.execSQL("CREATE TABLE " + Tables.SENSOR_GPS + " ( "
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ SensorGpsColumns.GPS_ACCURACY + " TEXT, "
+ SensorGpsColumns.GPS_ALTITUDE + " TEXT, "
+ SensorGpsColumns.GPS_BEARING + " TEXT, "
+ SensorGpsColumns.GPS_ACCURACY + " DOUBLE, "
+ SensorGpsColumns.GPS_ALTITUDE + " DOUBLE, "
+ SensorGpsColumns.GPS_BEARING + " DOUBLE, "
+ SensorGpsColumns.GPS_DEVICE + " TEXT, "
+ SensorGpsColumns.GPS_ELAPSED_REALTIME + " TEXT, "
+ SensorGpsColumns.GPS_LATITUDE + " TEXT, "
+ SensorGpsColumns.GPS_LONGITUDE + " TEXT, "
+ SensorGpsColumns.GPS_LATITUDE + " DOUBLE, "
+ SensorGpsColumns.GPS_LONGITUDE + " DOUBLE, "
+ SensorGpsColumns.GPS_PROVIDER + " TEXT, "
+ SensorGpsColumns.GPS_SPEED + " TEXT, "
+ SensorGpsColumns.GPS_SPEED + " DOUBLE, "
+ SensorGpsColumns.GPS_SYNCED + " INTEGER DEFAULT 0, "
+ SensorGpsColumns.GPS_TIME + " TEXT, "
+ SensorGpsColumns.GPS_TIME + " INTEGER, "
+ SensorGpsColumns.GPS_EVENT_FK + " INTEGER, "
+ "FOREIGN KEY (" + SensorGpsColumns.GPS_EVENT_FK + ") "
+ "REFERENCES " + Tables.EVENTS + "( " + EventColumns.EVENT_ID + " ))");
@@ -37,156 +37,151 @@ import com.sap.sailing.android.tracking.app.utils.AppPreferences;
import com.sap.sailing.android.tracking.app.utils.UniqueDeviceUuid;
import com.sap.sailing.server.gateway.deserialization.impl.FlatSmartphoneUuidAndGPSFixMovingJsonDeserializer;
public class TrackingService extends Service implements ConnectionCallbacks, OnConnectionFailedListener,
LocationListener {
public class TrackingService extends Service implements ConnectionCallbacks,
OnConnectionFailedListener, LocationListener {
private LocationClient locationClient;
private LocationRequest locationRequest;
private NotificationManager notificationManager;
private boolean locationUpdateRequested = false;
private AppPreferences prefs;
private ScheduledExecutorService scheduler;
private GPSQualityListener gpsQualityListener;
private final IBinder trackingBinder = new TrackingBinder();
private LocationClient locationClient;
private LocationRequest locationRequest;
private NotificationManager notificationManager;
private boolean locationUpdateRequested = false;
private AppPreferences prefs;
private ScheduledExecutorService scheduler;
private static final String TAG = TrackingService.class.getName();
private GPSQualityListener gpsQualityListener;
private final IBinder trackingBinder = new TrackingBinder();
public static final String WEB_SERVICE_PATH = "/sailingserver/api/v1/gps_fixes";
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION_ID = R.string.tracker_started;
private int eventId;
@Override
public void onCreate() {
super.onCreate();
prefs = new AppPreferences(this);
private static final String TAG = TrackingService.class.getName();
// http://developer.android.com/training/location/receive-location-updates.html
locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(prefs.getGPSFixInterval());
locationRequest.setFastestInterval(prefs.getGPSFixFastestInterval());
public static final String WEB_SERVICE_PATH = "/sailingserver/api/v1/gps_fixes";
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION_ID = R.string.tracker_started;
locationClient = new LocationClient(this, this, this);
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
private int eventId;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
@Override
public void onCreate() {
super.onCreate();
prefs = new AppPreferences(this);
if (intent != null) {
// http://developer.android.com/training/location/receive-location-updates.html
locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(prefs.getGPSFixInterval());
locationRequest.setFastestInterval(prefs.getGPSFixFastestInterval());
locationClient = new LocationClient(this, this, this);
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
if (BuildConfig.DEBUG) {
ExLog.i(this, TAG, "Starting Tracking Service with eventId: " + eventId);
}
if (intent.getAction() != null) {
if (intent.getAction().equals(getString(R.string.tracking_service_stop))) {
stopTracking();
} else {
if (intent.getExtras() != null)
{
eventId = intent.getExtras().getInt(getString(R.string.tracking_service_event_id_parameter));
startTracking();
}
}
} else {
stopTracking();
}
}
return Service.START_STICKY;
}
ExLog.i(this, TAG, "Starting Tracking Service with eventId: "
+ eventId);
}
public void startTracking() {
locationClient.connect();
locationUpdateRequested = true;
if (intent.getAction() != null) {
if (intent.getAction().equals(
getString(R.string.tracking_service_stop))) {
stopTracking();
} else {
if (intent.getExtras() != null) {
eventId = intent
.getExtras()
.getInt(getString(R.string.tracking_service_event_id_parameter));
startTracking();
}
}
} else {
stopTracking();
}
}
return Service.START_STICKY;
}
ExLog.i(this, TAG, "Started Tracking");
showNotification();
}
public void startTracking() {
locationClient.connect();
locationUpdateRequested = true;
public void stopTracking() {
if (locationClient.isConnected()) {
locationClient.removeLocationUpdates(this);
}
locationClient.disconnect();
locationUpdateRequested = false;
if (scheduler != null) {
scheduler.shutdown();
}
stopSelf();
ExLog.i(this, TAG, "Stopped Tracking");
}
ExLog.i(this, TAG, "Started Tracking");
showNotification();
}
@Override
public void onConnectionFailed(ConnectionResult arg0) {
ExLog.e(this, TAG, "Failed to connect to Google Play Services for location updates");
}
public void stopTracking() {
if (locationClient.isConnected()) {
locationClient.removeLocationUpdates(this);
}
locationClient.disconnect();
locationUpdateRequested = false;
if (scheduler != null) {
scheduler.shutdown();
}
stopSelf();
ExLog.i(this, TAG, "Stopped Tracking");
}
@Override
public void onConnected(Bundle arg0) {
if (locationUpdateRequested) {
locationClient.requestLocationUpdates(locationRequest, this);
}
}
@Override
public void onConnectionFailed(ConnectionResult arg0) {
ExLog.e(this, TAG,
"Failed to connect to Google Play Services for location updates");
}
@Override
public void onDisconnected() {
ExLog.i(this, TAG, "LocationClient was disconnected");
}
public void reportGPSQuality( float gpsAccurracy )
{
GPSQuality quality = GPSQuality.noSignal;
if (gpsQualityListener != null)
{
if (gpsAccurracy > 163)
{
quality = GPSQuality.poor;
}
else if (gpsAccurracy > 48)
{
quality = GPSQuality.good;
}
else if (gpsAccurracy < 48)
{
quality = GPSQuality.great;
}
gpsQualityListener.gpsQualityUpdated(quality);
}
}
@Override
public void onConnected(Bundle arg0) {
if (locationUpdateRequested) {
locationClient.requestLocationUpdates(locationRequest, this);
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void onLocationChanged(Location location) {
reportGPSQuality(location.getAccuracy());
ContentResolver cr = getContentResolver();
ContentValues cv = new ContentValues();
cv.put(SensorGps.GPS_LATITUDE, location.getLatitude());
cv.put(SensorGps.GPS_LONGITUDE, location.getLongitude());
cv.put(SensorGps.GPS_PROVIDER, location.getProvider());
cv.put(SensorGps.GPS_SPEED, location.getSpeed());
cv.put(SensorGps.GPS_TIME, location.getTime());
cv.put(SensorGps.GPS_EVENT_FK, eventId);
@Override
public void onDisconnected() {
ExLog.i(this, TAG, "LocationClient was disconnected");
}
cr.insert(SensorGps.CONTENT_URI, cv);
ensureTransmittingServiceIsRunning();
}
/**
* start transmitting service when a new fix arrives, because it ends itself,
* if there's no data to send.
*/
private void ensureTransmittingServiceIsRunning()
{
public void reportGPSQuality(float gpsAccurracy) {
GPSQuality quality = GPSQuality.noSignal;
if (gpsQualityListener != null) {
if (gpsAccurracy > 163) {
quality = GPSQuality.poor;
} else if (gpsAccurracy > 48) {
quality = GPSQuality.good;
} else if (gpsAccurracy < 48) {
quality = GPSQuality.great;
}
gpsQualityListener.gpsQualityUpdated(quality);
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void onLocationChanged(Location location) {
reportGPSQuality(location.getAccuracy());
ContentResolver cr = getContentResolver();
ContentValues cv = new ContentValues();
cv.put(SensorGps.GPS_LATITUDE, location.getLatitude());
cv.put(SensorGps.GPS_LONGITUDE, location.getLongitude());
cv.put(SensorGps.GPS_PROVIDER, location.getProvider());
cv.put(SensorGps.GPS_SPEED, location.getSpeed());
cv.put(SensorGps.GPS_TIME, location.getTime());
cv.put(SensorGps.GPS_EVENT_FK, eventId);
cr.insert(SensorGps.CONTENT_URI, cv);
ensureTransmittingServiceIsRunning();
}
/**
* start transmitting service when a new fix arrives, because it ends
* itself, if there's no data to send.
*/
private void ensureTransmittingServiceIsRunning() {
if (BuildConfig.DEBUG) {
ExLog.i(this, TAG,
"ensureTransmittingServiceIsRunning, starting TransmittingService");
@@ -195,69 +190,65 @@ public class TrackingService extends Service implements ConnectionCallbacks, OnC
Intent intent = new Intent(this, TransmittingService.class);
intent.setAction(getString(R.string.transmitting_service_start));
this.startService(intent);
}
}
@Override
public IBinder onBind(Intent intent) {
return trackingBinder;
}
@Override
public IBinder onBind(Intent intent) {
return trackingBinder;
}
@Override
public void onDestroy() {
stopTracking();
notificationManager.cancel(NOTIFICATION_ID);
Toast.makeText(this, R.string.tracker_stopped, Toast.LENGTH_SHORT).show();
}
@Override
public void onDestroy() {
stopTracking();
notificationManager.cancel(NOTIFICATION_ID);
Toast.makeText(this, R.string.tracker_stopped, Toast.LENGTH_SHORT)
.show();
}
private void showNotification() {
Intent intent = new Intent(this, RegattaActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
CharSequence text = getText(R.string.tracker_started);
Notification notification = new NotificationCompat.Builder(this).setContentTitle(getText(R.string.app_name))
.setContentText(text).setContentIntent(pi).setSmallIcon(R.drawable.icon).build();
notification.flags |= Notification.FLAG_NO_CLEAR;
startForeground(NOTIFICATION_ID, notification);
}
public void registerGPSQualityListener( GPSQualityListener listener )
{
private void showNotification() {
Intent intent = new Intent(this, RegattaActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
CharSequence text = getText(R.string.tracker_started);
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle(getText(R.string.app_name))
.setContentText(text).setContentIntent(pi)
.setSmallIcon(R.drawable.icon).build();
notification.flags |= Notification.FLAG_NO_CLEAR;
startForeground(NOTIFICATION_ID, notification);
}
public void registerGPSQualityListener(GPSQualityListener listener) {
gpsQualityListener = listener;
}
public void unregisterGPSQualityListener()
{
public void unregisterGPSQualityListener() {
gpsQualityListener = null;
}
public class TrackingBinder extends Binder {
public TrackingService getService() {
return TrackingService.this;
}
}
public enum GPSQuality {
noSignal (0),
poor (2),
good (3),
great (4);
noSignal(0), poor(2), good(3), great(4);
private final int gpsQuality;
GPSQuality(int quality)
{
GPSQuality(int quality) {
this.gpsQuality = quality;
}
public int toInt()
{
public int toInt() {
return this.gpsQuality;
}
}
public interface GPSQualityListener {
public void gpsQualityUpdated( GPSQuality quality );
public void gpsQualityUpdated(GPSQuality quality);
}
}
@@ -260,41 +260,61 @@ public class TransmittingService extends Service {
currentUpdateInterval = UPDATE_INTERVAL_DEFAULT;
}
sendFixesToAPI();
sendFixesToAPI(null);
}
private boolean getTrackingServiceIsCurrentlyTracking()
{
// TODO: IMPLEMENT THIS METHOD PROPERLY!!
return false;
}
private void sendFixesToAPI() {
private void sendFixesToAPI(List<String> failedHosts) {
// first, lets fetch all unsent fixes
List<GpsFix> fixes = getUnsentFixes();
List<GpsFix> fixes = getUnsentFixes(failedHosts);
// store ids
// store ids so we can delete the rows later
ArrayList<String> ids = new ArrayList<String>();
// create JSON
JSONArray jsonArray = new JSONArray();
String currentEventId = null;
String host = null;
for (GpsFix fix : fixes)
{
ids.add(String.valueOf(fix.id));
host = fix.host;
if (currentEventId == null)
{
currentEventId = fix.eventId;
host = fix.host;
}
if (currentEventId.equals(fix.eventId)) {
// still have more unsent Gps-Fixes for this event.
ids.add(String.valueOf(fix.id));
JSONObject json = new JSONObject();
try {
json.put("bearingDeg", fix.course);
json.put("timeMillis", fix.timestamp);
json.put("speedMperS", fix.speed);
json.put("lonDeg", fix.longitude);
json.put("deviceUuid", prefs.getDeviceIdentifier());
json.put("latDeg", fix.latitude);
} catch (JSONException ex) {
ExLog.i(this, TAG, "Error while building geolocation json "
+ ex.getMessage());
}
jsonArray.put(json);
} else {
// we don't have any more Gps-fixes for this batch, end
// collection, proceed with sending.
break;
}
JSONObject json = new JSONObject();
try {
json.put("bearingDeg", fix.course);
json.put("timeMillis", fix.timestamp);
json.put("speedMperS", fix.speed);
json.put("lonDeg", fix.longitude);
//json.put("deviceUuid", prefs.getDeviceIdentifier());
json.put("latDeg", fix.latitude);
} catch (JSONException ex) {
ExLog.i(this, TAG, "Error while building geolocation json " + ex.getMessage());
}
jsonArray.put(json);
}
if (jsonArray.length() > 0) {
// send
@@ -314,15 +334,14 @@ public class TransmittingService extends Service {
ExLog.i(this, TAG, "url: " + prefs.getServerURL() + prefs.getServerGpsFixesPostPath());
}
sendingAttempted = true;
if (host != null)
{
sendingAttempted = true;
VolleyHelper.getInstance(this).addRequest(
new JsonObjectRequest(host
+ prefs.getServerGpsFixesPostPath(), requestObject,
new FixSubmitListener(ids.toArray(idsArr)),
new FixSubmitErrorListener()));
new FixSubmitErrorListener(host, getTrackingServiceIsCurrentlyTracking(), failedHosts)));
}
else
{
@@ -416,18 +435,40 @@ public class TransmittingService extends Service {
// }
// }
private List<GpsFix> getUnsentFixes()
private List<GpsFix> getUnsentFixes(List<String> failedHosts)
{
String selectionClause = SensorGps.GPS_SYNCED + " = 0";
String projectionClauseStr = "events._id as _eid,sensor_gps.gps_time,sensor_gps.gps_latitude,"
+ "sensor_gps.gps_longitude,sensor_gps.gps_speed,sensor_gps.gps_bearing,sensor_gps.gps_synced,"
+ "events.event_server,sensor_gps._id as _gid";
String[] projectionClause = projectionClauseStr.split(",");
String sortAndLimitClause = SensorGps.GPS_TIME + " DESC LIMIT " + UPDATE_BATCH_SIZE;
ArrayList<GpsFix> list = new ArrayList<GpsFix>();
if (failedHosts != null)
{
if (failedHosts.size() > 0)
{
StringBuffer buf = new StringBuffer();
buf.append("( ");
for (String failedHost : failedHosts) {
buf.append("\"" + failedHost + "\",");
}
// remove the last comma
buf.setLength(buf.length() - 1);
buf.append(" )");
selectionClause += " AND " + Event.EVENT_SERVER + " NOT IN " + buf.toString();
}
}
Cursor cur = getContentResolver().query(EventGpsFixesJoined.CONTENT_URI, null, selectionClause, null, sortAndLimitClause);
ArrayList<GpsFix> list = new ArrayList<GpsFix>();
Cursor cur = getContentResolver().query(EventGpsFixesJoined.CONTENT_URI, projectionClause, selectionClause, null, sortAndLimitClause);
while (cur.moveToNext()) {
GpsFix gpsFix = new GpsFix();
gpsFix.id = cur.getInt(cur.getColumnIndex(SensorGps._ID));
gpsFix.id = cur.getInt(cur.getColumnIndex("_gid"));
gpsFix.timestamp = cur.getLong(cur.getColumnIndex(SensorGps.GPS_TIME));
gpsFix.latitude = cur.getDouble(cur.getColumnIndex(SensorGps.GPS_LATITUDE));
gpsFix.longitude = cur.getDouble(cur.getColumnIndex(SensorGps.GPS_LONGITUDE));
@@ -435,6 +476,7 @@ public class TransmittingService extends Service {
gpsFix.course = cur.getDouble(cur.getColumnIndex(SensorGps.GPS_BEARING));
gpsFix.synced = cur.getInt(cur.getColumnIndex(SensorGps.GPS_SYNCED));
gpsFix.host = cur.getString(cur.getColumnIndex(Event.EVENT_SERVER));
gpsFix.eventId = cur.getString(cur.getColumnIndex("_eid"));
list.add(gpsFix);
@@ -466,10 +508,32 @@ public class TransmittingService extends Service {
private class FixSubmitErrorListener implements ErrorListener {
private String host;
private List<String> failedHosts;
private boolean currentlyTracking = false;
public FixSubmitErrorListener(String host, boolean currentlyTracking, List<String> failedHosts) {
this.failedHosts = failedHosts;
this.host = host;
this.currentlyTracking = true;
}
@Override
public void onErrorResponse(VolleyError error) {
markFailedTransmission();
ExLog.e(TransmittingService.this, TAG, "Error while sending GPS fix " + error.getMessage());
if (!currentlyTracking)
{
if (failedHosts == null)
{
failedHosts = new ArrayList<String>();
failedHosts.add(host);
}
sendFixesToAPI(this.failedHosts);
ExLog.i(TransmittingService.this, TAG, "Retrying resend step with this failed hosts list: " + failedHosts);
}
}
}
@@ -483,12 +547,14 @@ public class TransmittingService extends Service {
public double course;
public int synced;
public String host;
public String eventId;
@Override
public String toString() {
return "ID: " + id + ", T: " + timestamp + ", LAT: " + latitude
+ ", LON: " + longitude + ", SPD: " + speed + ", CRS: "
+ course + ", SYN: " + synced + ", HOST: " + host;
+ course + ", SYN: " + synced + ", HOST: " + host
+ ", EVENT-ID: " + eventId;
}
}
@@ -29,9 +29,6 @@ public class BaseActivity extends ActionBarActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
prefs = new AppPreferences(this);
// TODO: Remove debug string
SqlDebugHelper.dumpAllTablesToConsole(this);
}
@Override
@@ -40,6 +37,14 @@ public class BaseActivity extends ActionBarActivity {
inflater.inflate(R.menu.options_menu, menu);
return true;
}
@Override
protected void onResume() {
super.onResume();
// TODO: Remove debug string
SqlDebugHelper.dumpAllTablesToConsole(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
@@ -27,7 +27,6 @@ public class AppPreferences {
public String getDeviceIdentifier() {
String DeviceIdentifier = PrefUtils.getString(context, R.string.preference_device_identifier_key,
R.string.preference_device_identifier_default);
System.out.println("Get DEVice Identifier: " + DeviceIdentifier);
return DeviceIdentifier;
}
@@ -9,6 +9,7 @@ import com.sap.sailing.android.shared.logging.ExLog;
import com.sap.sailing.android.tracking.app.provider.AnalyticsContract.Competitor;
import com.sap.sailing.android.tracking.app.provider.AnalyticsContract.Event;
import com.sap.sailing.android.tracking.app.provider.AnalyticsContract.Leaderboard;
import com.sap.sailing.android.tracking.app.provider.AnalyticsContract.SensorGps;
public class SqlDebugHelper {
@@ -71,6 +72,9 @@ public class SqlDebugHelper {
c3.close();
Cursor c4 = cr.query(SensorGps.CONTENT_URI, null, null, null, null);
ExLog.w(context, TAG, "--- NUMBER OF GPS FIXES IN DB: " + c4.getCount());
ExLog.w(context, TAG, "--- END OF SQL PRINTOUT" );
}
}