From 8e5436fdf0fc8dece98475ce3522fa803f20da21 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sun, 7 Jun 2026 16:08:58 +0200 Subject: [PATCH 01/12] fix: promote to foreground before start service notify as normal in foreground. --- ...ForegroundNotificationDisplayStrategy.java | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/app/src/main/org/runnerup/notification/ForegroundNotificationDisplayStrategy.java b/app/src/main/org/runnerup/notification/ForegroundNotificationDisplayStrategy.java index 93fce0417..d9405b792 100644 --- a/app/src/main/org/runnerup/notification/ForegroundNotificationDisplayStrategy.java +++ b/app/src/main/org/runnerup/notification/ForegroundNotificationDisplayStrategy.java @@ -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; @@ -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); } - ServiceCompat.startForeground(service, notificationId, notification, type); } @Override public void cancel(int notificationId) { + isForeground = false; ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_REMOVE); } } From f31cb4ba01a713e4b3d75d0942c3103ad94f45bf Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sun, 7 Jun 2026 15:56:12 +0200 Subject: [PATCH 02/12] fix: check dbWriter in onResume --- app/src/main/org/runnerup/tracker/Tracker.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/org/runnerup/tracker/Tracker.java b/app/src/main/org/runnerup/tracker/Tracker.java index 284d21402..3c1eb63fb 100644 --- a/app/src/main/org/runnerup/tracker/Tracker.java +++ b/app/src/main/org/runnerup/tracker/Tracker.java @@ -595,6 +595,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); From 3d65ed1b45586a645fa3d2159270eb675e40f069 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sun, 7 Jun 2026 16:33:43 +0200 Subject: [PATCH 03/12] fix: check for null db before saving If onDestroy gets called before saveActivity(), the workout cannot be saved. Avoid crash. --- app/src/main/org/runnerup/tracker/Tracker.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/org/runnerup/tracker/Tracker.java b/app/src/main/org/runnerup/tracker/Tracker.java index 3c1eb63fb..fd6d2f045 100644 --- a/app/src/main/org/runnerup/tracker/Tracker.java +++ b/app/src/main/org/runnerup/tracker/Tracker.java @@ -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); } @@ -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; + } ContentValues tmp = new ContentValues(); if (mHeartbeatNanos > 0) { long avgHR = Math.round(60 * mHeartbeats * 1000 * NANO_IN_MILLI / mHeartbeatNanos); // BPM From b364df916a5dc9d05dcd815d788dddb87c547ab7 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sun, 7 Jun 2026 16:40:04 +0200 Subject: [PATCH 04/12] fix: check for null steps when resuming --- app/src/main/org/runnerup/workout/Step.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/org/runnerup/workout/Step.java b/app/src/main/org/runnerup/workout/Step.java index 037bf3338..d83030bd0 100644 --- a/app/src/main/org/runnerup/workout/Step.java +++ b/app/src/main/org/runnerup/workout/Step.java @@ -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 From 330aa77c64a1466b7d8a289d0e7e086921cdb2ee Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sun, 7 Jun 2026 16:56:18 +0200 Subject: [PATCH 05/12] fix: safe parse double allow comm vs point If no previous default, assume 0 is OK defaults --- .../main/org/runnerup/db/PathSimplifier.java | 18 ++++++++---------- .../runnerup/export/RunKeeperSynchronizer.java | 7 ++----- .../org/runnerup/export/format/RunKeeper.java | 3 ++- app/src/main/org/runnerup/util/SafeParse.java | 7 +++++-- .../main/org/runnerup/view/ManualActivity.java | 2 +- .../org/runnerup/workout/WorkoutBuilder.java | 7 +------ 6 files changed, 19 insertions(+), 25 deletions(-) diff --git a/app/src/main/org/runnerup/db/PathSimplifier.java b/app/src/main/org/runnerup/db/PathSimplifier.java index 512603a40..b24520951 100644 --- a/app/src/main/org/runnerup/db/PathSimplifier.java +++ b/app/src/main/org/runnerup/db/PathSimplifier.java @@ -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 { @@ -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; diff --git a/app/src/main/org/runnerup/export/RunKeeperSynchronizer.java b/app/src/main/org/runnerup/export/RunKeeperSynchronizer.java index b481306c3..d41226285 100644 --- a/app/src/main/org/runnerup/export/RunKeeperSynchronizer.java +++ b/app/src/main/org/runnerup/export/RunKeeperSynchronizer.java @@ -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; @@ -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; diff --git a/app/src/main/org/runnerup/export/format/RunKeeper.java b/app/src/main/org/runnerup/export/format/RunKeeper.java index a1ccc6450..188c67895 100644 --- a/app/src/main/org/runnerup/export/format/RunKeeper.java +++ b/app/src/main/org/runnerup/export/format/RunKeeper.java @@ -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; /** @@ -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); diff --git a/app/src/main/org/runnerup/util/SafeParse.java b/app/src/main/org/runnerup/util/SafeParse.java index dfa5c62ca..4d5cf37e9 100644 --- a/app/src/main/org/runnerup/util/SafeParse.java +++ b/app/src/main/org/runnerup/util/SafeParse.java @@ -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; } - return defaultValue; } /** diff --git a/app/src/main/org/runnerup/view/ManualActivity.java b/app/src/main/org/runnerup/view/ManualActivity.java index 707098140..69d3ebe61 100644 --- a/app/src/main/org/runnerup/view/ManualActivity.java +++ b/app/src/main/org/runnerup/view/ManualActivity.java @@ -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); } diff --git a/app/src/main/org/runnerup/workout/WorkoutBuilder.java b/app/src/main/org/runnerup/workout/WorkoutBuilder.java index 51603f46b..48183eb31 100644 --- a/app/src/main/org/runnerup/workout/WorkoutBuilder.java +++ b/app/src/main/org/runnerup/workout/WorkoutBuilder.java @@ -579,13 +579,8 @@ public static void prepareWorkout(Resources res, SharedPreferences prefs, Workou ? prefs.getBoolean(res.getString(R.string.pref_autolap_active), false) : prefs.getBoolean(res.getString(R.string.pref_step_autolap_active), false); if (autolap) { - double val; String vals = prefs.getString(res.getString(R.string.pref_autolap), "1000"); - try { - val = Double.parseDouble(vals); - } catch (NumberFormatException e) { - val = 0; - } + double val = SafeParse.parseDouble(vals, 0.0); Log.d("WorkoutBuilder", "setAutolap(" + val + ")"); for (StepListEntry s : steps) { if (basic From 8f4f5bf4dde92a800d80a2534143c7093a598134 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Tue, 16 Jun 2026 22:57:14 +0200 Subject: [PATCH 06/12] fix: request permissions only if activity remains No dialog to request permissions if the activity is gone --- app/src/main/org/runnerup/view/StartFragment.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/org/runnerup/view/StartFragment.java b/app/src/main/org/runnerup/view/StartFragment.java index 1f3672090..1a442b4b1 100644 --- a/app/src/main/org/runnerup/view/StartFragment.java +++ b/app/src/main/org/runnerup/view/StartFragment.java @@ -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; @@ -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" From aff07ba20276c4c43f9974e6e415ccca328991a1 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sun, 7 Jun 2026 16:23:52 +0200 Subject: [PATCH 07/12] fix: check activity in oauth2 --- app/src/main/org/runnerup/export/SyncManager.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/org/runnerup/export/SyncManager.java b/app/src/main/org/runnerup/export/SyncManager.java index 1c0ae4679..124edf670 100644 --- a/app/src/main/org/runnerup/export/SyncManager.java +++ b/app/src/main/org/runnerup/export/SyncManager.java @@ -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: From a9b3709a18248706698bdd607a4a83331e4511c6 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sat, 4 Jul 2026 23:35:42 +0200 Subject: [PATCH 08/12] fix: exception showing title for upload spinner --- app/src/main/org/runnerup/export/SyncManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/org/runnerup/export/SyncManager.java b/app/src/main/org/runnerup/export/SyncManager.java index 124edf670..ac43f0731 100644 --- a/app/src/main/org/runnerup/export/SyncManager.java +++ b/app/src/main/org/runnerup/export/SyncManager.java @@ -596,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); From 9cc5ced11e7020ba2f784c7d9ec38388ce82239a Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sat, 4 Jul 2026 23:39:49 +0200 Subject: [PATCH 09/12] fix: livelog exception getting Step likely no steps for this workout --- app/src/main/org/runnerup/workout/RepeatStep.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/org/runnerup/workout/RepeatStep.java b/app/src/main/org/runnerup/workout/RepeatStep.java index 5e5fe0938..dfcdccc97 100644 --- a/app/src/main/org/runnerup/workout/RepeatStep.java +++ b/app/src/main/org/runnerup/workout/RepeatStep.java @@ -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); } From 1f013550e2ce4eea33f1e72e463c9a29978752bf Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sun, 5 Jul 2026 09:35:54 +0200 Subject: [PATCH 10/12] fix: exception for speech after detachment (#1367) As speech is asynch, a fragment may be detached when it occurs. --- app/src/main/org/runnerup/view/AudioCueSettingsFragment.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/org/runnerup/view/AudioCueSettingsFragment.java b/app/src/main/org/runnerup/view/AudioCueSettingsFragment.java index eca2ee565..b6cb93ccd 100644 --- a/app/src/main/org/runnerup/view/AudioCueSettingsFragment.java +++ b/app/src/main/org/runnerup/view/AudioCueSettingsFragment.java @@ -351,9 +351,11 @@ 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() From 8b1ee43a881aa9406a10934a281ec41b0c96ca16 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sat, 4 Jul 2026 22:54:15 +0200 Subject: [PATCH 11/12] fix: sanitize audio settings name Illegal characters causes exceptions --- .../view/AudioCueSettingsFragment.java | 41 ++++++++++++++++--- common/src/main/res/values/strings.xml | 3 +- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/app/src/main/org/runnerup/view/AudioCueSettingsFragment.java b/app/src/main/org/runnerup/view/AudioCueSettingsFragment.java index b6cb93ccd..d5ea23251 100644 --- a/app/src/main/org/runnerup/view/AudioCueSettingsFragment.java +++ b/app/src/main/org/runnerup/view/AudioCueSettingsFragment.java @@ -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; @@ -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); @@ -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) -> { @@ -223,7 +253,7 @@ private void deleteAudioSchemeImpl(String name) { + File.separator + PREFS_DIR + "/" - + name + + sanitizeSettingsName(name) + SUFFIX + ".xml"); //noinspection ResultOfMethodCallIgnored @@ -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; } @@ -357,9 +388,7 @@ private void CreateNewNoTtsAvailableDialog() { if (settingsName == null || settingsName.contentEquals(DEFAULT)) 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); diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index e5183dafe..92502c178 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -156,7 +156,8 @@ Loading Saving Cancelling, please wait… - Are you sure? + Are_you_sure + Delete audio cue? Configure audio cues Edit workouts On the Interval tab, use Recovery instead of Rest (pause) steps From 2e6b7ae2fa81e6a1c52e84e8363c17f76f417505 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sun, 7 Jun 2026 15:37:08 +0200 Subject: [PATCH 12/12] fix: check for illegal characters in new audio scheme names --- .../runnerup/view/AudioCueSettingsFragment.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/org/runnerup/view/AudioCueSettingsFragment.java b/app/src/main/org/runnerup/view/AudioCueSettingsFragment.java index d5ea23251..b8f6487d4 100644 --- a/app/src/main/org/runnerup/view/AudioCueSettingsFragment.java +++ b/app/src/main/org/runnerup/view/AudioCueSettingsFragment.java @@ -351,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; } + createNewAudioScheme(scheme); + updateSortOrder(scheme); + switchTo(scheme); }) .setNegativeButton(org.runnerup.common.R.string.Cancel, (dialog, which) -> {}) .show();