diff --git a/build.gradle b/build.gradle
index db0b2c0..369f7f5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,13 +1,13 @@
apply plugin: 'com.android.library'
android {
- compileSdkVersion 24
- buildToolsVersion "25.0.0"
+ compileSdkVersion project.appCompileSdkVersion
+ buildToolsVersion project.appBuildToolVersion
publishNonDefault true
defaultConfig {
minSdkVersion 15
- targetSdkVersion project.appTargetSdkVersion
+ targetSdkVersion 23
versionCode 1
versionName "1.0"
@@ -22,11 +22,18 @@ android {
}
}
+repositories {
+ maven { url 'https://maven.fabric.io/public' }
+}
+
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
- compile 'com.android.support:appcompat-v7:23.1.1'
+ compile "com.android.support:appcompat-v7:${project.supportLibVersion}"
testCompile 'junit:junit:4.12'
+ compile('com.crashlytics.sdk.android:crashlytics:2.5.5@aar') {
+ transitive = true;
+ }
}
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index c90e544..5fd4509 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -6,8 +6,8 @@
android:label="@string/app_name"
android:supportsRtl="true">
+ android:name=".SensorListener"
+ android:exported="false" />
diff --git a/src/main/java/com/sukesan1984/stepsensorlib/BootReceiver.java b/src/main/java/com/sukesan1984/stepsensorlib/BootReceiver.java
index 162d9fd..6669256 100644
--- a/src/main/java/com/sukesan1984/stepsensorlib/BootReceiver.java
+++ b/src/main/java/com/sukesan1984/stepsensorlib/BootReceiver.java
@@ -4,36 +4,9 @@
import android.content.Context;
import android.content.Intent;
-import com.sukesan1984.stepsensorlib.util.DateUtils;
-import com.sukesan1984.stepsensorlib.util.Logger;
-import com.sukesan1984.stepsensorlib.util.SensorListener;
-
-/**
- * Created by kosuketakami on 2016/11/05.
- */
-
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- Database db = Database.getInstance(context);
-
- if (!PreferenceManager.readCorrectShutDown(context, false)) {
- if (BuildConfig.DEBUG) {
- Logger.log("Incorrect shutdown");
- }
- int steps = db.getLastUpdatedSteps();
- if (BuildConfig.DEBUG) {
- Logger.log("Trying to recover " + steps + " steps");
- }
- db.updateOrInsert(DateUtils.getCurrentDateAndHour(), steps);
- }
- // last entry might still have a negative step value, so remove that
- // row if that's the case
- db.removeNegativeEntries();
- db.close();
-
- PreferenceManager.deleteCorrectShutDown(context);
- PreferenceManager.deleteStepsSinceBoot(context);
context.startService(new Intent(context, SensorListener.class));
}
}
diff --git a/src/main/java/com/sukesan1984/stepsensorlib/Database.java b/src/main/java/com/sukesan1984/stepsensorlib/Database.java
index f328a93..fb2ef5f 100644
--- a/src/main/java/com/sukesan1984/stepsensorlib/Database.java
+++ b/src/main/java/com/sukesan1984/stepsensorlib/Database.java
@@ -6,29 +6,22 @@
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
-
+import android.support.annotation.Nullable;
import com.sukesan1984.stepsensorlib.model.ChunkStepCount;
import com.sukesan1984.stepsensorlib.util.DateUtils;
import com.sukesan1984.stepsensorlib.util.Logger;
-
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-/**
- *
- */
-
-public class Database extends SQLiteOpenHelper {
+class Database extends SQLiteOpenHelper {
private final static String TABLE_NAME = "steps";
private final static String COLUMN_DATE_AND_HOUR = "date_and_hour";
private final static String COLUMN_STEPS = "steps";
private final static String COLUMN_IS_RECORDED_ON_SERVER = "is_recorded_on_server";
private final static String COLUMN_LAST_UPDATED = "last_updated";
- private final static int DB_VERSION = 1;
+ private final static int DB_VERSION = 2;
private static Database instance;
- private static final AtomicInteger openCounter = new AtomicInteger();
private Database(final Context context) {
super(context, TABLE_NAME, null, DB_VERSION);
@@ -38,21 +31,14 @@ public static synchronized Database getInstance(final Context c) {
if (instance == null) {
instance = new Database(c.getApplicationContext());
}
-
- openCounter.incrementAndGet();
-
return instance;
}
- @Override
- public void close() {
- if (openCounter.decrementAndGet() == 0) {
- super.close();
- }
- }
-
@Override
public void onCreate(SQLiteDatabase db) {
+ if (db == null) {
+ return;
+ }
db.execSQL("CREATE TABLE " + TABLE_NAME + " (" +
COLUMN_DATE_AND_HOUR + " INTEGER UNIQUE, " +
COLUMN_STEPS + " INTEGER, " +
@@ -64,254 +50,132 @@ public void onCreate(SQLiteDatabase db) {
* delete all
*/
public void deleteAll() {
- SQLiteDatabase db = getWritableDatabase();
- db.delete(TABLE_NAME, "", new String[]{});
- db.close();
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- // on upgrade
- }
-
- /**
- * Query the 'steps' table. Remember to close the cursor!
- *
- * @param columns the colums
- * @param selection the selection
- * @param selectionArgs the selction arguments
- * @param groupBy the group by statement
- * @param having the having statement
- * @param orderBy the order by statement
- * @return the cursor
- */
- public Cursor query(final String[] columns, final String selection,
- final String[] selectionArgs, final String groupBy, final String having,
- final String orderBy, final String limit) {
- return getReadableDatabase()
- .query(TABLE_NAME, columns, selection, selectionArgs, groupBy, having, orderBy, limit);
- }
-
- /**
- * @param dateAndHour the dateAndHour in ms since 1970
- * @param stepsSinceBoot the steps since boot
- */
- public void updateOrInsert(long dateAndHour, int stepsSinceBoot) {
- SQLiteDatabase db = getWritableDatabase();
- db.beginTransaction();
+ SQLiteDatabase db = null;
try {
- updateOrInsertWithoutTransaction(db, dateAndHour, stepsSinceBoot);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
+ db = getWritableDatabase();
+ db.delete(TABLE_NAME, "", new String[]{});
+ } catch (Exception e) {
+ e.printStackTrace();
}
}
- private void updateOrInsertWithoutTransaction(SQLiteDatabase db, long dateAndHour, int stepsSinceBoot) {
- Cursor c = getByDateAndHour(dateAndHour);
- initializeLastUpdatedSteps(db, stepsSinceBoot);
- int lastUpdatedSteps = getLastUpdatedSteps();
- if (lastUpdatedSteps > stepsSinceBoot) {
- saveLastUpdatedSteps(db, stepsSinceBoot);
- return;
- }
-
-
- int addedSteps = stepsSinceBoot - lastUpdatedSteps;
- if (c.getCount() == 0 && stepsSinceBoot >= 0) {
- insertNewDateAndHour(db, dateAndHour, addedSteps);
- } else {
- addToLastEntry(db, addedSteps);
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion < 2) {
+ db.delete("steps", "date_and_hour = ?", new String[]{"-1"});
}
- saveLastUpdatedSteps(db, stepsSinceBoot);
}
/**
- * replace steps count if steps is larger than current databace value.
+ * Add step count in table. If no row matching to targetDateAndHour is exist, it will be created.
*
- * @param dateAndHour
- * @param steps
+ * @param targetDateAndHour Key for table.
+ * @param stepsToAdd Count to be added.
+ * @return Total count after add, or negative value when failed.
+ * @throws IllegalArgumentException if stepsToAdd is negative value.
*/
- public void insertOrReplaceSteps(final long dateAndHour, final int steps) {
- SQLiteDatabase db = getWritableDatabase();
- Cursor c = getByDateAndHour(dateAndHour);
- db.beginTransaction();
+ public int addSteps(long targetDateAndHour, int stepsToAdd) {
+ if (stepsToAdd < 0) throw new IllegalArgumentException("stepsToAdd should not be negative value.");
+ SQLiteDatabase db = null;
try {
- if (c.getCount() == 0) {
- if (steps > 0) {
- insertNewDateAndHour(db, dateAndHour, steps);
- } else {
- return;
- }
- } else if (c.moveToFirst() && c.getInt(0) < steps) {
- Logger.log("#### update");
- Logger.log("#### data_and_hour: " + dateAndHour);
- Logger.log("#### steps: " + steps);
- ContentValues values = new ContentValues();
- values.put(COLUMN_DATE_AND_HOUR, dateAndHour);
- values.put(COLUMN_STEPS, steps);
- values.put(COLUMN_LAST_UPDATED, DateUtils.getCurrentTimeMllis());
- db.update(TABLE_NAME, values,
- COLUMN_DATE_AND_HOUR + " = ?",
- new String[]{String.valueOf(dateAndHour)});
- }
+ db = getWritableDatabase();
+ db.beginTransaction();
+ int currentSteps = getStepsImpl(db, targetDateAndHour);
+ int newSteps = currentSteps + stepsToAdd;
+ insertOrReplaceStepRow(db, targetDateAndHour, newSteps, false);
db.setTransactionSuccessful();
+ return newSteps;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return -1;
} finally {
- db.endTransaction();
+ if (db != null) {
+ db.endTransaction();
+ }
}
-
}
- /**
- * Inserts a new entry in the database, if there is no entry for the given
- * dateAndHour yet.
- *
- *
- * @param dateAndHour the dateAndHour in ms since 1970
- * @param steps the current step value
- */
-
- private void insertNewDateAndHour(SQLiteDatabase db, final long dateAndHour, final int steps) {
- Logger.log("insert new date and hour");
- Logger.log("date_and_hour: " + dateAndHour);
- Logger.log("steps: " + steps);
+ private void insertOrReplaceStepRow(SQLiteDatabase db, long dateAndHour, int steps, @Nullable Boolean markAsRecorded) {
ContentValues values = new ContentValues();
values.put(COLUMN_DATE_AND_HOUR, dateAndHour);
- // use the negative steps as offset
values.put(COLUMN_STEPS, steps);
values.put(COLUMN_LAST_UPDATED, DateUtils.getCurrentTimeMllis());
- db.insert(TABLE_NAME, null, values);
- if (BuildConfig.DEBUG) {
- Logger.log("insertDayAndHour" + dateAndHour + " / " + steps);
- logState();
+ if (markAsRecorded != null) {
+ values.put(COLUMN_IS_RECORDED_ON_SERVER, markAsRecorded ? 1 : 0);
}
+ db.replaceOrThrow(TABLE_NAME, null, values);
}
- private Cursor getByDateAndHour(long dateAndHour) {
- return getReadableDatabase().query(TABLE_NAME, new String[]{COLUMN_STEPS},
- COLUMN_DATE_AND_HOUR + " = ?",
- new String[]{String.valueOf(dateAndHour)}, null, null, null);
- }
-
- private void addToLastEntry(SQLiteDatabase db, int steps) {
- if (steps > 0) {
- db.execSQL("UPDATE " + TABLE_NAME + " SET " + COLUMN_STEPS + " = " + COLUMN_STEPS + " + " + steps
- + ", " + COLUMN_IS_RECORDED_ON_SERVER + " = 0, " + COLUMN_LAST_UPDATED + " =" + DateUtils.getCurrentTimeMllis() +
- " WHERE " + COLUMN_DATE_AND_HOUR + "= (SELECT MAX(" + COLUMN_DATE_AND_HOUR + ") FROM " + TABLE_NAME + ");");
- }
- }
-
- /**
- * Writes the current steps database to the log.
- */
- public void logState() {
- if (BuildConfig.DEBUG) {
- Cursor c = getReadableDatabase()
- .query(TABLE_NAME, null, null, null, null, null, COLUMN_DATE_AND_HOUR + " DESC", null);
- Logger.log(c);
- c.close();
- }
- }
-
- /**
- * @param dateAndHour
- * @return
- */
public int getSteps(final long dateAndHour) {
Logger.log("getStep dateAndHour" + dateAndHour);
- Cursor c = getByDateAndHour(dateAndHour);
-
- c.moveToFirst();
- int steps;
- if (c.getCount() == 0) {
- steps = Integer.MIN_VALUE;
- } else {
- steps = c.getInt(0);
- }
- c.close();
- return steps;
+ SQLiteDatabase db = getReadableDatabase();
+ return getStepsImpl(db, dateAndHour);
}
- public int getSteps(final long start, final long end) {
- Cursor c = getReadableDatabase()
- .query(TABLE_NAME, new String[]{"SUM(" + COLUMN_STEPS + ")"},
- COLUMN_DATE_AND_HOUR + " >= ? AND " +
- COLUMN_DATE_AND_HOUR + " <= ?",
- new String[]{String.valueOf(start), String.valueOf(end)}, null, null, null);
- int sumSteps;
- if (c.getCount() == 0) {
- sumSteps = 0;
- } else {
+ private int getStepsImpl(SQLiteDatabase db, long dateAndHour) {
+ Cursor c = db.query(TABLE_NAME, new String[]{COLUMN_STEPS},
+ COLUMN_DATE_AND_HOUR + " = ?",
+ new String[]{String.valueOf(dateAndHour)}, null, null, null);
+ try {
+ if (c == null) {
+ return 0;
+ }
c.moveToFirst();
- sumSteps = c.getInt(0);
+ if (c.getCount() == 0) {
+ return 0;
+ }
+ return c.getInt(0);
+ } finally {
+ closeCursor(c);
}
- c.close();
- return sumSteps;
- }
-
- public List getChunkStepsFrom(final long start) {
- Cursor c = getReadableDatabase()
- .query(TABLE_NAME, new String[]{COLUMN_DATE_AND_HOUR, COLUMN_STEPS},
- COLUMN_DATE_AND_HOUR + " >= ?", new String[]{String.valueOf(start)}, null, null, null);
- return createChunkedStepCounts(c);
- }
-
- /**
- * Removes all entries with negative values.
- *
- * Only call this directly after boot, otherwise it might remove the current
- * day as the current offset is likely to be negative.
- */
- void removeNegativeEntries() {
- getWritableDatabase().delete(TABLE_NAME, COLUMN_STEPS + " < ?", new String[]{"0"});
- }
-
- /**
- * Remove invalid entries from the database.
- * Currently, an invalid input is such with steps >= 200,000
- */
- public void removeInvalidEntries() {
- getWritableDatabase().delete(TABLE_NAME, COLUMN_STEPS + " >= ?", new String[]{"200000"});
}
- /**
- * @param stepsSinceBoot steps after boot.
- */
- public void resetLastUpdatedSteps(final int stepsSinceBoot) {
- SQLiteDatabase db = getWritableDatabase();
- db.beginTransaction();
+ public int getSteps(final long start, final long end) {
+ Cursor c = null;
try {
- updateOrInsertWithoutTransaction(db, DateUtils.getCurrentDateAndHour(), stepsSinceBoot);
- saveLastUpdatedSteps(db, 0);
- db.setTransactionSuccessful();
+ c = getReadableDatabase()
+ .query(TABLE_NAME, new String[]{"SUM(" + COLUMN_STEPS + ")"},
+ COLUMN_DATE_AND_HOUR + " >= ? AND " +
+ COLUMN_DATE_AND_HOUR + " <= ?",
+ new String[]{String.valueOf(start), String.valueOf(end)}, null, null, null);
+ if (c == null) {
+ return 0;
+ }
+ int sumSteps;
+ if (c.getCount() == 0) {
+ sumSteps = 0;
+ } else {
+ c.moveToFirst();
+ sumSteps = c.getInt(0);
+ }
+ return sumSteps;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return 0;
} finally {
- db.endTransaction();
+ closeCursor(c);
}
}
- private void initializeLastUpdatedSteps(SQLiteDatabase db, final int stepsSinceBoot) {
- // initialize if there is no date
- if (getSteps(-1) == Integer.MIN_VALUE) {
- saveLastUpdatedSteps(db, stepsSinceBoot);
- }
- }
+ @NonNull
+ public List getChunkStepsSince(final long start) {
+ Cursor c = null;
+ try {
+ c = getReadableDatabase()
+ .query(TABLE_NAME, new String[]{COLUMN_DATE_AND_HOUR, COLUMN_STEPS},
+ COLUMN_DATE_AND_HOUR + " >= ?", new String[]{String.valueOf(start)}, null, null, null);
- private void saveLastUpdatedSteps(SQLiteDatabase db, final int steps) {
- ContentValues values = new ContentValues();
- values.put(COLUMN_STEPS, steps);
- if (db.update(TABLE_NAME, values, COLUMN_DATE_AND_HOUR + " = -1", null) == 0) {
- values.put(COLUMN_DATE_AND_HOUR, -1);
- values.put(COLUMN_LAST_UPDATED, DateUtils.getCurrentTimeMllis());
- db.insert(TABLE_NAME, null, values);
- }
- if (BuildConfig.DEBUG) {
- Logger.log("saving steps in db: " + steps);
- }
- }
+ if (c == null) {
+ return new ArrayList<>();
+ }
- public int getLastUpdatedSteps() {
- int currentSteps = getSteps(-1);
- return currentSteps == Integer.MIN_VALUE ? 0 : currentSteps;
+ return createChunkedStepCounts(c);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return new ArrayList<>();
+ } finally {
+ closeCursor(c);
+ }
}
public int getTodayStep() {
@@ -320,16 +184,29 @@ public int getTodayStep() {
}
@NonNull
- public List getNotRecordedChunkedStepCounts() {
- Cursor c = getReadableDatabase()
- .query(TABLE_NAME, new String[]{COLUMN_DATE_AND_HOUR, COLUMN_STEPS},
- COLUMN_DATE_AND_HOUR + " != ? and " +
- COLUMN_IS_RECORDED_ON_SERVER + " = ?", new String[]{"-1", "0"}, null, null, null);
- Logger.log("Not recoreded Chunk Size: " + c.getCount());
- return createChunkedStepCounts(c);
+ public List getNotRecordedChunkStepCounts() {
+ Cursor c = null;
+ try {
+ c = getReadableDatabase()
+ .query(TABLE_NAME, new String[]{COLUMN_DATE_AND_HOUR, COLUMN_STEPS},
+ COLUMN_DATE_AND_HOUR + " != ? and " +
+ COLUMN_IS_RECORDED_ON_SERVER + " = ?", new String[]{"-1", "0"}, null, null, null);
+ Logger.log("Not recoreded Chunk Size: " + c.getCount());
+ // cursor close is in method
+ return createChunkedStepCounts(c);
+ } catch (Exception e) {
+ e.printStackTrace();
+ if (c != null) {
+ c.close();
+ }
+ return new ArrayList<>();
+ }
}
private List createChunkedStepCounts(Cursor c) {
+ if (c == null) {
+ return null;
+ }
List lists = new ArrayList<>();
if (c.getCount() == 0) {
@@ -338,37 +215,33 @@ private List createChunkedStepCounts(Cursor c) {
while (c.moveToNext()) {
lists.add(new ChunkStepCount(c.getLong(0), c.getInt(1)));
}
- c.close();
return lists;
}
- public void updateToRecorded(long[] dateAndHours) {
- ContentValues values = new ContentValues();
- values.put(COLUMN_IS_RECORDED_ON_SERVER, "1");
- int length = dateAndHours.length;
- String args = "";
- String[] dateAndHoursString = new String[length];
- for (int i = 0; i < length; i++) {
- if (i == 0) {
- args = "?";
- } else {
- args += ", ?";
- }
- dateAndHoursString[i] = String.valueOf(dateAndHours[i]);
- }
- SQLiteDatabase db = getWritableDatabase();
- db.beginTransaction();
+ public void increaseByServerChunkStepCounts(List chunkStepCounts) {
+ SQLiteDatabase db = null;
try {
- int rows = db.update(TABLE_NAME, values, String.format(COLUMN_DATE_AND_HOUR + " in (%s)", args), dateAndHoursString);
- Logger.log("updated number: " + rows);
- logState();
+ db = getWritableDatabase();
+ db.beginTransaction();
+ for (ChunkStepCount chunkStepCount : chunkStepCounts) {
+ increaseBySeverChunkStepCount(db, chunkStepCount);
+ }
db.setTransactionSuccessful();
} finally {
- db.endTransaction();
+ if (db != null) {
+ db.endTransaction();
+ }
+ }
+ }
+
+ private void increaseBySeverChunkStepCount(SQLiteDatabase db, ChunkStepCount chunkStepCount) {
+ int currentSteps = getStepsImpl(db, chunkStepCount.unixTimeMillis);
+ if (chunkStepCount.steps >= currentSteps) {
+ insertOrReplaceStepRow(db, chunkStepCount.unixTimeMillis, chunkStepCount.steps, true);
}
+ }
- Logger.log("update to Recorded: " + dateAndHoursString.toString());
- db.close();
- return;
+ private void closeCursor(@Nullable Cursor cursor) {
+ if (cursor != null) cursor.close();
}
}
diff --git a/src/main/java/com/sukesan1984/stepsensorlib/PreferenceManager.java b/src/main/java/com/sukesan1984/stepsensorlib/PreferenceManager.java
index a0d965e..b5c7b4f 100644
--- a/src/main/java/com/sukesan1984/stepsensorlib/PreferenceManager.java
+++ b/src/main/java/com/sukesan1984/stepsensorlib/PreferenceManager.java
@@ -4,11 +4,8 @@
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
-/**
- * Created by kosuketakami on 2017/01/16.
- */
-
-public class PreferenceManager {
+// TODO: use this for service restart
+class PreferenceManager {
private static String PEDOMETER = "pedometer";
private static String CORRECT_SHUTDOWN = "corretctShutdown";
private static String STEPS_SINCE_BOOT = "stepsSinceBoot";
diff --git a/src/main/java/com/sukesan1984/stepsensorlib/SensorListener.java b/src/main/java/com/sukesan1984/stepsensorlib/SensorListener.java
new file mode 100644
index 0000000..3e7cc12
--- /dev/null
+++ b/src/main/java/com/sukesan1984/stepsensorlib/SensorListener.java
@@ -0,0 +1,129 @@
+package com.sukesan1984.stepsensorlib;
+
+import android.annotation.TargetApi;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Build;
+import android.os.IBinder;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.sukesan1984.stepsensorlib.util.Logger;
+
+public class SensorListener extends Service implements SensorEventListener {
+
+ private final static int MICROSECONDS_IN_ONE_MINUTE = 60 * 1000 * 1000;
+ private final static String EXTRA_RESET_DATA = BuildConfig.APPLICATION_ID + ".ResetData";
+ private static final int MAX_DELAY_MINUTES = 1;
+
+ /**
+ * Start service, and write unsaved data to DB.
+ */
+ static Intent createIntent(@NonNull Context context) {
+ return new Intent(context, SensorListener.class);
+ }
+
+ public static Intent createIntentForReset(Context context) {
+ return new Intent(context, SensorListener.class).putExtra(EXTRA_RESET_DATA, true);
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // nobody knows what happens here: step value might magically decrease
+ // when this method is called...
+ Logger.log(sensor.getName() + " accuracy changed: " + accuracy);
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ int stepsSinceBoot = (int) event.values[0];
+ StepCountCoordinator.getInstance().onStepCounterEvent(this, stepsSinceBoot);
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
+ Logger.log("################################## onStartCommand called ####################################");
+ if (intent != null && intent.getBooleanExtra(EXTRA_RESET_DATA, false)) {
+ Logger.log("Deleting all data and stopping service.");
+ unregisterSensor();
+ StepCountCoordinator.getInstance().reset();
+ Database.getInstance(this).deleteAll();
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+
+ // restart service every minutes get the current step count
+ ((AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE))
+ .set(AlarmManager.RTC, System.currentTimeMillis() + MAX_DELAY_MINUTES * 60 * 1000,
+ PendingIntent.getService(this, 2, createIntent(this), PendingIntent.FLAG_UPDATE_CURRENT));
+ StepCountCoordinator.getInstance().saveSteps(this);
+ return START_STICKY;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Logger.log("SensorListener onCreate");
+ registerSensor();
+ }
+
+ @Override
+ public void onTaskRemoved(Intent rootIntent) {
+ super.onTaskRemoved(rootIntent);
+ Logger.log("sensor service task removed");
+
+ // restart service
+ ((AlarmManager) getSystemService(Context.ALARM_SERVICE))
+ .set(AlarmManager.RTC, System.currentTimeMillis() + 500, PendingIntent
+ .getService(this, 3, createIntent(this), 0));
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Logger.log("SensorListener onDestroy");
+ unregisterSensor();
+ }
+
+ private void unregisterSensor() {
+ try {
+ SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
+ sm.unregisterListener(this);
+ } catch (Exception e) {
+ if (BuildConfig.DEBUG) {
+ Logger.log(e);
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ private void registerSensor() {
+ Logger.log("register sensor listener");
+ SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
+ Logger.log("step sensors: " + sm.getSensorList(Sensor.TYPE_STEP_COUNTER).size());
+ if (sm.getSensorList(Sensor.TYPE_STEP_COUNTER).size() < 1) {
+ return;
+ }
+ Logger.log("default: " + sm.getDefaultSensor(Sensor.TYPE_STEP_COUNTER).getName());
+
+ // enable batching with delay of max 5min
+ if (StepSensorFacade.isValidStepSensorDevice(this)) {
+ sm.registerListener(this, sm.getDefaultSensor(Sensor.TYPE_STEP_COUNTER),
+ SensorManager.SENSOR_DELAY_NORMAL, MAX_DELAY_MINUTES * MICROSECONDS_IN_ONE_MINUTE);
+ }
+ }
+}
diff --git a/src/main/java/com/sukesan1984/stepsensorlib/ShutdownReceiver.java b/src/main/java/com/sukesan1984/stepsensorlib/ShutdownReceiver.java
index ba1c52d..16eb2c2 100644
--- a/src/main/java/com/sukesan1984/stepsensorlib/ShutdownReceiver.java
+++ b/src/main/java/com/sukesan1984/stepsensorlib/ShutdownReceiver.java
@@ -5,30 +5,11 @@
import android.content.Intent;
import com.sukesan1984.stepsensorlib.util.Logger;
-import com.sukesan1984.stepsensorlib.util.SensorListener;
-
-/**
- * Created by kosuketakami on 2016/11/05.
- */
public class ShutdownReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (BuildConfig.DEBUG) Logger.log("shutting down");
-
- context.startService(new Intent(context, SensorListener.class));
-
- // if the user used a root script for shutdown, the DEVICE_SHUTDOWN
- // broadcast might not be send. Thereforee, the app will check this
- // setting on the next boot and displays an error message if it's not
- // set to true
- PreferenceManager.writeCorrectShutDown(context, true);
-
- Database db = Database.getInstance(context);
-
- db.resetLastUpdatedSteps(db.getLastUpdatedSteps());
- if (BuildConfig.DEBUG) db.logState();
-
- db.close();
+ StepCountCoordinator.getInstance().saveSteps(context);
}
}
diff --git a/src/main/java/com/sukesan1984/stepsensorlib/StepCountCoordinator.java b/src/main/java/com/sukesan1984/stepsensorlib/StepCountCoordinator.java
new file mode 100644
index 0000000..6e73916
--- /dev/null
+++ b/src/main/java/com/sukesan1984/stepsensorlib/StepCountCoordinator.java
@@ -0,0 +1,96 @@
+package com.sukesan1984.stepsensorlib;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import com.sukesan1984.stepsensorlib.util.DateUtils;
+
+class StepCountCoordinator {
+ private static final String TAG = "StepCountCoordinator";
+ private static final StepCountCoordinator singleton = new StepCountCoordinator();
+ // https://finc.slack.com/archives/C3552EVFV/p1497343956278850
+ // https://finc.slack.com/archives/C2AFVMQ5V/p1495508346662505
+ private static final long MAX_STEPS_PER_HOUR = 18000;
+
+ @Nullable
+ private Long dateAndHourOfLastEvent;
+ private int lastSteps;
+ private int unsavedSteps = 0;
+
+ public static StepCountCoordinator getInstance() {
+ return singleton;
+ }
+
+ private StepCountCoordinator() {
+ }
+
+ public synchronized void onStepCounterEvent(Context context, int stepsSinceBoot) {
+ if (stepsSinceBoot < 0) {
+ Log.d(TAG, "onStepCounterEvent: Oops... stepsSinceBoot is negative... This SHOULD NOT happen...");
+ return;
+ }
+
+ long dateAndHour = DateUtils.getCurrentDateAndHour();
+ if (dateAndHourOfLastEvent == null) {
+ dateAndHourOfLastEvent = dateAndHour;
+ lastSteps = stepsSinceBoot;
+ Log.v(TAG, "onStepCounterEvent: lastSteps is set to " + lastSteps);
+ return;
+ }
+
+ if (dateAndHour != dateAndHourOfLastEvent) {
+ Log.d(TAG, "Save by hour change.");
+ saveSteps(context);
+ }
+
+ int increment = stepsSinceBoot - lastSteps;
+ if (increment > MAX_STEPS_PER_HOUR) {
+ Log.d(TAG, "onStepCounterEvent: Skipping steps, increment " + increment + " exceeds limit of " + MAX_STEPS_PER_HOUR + ".");
+ lastSteps = stepsSinceBoot;
+ return;
+ }
+ if (increment < 0) {
+ Log.d(TAG, "onStepCounterEvent: Discarding steps event, negative increment "
+ + increment + " happened! (stepsSinceBoot: " + stepsSinceBoot + ")");
+ // XXX: Resetting can cause extra steps to be added if the problem is order of event is randomized.
+ // But it can be sudden decrease of internal counter of sensor, then no step recorded
+ // until restarting the app or counter reaches lastSteps value. Counting extra steps is
+ // far more better than no steps, so I choose to reset here!
+ lastSteps = stepsSinceBoot;
+ return;
+ }
+
+ unsavedSteps += increment;
+ Log.v(TAG, "onStepCounterEvent: " + unsavedSteps);
+ dateAndHourOfLastEvent = dateAndHour;
+ lastSteps = stepsSinceBoot;
+ }
+
+ public synchronized void saveSteps(Context context) {
+ if (dateAndHourOfLastEvent == null) return;
+ Log.v(TAG, "saveSteps: " + unsavedSteps);
+ if (unsavedSteps == 0) return;
+ Database database = Database.getInstance(context);
+ int newSteps = database.addSteps(dateAndHourOfLastEvent, unsavedSteps);
+ if (newSteps < 0) {
+ Log.e(TAG, "Failed to save steps.");
+ } else {
+ unsavedSteps = 0;
+ }
+ }
+
+ public synchronized int getTodaySteps(Context context) {
+ long dateAndHour = DateUtils.getCurrentDateAndHour();
+ if (dateAndHourOfLastEvent != null && dateAndHour != dateAndHourOfLastEvent) {
+ // Save and flush step count before fetch.
+ saveSteps(context);
+ }
+ return Database.getInstance(context).getTodayStep() + unsavedSteps;
+ }
+
+ public synchronized void reset() {
+ dateAndHourOfLastEvent = null;
+ lastSteps = 0;
+ unsavedSteps = 0;
+ }
+}
diff --git a/src/main/java/com/sukesan1984/stepsensorlib/StepSensorFacade.java b/src/main/java/com/sukesan1984/stepsensorlib/StepSensorFacade.java
index 60c8202..7e995ae 100644
--- a/src/main/java/com/sukesan1984/stepsensorlib/StepSensorFacade.java
+++ b/src/main/java/com/sukesan1984/stepsensorlib/StepSensorFacade.java
@@ -3,15 +3,58 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
+import android.support.annotation.NonNull;
+
+import com.sukesan1984.stepsensorlib.model.ChunkStepCount;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* Step Sensor Facade
*/
public class StepSensorFacade {
+ private StepSensorFacade() {
+ throw new AssertionError();
+ }
+
public static boolean isValidStepSensorDevice(Context context) {
// TODO: get from server side black list model.
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
&& context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_STEP_COUNTER);
}
+
+ public static void startService(Context context) {
+ context.startService(SensorListener.createIntent(context));
+ }
+
+ public static void saveNow(Context context) {
+ StepCountCoordinator.getInstance().saveSteps(context);
+ }
+
+ public static int getTodaySteps(Context context) {
+ return StepCountCoordinator.getInstance().getTodaySteps(context);
+ }
+
+ public static void clearAllData(Context context) {
+ context.startService(SensorListener.createIntentForReset(context));
+ }
+
+ public static void increaseByServerChunkStepCounts(Context context, List chunkStepCounts) {
+ saveNow(context);
+ Database.getInstance(context).increaseByServerChunkStepCounts(chunkStepCounts);
+ }
+
+ @NonNull
+ public static List getChunkStepsSince(Context context, long dateAndHour) {
+ saveNow(context);
+ return Database.getInstance(context).getChunkStepsSince(dateAndHour);
+ }
+
+ @NonNull
+ public static List getNotRecordedChunkStepCounts(Context context) {
+ saveNow(context);
+ return Database.getInstance(context).getNotRecordedChunkStepCounts();
+ }
}
diff --git a/src/main/java/com/sukesan1984/stepsensorlib/util/Logger.java b/src/main/java/com/sukesan1984/stepsensorlib/util/Logger.java
index 4da95ad..2e21b00 100644
--- a/src/main/java/com/sukesan1984/stepsensorlib/util/Logger.java
+++ b/src/main/java/com/sukesan1984/stepsensorlib/util/Logger.java
@@ -15,11 +15,15 @@
*/
package com.sukesan1984.stepsensorlib.util;
+import android.app.ActivityManager;
+import android.content.Context;
import android.database.Cursor;
+import com.crashlytics.android.Crashlytics;
import com.sukesan1984.stepsensorlib.BuildConfig;
import java.util.Date;
+import java.util.List;
public abstract class Logger {
@@ -33,17 +37,62 @@ public static void log(Throwable ex) {
}
}
+
+ private static String stackToString(StackTraceElement[] stackTrace) {
+ String stack = "Finc-log Stacktrace:";
+ for (StackTraceElement stackTraceElement : stackTrace) {
+ stack += ",at " + (stackTraceElement.toString());
+ }
+ return stack;
+ }
+
+ public static void logInCrashlytics(Context context){
+ Crashlytics.log("Thread id: " + Thread.currentThread().getId());
+ Crashlytics.log("Process id: " + android.os.Process.myPid());
+ Crashlytics.log("Process name: " + getProcessName(context));
+ if (BuildConfig.DEBUG) {
+ Crashlytics.log("Stack: " + stackToString(Thread.currentThread().getStackTrace()));
+ }
+ }
+
+
+ private static String getProcessName(Context context) {
+ int pid = android.os.Process.myPid();
+ ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ List infos = manager.getRunningAppProcesses();
+ if (infos != null) {
+ for (ActivityManager.RunningAppProcessInfo processInfo : infos) {
+ if (processInfo.pid == pid) {
+ return processInfo.processName;
+ }
+ }
+ }
+
+ return null;
+ }
+
public static void log(final Cursor c) {
if (!BuildConfig.DEBUG) return;
c.moveToFirst();
String title = "";
- for (int i = 0; i < c.getColumnCount(); i++)
+ for (int i = 0; i < c.getColumnCount(); i++) {
title += c.getColumnName(i) + "\t| ";
+ }
log(title);
while (!c.isAfterLast()) {
title = "";
- for (int i = 0; i < c.getColumnCount(); i++)
- title += c.getString(i) + "\t| ";
+ for (int i = 0; i < c.getColumnCount(); i++) {
+ final int columnTitleLength = c.getColumnName(i).length();
+ int columnValueLength = c.getString(i).length();
+ int diffLength = columnTitleLength - columnValueLength > 0
+ ? columnTitleLength - columnValueLength
+ : 0;
+ String diff = "";
+ for (int diffCount = 0; diffCount < diffLength; diffCount++) {
+ diff += " ";
+ }
+ title += c.getString(i) + diff + "\t| ";
+ }
log(title);
c.moveToNext();
}
diff --git a/src/main/java/com/sukesan1984/stepsensorlib/util/SensorListener.java b/src/main/java/com/sukesan1984/stepsensorlib/util/SensorListener.java
deleted file mode 100644
index a468176..0000000
--- a/src/main/java/com/sukesan1984/stepsensorlib/util/SensorListener.java
+++ /dev/null
@@ -1,176 +0,0 @@
-package com.sukesan1984.stepsensorlib.util;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.IBinder;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.util.Log;
-
-import com.sukesan1984.stepsensorlib.BuildConfig;
-import com.sukesan1984.stepsensorlib.Database;
-import com.sukesan1984.stepsensorlib.PreferenceManager;
-import com.sukesan1984.stepsensorlib.StepSensorFacade;
-
-/**
- * Created by kosuketakami on 2016/11/05.
- */
-
-public class SensorListener extends Service implements SensorEventListener {
-
- public final static String ACTION_PAUSE = "pause";
- private static int steps;
-
- private static boolean WAIT_FOR_VALID_STEPS = false;
-
- private final static int MICROSECONDS_IN_ONE_MINUTE = 60000000;
-
- public static Intent createIntent(@NonNull Context context) {
- return new Intent(context, SensorListener.class);
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- // nobody knows what happens here: step value might magically decrease
- // when this method is called...
- if (BuildConfig.DEBUG) {
- Logger.log(sensor.getName() + " accuracy changed: " + accuracy);
- }
- }
-
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (event.values[0] > Integer.MAX_VALUE) {
- if (BuildConfig.DEBUG) {
- Logger.log("probably not a real value: " + event.values[0]);
- return;
- }
- } else {
- steps = (int) event.values[0];
- if (WAIT_FOR_VALID_STEPS && steps > 0) {
- Logger.log("periodically save");
- WAIT_FOR_VALID_STEPS = false;
- Database db = Database.getInstance(this);
- db.updateOrInsert(DateUtils.getCurrentDateAndHour(), steps);
- reRegisterSensor();
- int difference = steps - PreferenceManager.readStepsSinceBoot(this, steps);
- if (difference > 0) {
- PreferenceManager.writeStepsSinceBoot(this, steps);
- }
- db.close();
- }
- }
- }
-
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.d("Hoge", "################################## onStartCommand");
- if (intent != null && ACTION_PAUSE.equals(intent.getStringExtra("action"))) {
- if (BuildConfig.DEBUG) {
- Logger.log("onStartCommand action: " + intent.getStringExtra("action"));
- }
- if (steps == 0) {
- Database db = Database.getInstance(this);
- steps = db.getLastUpdatedSteps();
- db.close();
- }
- SharedPreferences prefs = getSharedPreferences("pedometer", Context.MODE_PRIVATE);
- ((AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE))
- .cancel(PendingIntent.getService(getApplicationContext(), 2,
- new Intent(this, SensorListener.class),
- PendingIntent.FLAG_UPDATE_CURRENT));
- stopSelf();
- return START_NOT_STICKY;
- }
-
- // restart service every minutes get the current step count
- ((AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE))
- .set(AlarmManager.RTC, System.currentTimeMillis() + 60 * 1000,
- PendingIntent.getService(getApplicationContext(), 2, new Intent(this, SensorListener.class),
- PendingIntent.FLAG_UPDATE_CURRENT));
- WAIT_FOR_VALID_STEPS = true;
-
- return START_STICKY;
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- if (BuildConfig.DEBUG) {
- Logger.log("SensorListener onCreate");
- }
- reRegisterSensor();
- }
-
- @Override
- public void onTaskRemoved(Intent rootIntent) {
- super.onTaskRemoved(rootIntent);
- if (BuildConfig.DEBUG) {
- Logger.log("sensor service task removed");
- }
-
- ((AlarmManager) getSystemService(Context.ALARM_SERVICE))
- .set(AlarmManager.RTC, System.currentTimeMillis() + 500, PendingIntent
- .getService(this, 3, new Intent(this, SensorListener.class), 0));
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (BuildConfig.DEBUG) {
- Logger.log("SensorListener onDestroy");
- try {
- SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
- sm.unregisterListener(this);
- } catch (Exception e) {
- if (BuildConfig.DEBUG) {
- Logger.log(e);
- e.printStackTrace();
- }
- }
- }
- }
-
- private void reRegisterSensor() {
- if (BuildConfig.DEBUG) {
- Logger.log("re-register sensor listener");
- }
- SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
- try {
- sm.unregisterListener(this);
- } catch (Exception e) {
- if (BuildConfig.DEBUG) {
- Logger.log(e);
- e.printStackTrace();
- }
- }
-
- if (BuildConfig.DEBUG) {
- Logger.log("step sensors: " + sm.getSensorList(Sensor.TYPE_STEP_COUNTER).size());
- if (sm.getSensorList(Sensor.TYPE_STEP_COUNTER).size() < 1) {
- return;
- }
- Logger.log("default: " + sm.getDefaultSensor(Sensor.TYPE_STEP_COUNTER).getName());
- }
-
- // enable batching with delay of max 5min
- if (StepSensorFacade.isValidStepSensorDevice(this)) {
- sm.registerListener(this, sm.getDefaultSensor(Sensor.TYPE_STEP_COUNTER),
- SensorManager.SENSOR_DELAY_NORMAL, 5 * MICROSECONDS_IN_ONE_MINUTE);
- }
- }
-}