Add gzip compresion and emergency contact

The ride files will now be .json.gzip
Users will also now be able to add an emergency contact phone number.
Ride logger will use that number to update the contact on the riders current
location.  It will also notify them if the acceleration is above 15 or m/s^2.
If so the emergency contact will get an sms with the current location.

TODO:  15m/s^2 is easy to trip.  I need to research a way to minimize false positives.
This commit is contained in:
Chet Henry
2014-11-22 17:16:55 -07:00
parent 6ef21005d6
commit f37760370a
6 changed files with 338 additions and 74 deletions

View File

@@ -4,12 +4,10 @@
package="com.ridelogger"
android:versionCode="030100"
android:versionName="3.1.0" >
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="20" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"

View File

@@ -0,0 +1,15 @@
package com.ridelogger;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
public class GzipWriter extends GZIPOutputStream {
public GzipWriter(OutputStream os) throws IOException {
super(os);
}
public void write(String data) throws IOException {
write(data.getBytes());
}
}

View File

@@ -1,9 +1,10 @@
package com.ridelogger;
import java.io.BufferedWriter;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
@@ -13,6 +14,8 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;
import com.dsi.ant.plugins.antplus.pcc.defines.DeviceType;
import com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult;
@@ -35,6 +38,8 @@ import android.content.SharedPreferences;
import android.os.Environment;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.telephony.SmsManager;
/**
* RideService
@@ -43,7 +48,7 @@ import android.support.v4.app.NotificationCompat;
*/
public class RideService extends Service
{
public static BufferedWriter buf; //writes to log file buffered
public static GzipWriter buf; //writes to log file buffered
public static long startTime; //start time of the ride
public static Map<String, String> currentValues; //hash of current values
public boolean rideStarted = false; //have we started logging the ride
@@ -60,6 +65,11 @@ public class RideService extends Service
SharedPreferences settings; //Object to load our setting from android's storage
public Boolean snoop = false; //should we log others ant+ devices
Set<String> pairedAnts; //list of ant devices to pair with
private TimerTask smsHomeTimerTask; //timer task to send periodic messages to emergency contact
public boolean phoneHome = false; //if we should send the messages or not
private Timer timer; //timer class to control the periodic messages
public boolean detectCrash = false; //should we try to detect crashes and message emergency contact
public String emergencyNumbuer; //the number to send the messages to
/**
* starts the ride on service start
@@ -104,7 +114,7 @@ public class RideService extends Service
if(rideStarted) return;
startTime = System.currentTimeMillis();
fileName = "ride-" + startTime + ".json";
fileName = "ride-" + startTime + ".json.gzip";
currentValues = new HashMap<String, String>();
SimpleDateFormat f = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
@@ -115,12 +125,14 @@ public class RideService extends Service
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(startTime);
String month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.US);
String week_day = cal.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.US);
String year = Integer.toString(cal.get(Calendar.YEAR));
settings = getSharedPreferences(StartActivity.PREFS_NAME, 0);
String rider_name = settings.getString(StartActivity.RIDER_NAME, "");
final Set<String> pairedAnts = settings.getStringSet(StartActivity.PAIRED_ANTS, null);
String month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.US);
String weekDay = cal.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.US);
String year = Integer.toString(cal.get(Calendar.YEAR));
settings = getSharedPreferences(StartActivity.PREFS_NAME, 0);
emergencyNumbuer = settings.getString(StartActivity.EMERGENCY_NUMBER, "");
detectCrash = settings.getBoolean(StartActivity.DETECT_CRASH, false);
phoneHome = settings.getBoolean(StartActivity.PHONE_HOME, false);
pairedAnts = settings.getStringSet(StartActivity.PAIRED_ANTS, null);
currentValues.put("SECS", "0.0");
@@ -131,7 +143,7 @@ public class RideService extends Service
"\"DEVICETYPE\":\"Android\"," +
"\"IDENTIFIER\":\"\"," +
"\"TAGS\":{" +
"\"Athlete\":\"" + rider_name + "\"," +
"\"Athlete\":\"" + settings.getString(StartActivity.RIDER_NAME, "") + "\"," +
"\"Calendar Text\":\"Auto Recored Android Ride\"," +
"\"Change History\":\"\"," +
"\"Data\":\"\"," +
@@ -143,7 +155,7 @@ public class RideService extends Service
"\"Notes\":\"\"," +
"\"Objective\":\"\"," +
"\"Sport\":\"Bike\"," +
"\"Weekday\":\"" + week_day + "\"," +
"\"Weekday\":\"" + weekDay + "\"," +
"\"Workout Code\":\"\"," +
"\"Year\":\"" + year + "\"" +
"}," +
@@ -160,46 +172,63 @@ public class RideService extends Service
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOCUMENTS
) + "/Rides",
"ride-" + startTime + ".json"
fileName
);
try {
dir.mkdirs();
file.createNewFile();
buf = new BufferedWriter(new FileWriter(file, true));
OutputStream bufWriter = new BufferedOutputStream(new FileOutputStream(file));
buf = new GzipWriter(bufWriter);
buf.write(rideHeadder);
sensors.put("GPS", new Gps(this));
sensors.put("AndroidSensors", new Sensors(this));
mCallback = new MultiDeviceSearch.SearchCallbacks(){
public void onDeviceFound(final MultiDeviceSearchResult deviceFound)
{
if (!deviceFound.isAlreadyConnected()) {
if(pairedAnts == null || pairedAnts.contains(Integer.toString(deviceFound.getAntDeviceNumber()))) {
launchConnection(deviceFound, false);
} else if (snoop) {
launchConnection(deviceFound, true);
if(!pairedAnts.isEmpty()){
mCallback = new MultiDeviceSearch.SearchCallbacks(){
public void onDeviceFound(final MultiDeviceSearchResult deviceFound)
{
if (!deviceFound.isAlreadyConnected()) {
if(pairedAnts == null || pairedAnts.contains(Integer.toString(deviceFound.getAntDeviceNumber()))) {
launchConnection(deviceFound, false);
} else if (snoop) {
launchConnection(deviceFound, true);
}
}
}
}
@Override
public void onSearchStopped(RequestAccessResult arg0) {}
};
mRssiCallback = new MultiDeviceSearch.RssiCallback() {
@Override
public void onRssiUpdate(final int resultId, final int rssi){}
};
// start the multi-device search
mSearch = new MultiDeviceSearch(this, EnumSet.allOf(DeviceType.class), mCallback, mRssiCallback);
@Override
public void onSearchStopped(RequestAccessResult arg0) {}
};
mRssiCallback = new MultiDeviceSearch.RssiCallback() {
@Override
public void onRssiUpdate(final int resultId, final int rssi){}
};
// start the multi-device search
mSearch = new MultiDeviceSearch(this, EnumSet.allOf(DeviceType.class), mCallback, mRssiCallback);
}
} catch (IOException e) {}
}
rideStarted = true;
if(phoneHome) {
timer = new Timer();
smsHomeTimerTask = new TimerTask() {
@Override
public void run() {
phoneHome();
}
};
timer.scheduleAtFixedRate(smsHomeTimerTask, 600000, 600000); //every ten min let them know where you are at
phoneStart();
}
//build the notification in the top android drawer
NotificationCompat.Builder mBuilder = new NotificationCompat
.Builder(this)
@@ -217,7 +246,55 @@ public class RideService extends Service
startForeground(notifyID, mBuilder.build());
}
/**
* let a love one know where you are at about every 10 min
*/
public void phoneCrash(double mag) {
String body = "CRASH DETECTED!\n";
if(currentValues.containsKey("LAT") && currentValues.containsKey("LON")) {
body = body + "https://www.google.com/maps/@" + currentValues.get("LAT") + "," + currentValues.get("LON") + ",10z";
} else {
body = body + "Unknow location.";
}
body = body + "\n Mag: " + String.valueOf(mag);
smsHome(body);
}
/**
* let a love one know where you are at about every 10 min
*/
public void phoneStart() {
String body = "I'm starting my ride";
if(currentValues.containsKey("LAT") && currentValues.containsKey("LON")) {
body = body + ":\n https://www.google.com/maps/@" + currentValues.get("LAT") + "," + currentValues.get("LON") + ",10z";
} else {
body = body + ".";
}
smsHome(body);
}
/**
* let a love one know where you are at about every 10 min
*/
public void phoneHome() {
String body = "I'm here:\n https://www.google.com/maps/@" + currentValues.get("LAT") + "," + currentValues.get("LON") + ",10z";
smsHome(body);
}
/**
* send a sms message
*/
public void smsHome(String body) {
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(emergencyNumbuer, null, body, null, null);
}
//stop the ride and clean up resources
protected void stopRide() {
@@ -228,7 +305,14 @@ public class RideService extends Service
}
//stop the Ant+ search
mSearch.close();
if(mSearch != null) {
mSearch.close();
}
//stop the phoneHome timer if we need to.
if(timer != null) {
timer.cancel();
}
try {
buf.write("]}}");

View File

@@ -14,20 +14,26 @@ import android.app.ActivityManager.RunningServiceInfo;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.text.InputType;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
public class StartActivity extends FragmentActivity
{
Intent rsi;
public static final String PREFS_NAME = "RideLogger";
public static final String RIDER_NAME = "RiderName";
public static final String PAIRED_ANTS = "PairedAnts";
public static final String PREFS_NAME = "RideLogger";
public static final String RIDER_NAME = "RiderName";
public static final String EMERGENCY_NUMBER = "EmergencyNumbuer";
public static final String DETECT_CRASH = "DetectCrash";
public static final String PHONE_HOME = "PhoneHome";
public static final String PAIRED_ANTS = "PairedAnts";
SharedPreferences settings;
MultiDeviceSearch mSearch;
@@ -42,35 +48,58 @@ public class StartActivity extends FragmentActivity
settings = getSharedPreferences(PREFS_NAME, 0);
final String riderName = settings.getString(RIDER_NAME, "");
if(riderName == "") {
// 1. Instantiate an AlertDialog.Builder with its constructor
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// 2. Chain together various setter methods to set the dialog characteristics
builder.setMessage("What is your Golder Cheata Rider Name")
.setTitle("Chose Rider Name");
// Set up the input
final EditText riderNameInput = new EditText(this);
// Specify the type of input expected; this, for example, sets the input as a password, and will mask the text
riderNameInput.setInputType(InputType.TYPE_CLASS_TEXT);
builder.setView(riderNameInput);
builder.setPositiveButton("Set", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
String riderName = riderNameInput.getText().toString();
if(riderName != "" && riderName != null) {
SharedPreferences.Editor editor = settings.edit();
editor.putString(RIDER_NAME, riderName);
editor.commit();
setupAnt();
}
final Runnable setupAntRunnable = new Runnable() {
@Override
public void run() {
setupAnt();
}
});
// 3. Get the AlertDialog from create()
AlertDialog dialog = builder.create();
dialog.show();
};
final Runnable setupPhoneHomeRunnable = new Runnable() {
@Override
public void run() {
promptDialogCheckBox(
"Update Emergency Contact",
"Should a text messesage be sent on ride start and every 10 min. to your emergency contact?",
PHONE_HOME,
setupAntRunnable
);
}
};
final Runnable setupDetectCrashRunnable = new Runnable() {
@Override
public void run() {
promptDialogCheckBox(
"Crash Detection",
"Should a text messesage be sent on crash detction to your emergency contact?",
DETECT_CRASH,
setupPhoneHomeRunnable
);
}
};
Runnable setupEmergencyContactRunnable = new Runnable() {
@Override
public void run() {
promptDialogInput(
"Emergency Contact",
"Emergency phone number to update position on crash detection",
EMERGENCY_NUMBER,
setupDetectCrashRunnable
);
}
};
promptDialogInput(
"Chose Rider Name",
"What is your Golder Cheata Rider Name",
RIDER_NAME,
setupEmergencyContactRunnable
);
} else {
toggleRide();
finish();
@@ -78,6 +107,132 @@ public class StartActivity extends FragmentActivity
}
/**
* TextBox Dialog Prompt
* @param title
* @param message
* @param settingSaveKey
* @param callBack
*/
public void promptDialogInput(String title, String message, final String settingSaveKey, final Runnable callBack) {
// Set up the input
final EditText input = new EditText(this);
// Specify the type of input expected; this, for example, sets the input as a password, and will mask the text
input.setInputType(InputType.TYPE_CLASS_TEXT);
OnClickListener positiveClick = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
String value = input.getText().toString();
if(value != "" && value != null) {
SharedPreferences.Editor editor = settings.edit();
editor.putString(settingSaveKey, value).commit();
callBack.run();
}
}
};
promptDialog(title, message, input, positiveClick);
}
/**
* TextBox Dialog Prompt
* @param title
* @param message
* @param settingSaveKey
* @param callBack
*/
public void promptDialogPhone(String title, String message, final String settingSaveKey, final Runnable callBack) {
// Set up the input
final EditText input = new EditText(this);
// Specify the type of input expected; this, for example, sets the input as a password, and will mask the text
input.setInputType(InputType.TYPE_CLASS_PHONE);
OnClickListener positiveClick = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
String value = input.getText().toString();
if(value != "" && value != null) {
SharedPreferences.Editor editor = settings.edit();
editor.putString(settingSaveKey, value).commit();
callBack.run();
}
}
};
promptDialog(title, message, input, positiveClick);
}
/**
* Checkbox dialog prompt
* @param title
* @param message
* @param settingSaveKey
* @param callBack
*/
public void promptDialogCheckBox(String title, String message, final String settingSaveKey, final Runnable callBack) {
final CheckBox input = new CheckBox(this);
OnClickListener positiveClick = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
if(input.isChecked()) {
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean(settingSaveKey, true).commit();
callBack.run();
}
}
};
promptDialog(title, message, input, positiveClick);
}
/**
* very general dialog prompt without positiveLabel
* @param title
* @param message
* @param view
* @param positiveClick
*/
public void promptDialog(
String title,
String message,
View view,
OnClickListener positiveClick
) {
promptDialog(title, message, view, positiveClick, null);
}
/**
* very general dialog prompt
* @param title
* @param message
* @param view
* @param positiveClick
* @param positiveLabel
*/
public void promptDialog(
String title,
String message,
View view,
OnClickListener positiveClick,
String positiveLabel
) {
if(positiveLabel == null) positiveLabel = "Next";
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// 2. Chain together various setter methods to set the dialog characteristics
AlertDialog dialog = builder
.setMessage(title)
.setTitle(message)
.setView(view)
.setPositiveButton(positiveLabel, positiveClick)
.create();
// 3. Get the AlertDialog from create()
dialog.show();
}
/**
* try to pair some ant+ devices
*/
@@ -85,6 +240,7 @@ public class StartActivity extends FragmentActivity
MultiDeviceSearch.SearchCallbacks mCallback;
MultiDeviceSearch.RssiCallback mRssiCallback;
final ArrayList<MultiDeviceSearchResult> foundDevices = new ArrayList<MultiDeviceSearchResult>();
selectDevicesDialog(foundDevices);
mCallback = new MultiDeviceSearch.SearchCallbacks(){
public void onDeviceFound(final MultiDeviceSearchResult deviceFound)
{

View File

@@ -1,8 +1,8 @@
package com.ridelogger.listners;
import com.ridelogger.GzipWriter;
import com.ridelogger.RideService;
import java.io.BufferedWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Iterator;
@@ -16,7 +16,7 @@ import java.util.Map.Entry;
*/
public class Base<T>
{
public static BufferedWriter buf;
public static GzipWriter buf;
public static long startTime;
public static Map<String, String> currentValues;

View File

@@ -18,8 +18,8 @@ import android.hardware.SensorManager;
*/
public class Sensors extends Base<Object>
{
private SensorManager mSensorManager;
public static final double CRASHMAGNITUDE = 15.0;
private SensorManager mSensorManager;
private Sensor mLight;
private Sensor mAccel;
private Sensor mPress;
@@ -71,6 +71,17 @@ public class Sensors extends Base<Object>
map.put("ms2y", reduceNumberToString(event.values[1]));
map.put("ms2z", reduceNumberToString(event.values[2]));
alterCurrentData(map);
if(context.detectCrash) {
float ax = event.values[0];
float ay = event.values[1];
float az = event.values[2];
double amag = Math.sqrt(ax*ax+ay*ay+az*az);
if(amag > CRASHMAGNITUDE) {
context.phoneCrash(amag);
}
}
}
};