implements CursorFilter.CursorFilterClient, Filterable {
+
+ protected Context mContext;
+ private Cursor mCursor;
+ private boolean mDataValid;
+ private int mRowIdColumn;
+ private DataSetObserver mDataSetObserver;
+ private ChangeObserver mChangeObserver;
+ private CursorFilter mCursorFilter;
+ private FilterQueryProvider mFilterQueryProvider;
+ private boolean mUseIndividualNotifies;
+
+ private String mIdIdentifier = "_id";
+
+ public CursorRecyclerViewAdapter(Context context, Cursor cursor, String idIdentifier) {
+ this(context, null);
+ this.setIdIdentifier(idIdentifier);
+ this.changeCursor(cursor);
+ }
+
+ public CursorRecyclerViewAdapter(Context context, Cursor cursor) {
+ mContext = context;
+ mCursor = cursor;
+ mDataValid = cursor != null;
+ mRowIdColumn = mDataValid ? mCursor.getColumnIndex(mIdIdentifier) : -1;
+
+ mDataSetObserver = new NotifyingDataSetObserver();
+ mChangeObserver = new ChangeObserver();
+
+ if (mDataValid) {
+ if (mChangeObserver != null) cursor.registerContentObserver(mChangeObserver);
+ if (mDataSetObserver != null) cursor.registerDataSetObserver(mDataSetObserver);
+ }
+ }
+
+ public void setIdIdentifier(String identifier) {
+ mIdIdentifier = TextUtils.isEmpty(identifier) ?
+ "_id" :
+ identifier;
+ }
+
+ public Cursor getCursor() {
+ return mCursor;
+ }
+
+ @Override
+ public int getItemCount() {
+ if (mDataValid && mCursor != null) {
+ return mCursor.getCount();
+ }
+ return 0;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
+ return mCursor.getLong(mRowIdColumn);
+ }
+ return 0;
+ }
+
+ @Override
+ public void setHasStableIds(boolean hasStableIds) {
+ super.setHasStableIds(hasStableIds);
+ }
+
+ public abstract void onBindViewHolder(VH viewHolder, Cursor cursor);
+
+ @Override
+ public void onBindViewHolder(VH viewHolder, int position) {
+ if (!mDataValid) {
+ throw new IllegalStateException("this should only be called when the cursor is valid");
+ }
+ if (!mCursor.moveToPosition(position)) {
+ throw new IllegalStateException("couldn't move cursor to position " + position);
+ }
+ onBindViewHolder(viewHolder, mCursor);
+ }
+
+ /**
+ * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
+ * closed.
+ */
+ public void changeCursor(Cursor cursor) {
+ Cursor old = swapCursor(cursor);
+ if (old != null) {
+ old.close();
+ }
+ }
+
+ /**
+ * Swap in a new Cursor, returning the old Cursor. Unlike
+ * {@link #changeCursor(Cursor)}, the returned old Cursor is not
+ * closed.
+ */
+ public Cursor swapCursor(Cursor newCursor) {
+ if (newCursor == mCursor) {
+ return null;
+ }
+ // unregister observers on old cursor
+ final Cursor oldCursor = mCursor;
+ if (oldCursor != null) {
+ if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
+ if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
+ }
+ // register observers on the new cursor
+ mCursor = newCursor;
+ if (mCursor != null) {
+ if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
+ if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
+
+ mRowIdColumn = newCursor.getColumnIndexOrThrow(mIdIdentifier);
+ mDataValid = true;
+ } else {
+ mRowIdColumn = -1;
+ mDataValid = false;
+ }
+
+ // notify that Dataset has Changed
+ boolean notifyDataChanged = true;
+
+ // check if we can get better notifies
+ if(mUseIndividualNotifies && oldCursor != null && newCursor != null && !oldCursor.isClosed() && !newCursor.isClosed()) {
+ notifyDataChanged = notifyItems(oldCursor, newCursor);
+ }
+
+ if(notifyDataChanged) {
+ notifyDataSetChanged();
+ }
+
+ return oldCursor;
+ }
+
+ public void setUseIndividualNotifies(boolean use) {
+ mUseIndividualNotifies = use;
+ }
+
+ /**
+ * Compares the two cursors and calls notifyChanged on every item that changed.
+ * @param oldCursor
+ * @param newCursor
+ * @return
+ */
+ private boolean notifyItems(Cursor oldCursor, Cursor newCursor) {
+ String[] columns = new String[] { BaseColumns._ID };
+ CursorJoiner joiner = new CursorJoiner(oldCursor, columns, newCursor, columns);
+ for (CursorJoiner.Result res : joiner) {
+ switch (res) {
+ case LEFT:
+ notifyItemRemoved(newCursor.getPosition());
+ break;
+ case RIGHT:
+ notifyItemInserted(newCursor.getPosition());
+ break;
+ case BOTH:
+// for(int i = 0; i < newCursor.getColumnCount(); i++) {
+// if(!oldCursor.getString(i).equals(newCursor.getString(i))) {
+// notifyItemChanged(newCursor.getPosition());
+// }
+// }
+ if (getRowHash(oldCursor) != getRowHash(newCursor)) {
+ notifyItemChanged(newCursor.getPosition());
+ }
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ private int getRowHash(Cursor cursor) {
+ StringBuilder result = new StringBuilder("row");
+ for (int i = 0; i < cursor.getColumnCount(); i++) {
+ result.append(cursor.getString(i));
+ }
+ return result.toString().hashCode();
+ }
+
+ /**
+ * Converts the cursor into a CharSequence. Subclasses should override this
+ * method to convert their results. The default implementation returns an
+ * empty String for null values or the default String representation of
+ * the value.
+ *
+ * @param cursor the cursor to convert to a CharSequence
+ * @return a CharSequence representing the value
+ */
+ public CharSequence convertToString(Cursor cursor) {
+ return cursor == null ? "" : cursor.toString();
+ }
+
+ /**
+ * Runs a query with the specified constraint. This query is requested
+ * by the filter attached to this adapter.
+ *
+ * The query is provided by a
+ * {@link FilterQueryProvider}.
+ * If no provider is specified, the current cursor is not filtered and returned.
+ *
+ * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
+ * and the previous cursor is closed.
+ *
+ * This method is always executed on a background thread, not on the
+ * application's main thread (or UI thread.)
+ *
+ * Contract: when constraint is null or empty, the original results,
+ * prior to any filtering, must be returned.
+ *
+ * @param constraint the constraint with which the query must be filtered
+ *
+ * @return a Cursor representing the results of the new query
+ *
+ * @see #getFilter()
+ * @see #getFilterQueryProvider()
+ * @see #setFilterQueryProvider(FilterQueryProvider)
+ */
+ public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+ if (mFilterQueryProvider != null) {
+ return mFilterQueryProvider.runQuery(constraint);
+ }
+
+ return mCursor;
+ }
+
+ public Filter getFilter() {
+ if (mCursorFilter == null) {
+ mCursorFilter = new CursorFilter(this);
+ }
+ return mCursorFilter;
+ }
+
+ /**
+ * Returns the query filter provider used for filtering. When the
+ * provider is null, no filtering occurs.
+ *
+ * @return the current filter query provider or null if it does not exist
+ *
+ * @see #setFilterQueryProvider(FilterQueryProvider)
+ * @see #runQueryOnBackgroundThread(CharSequence)
+ */
+ public FilterQueryProvider getFilterQueryProvider() {
+ return mFilterQueryProvider;
+ }
+
+ /**
+ * Sets the query filter provider used to filter the current Cursor.
+ * The provider's
+ * {@link FilterQueryProvider#runQuery(CharSequence)}
+ * method is invoked when filtering is requested by a client of
+ * this adapter.
+ *
+ * @param filterQueryProvider the filter query provider or null to remove it
+ *
+ * @see #getFilterQueryProvider()
+ * @see #runQueryOnBackgroundThread(CharSequence)
+ */
+ public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
+ mFilterQueryProvider = filterQueryProvider;
+ }
+
+ /**
+ * Called when the {@link ContentObserver} on the cursor receives a change notification.
+ * Can be implemented by sub-class.
+ *
+ * @see ContentObserver#onChange(boolean)
+ */
+ protected void onContentChanged() {
+
+ }
+
+ private class ChangeObserver extends ContentObserver {
+ public ChangeObserver() {
+ super(new Handler());
+ }
+
+ @Override
+ public boolean deliverSelfNotifications() {
+ return true;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ onContentChanged();
+ }
+ }
+
+ private class NotifyingDataSetObserver extends DataSetObserver {
+ private final String TAG = NotifyingDataSetObserver.class.getSimpleName();
+ @Override
+ public void onChanged() {
+ super.onChanged();
+ mDataValid = true;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onInvalidated() {
+ super.onInvalidated();
+ mDataValid = false;
+ notifyDataSetChanged();
+ //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/DBHandler.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/DBHandler.java
deleted file mode 100644
index 015ac1d..0000000
--- a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/DBHandler.java
+++ /dev/null
@@ -1,192 +0,0 @@
-package org.secuso.privacyfriendlybreakreminder.database;
-
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.util.Log;
-
-import org.secuso.privacyfriendlybreakreminder.database.data.Exercise;
-import org.secuso.privacyfriendlybreakreminder.database.columns.ExerciseColumns;
-import org.secuso.privacyfriendlybreakreminder.database.columns.ExercisesLocalColumns;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-public class DBHandler extends SQLiteOpenHelper {
-
- private SQLiteDatabase dataBase;
- private static final String DATABASE_NAME = "exercises.sqlite";
- private static final String DATABASE_PATH = "/data/data/org.secuso.privacyfriendlybreakreminder/databases/";
- private static final int DATABASE_VERSION = 3;
-
- private static final String[] deleteQueryList = {
- ExerciseColumns.SQL_DELETE_ENTRIES,
- ExercisesLocalColumns.SQL_DELETE_ENTRIES};
-
- public DBHandler(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
-
- //Check if database exists
- File databaseFile = context.getDatabasePath(DATABASE_NAME);
- if (!databaseFile.exists()) {
- this.getReadableDatabase();
- try {
- copyDataBase(context);
- this.close();
- } catch (Exception e) {
- Log.v("db log", "Copying data didn´t work!!");
- }
- }
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
-
- }
-
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- execSQLList(db, deleteQueryList);
- }
-
- public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- onUpgrade(db, oldVersion, newVersion);
- }
-
- private void execSQLList(SQLiteDatabase db, String[] queryList) {
- for (String query : queryList) {
- db.execSQL(query);
- }
- }
-
-
- public Cursor getExerciseCursor(String language) {
- SQLiteDatabase database = getReadableDatabase();
-
- return database.rawQuery(buildQuery(false), new String[]{language});
- }
-
- public List getExerciseList(String language) {
- SQLiteDatabase database = getReadableDatabase();
-
- Cursor c = database.rawQuery(buildQuery(false), new String[]{language});
-
- List result = new ArrayList<>();
-
- if(c != null) {
-
- while(!c.isAfterLast()) {
- result.add(ExerciseColumns.getExercise(c));
- c.moveToNext();
- }
-
- c.close();
- }
-
- return result;
- }
-
- /**
- * SELECT
- * E._id,
- * E.section,
- * E.image_id,
- * L.local_id,
- * L.language,
- * L.exercise_id,
- * L.name,
- * L.description,
- * L.execution
- * FROM exercises E LEFT OUTER JOIN exercises_local L
- * ON E._id = L.exercise_id
- * WHERE L.language = "de" [AND E.section LIKE %?%]
- * ORDER BY E._id ASC
- *
- * @return the sql query without the ; at the end.
- */
- private String buildQuery(boolean addSectionCheck) {
- StringBuilder sqlQuery = new StringBuilder();
-
- sqlQuery.append("SELECT ");
-
- for(String field : ExerciseColumns.PROJECTION) {
- sqlQuery.append("E.").append(field).append(", ");
- }
- for(String field : ExercisesLocalColumns.PROJECTION) {
- sqlQuery.append("L.").append(field).append(", ");
- }
- // delete the last comma
- sqlQuery.setLength(sqlQuery.length()-2);
-
- sqlQuery.append(" FROM ");
- sqlQuery.append(ExerciseColumns.TABLE_NAME);
- sqlQuery.append(" E LEFT OUTER JOIN ");
- sqlQuery.append(ExercisesLocalColumns.TABLE_NAME);
- sqlQuery.append(" L");
-
- sqlQuery.append("ON E.");
- sqlQuery.append(ExerciseColumns._ID);
- sqlQuery.append(" = L.");
- sqlQuery.append(ExercisesLocalColumns.EXERCISE_ID);
-
- sqlQuery.append("WHERE ");
- sqlQuery.append("L.");
- sqlQuery.append(ExercisesLocalColumns.LANGUAGE);
- sqlQuery.append("= ? ");
-
- if(addSectionCheck) {
- sqlQuery.append("AND E.");
- sqlQuery.append(ExerciseColumns.SECTION);
- sqlQuery.append("LIKE ? ");
- }
-
- sqlQuery.append("ORDER BY E.");
- sqlQuery.append(ExerciseColumns._ID);
- sqlQuery.append(" ASC");
-
- return sqlQuery.toString();
- }
-
-
- public List getExercisesFromSection(String language, String section) {
- SQLiteDatabase database = getReadableDatabase();
-
- Cursor c = database.rawQuery(buildQuery(true), new String[]{language, "%"+section+"%"});
-
- List result = new ArrayList<>();
-
- if(c != null) {
-
- while(!c.isAfterLast()) {
- result.add(ExerciseColumns.getExercise(c));
- c.moveToNext();
- }
-
- c.close();
- }
-
- return result;
- }
-
- private void copyDataBase(Context context) throws IOException {
- InputStream myInput = context.getAssets().open(DATABASE_NAME);
- String outFileName = DATABASE_PATH + DATABASE_NAME;
- OutputStream myOutput = new FileOutputStream(outFileName);
-
- byte[] buffer = new byte[1024];
- int length;
- while ((length = myInput.read(buffer)) > 0) {
- myOutput.write(buffer, 0, length);
- }
-
- myOutput.flush();
- myOutput.close();
- myInput.close();
- }
-}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/SQLiteHelper.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/SQLiteHelper.java
new file mode 100644
index 0000000..284a5dd
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/SQLiteHelper.java
@@ -0,0 +1,291 @@
+package org.secuso.privacyfriendlybreakreminder.database;
+
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+import org.secuso.privacyfriendlybreakreminder.database.columns.ExerciseSetColumns;
+import org.secuso.privacyfriendlybreakreminder.database.data.Exercise;
+import org.secuso.privacyfriendlybreakreminder.database.columns.ExerciseColumns;
+import org.secuso.privacyfriendlybreakreminder.database.columns.ExerciseLocalColumns;
+import org.secuso.privacyfriendlybreakreminder.database.data.ExerciseSet;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SQLiteHelper extends SQLiteOpenHelper {
+
+ private static final String TAG = SQLiteHelper.class.getSimpleName();
+
+ private Context mContext;
+ private static final String DATABASE_NAME = "exercises.sqlite";
+ private static final String DATABASE_PATH = "/data/data/org.secuso.privacyfriendlybreakreminder/databases/";
+ private static final int DATABASE_VERSION = 4;
+
+ private static final String[] deleteQueryList = {
+ ExerciseColumns.SQL_DELETE_ENTRIES,
+ ExerciseLocalColumns.SQL_DELETE_ENTRIES,
+ ExerciseSetColumns.SQL_DELETE_ENTRIES};
+
+ private boolean onCreate;
+ private boolean onUpgrade;
+
+ public SQLiteHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ mContext = context;
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ if (onCreate || onUpgrade) {
+ onCreate = onUpgrade = false;
+ copyDatabaseFromAssets(db);
+ }
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ onCreate = true;
+ }
+
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ onUpgrade = true;
+ }
+
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ onUpgrade(db, oldVersion, newVersion);
+ }
+
+
+ public synchronized Cursor getExerciseCursor(String language) {
+ SQLiteDatabase database = getReadableDatabase();
+ return database.rawQuery(buildQuery(false), new String[]{language});
+ }
+
+ public synchronized List getExerciseList(String language) {
+ Cursor c = getExerciseCursor(language);
+ return buildExerciseList(c);
+ }
+
+ public synchronized Cursor getExerciseCursorForSet(int setId, String language) {
+ SQLiteDatabase database = getReadableDatabase();
+
+ String sql = "SELECT *\n" +
+ "FROM exercise_set ES LEFT OUTER JOIN exercise_set_exercises ESE\n" +
+ "\tON ES.exercise_set_id = ESE.exercise_set_id\n" +
+ "LEFT OUTER JOIN exercises E\n" +
+ "\tON ESE.exercise_id = E.exercise_id\n" +
+ "LEFT OUTER JOIN exercises_local L\n" +
+ "\tON E.exercise_id = L.exercise_id\n" +
+ "WHERE ES.exercise_set_id = ? AND L.language = ?\n" +
+ "ORDER BY ESE.exercise_id ASC";
+
+ String sql2 = "SELECT *\n" +
+ "\tFROM (SELECT * \n" +
+ "\t\t\tFROM (SELECT *\n" +
+ "\t\t\t\tFROM "+ExerciseSetColumns.TABLE_NAME+" ES LEFT OUTER JOIN exercise_set_exercises ESE\n" +
+ "\t\t\t\tON ES."+ExerciseSetColumns._ID+" = ESE."+ExerciseSetColumns._ID+"\n" +
+ "\t\t\t\tWHERE ES."+ExerciseSetColumns._ID+" = ?\n" +
+ "\t\t\t\tORDER BY ESE."+ExerciseColumns._ID+" ASC) ES_ESE \n" +
+ "\t\t\tLEFT OUTER JOIN "+ExerciseColumns.TABLE_NAME+" E\n" +
+ "\t\t\tON ES_ESE."+ExerciseColumns._ID+" = E."+ExerciseColumns._ID+") ES_ESE_E \n" +
+ "\t\tLEFT OUTER JOIN "+ExerciseLocalColumns.TABLE_NAME+" L\n" +
+ "\t\tON ES_ESE_E."+ExerciseColumns._ID+" = L."+ExerciseLocalColumns.EXERCISE_ID+"\n" +
+ "\t\tWHERE L."+ExerciseLocalColumns.LANGUAGE+" = ?";
+
+ return database.rawQuery(sql, new String[]{String.valueOf(setId), language});
+ }
+
+ public synchronized ExerciseSet getExerciseListForSet(int setId, String language) {
+ Cursor c = getExerciseCursorForSet(setId, language);
+
+ ExerciseSet result = null;
+
+ if(c != null) {
+
+ c.moveToFirst();
+
+ result = ExerciseSetColumns.fromCursor(c);
+
+ while(!c.isAfterLast()) {
+ result.add(ExerciseColumns.fromCursor(c));
+ c.moveToNext();
+ }
+
+ c.close();
+ }
+
+ return result;
+ }
+
+ private List buildExerciseList(Cursor c) {
+ List result = new ArrayList<>();
+
+ if(c != null) {
+
+ c.moveToFirst();
+
+ while(!c.isAfterLast()) {
+ result.add(ExerciseColumns.fromCursor(c));
+ c.moveToNext();
+ }
+
+ c.close();
+ }
+
+ return result;
+ }
+
+
+ private String buildQuery(boolean addSectionCheck) {
+ return buildQuery(addSectionCheck, "");
+ }
+
+ public synchronized List getExercisesFromSection(String language, String section) { // TODO: Rename after old activities are deleted
+ SQLiteDatabase database = getReadableDatabase();
+
+ Cursor c = database.rawQuery(buildQuery(true), new String[]{language, "%"+section+"%"});
+
+ return buildExerciseList(c);
+ }
+
+ /**
+ * SELECT
+ * E._id,
+ * E.section,
+ * E.image_id,
+ * L.local_id,
+ * L.language,
+ * L.exercise_id,
+ * L.name,
+ * L.description,
+ * L.execution
+ * FROM exercises E LEFT OUTER JOIN exercises_local L
+ * ON E._id = L.exercise_id
+ * WHERE L.language = "de" [AND E.section LIKE %?%]
+ * ORDER BY E._id ASC
+ *
+ * @return the sql query without the ; at the end.
+ */
+ private String buildQuery(boolean addSectionCheck, String customWhereClause) {
+ StringBuilder sqlQuery = new StringBuilder();
+
+ sqlQuery.append("SELECT ");
+
+ for(String field : ExerciseColumns.PROJECTION) {
+ sqlQuery.append("E.").append(field).append(", ");
+ }
+ for(String field : ExerciseLocalColumns.PROJECTION) {
+ sqlQuery.append("L.").append(field).append(", ");
+ }
+ // delete the last comma
+ sqlQuery.setLength(sqlQuery.length()-2);
+
+ sqlQuery.append(" FROM ");
+ sqlQuery.append(ExerciseColumns.TABLE_NAME);
+ sqlQuery.append(" E LEFT OUTER JOIN ");
+ sqlQuery.append(ExerciseLocalColumns.TABLE_NAME);
+ sqlQuery.append(" L ");
+
+ sqlQuery.append("ON E.");
+ sqlQuery.append(ExerciseColumns._ID);
+ sqlQuery.append(" = L.");
+ sqlQuery.append(ExerciseLocalColumns.EXERCISE_ID);
+ sqlQuery.append(" ");
+
+ sqlQuery.append("WHERE ");
+ sqlQuery.append("L.");
+ sqlQuery.append(ExerciseLocalColumns.LANGUAGE);
+ sqlQuery.append(" = ? ");
+
+ if(addSectionCheck) {
+ sqlQuery.append("AND E.");
+ sqlQuery.append(ExerciseColumns.SECTION);
+ sqlQuery.append(" LIKE ? ");
+ }
+
+ sqlQuery.append("ORDER BY E.");
+ sqlQuery.append(ExerciseColumns._ID);
+ sqlQuery.append(" ASC");
+
+ return sqlQuery.toString();
+ }
+
+ private void copyDataBase(Context context) throws IOException {
+ InputStream myInput = context.getAssets().open(DATABASE_NAME);
+ String outFileName = DATABASE_PATH + DATABASE_NAME;
+ OutputStream myOutput = new FileOutputStream(outFileName);
+
+ byte[] buffer = new byte[1024];
+ int length;
+ while ((length = myInput.read(buffer)) > 0) {
+ myOutput.write(buffer, 0, length);
+ }
+
+ myOutput.flush();
+ myOutput.close();
+ myInput.close();
+
+ SQLiteDatabase copiedDb = context.openOrCreateDatabase(DATABASE_NAME, 0, null);
+ copiedDb.execSQL("PRAGMA user_version = " + DATABASE_VERSION);
+ copiedDb.close();
+ }
+
+ /**
+ * Copy packaged database from assets folder to the database created in the
+ * application package context.
+ *
+ * @param db
+ * The target database in the application package context.
+ */
+ private void copyDatabaseFromAssets(SQLiteDatabase db) {
+ Log.i(TAG, "copyDatabase");
+ InputStream myInput = null;
+ OutputStream myOutput = null;
+ try {
+ // Open db packaged as asset as the input stream
+ myInput = mContext.getAssets().open(DATABASE_NAME);
+
+ // Open the db in the application package context:
+ myOutput = new FileOutputStream(db.getPath());
+
+ // Transfer db file contents:
+ byte[] buffer = new byte[1024];
+ int length;
+ while ((length = myInput.read(buffer)) > 0) {
+ myOutput.write(buffer, 0, length);
+ }
+ myOutput.flush();
+
+ // Set the version of the copied database to the current
+ // version:
+ SQLiteDatabase copiedDb = mContext.openOrCreateDatabase(DATABASE_NAME, 0, null);
+ copiedDb.execSQL("PRAGMA user_version = " + DATABASE_VERSION);
+ copiedDb.close();
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new Error(TAG + " Error copying database");
+ } finally {
+ // Close the streams
+ try {
+ if (myOutput != null) {
+ myOutput.close();
+ }
+ if (myInput != null) {
+ myInput.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new Error(TAG + " Error closing streams");
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseColumns.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseColumns.java
index 759d872..ce5a19d 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseColumns.java
+++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseColumns.java
@@ -2,7 +2,6 @@ package org.secuso.privacyfriendlybreakreminder.database.columns;
import android.content.ContentValues;
import android.database.Cursor;
-import android.provider.BaseColumns;
import org.secuso.privacyfriendlybreakreminder.database.data.Exercise;
@@ -10,10 +9,11 @@ import org.secuso.privacyfriendlybreakreminder.database.data.Exercise;
* Created by Christopher Beckmann on 23.08.2017.
*/
-public class ExerciseColumns implements BaseColumns {
+public final class ExerciseColumns {
public static final String TABLE_NAME = "exercises";
+ public static final String _ID = "exercise_id";
public static final String SECTION = "section";
public static final String IMAGE_ID = "image_id";
@@ -24,8 +24,8 @@ public class ExerciseColumns implements BaseColumns {
};
public static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + TABLE_NAME;
- public static Exercise getExercise(Cursor c) {
- Exercise e = ExercisesLocalColumns.getExercise(c);
+ public static Exercise fromCursor(Cursor c) {
+ Exercise e = ExerciseLocalColumns.fromCursor(c);
e.setId(c.getInt(c.getColumnIndexOrThrow(ExerciseColumns._ID)));
e.setSection(c.getString(c.getColumnIndexOrThrow(ExerciseColumns.SECTION)));
@@ -42,8 +42,10 @@ public class ExerciseColumns implements BaseColumns {
values.put(ExerciseColumns.SECTION, record.getSection());
values.put(ExerciseColumns.IMAGE_ID, record.getImageID());
- values.putAll(ExercisesLocalColumns.getValues(record));
+ values.putAll(ExerciseLocalColumns.getValues(record));
return values;
}
+
+ private ExerciseColumns() {}
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExercisesLocalColumns.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseLocalColumns.java
similarity index 64%
rename from app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExercisesLocalColumns.java
rename to app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseLocalColumns.java
index 5721b89..c58f322 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExercisesLocalColumns.java
+++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseLocalColumns.java
@@ -2,7 +2,6 @@ package org.secuso.privacyfriendlybreakreminder.database.columns;
import android.content.ContentValues;
import android.database.Cursor;
-import android.provider.BaseColumns;
import org.secuso.privacyfriendlybreakreminder.database.data.Exercise;
@@ -10,7 +9,7 @@ import org.secuso.privacyfriendlybreakreminder.database.data.Exercise;
* Created by Christopher Beckmann on 25.08.2017.
*/
-public class ExercisesLocalColumns implements BaseColumns {
+public final class ExerciseLocalColumns {
public static final String TABLE_NAME = "exercises_local";
@@ -31,14 +30,14 @@ public class ExercisesLocalColumns implements BaseColumns {
};
public static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + TABLE_NAME;
- public static Exercise getExercise(Cursor c) {
+ public static Exercise fromCursor(Cursor c) {
Exercise e = new Exercise();
- e.setLocalId(c.getInt(c.getColumnIndexOrThrow(ExercisesLocalColumns._ID)));
- e.setLanguage(c.getString(c.getColumnIndexOrThrow(ExercisesLocalColumns.LANGUAGE)));
- e.setDescription(c.getString(c.getColumnIndexOrThrow(ExercisesLocalColumns.DESCRIPTION)));
- e.setExecution(c.getString(c.getColumnIndexOrThrow(ExercisesLocalColumns.EXECUTION)));
- e.setName(c.getString(c.getColumnIndexOrThrow(ExercisesLocalColumns.NAME)));
+ e.setLocalId(c.getInt(c.getColumnIndexOrThrow(ExerciseLocalColumns._ID)));
+ e.setLanguage(c.getString(c.getColumnIndexOrThrow(ExerciseLocalColumns.LANGUAGE)));
+ e.setDescription(c.getString(c.getColumnIndexOrThrow(ExerciseLocalColumns.DESCRIPTION)));
+ e.setExecution(c.getString(c.getColumnIndexOrThrow(ExerciseLocalColumns.EXECUTION)));
+ e.setName(c.getString(c.getColumnIndexOrThrow(ExerciseLocalColumns.NAME)));
return e;
}
@@ -47,13 +46,15 @@ public class ExercisesLocalColumns implements BaseColumns {
ContentValues values = new ContentValues();
if(record.getLocalId() != -1) {
- values.put(ExercisesLocalColumns._ID, record.getLocalId());
+ values.put(ExerciseLocalColumns._ID, record.getLocalId());
}
- values.put(ExercisesLocalColumns.LANGUAGE, record.getLanguage());
- values.put(ExercisesLocalColumns.DESCRIPTION, record.getDescription());
- values.put(ExercisesLocalColumns.EXECUTION, record.getExecution());
- values.put(ExercisesLocalColumns.NAME, record.getName());
+ values.put(ExerciseLocalColumns.LANGUAGE, record.getLanguage());
+ values.put(ExerciseLocalColumns.DESCRIPTION, record.getDescription());
+ values.put(ExerciseLocalColumns.EXECUTION, record.getExecution());
+ values.put(ExerciseLocalColumns.NAME, record.getName());
return values;
}
+
+ private ExerciseLocalColumns() {}
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseSetColumns.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseSetColumns.java
new file mode 100644
index 0000000..e14306b
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseSetColumns.java
@@ -0,0 +1,49 @@
+package org.secuso.privacyfriendlybreakreminder.database.columns;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.provider.BaseColumns;
+
+import org.secuso.privacyfriendlybreakreminder.database.data.Exercise;
+import org.secuso.privacyfriendlybreakreminder.database.data.ExerciseSet;
+
+/**
+ * Created by Christopher Beckmann on 03.09.2017.
+ */
+
+public final class ExerciseSetColumns {
+
+ public static final String TABLE_NAME = "exercise_set";
+
+ public static final String _ID = "exercise_set_id";
+ public static final String NAME = "name";
+
+ public static final String[] PROJECTION = {
+ _ID,
+ NAME
+ };
+
+ public static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + TABLE_NAME;
+
+ public static ExerciseSet fromCursor(Cursor c) {
+ ExerciseSet e = new ExerciseSet();
+
+ e.setId(c.getInt(c.getColumnIndexOrThrow(ExerciseSetColumns._ID)));
+ e.setName(c.getString(c.getColumnIndexOrThrow(ExerciseSetColumns.NAME)));
+
+ return e;
+ }
+
+ public static ContentValues getValues(ExerciseSet record) {
+ ContentValues values = new ContentValues();
+
+ if(record.getId() != -1) {
+ values.put(ExerciseSetColumns._ID, record.getId());
+ }
+ values.put(ExerciseSetColumns.NAME, record.getName());
+
+ return values;
+ }
+
+ private ExerciseSetColumns() {}
+}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/Exercise.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/Exercise.java
index bd023d9..d43b898 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/Exercise.java
+++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/Exercise.java
@@ -4,12 +4,12 @@ package org.secuso.privacyfriendlybreakreminder.database.data;
public class Exercise {
private int id;
private int localId;
- private String section;
- private String execution;
- private String description;
- private String name;
+ private String section = "";
+ private String execution = "";
+ private String description = "";
+ private String name = "";
private String imageID;
- private String language;
+ private String language = "";
public Exercise() {
this.localId = -1;
@@ -60,4 +60,35 @@ public class Exercise {
public void setDescription(String description) {
this.description = description;
}
+
+ @Override
+ public int hashCode() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(id)
+ .append(localId)
+ .append(section)
+ .append(execution)
+ .append(description)
+ .append(name)
+ .append(imageID)
+ .append(language);
+
+ return sb.toString().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if(!(object instanceof Exercise)) return false;
+ Exercise other = (Exercise) object;
+
+ return this.id != other.id
+ || this.localId != other.localId
+ || !this.section.equals(other.section)
+ || !this.execution.equals(other.execution)
+ || !this.description.equals(other.description)
+ || !this.name.equals(other.name)
+ || !this.imageID.equals(other.imageID)
+ || !this.language.equals(other.language);
+ }
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/ExerciseSet.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/ExerciseSet.java
new file mode 100644
index 0000000..acf1568
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/ExerciseSet.java
@@ -0,0 +1,53 @@
+package org.secuso.privacyfriendlybreakreminder.database.data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Christopher Beckmann on 03.09.2017.
+ */
+
+public class ExerciseSet {
+ private int id = -1;
+ private String name = null;
+ private List exercises = new ArrayList<>();
+
+ public ExerciseSet() {}
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void add(Exercise exercise) {
+ if(!exercises.contains(exercise)) {
+ exercises.add(exercise);
+ }
+ }
+
+ public void remove(Exercise exercise) {
+ if(exercises.contains(exercise)) {
+ exercises.remove(exercise);
+ }
+ }
+
+ public void get(int index) {
+ exercises.get(index);
+ }
+
+ public int size() {
+ return exercises.size();
+ }
+
+}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/service/TimerService.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/service/TimerService.java
index 052041e..a7920fb 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/service/TimerService.java
+++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/service/TimerService.java
@@ -22,6 +22,7 @@ import org.secuso.privacyfriendlybreakreminder.R;
import org.secuso.privacyfriendlybreakreminder.activities.TimerActivity;
import java.io.FileDescriptor;
+import java.util.Locale;
import java.util.Timer;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
@@ -74,6 +75,7 @@ public class TimerService extends Service {
mTimer = createTimer(duration);
mTimer.start();
isRunning = true;
+ sendBroadcast(buildBroadcast());
}
}
@@ -81,6 +83,7 @@ public class TimerService extends Service {
if(isRunning) {
mTimer.cancel();
isRunning = false;
+ sendBroadcast(buildBroadcast());
}
}
@@ -89,17 +92,29 @@ public class TimerService extends Service {
mTimer = createTimer(remainingDuration);
mTimer.start();
isRunning = true;
+ sendBroadcast(buildBroadcast());
}
}
public synchronized void resetTimer() {
if(isRunning) {
mTimer.cancel();
- isRunning = false;
- remainingDuration = 0;
+ mTimer = createTimer(initialDuration);
+ mTimer.start();
}
+ remainingDuration = initialDuration;
+ sendBroadcast(buildBroadcast());
}
+ public synchronized void stopAndResetTimer() {
+ if(isRunning) mTimer.cancel();
+ isRunning = false;
+ remainingDuration = initialDuration;
+ sendBroadcast(buildBroadcast());
+ }
+
+ public synchronized boolean isPaused() { return !isRunning && remainingDuration > 0 && remainingDuration != initialDuration; }
+
public synchronized boolean isRunning() {
return isRunning;
}
@@ -111,29 +126,36 @@ public class TimerService extends Service {
@Override
public void onTick(long millisUntilFinished) {
- int secondsUntilFinished = (int) Math.ceil(millisUntilFinished / 1000.0);
+ synchronized (TimerService.this) {
+ remainingDuration = millisUntilFinished;
+ }
- remainingDuration = millisUntilFinished;
-
- Intent broadcast = new Intent(TIMER_BROADCAST);
- broadcast.putExtra("onTickMillis", millisUntilFinished);
- broadcast.putExtra("countdown_seconds", secondsUntilFinished);
- sendBroadcast(broadcast);
+ sendBroadcast(buildBroadcast());
}
@Override
public void onFinish() {
- // TODO: finish broadcast
- Intent broadcast = new Intent(TIMER_BROADCAST);
+ Intent broadcast = buildBroadcast();
broadcast.putExtra("done", true);
- broadcast.putExtra("onTickMillis", 0);
- broadcast.putExtra("countdown_seconds", 0);
- sendBroadcast(broadcast);
- resetTimer();
+ sendBroadcast(buildBroadcast());
+
+ stopAndResetTimer();
}
};
}
+ private synchronized Intent buildBroadcast() {
+ int secondsUntilFinished = (int) Math.ceil(remainingDuration / 1000.0);
+
+ Intent broadcast = new Intent(TIMER_BROADCAST);
+ broadcast.putExtra("onTickMillis", remainingDuration);
+ broadcast.putExtra("initialMillis", initialDuration);
+ broadcast.putExtra("countdown_seconds", secondsUntilFinished);
+ broadcast.putExtra("isRunning", isRunning());
+ broadcast.putExtra("isPaused", isPaused());
+ return (broadcast);
+ }
+
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
@@ -157,15 +179,9 @@ public class TimerService extends Service {
int seconds = secondsUntilFinished % 60;
int minutes = minutesUntilFinished % 60;
- StringBuilder sb = new StringBuilder();
+ String time = String.format(Locale.US, "%02d:%02d:%02d", hours, minutes, seconds);
- if(hours > 0) sb.append(hours).append(":");
- if(minutes < 10) sb.append(0);
- sb.append(minutes).append(":");
- if(seconds < 10) sb.append(0);
- sb.append(seconds);
-
- builder.setContentText(sb.toString());
+ builder.setContentText(time);
builder.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, TimerActivity.class), FLAG_UPDATE_CURRENT));
builder.setColor(ContextCompat.getColor(this, R.color.colorAccent));
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
@@ -189,6 +205,10 @@ public class TimerService extends Service {
context.startService(new Intent(context.getApplicationContext(), TimerService.class));
}
+ public synchronized long getRemainingDuration() {
+ return remainingDuration;
+ }
+
public class TimerServiceBinder extends Binder {
public TimerService getService() {
return TimerService.this;
diff --git a/app/src/main/res/drawable-hdpi/ic_play_arrow_black_48dp.png b/app/src/main/res/drawable-hdpi/ic_play_arrow_black_48dp.png
new file mode 100644
index 0000000..5345ee3
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_play_arrow_black_48dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_play_arrow_black_48dp.png b/app/src/main/res/drawable-mdpi/ic_play_arrow_black_48dp.png
new file mode 100644
index 0000000..f208795
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_play_arrow_black_48dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_play_arrow_black_48dp.png b/app/src/main/res/drawable-xhdpi/ic_play_arrow_black_48dp.png
new file mode 100644
index 0000000..d12d495
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_arrow_black_48dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_48dp.png b/app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_48dp.png
new file mode 100644
index 0000000..1c57756
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_48dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_48dp.png
new file mode 100644
index 0000000..904bbdb
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_48dp.png differ
diff --git a/app/src/main/res/drawable/circular_small.xml b/app/src/main/res/drawable/circular_small.xml
new file mode 100644
index 0000000..7e6da40
--- /dev/null
+++ b/app/src/main/res/drawable/circular_small.xml
@@ -0,0 +1,22 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_exercise.xml b/app/src/main/res/layout/activity_exercise.xml
new file mode 100644
index 0000000..00ec4a7
--- /dev/null
+++ b/app/src/main/res/layout/activity_exercise.xml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_exercise_set.xml b/app/src/main/res/layout/activity_exercise_set.xml
new file mode 100644
index 0000000..31a213e
--- /dev/null
+++ b/app/src/main/res/layout/activity_exercise_set.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_timer.xml b/app/src/main/res/layout/activity_timer.xml
index db5923d..5d1f4be 100644
--- a/app/src/main/res/layout/activity_timer.xml
+++ b/app/src/main/res/layout/activity_timer.xml
@@ -4,33 +4,112 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_margin="@dimen/activity_horizontal_margin"
+ android:padding="0dp"
+ android:layout_margin="0dp"
tools:context="org.secuso.privacyfriendlybreakreminder.activities.TimerActivity">
+
+
+
+
+ android:layout_marginEnd="8dp"
+ app:layout_constraintHorizontal_bias="0.0"
+ android:layout_marginTop="0dp"
+ app:layout_constraintTop_toBottomOf="@id/exercise">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:visibility="invisible" />
+ android:textSize="36sp"
+ android:textStyle="bold"
+ android:visibility="invisible" />
+ app:layout_constraintHorizontal_bias="0.33"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp" />
+ app:layout_constraintHorizontal_bias="0.66"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp" />
diff --git a/app/src/main/res/layout/layout_exercise.xml b/app/src/main/res/layout/layout_exercise.xml
new file mode 100644
index 0000000..0448b91
--- /dev/null
+++ b/app/src/main/res/layout/layout_exercise.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_exercise_set.xml b/app/src/main/res/layout/layout_exercise_set.xml
new file mode 100644
index 0000000..3ae59d2
--- /dev/null
+++ b/app/src/main/res/layout/layout_exercise_set.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 545b9c6..4934cf3 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -13,6 +13,13 @@
- true
+
+