Skip to content
Merged
18 changes: 8 additions & 10 deletions app/src/main/org/runnerup/db/PathSimplifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.Locale;
import org.runnerup.R;
import org.runnerup.common.util.Constants;
import org.runnerup.util.SafeParse;

/** Wrapper for com.goebl.simplify.Simplify. */
public class PathSimplifier {
Expand Down Expand Up @@ -82,16 +83,13 @@ public PathSimplifier(Context context) {
// get user settings

// tolerance in meters (default to R.string.path_simplification_default_tolerance)
double tolerance;
try {
tolerance =
Double.parseDouble(
prefs.getString(
res.getString(R.string.pref_path_simplification_tolerance),
res.getString(R.string.path_simplification_default_tolerance)));
} catch (Exception ex) {
tolerance = Double.parseDouble(res.getString(R.string.path_simplification_default_tolerance));
}
double tolerance =
SafeParse.parseDouble(
prefs.getString(
res.getString(R.string.pref_path_simplification_tolerance),
res.getString(R.string.path_simplification_default_tolerance)),
SafeParse.parseDouble(
res.getString(R.string.path_simplification_default_tolerance), 3.0));
// squared tolerance in meters has to be transformed to tolerance in degrees
this.toleranceDeg = tolerance / ONE_DEGREE;

Expand Down
7 changes: 2 additions & 5 deletions app/src/main/org/runnerup/export/RunKeeperSynchronizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import org.runnerup.export.oauth2client.OAuth2Server;
import org.runnerup.export.util.SyncHelper;
import org.runnerup.util.Formatter;
import org.runnerup.util.SafeParse;
import org.runnerup.util.SyncActivityItem;
import org.runnerup.workout.Sport;

Expand Down Expand Up @@ -556,11 +557,7 @@ private double getLapLength() {
if (prefs.getBoolean(res.getString(R.string.pref_autolap_active), false)) {
String autoLap =
prefs.getString(res.getString(R.string.pref_autolap), String.valueOf(lapLength));
try {
lapLength = Double.parseDouble(autoLap);
} catch (NumberFormatException e) {
return lapLength;
}
lapLength = SafeParse.parseDouble(autoLap, lapLength);
return lapLength;
}
return lapLength;
Expand Down
11 changes: 9 additions & 2 deletions app/src/main/org/runnerup/export/SyncManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,12 @@ private void handleAuth(Callback callback, final Synchronizer l, AuthMethod auth
authCallback = callback;
switch (authMethod) {
case OAUTH2:
mActivity.startActivityForResult(l.getAuthIntent(mActivity), CONFIGURE_REQUEST);
if (mActivity != null) {
mActivity.startActivityForResult(l.getAuthIntent(mActivity), CONFIGURE_REQUEST);
} else {
Log.e(getClass().getName(), "Cannot start auth activity, no Activity context");
handleAuthComplete(l, Status.ERROR);
}
return;
case USER_PASS:
case USER_PASS_URL:
Expand Down Expand Up @@ -591,7 +596,9 @@ private void nextSynchronizer() {
return;
}

mSpinner.setTitle("Uploading (" + pendingSynchronizers.size() + ")");
if (mSpinner != null && mSpinner.isShowing()) {
mSpinner.setTitle("Uploading (" + pendingSynchronizers.size() + ")");
}
final Synchronizer synchronizer = synchronizers.get(pendingSynchronizers.iterator().next());
pendingSynchronizers.remove(synchronizer.getName());
doUpload(synchronizer);
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/org/runnerup/export/format/RunKeeper.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.runnerup.db.entities.LocationEntity;
import org.runnerup.export.RunKeeperSynchronizer;
import org.runnerup.util.JsonWriter;
import org.runnerup.util.SafeParse;
import org.runnerup.workout.Sport;

/**
Expand Down Expand Up @@ -182,7 +183,7 @@ public static ActivityEntity parseToActivity(JSONObject response, double unitMet
newActivity.setComment(response.getString("notes"));
}
newActivity.setTime((long) Float.parseFloat(response.getString("duration")));
newActivity.setDistance(Double.parseDouble(response.getString("total_distance")));
newActivity.setDistance(SafeParse.parseDouble(response.getString("total_distance"), 0.0));

String startTime = response.getString("start_time");
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss", Locale.US);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.Manifest;
import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Build;
Expand All @@ -16,25 +17,35 @@ public ForegroundNotificationDisplayStrategy(Service service) {
this.service = service;
}

private boolean isForeground = false;

@Override
public void notify(int notificationId, Notification notification) {
int type = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (ContextCompat.checkSelfPermission(service, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
type = ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
&& ContextCompat.checkSelfPermission(service, Manifest.permission.ACTIVITY_RECOGNITION)
== PackageManager.PERMISSION_GRANTED) {
type |= ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
if (!isForeground) {
int type = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (ContextCompat.checkSelfPermission(service, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
type = ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
&& ContextCompat.checkSelfPermission(service, Manifest.permission.ACTIVITY_RECOGNITION)
== PackageManager.PERMISSION_GRANTED) {
type |= ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
}
}
ServiceCompat.startForeground(service, notificationId, notification, type);
isForeground = true;
} else {
android.app.NotificationManager notificationManager =
(android.app.NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(notificationId, notification);
}
Comment thread
gerhardol marked this conversation as resolved.
ServiceCompat.startForeground(service, notificationId, notification, type);
}

@Override
public void cancel(int notificationId) {
isForeground = false;
ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_REMOVE);
}
}
16 changes: 15 additions & 1 deletion app/src/main/org/runnerup/tracker/Tracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,12 @@ public void completeActivity(boolean save, Double manualDistance) {
ContentValues tmp = new ContentValues();
tmp.put("deleted", 1);
String[] key = {Long.toString(mActivityId)};
mDB.update(DB.ACTIVITY.TABLE, tmp, "_id = ?", key);

if (mDB == null) {
android.util.Log.e("Tracker", "completeActivity called but mDB is null");
} else {
mDB.update(DB.ACTIVITY.TABLE, tmp, "_id = ?", key);
}
liveLog(DB.LOCATION.TYPE_DISCARD);
}

Expand All @@ -564,6 +569,10 @@ public void completeActivity(boolean save, Double manualDistance) {
}

private void saveActivity(Double manualDistance) {
if (mDB == null) {
android.util.Log.e("Tracker", "saveActivity called but mDB is null");
return;
}
Comment on lines 571 to +575
ContentValues tmp = new ContentValues();
if (mHeartbeatNanos > 0) {
long avgHR = Math.round(60 * mHeartbeats * 1000 * NANO_IN_MILLI / mHeartbeatNanos); // BPM
Expand Down Expand Up @@ -595,6 +604,11 @@ private void saveActivity(Double manualDistance) {
}

private void setNextLocationType(int newType) {
if (mDBWriter == null) {
android.util.Log.w(
"Tracker", "setNextLocationType: mDBWriter is null (newType=" + newType + ")");
return;
}
ContentValues key = mDBWriter.getKey();
key.put(DB.LOCATION.TYPE, newType);
mDBWriter.setKey(key);
Expand Down
7 changes: 5 additions & 2 deletions app/src/main/org/runnerup/util/SafeParse.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,14 @@ public static long parseLong(String string, long defaultValue) {
}

public static double parseDouble(String string, double defaultValue) {
if (string == null) return defaultValue;
try {
return Double.parseDouble(string);
// Normalize input: replace comma with dot to support international format
String normalized = string.replace(',', '.');
return Double.parseDouble(normalized);
} catch (Exception ex) {
return defaultValue;
}
Comment thread
gerhardol marked this conversation as resolved.
return defaultValue;
}

/**
Expand Down
60 changes: 49 additions & 11 deletions app/src/main/org/runnerup/view/AudioCueSettingsFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
Expand Down Expand Up @@ -59,11 +60,39 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
setHasOptionsMenu(true); // this fragment has menu items
}

private String sanitizeSettingsName(String name) {
if (name == null) return null;
return name.replaceAll("[\\\\/:*?\"<>|\\p{Cntrl}]", "_");
}

@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
settingsName = requireArguments().getString("name");
String optionName = requireArguments().getString("name");
settingsName = sanitizeSettingsName(optionName);

if (settingsName != null) {
if (!settingsName.equals(optionName)) {
// illegal names could previously be created, raised exceptions
Log.d(getClass().getName(), "Audio cue name contains illegal characters: " + optionName);
settingsName = optionName;
new AlertDialog.Builder(requireContext())
.setMessage(org.runnerup.common.R.string.Delete_audio_cue)
.setPositiveButton(
org.runnerup.common.R.string.Yes,
(dialog, which) -> {
dialog.dismiss();
deleteAudioScheme();
})
.setNegativeButton(
org.runnerup.common.R.string.No,
(dialog, which) -> {
// Do nothing but close the dialog
dialog.dismiss();
})
.show();
return;
}

PreferenceManager prefMgr = getPreferenceManager();
prefMgr.setSharedPreferencesName(settingsName + SUFFIX);
prefMgr.setSharedPreferencesMode(MODE_PRIVATE);
Expand Down Expand Up @@ -182,8 +211,9 @@ public boolean onOptionsItemSelected(MenuItem item) {
createNewAudioSchemeDialog();
return true;
}
// deleteMenuItem selected
new AlertDialog.Builder(requireContext())
.setMessage(org.runnerup.common.R.string.Are_you_sure)
.setMessage(org.runnerup.common.R.string.Delete_audio_cue)
.setPositiveButton(
org.runnerup.common.R.string.Yes,
(dialog, which) -> {
Expand Down Expand Up @@ -223,7 +253,7 @@ private void deleteAudioSchemeImpl(String name) {
+ File.separator
+ PREFS_DIR
+ "/"
+ name
+ sanitizeSettingsName(name)
+ SUFFIX
+ ".xml");
//noinspection ResultOfMethodCallIgnored
Expand Down Expand Up @@ -291,6 +321,7 @@ private void switchTo(String name) {
}

if (name != null && settingsName != null && name.contentEquals(settingsName)) {
Log.e(getClass().getName(), "Settings name: " + settingsName + " do not match: " + name);
return;
}

Expand Down Expand Up @@ -320,11 +351,18 @@ private void createNewAudioSchemeDialog() {
org.runnerup.common.R.string.OK,
(dialog, which) -> {
String scheme = editText.getText().toString();
if (!scheme.contentEquals("")) {
createNewAudioScheme(scheme);
updateSortOrder(scheme);
switchTo(scheme);
if (!scheme.equals(sanitizeSettingsName(scheme))
|| scheme.isEmpty()
|| scheme.contains("/")
|| scheme.contains("\\")
|| scheme.contains("..")) {
Log.d(
getClass().getName(), "Audio cue name contains illegal characters: " + scheme);
return;
}
Comment on lines 353 to 362
createNewAudioScheme(scheme);
updateSortOrder(scheme);
switchTo(scheme);
})
.setNegativeButton(org.runnerup.common.R.string.Cancel, (dialog, which) -> {})
.show();
Expand All @@ -351,13 +389,13 @@ private void CreateNewNoTtsAvailableDialog() {
return;
}

Context context = getContext();
if (context == null) return;
SharedPreferences prefs;
if (settingsName == null || settingsName.contentEquals(DEFAULT))
prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
prefs = PreferenceManager.getDefaultSharedPreferences(context);
else
prefs =
requireContext()
.getSharedPreferences(settingsName + SUFFIX, Context.MODE_PRIVATE);
prefs = context.getSharedPreferences(settingsName + SUFFIX, Context.MODE_PRIVATE);
final boolean mute =
prefs.getBoolean(getResources().getString(R.string.pref_mute_bool), false);

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/org/runnerup/view/ManualActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ final void saveEntry() {
}
double dist = 0;
if (distance.length() > 0) {
dist = Double.parseDouble(distance.toString()); // convert to
dist = SafeParse.parseDouble(distance.toString(), 0.0); // convert to
// meters
save.put(DB.ACTIVITY.DISTANCE, dist);
}
Expand Down
10 changes: 7 additions & 3 deletions app/src/main/org/runnerup/view/StartFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.runnerup.view;

import android.Manifest;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
Expand Down Expand Up @@ -857,9 +858,12 @@ private boolean checkPermissions(boolean popup) {
builder
.setPositiveButton(
org.runnerup.common.R.string.OK,
(dialog, id) ->
ActivityCompat.requestPermissions(
requireActivity(), permissions, REQUEST_LOCATION))
(dialog, id) -> {
Activity activity = getActivity();
if (activity != null) {
ActivityCompat.requestPermissions(activity, permissions, REQUEST_LOCATION);
}
})
.setMessage(
baseMessage
+ "\n"
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/org/runnerup/workout/RepeatStep.java
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,17 @@ public void onComplete(Scope scope, Workout s) {

@Override
public double getDistance(Workout w, Scope s) {
if (currentStep < 0 || currentStep >= steps.size()) {
return 0.0;
}
return steps.get(currentStep).getDistance(w, s);
}

@Override
public double getTime(Workout w, Scope s) {
if (currentStep < 0 || currentStep >= steps.size()) {
return 0.0;
}
return steps.get(currentStep).getTime(w, s);
Comment thread
gerhardol marked this conversation as resolved.
}

Expand Down
6 changes: 5 additions & 1 deletion app/src/main/org/runnerup/workout/Step.java
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,11 @@ public void onResume(Workout s) {
for (Trigger t : triggers) {
t.onResume(s);
}
s.tracker.resume();
if (s.tracker != null) {
s.tracker.resume();
} else {
android.util.Log.w("Step", "onResume: s.tracker is null");
}
}

@Override
Expand Down
Loading