diff --git a/app/res/layout/filepermission.xml b/app/res/layout/filepermission.xml
index 039451d3c..9fbc316f0 100644
--- a/app/res/layout/filepermission.xml
+++ b/app/res/layout/filepermission.xml
@@ -55,24 +55,4 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/org/runnerup/db/DBHelper.java b/app/src/main/org/runnerup/db/DBHelper.java
index 7ffbcad25..69bce9097 100644
--- a/app/src/main/org/runnerup/db/DBHelper.java
+++ b/app/src/main/org/runnerup/db/DBHelper.java
@@ -17,7 +17,6 @@
package org.runnerup.db;
-import android.annotation.TargetApi;
import android.support.v7.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ContentValues;
@@ -27,9 +26,10 @@
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.AsyncTask;
-import android.os.Build;
import android.util.Log;
+import org.json.JSONException;
+import org.json.JSONObject;
import org.runnerup.R;
import org.runnerup.common.util.Constants;
import org.runnerup.db.entities.DBEntity;
@@ -41,7 +41,6 @@
import org.runnerup.export.FunBeatSynchronizer;
import org.runnerup.export.GarminSynchronizer;
import org.runnerup.export.GoogleFitSynchronizer;
-import org.runnerup.export.GooglePlusSynchronizer;
import org.runnerup.export.JoggSESynchronizer;
import org.runnerup.export.MapMyRunSynchronizer;
import org.runnerup.export.NikePlusSynchronizer;
@@ -53,7 +52,9 @@
import org.runnerup.export.RuntasticSynchronizer;
import org.runnerup.export.StravaSynchronizer;
import org.runnerup.util.FileUtil;
+import org.runnerup.workout.FileFormats;
+import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -133,7 +134,7 @@ public class DBHelper extends SQLiteOpenHelper implements
+ (DB.ACCOUNT.NAME + " text not null, ")
+ (DB.ACCOUNT.DESCRIPTION + " text, ") //DBVERSION update: remove
+ (DB.ACCOUNT.URL + " text, ") //DBVERSION update: remove
- + (DB.ACCOUNT.FORMAT + " text not null, ") //DBVERSION update: remove
+ + (DB.ACCOUNT.FORMAT + " text not null, ") // tcx/gpx, for file uploads
+ (DB.ACCOUNT.FLAGS + " integer not null default " + DB.ACCOUNT.DEFAULT_FLAGS + ", ") //Mostly not used but dynamic changes could be stored here
+ (DB.ACCOUNT.ENABLED + " integer not null default 1,") //Account is not hidden/disabled
+ (DB.ACCOUNT.AUTH_METHOD + " text not null, ") //DBVERSION update: remove
@@ -391,7 +392,7 @@ private static void echoDo(SQLiteDatabase arg0, String str) {
}
private void migrateFileSynchronizerInfo(SQLiteDatabase arg0) {
- //Migrate storage of parameters, FORMAT is removed
+ // Migrate storage of parameters
String from[] = { "_id", DB.ACCOUNT.FORMAT, DB.ACCOUNT.AUTH_CONFIG };
String args[] = { FileSynchronizer.NAME };
Cursor c = arg0.query(DB.ACCOUNT.TABLE, from,
@@ -402,14 +403,31 @@ private void migrateFileSynchronizerInfo(SQLiteDatabase arg0) {
if (c.moveToFirst()) {
ContentValues tmp = DBHelper.get(c);
//URL was stored in AUTH_CONFIG previously, FORMAT migrated too
- String oldUrl = tmp.getAsString(DB.ACCOUNT.AUTH_CONFIG);
+ String oldAuthConfig = tmp.getAsString(DB.ACCOUNT.AUTH_CONFIG);
//DBVERSION update, not needed in onUpgrade()
- if (oldUrl.startsWith("/")) {
- tmp.put(DB.ACCOUNT.URL, oldUrl);
+ if (oldAuthConfig.startsWith("/")) {
+ tmp.put(DB.ACCOUNT.URL, oldAuthConfig);
String authConfig = FileSynchronizer.contentValuesToAuthConfig(tmp);
tmp = new ContentValues();
tmp.put(DB.ACCOUNT.AUTH_CONFIG, authConfig);
+ tmp.put(DB.ACCOUNT.FORMAT, FileFormats.DEFAULT_FORMATS.toString());
arg0.update(DB.ACCOUNT.TABLE, tmp, DB.ACCOUNT.NAME + " = ?", args);
+ } else {
+ try {
+ // Check if AUTH_CONFIG contains deprecated FORMAT field
+ JSONObject authcfg = new JSONObject(oldAuthConfig);
+ String format = authcfg.optString(DB.ACCOUNT.FORMAT, null);
+ if (format != null) {
+ // Move deprecated FORMAT field in AUTH_CONFIG to ACCOUNT.FORMAT
+ authcfg.put(DB.ACCOUNT.FORMAT, null);
+ tmp = new ContentValues();
+ tmp.put(DB.ACCOUNT.AUTH_CONFIG, authcfg.toString());
+ tmp.put(DB.ACCOUNT.FORMAT, format);
+ arg0.update(DB.ACCOUNT.TABLE, tmp, DB.ACCOUNT.NAME + " = ?", args);
+ }
+ } catch (JSONException e) {
+ Log.w("DBHelper", "Failed to parse File auth config", e);
+ }
}
}
c.close();
@@ -486,8 +504,8 @@ private static void insertAccount(SQLiteDatabase arg0, String name, int enabled,
if (flags >= 0) {
arg1.put(DB.ACCOUNT.FLAGS, flags);
}
+ arg1.put(DB.ACCOUNT.FORMAT, FileFormats.DEFAULT_FORMATS.toString());
//DBVERSION update, must provide dummy data
- arg1.put(DB.ACCOUNT.FORMAT, "tcx");
arg1.put(DB.ACCOUNT.AUTH_METHOD, "dummy");
//SQLite has no UPSERT command. Optimize for no change.
diff --git a/app/src/main/org/runnerup/export/DropboxSynchronizer.java b/app/src/main/org/runnerup/export/DropboxSynchronizer.java
index c5ae8f78b..c43c17a37 100644
--- a/app/src/main/org/runnerup/export/DropboxSynchronizer.java
+++ b/app/src/main/org/runnerup/export/DropboxSynchronizer.java
@@ -17,6 +17,7 @@
package org.runnerup.export;
import android.app.Activity;
import android.content.ContentValues;
+import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
@@ -28,17 +29,17 @@
import org.runnerup.R;
import org.runnerup.common.util.Constants;
import org.runnerup.common.util.Constants.DB;
+import org.runnerup.db.PathSimplifier;
+import org.runnerup.export.format.GPX;
import org.runnerup.export.format.TCX;
import org.runnerup.export.oauth2client.OAuth2Activity;
import org.runnerup.export.oauth2client.OAuth2Server;
import org.runnerup.export.util.SyncHelper;
+import org.runnerup.workout.FileFormats;
import org.runnerup.workout.Sport;
import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.HttpURLConnection;
@@ -59,11 +60,16 @@ public class DropboxSynchronizer extends DefaultSynchronizer implements OAuth2Se
private long id = 0;
private String access_token = null;
+ private FileFormats mFormat;
+ private PathSimplifier simplifier = null;
- DropboxSynchronizer() {
+ DropboxSynchronizer(Context context) {
if (ENABLED == 0) {
Log.w(NAME, "No client id configured in this build");
}
+ this.simplifier = PathSimplifier.isEnabledForExportGpx(context) ?
+ new PathSimplifier(context, true) :
+ null;
}
@Override
@@ -129,6 +135,7 @@ public void init(ContentValues config) {
String authConfig = config.getAsString(DB.ACCOUNT.AUTH_CONFIG);
if (authConfig != null) {
try {
+ mFormat = new FileFormats(config.getAsString(DB.ACCOUNT.FORMAT));
JSONObject tmp = new JSONObject(authConfig);
parseAuthData(tmp);
} catch (Exception e) {
@@ -216,6 +223,73 @@ private String getDesc(SQLiteDatabase db, final long mID) {
return desc;
}
+ // upload a single file
+ private Status uploadFile(SQLiteDatabase db, final long mID, Sport sport,
+ StringWriter writer, String fileExt)
+ throws IOException, JSONException {
+
+ Status s;
+
+ // Upload to default directory /Apps/RunnerUp
+ String file = String.format(Locale.getDefault(), "/RunnerUp_%s_%04d_%s.%s",
+ android.os.Build.MODEL.replaceAll("\\s","_"), mID, sport.TapiriikType(),
+ fileExt);
+
+ HttpURLConnection conn = (HttpURLConnection) new URL(UPLOAD_URL).openConnection();
+ conn.setDoOutput(true);
+ conn.setRequestMethod(RequestMethod.POST.name());
+ conn.addRequestProperty("Content-Type", "application/octet-stream");
+ conn.setRequestProperty("Authorization", "Bearer " + access_token);
+ JSONObject parameters = new JSONObject();
+ try {
+ parameters.put("path", file);
+ parameters.put("mode", "add");
+ parameters.put("autorename", true);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ return Status.ERROR;
+ }
+ conn.addRequestProperty("Dropbox-API-Arg", parameters.toString());
+ OutputStream out = new BufferedOutputStream(conn.getOutputStream());
+ out.write(writer.getBuffer().toString().getBytes());
+ out.flush();
+ out.close();
+
+ int responseCode = conn.getResponseCode();
+ String amsg = conn.getResponseMessage();
+ Log.v(getName(), "code: " + responseCode + ", amsg: " + amsg+" ");
+
+ JSONObject obj = SyncHelper.parse(conn, getName());
+
+ if (obj != null && responseCode >= HttpURLConnection.HTTP_OK && responseCode < HttpURLConnection.HTTP_MULT_CHOICE) {
+ s = Status.OK;
+ s.activityId = mID;
+ if (obj.has("id")) {
+ // Note: duplicate will not set activity_id
+ s.externalId = noNullStr(obj.getString("id"));
+ if (s.externalId != null) {
+ s.externalIdStatus = ExternalIdStatus.OK;
+ }
+ }
+ return s;
+ }
+ String error = obj != null && obj.has("error") ?
+ noNullStr(obj.getString("error")) :
+ "";
+ Log.e(getName(),"Error uploading, code: " +
+ responseCode + ", amsg: " + amsg + " " + error + ", json: " + (obj == null ? "" : obj));
+ if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ // token no longer valid
+ access_token = null;
+ s = Status.NEED_AUTH;
+ s.authMethod = AuthMethod.OAUTH2;
+ } else {
+ s = Status.ERROR;
+ }
+
+ return s;
+ }
+
@Override
public Status upload(SQLiteDatabase db, final long mID) {
Status s = connect();
@@ -225,96 +299,50 @@ public Status upload(SQLiteDatabase db, final long mID) {
Sport sport = Sport.RUNNING;
try {
- String[] columns = {
- Constants.DB.ACTIVITY.SPORT
- };
- Cursor c = null;
- try {
- c = db.query(Constants.DB.ACTIVITY.TABLE, columns, "_id = " + mID,
- null, null, null, null);
- if (c.moveToFirst()) {
- sport = Sport.valueOf(c.getInt(0));
- }
- } finally {
- if (c != null) {
- c.close();
- }
- }
- // Upload to default directory /Apps/RunnerUp
- String file = String.format(Locale.getDefault(), "/RunnerUp_%s_%04d_%s.tcx",
- android.os.Build.MODEL.replaceAll("\\s","_"), mID, sport.TapiriikType());
- StringWriter writer = new StringWriter();
- TCX tcx = new TCX(db);
- tcx.export(mID, writer);
-
- HttpURLConnection conn = (HttpURLConnection) new URL(UPLOAD_URL).openConnection();
- conn.setDoOutput(true);
- conn.setRequestMethod(RequestMethod.POST.name());
- conn.addRequestProperty("Content-Type", "application/octet-stream");
- conn.setRequestProperty("Authorization", "Bearer " + access_token);
- JSONObject parameters = new JSONObject();
+ String[] columns = { Constants.DB.ACTIVITY.SPORT };
+ Cursor c = null;
try {
- parameters.put("path", file);
- parameters.put("mode", "add");
- parameters.put("autorename", true);
- } catch (JSONException e) {
- e.printStackTrace();
- return Status.ERROR;
- }
- conn.addRequestProperty("Dropbox-API-Arg", parameters.toString());
- OutputStream out = new BufferedOutputStream(conn.getOutputStream());
- out.write(writer.getBuffer().toString().getBytes());
- out.flush();
- out.close();
-
- int responseCode = conn.getResponseCode();
- String amsg = conn.getResponseMessage();
- Log.v(getName(), "code: " + responseCode + ", amsg: " + amsg+" ");
-
- JSONObject obj = SyncHelper.parse(conn, getName());
-
- if (obj != null && responseCode >= HttpURLConnection.HTTP_OK && responseCode < HttpURLConnection.HTTP_MULT_CHOICE) {
- s = Status.OK;
- s.activityId = mID;
- if (obj.has("id")) {
- // Note: duplicate will not set activity_id
- s.externalId = noNullStr(obj.getString("id"));
- if (s.externalId != null) {
- s.externalIdStatus = ExternalIdStatus.OK;
- }
+ c = db.query(Constants.DB.ACTIVITY.TABLE, columns, "_id = " + mID,
+ null, null, null, null);
+ if (c.moveToFirst()) {
+ sport = Sport.valueOf(c.getInt(0));
+ }
+ } finally {
+ if (c != null) {
+ c.close();
}
- return s;
}
- String error = obj != null && obj.has("error") ?
- noNullStr(obj.getString("error")) :
- "";
- Log.e(getName(),"Error uploading, code: " +
- responseCode + ", amsg: " + amsg + " " + error + ", json: " + (obj == null ? "" : obj));
- if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
- // token no longer valid
- access_token = null;
- s = Status.NEED_AUTH;
- s.authMethod = AuthMethod.OAUTH2;
+
+ StringWriter writer = new StringWriter();
+ if (mFormat.contains(FileFormats.TCX)) {
+ TCX tcx = new TCX(db);
+ tcx.export(mID, writer);
+ s = uploadFile(db, mID, sport, writer, FileFormats.TCX.getValue());
+ }
+ if (s == Status.OK && mFormat.contains(FileFormats.GPX)) {
+ GPX gpx = new GPX(db, true, true, simplifier);
+ gpx.export(mID, writer);
+ s = uploadFile(db, mID, sport, writer, FileFormats.GPX.getValue());
}
- s = Status.ERROR;
- return s;
- } catch (IOException e) {
- s = Status.ERROR;
- s.ex = e;
- } catch (JSONException e) {
+ } catch (Exception e) {
+ Log.e(getName(),"Error uploading, exception: ", e);
s = Status.ERROR;
s.ex = e;
}
-
- s.ex.printStackTrace();
return s;
}
@Override
public boolean checkSupport(Feature f) {
- return f == Feature.UPLOAD;
+ switch (f) {
+ case UPLOAD:
+ case FILE_FORMAT:
+ return true;
+ default:
+ return false;
+ }
}
}
diff --git a/app/src/main/org/runnerup/export/FileSynchronizer.java b/app/src/main/org/runnerup/export/FileSynchronizer.java
index 32c6b53ee..753113209 100644
--- a/app/src/main/org/runnerup/export/FileSynchronizer.java
+++ b/app/src/main/org/runnerup/export/FileSynchronizer.java
@@ -33,6 +33,7 @@
import org.runnerup.db.PathSimplifier;
import org.runnerup.export.format.GPX;
import org.runnerup.export.format.TCX;
+import org.runnerup.workout.FileFormats;
import org.runnerup.workout.Sport;
import java.io.BufferedOutputStream;
@@ -50,7 +51,7 @@ public class FileSynchronizer extends DefaultSynchronizer {
private long id = 0;
private String mPath;
- private String mFormat;
+ private FileFormats mFormat;
private PathSimplifier simplifier = null;
FileSynchronizer() {}
@@ -86,8 +87,6 @@ public String getPublicUrl() {
static public String contentValuesToAuthConfig(ContentValues config) {
FileSynchronizer f = new FileSynchronizer();
f.mPath = config.getAsString(DB.ACCOUNT.URL);
- f.mFormat = config.getAsString(DB.ACCOUNT.FORMAT);
-
return f.getAuthConfig();
}
@@ -96,9 +95,9 @@ public void init(ContentValues config) {
String authConfig = config.getAsString(DB.ACCOUNT.AUTH_CONFIG);
if (authConfig != null) {
try {
+ mFormat = new FileFormats(config.getAsString(DB.ACCOUNT.FORMAT));
JSONObject tmp = new JSONObject(authConfig);
mPath = tmp.optString(DB.ACCOUNT.URL, null);
- mFormat = tmp.optString(DB.ACCOUNT.FORMAT);
} catch (JSONException e) {
Log.w(getName(), "init: Dropping config due to failure to parse json from " + authConfig + ", " + e);
}
@@ -112,9 +111,8 @@ public String getAuthConfig() {
if (isConfigured()) {
try {
tmp.put(DB.ACCOUNT.URL, mPath);
- tmp.put(DB.ACCOUNT.FORMAT, mFormat);
} catch (JSONException e) {
- Log.w(getName(), "getAuthConfig: Failure to create json for " + mPath + ", " + mFormat + ", " + e);
+ Log.w(getName(), "getAuthConfig: Failure to create json for " + mPath + ", " + e);
}
}
return tmp.toString();
@@ -122,7 +120,7 @@ public String getAuthConfig() {
@Override
public boolean isConfigured() {
- return !TextUtils.isEmpty(mPath) && !TextUtils.isEmpty(mFormat);
+ return !TextUtils.isEmpty(mPath);
}
@Override
@@ -177,17 +175,17 @@ public Status upload(SQLiteDatabase db, final long mID) {
String fileBase = new File(mPath).getAbsolutePath() + File.separator +
String.format(Locale.getDefault(), "RunnerUp_%04d_%s.", mID, sport.TapiriikType());
- if (mFormat.contains("tcx")) {
+ if (mFormat.contains(FileFormats.TCX)) {
TCX tcx = new TCX(db);
- File file = new File(fileBase + "tcx");
+ File file = new File(fileBase + FileFormats.TCX.getValue());
OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
tcx.export(mID, new OutputStreamWriter(out));
s.externalId = Uri.fromFile(file).toString();
s.externalIdStatus = ExternalIdStatus.NONE; //Not working yet
}
- if (mFormat.contains("gpx")) {
+ if (mFormat.contains(FileFormats.GPX)) {
GPX gpx = new GPX(db, true, true, simplifier);
- File file = new File(fileBase + "gpx");
+ File file = new File(fileBase + FileFormats.GPX.getValue());
OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
gpx.export(mID, new OutputStreamWriter(out));
}
@@ -202,6 +200,7 @@ public Status upload(SQLiteDatabase db, final long mID) {
public boolean checkSupport(Feature f) {
switch (f) {
case UPLOAD:
+ case FILE_FORMAT:
return true;
default:
return false;
diff --git a/app/src/main/org/runnerup/export/SyncManager.java b/app/src/main/org/runnerup/export/SyncManager.java
index 5bad6ec02..e8eb0f808 100644
--- a/app/src/main/org/runnerup/export/SyncManager.java
+++ b/app/src/main/org/runnerup/export/SyncManager.java
@@ -216,7 +216,7 @@ public Synchronizer add(ContentValues config) {
} else if (synchronizerName.contentEquals(RunalyzeSynchronizer.NAME)) {
synchronizer = new RunalyzeSynchronizer();
} else if (synchronizerName.contentEquals(DropboxSynchronizer.NAME)) {
- synchronizer = new DropboxSynchronizer();
+ synchronizer = new DropboxSynchronizer(mContext);
} else {
Log.e(getClass().getName(), "synchronizer does not exist: " + synchronizerName);;
}
@@ -476,10 +476,6 @@ private void askFileUrl(final Synchronizer sync) {
final TextView tv1 = (TextView) view.findViewById(R.id.fileuri);
final TextView tvAuthNotice = (TextView) view.findViewById(R.id.textViewAuthNotice);
- final CheckBox cbtcx = (CheckBox) view.findViewById(R.id.tcxformat);
- final CheckBox cbgpx = (CheckBox) view.findViewById(R.id.gpxformat);
- cbtcx.setChecked(true);
-
String path;
if (Build.VERSION.SDK_INT >= 19) {
//noinspection InlinedApi
@@ -508,16 +504,8 @@ private void askFileUrl(final Synchronizer sync) {
@Override
public void onClick(DialogInterface dialog, int which) {
//Set default values
- String format = "";
- if (cbtcx.isChecked()) {
- format = "tcx,";
- }
- if (cbgpx.isChecked()) {
- format += "gpx,";
- }
ContentValues tmp = new ContentValues();
- tmp.put(DB.ACCOUNT.FORMAT, format);
tmp.put(DB.ACCOUNT.URL, tv1.getText().toString());
ContentValues config = new ContentValues();
config.put("_id", sync.getId());
diff --git a/app/src/main/org/runnerup/export/Synchronizer.java b/app/src/main/org/runnerup/export/Synchronizer.java
index 454208033..db5a8c432 100644
--- a/app/src/main/org/runnerup/export/Synchronizer.java
+++ b/app/src/main/org/runnerup/export/Synchronizer.java
@@ -70,8 +70,8 @@ enum Feature {
LIVE, // live feed of activity
SKIP_MAP, // skip map in upload
ACTIVITY_LIST, //list recorded activities
- GET_ACTIVITY //download recorded activity
-
+ GET_ACTIVITY, //download recorded activity
+ FILE_FORMAT // upload as file in different possible formats
}
/**
diff --git a/app/src/main/org/runnerup/view/AccountActivity.java b/app/src/main/org/runnerup/view/AccountActivity.java
index 8f32a6880..9628b42d1 100644
--- a/app/src/main/org/runnerup/view/AccountActivity.java
+++ b/app/src/main/org/runnerup/view/AccountActivity.java
@@ -46,6 +46,7 @@
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
+import android.widget.Toast;
import org.runnerup.R;
import org.runnerup.common.util.Constants;
@@ -57,6 +58,7 @@
import org.runnerup.export.Synchronizer.Status;
import org.runnerup.util.Bitfield;
import org.runnerup.widget.WidgetUtil;
+import org.runnerup.workout.FileFormats;
import java.util.ArrayList;
@@ -67,6 +69,7 @@ public class AccountActivity extends AppCompatActivity implements Constants {
private final ArrayList mCursors = new ArrayList<>();
private long flags;
+ private FileFormats format;
private SyncManager syncManager = null;
private EditText mRunnerUpLiveApiAddress = null;
@@ -132,7 +135,7 @@ private void fillData() {
// Fields from the database (projection)
// Must include the _id column for the adapter to work
String[] from = new String[]{
- "_id", DB.ACCOUNT.NAME, DB.ACCOUNT.FLAGS, DB.ACCOUNT.AUTH_CONFIG
+ "_id", DB.ACCOUNT.NAME, DB.ACCOUNT.FLAGS, DB.ACCOUNT.FORMAT, DB.ACCOUNT.AUTH_CONFIG
};
String args[] = {
@@ -147,6 +150,7 @@ private void fillData() {
ContentValues tmp = DBHelper.get(c);
synchronizer = syncManager.add(tmp);
flags = tmp.getAsLong(DB.ACCOUNT.FLAGS);
+ format = new FileFormats(tmp.getAsString(DB.ACCOUNT.FORMAT));
if (synchronizer == null) {
return;
}
@@ -204,6 +208,18 @@ private void fillData() {
btn.setVisibility(View.GONE);
}
+ if (synchronizer.checkSupport(Synchronizer.Feature.FILE_FORMAT)) {
+ // Add file format checkboxes
+ addRow(getResources().getString(R.string.File_format), null);
+ for (FileFormats.Format f: FileFormats.ALL_FORMATS) {
+ CheckBox cb = new CheckBox(this);
+ cb.setChecked(format.contains(f));
+ cb.setTag(f);
+ cb.setOnCheckedChangeListener(sendCBChecked);
+ addRow(f.getName(), cb);
+ }
+ }
+
if (synchronizer.checkSupport(Synchronizer.Feature.FEED)) {
CheckBox cb = new CheckBox(this);
cb.setTag(DB.ACCOUNT.FLAG_FEED);
@@ -325,17 +341,35 @@ public void onClick(View v) {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
ContentValues tmp = new ContentValues();
- int flag = (Integer) buttonView.getTag();
- switch (flag) {
- case DB.ACCOUNT.FLAG_UPLOAD:
- case DB.ACCOUNT.FLAG_FEED:
- case DB.ACCOUNT.FLAG_LIVE:
- flags = Bitfield.set(flags, flag, isChecked);
- break;
- case DB.ACCOUNT.FLAG_SKIP_MAP:
- flags = Bitfield.set(flags, flag, !isChecked);
+ Object flag = buttonView.getTag();
+ if (flag instanceof FileFormats.Format) {
+ if (isChecked) {
+ format.add((FileFormats.Format) flag);
+ } else {
+ format.remove((FileFormats.Format) flag);
+ // At least one format needed
+ if (TextUtils.isEmpty(format.toString())) {
+ // Recheck unchecked format
+ format.add((FileFormats.Format) flag);
+ buttonView.setChecked(true);
+ Toast.makeText(getApplicationContext(),
+ getResources().getString(R.string.File_need_one_format),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ tmp.put(DB.ACCOUNT.FORMAT, format.toString());
+ } else {
+ switch ((int) flag) {
+ case DB.ACCOUNT.FLAG_UPLOAD:
+ case DB.ACCOUNT.FLAG_FEED:
+ case DB.ACCOUNT.FLAG_LIVE:
+ flags = Bitfield.set(flags, (Integer) flag, isChecked);
+ break;
+ case DB.ACCOUNT.FLAG_SKIP_MAP:
+ flags = Bitfield.set(flags, (Integer) flag, !isChecked);
+ }
+ tmp.put(DB.ACCOUNT.FLAGS, flags);
}
- tmp.put(DB.ACCOUNT.FLAGS, flags);
String args[] = {
mSynchronizerName
};
diff --git a/app/src/main/org/runnerup/view/DetailActivity.java b/app/src/main/org/runnerup/view/DetailActivity.java
index 4c7ccd517..f53e38843 100644
--- a/app/src/main/org/runnerup/view/DetailActivity.java
+++ b/app/src/main/org/runnerup/view/DetailActivity.java
@@ -414,6 +414,7 @@ private void requery() {
+ (" acc." + DB.ACCOUNT.NAME + ", ")
+ (" acc." + DB.ACCOUNT.FLAGS + ", ")
+ (" acc." + DB.ACCOUNT.AUTH_CONFIG + ", ")
+ + (" acc." + DB.ACCOUNT.FORMAT + ", ")
+ (" rep._id as repid, ")
+ (" rep." + DB.EXPORT.ACCOUNT + ", ")
+ (" rep." + DB.EXPORT.ACTIVITY + ", ")
diff --git a/app/src/main/org/runnerup/workout/FileFormats.java b/app/src/main/org/runnerup/workout/FileFormats.java
new file mode 100644
index 000000000..0d06087c4
--- /dev/null
+++ b/app/src/main/org/runnerup/workout/FileFormats.java
@@ -0,0 +1,106 @@
+package org.runnerup.workout;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class FileFormats {
+
+ private final boolean readonly;
+ private String formats;
+
+ public static class Format {
+
+ final private String name;
+ final private String value;
+
+ Format(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+ public String getValue() {
+ return value;
+ }
+ }
+
+ public final static Format GPX = new Format("GPX", "gpx");
+ public final static Format TCX = new Format("TCX", "tcx");
+ public final static List ALL_FORMATS;
+ public final static FileFormats DEFAULT_FORMATS;
+
+ static {
+ List formatList = Arrays.asList(TCX, GPX);
+ ALL_FORMATS = Collections.unmodifiableList(formatList);
+ DEFAULT_FORMATS = new FileFormats(FileFormats.TCX.getValue(), true);
+ }
+
+ public FileFormats() {
+ this(null, false);
+ }
+
+ public FileFormats(String formats) {
+ this(formats, false);
+ }
+
+ private FileFormats(String formats, boolean readonly) {
+ this.readonly = readonly;
+ this.formats = formats == null ? "" : formats;
+ }
+
+ public boolean contains(Format format) {
+ if (format == null) {
+ throw new IllegalArgumentException();
+ }
+ // search for the format type between 2 word boundaries, anywhere in the string
+ return formats.matches(".*\\b" + format.getValue() + "\\b.*");
+ }
+
+ public boolean remove(Format format) {
+ if (format == null) {
+ throw new IllegalArgumentException();
+ }
+ if (readonly) {
+ throw new UnsupportedOperationException();
+ }
+ if (contains(format)) {
+ formats = formats.replaceAll(",?" + format.getValue() + "\\b", "");
+ // cleanup commas
+ formats = formats.replaceAll(",$", "");
+ formats = formats.replaceAll("^,", "");
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public boolean add(Format format) {
+ if (format == null) {
+ throw new IllegalArgumentException();
+ }
+ if (readonly) {
+ throw new UnsupportedOperationException();
+ }
+ if (formats.length() == 0) {
+ formats = format.getValue();
+ return true;
+ } else {
+ if (contains(format)) {
+ return false;
+ } else {
+ formats += "," + format.getValue();
+ // cleanup commas
+ formats = formats.replaceAll(",,", ",");
+ return true;
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return formats;
+ }
+}
diff --git a/app/test/java/org/runnerup/workout/FileFormatsTest.java b/app/test/java/org/runnerup/workout/FileFormatsTest.java
new file mode 100644
index 000000000..5615a0d9b
--- /dev/null
+++ b/app/test/java/org/runnerup/workout/FileFormatsTest.java
@@ -0,0 +1,167 @@
+package org.runnerup.workout;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class FileFormatsTest {
+
+ @Test
+ public void nullConstructor() {
+ FileFormats formats = new FileFormats();
+ assertNotNull(formats.toString());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void nullContains() {
+ FileFormats formats = new FileFormats();
+ formats.contains(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void nullAdd() {
+ FileFormats formats = new FileFormats();
+ formats.add(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void nullRemove() {
+ FileFormats formats = new FileFormats();
+ formats.remove(null);
+ }
+
+ @Test
+ public void defaultNotEmpty() {
+ FileFormats formats = FileFormats.DEFAULT_FORMATS;
+ boolean notEmpty = false;
+ for (FileFormats.Format f: FileFormats.ALL_FORMATS) {
+ notEmpty = notEmpty || formats.contains(f);
+ }
+ assertTrue(notEmpty);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void defaultNoRemove() {
+ FileFormats formats = FileFormats.DEFAULT_FORMATS;
+ formats.remove(FileFormats.TCX);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void defaultNoAdd() {
+ FileFormats formats = FileFormats.DEFAULT_FORMATS;
+ formats.add(FileFormats.GPX);
+ }
+
+
+ @Test
+ public void addOnce() {
+ FileFormats formats;
+ for (FileFormats.Format f: FileFormats.ALL_FORMATS) {
+ formats = new FileFormats();
+ formats.add(f);
+ assertEquals(formats.toString(), f.getValue());
+ }
+ }
+
+ @Test
+ public void addAll() {
+ FileFormats formats = new FileFormats();
+ for (FileFormats.Format f: FileFormats.ALL_FORMATS) {
+ assertFalse(formats.contains(f));
+ }
+ for (FileFormats.Format f: FileFormats.ALL_FORMATS) {
+ formats.add(f);
+ }
+ for (FileFormats.Format f: FileFormats.ALL_FORMATS) {
+ assertTrue(formats.contains(f));
+ }
+ }
+
+ @Test
+ public void addTwice() {
+ FileFormats formats = new FileFormats();
+ for (FileFormats.Format f: FileFormats.ALL_FORMATS) {
+ assertTrue(formats.add(f));
+ }
+ String v1 = formats.toString();
+ for (FileFormats.Format f: FileFormats.ALL_FORMATS) {
+ assertFalse(formats.add(f));
+ }
+ String v2 = formats.toString();
+ assertEquals(v1, v2);
+ }
+
+ @Test
+ public void removeOnce() {
+ FileFormats formats = new FileFormats();
+ String v1 = formats.toString();
+ for (FileFormats.Format f: FileFormats.ALL_FORMATS) {
+ assertTrue(formats.add(f));
+ }
+ for (FileFormats.Format f: FileFormats.ALL_FORMATS) {
+ assertTrue(formats.remove(f));
+ }
+ String v2 = formats.toString();
+ assertEquals(v1, v2);
+ }
+
+ @Test
+ public void removeTwice() {
+ FileFormats formats = new FileFormats();
+ String v1 = formats.toString();
+ for (FileFormats.Format f: FileFormats.ALL_FORMATS) {
+ assertTrue(formats.add(f));
+ }
+ for (FileFormats.Format f: FileFormats.ALL_FORMATS) {
+ assertTrue(formats.remove(f));
+ assertFalse(formats.remove(f));
+ }
+ String v2 = formats.toString();
+ assertEquals(v1, v2);
+ }
+
+ @Test
+ public void legacyCompat() {
+ FileFormats formats;
+ // one format
+ formats = new FileFormats("gpx,");
+ assertTrue(formats.contains(FileFormats.GPX));
+ assertTrue(formats.add(FileFormats.TCX));
+ assertTrue(formats.remove(FileFormats.GPX));
+ assertFalse(formats.contains(FileFormats.GPX));
+ assertTrue(formats.contains(FileFormats.TCX));
+
+ // both formats
+ formats = new FileFormats("gpx,tcx,");
+ String v1 = formats.toString();
+ assertTrue(formats.contains(FileFormats.TCX));
+ assertFalse(formats.add(FileFormats.TCX));
+ String v2 = formats.toString();
+ assertEquals(v1, v2);
+ assertTrue(formats.remove(FileFormats.TCX));
+ assertFalse(formats.contains(FileFormats.TCX));
+ assertEquals(formats.toString(), FileFormats.GPX.getValue());
+ }
+
+ @Test
+ public void futureProof() {
+ // playing with longer list
+ FileFormats formats;
+ formats = new FileFormats("gpx,a,b,c,d,");
+ assertTrue(formats.contains(FileFormats.GPX));
+ assertTrue(formats.add(FileFormats.TCX));
+ assertTrue(formats.remove(FileFormats.GPX));
+ assertFalse(formats.contains(FileFormats.GPX));
+ assertTrue(formats.contains(FileFormats.TCX));
+ assertTrue(formats.add(FileFormats.GPX));
+ assertTrue(formats.contains(FileFormats.GPX));
+ assertTrue(formats.remove(FileFormats.TCX));
+ assertFalse(formats.contains(FileFormats.TCX));
+ assertTrue(formats.remove(FileFormats.GPX));
+ assertFalse(formats.contains(FileFormats.GPX));
+ assertEquals(formats.toString(), ("a,b,c,d"));
+ }
+}
diff --git a/common/src/main/res/values-fr/strings.xml b/common/src/main/res/values-fr/strings.xml
index 7d3ec8204..62143be5d 100644
--- a/common/src/main/res/values-fr/strings.xml
+++ b/common/src/main/res/values-fr/strings.xml
@@ -350,4 +350,6 @@
Algorithme
Choisissez entre rapidité et haute qualité de la simplification de la trace
Simplifier la trace
+ Fichier(s) au format:
+ Sélectionnez au moins un format !
diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml
index 20f3b0109..f0497d74b 100644
--- a/common/src/main/res/values/strings.xml
+++ b/common/src/main/res/values/strings.xml
@@ -351,4 +351,6 @@
Algorithm
Choose fast or high quality path simplification
Simplify path
+ Upload file(s) as:
+ At least one format needed!