diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index a59f897..d0f9f2a 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -11,7 +11,7 @@ android { namespace 'btools.routingapp' applicationId "btools.routingapp" - versionCode 47 + versionCode 48 versionName project.version resValue('string', 'app_version', defaultConfig.versionName) @@ -89,19 +89,20 @@ android { dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation "androidx.constraintlayout:constraintlayout:2.1.4" - implementation 'androidx.work:work-runtime:2.8.0' + implementation 'androidx.work:work-runtime:2.8.1' implementation 'com.google.android.material:material:1.8.0' implementation project(':brouter-mapaccess') implementation project(':brouter-core') implementation project(':brouter-expressions') implementation project(':brouter-util') + implementation 'androidx.preference:preference:1.2.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - androidTestImplementation 'androidx.work:work-testing:2.8.0' + androidTestImplementation 'androidx.work:work-testing:2.8.1' } gradle.projectsEvaluated { diff --git a/brouter-routing-app/src/main/AndroidManifest.xml b/brouter-routing-app/src/main/AndroidManifest.xml index 842b8d9..bf68f8c 100644 --- a/brouter-routing-app/src/main/AndroidManifest.xml +++ b/brouter-routing-app/src/main/AndroidManifest.xml @@ -18,6 +18,7 @@ android:label="@string/app_name" android:preserveLegacyExternalStorage="true" android:hasFragileUserData="true" + android:enableOnBackInvokedCallback="true" android:largeHeap="true" android:roundIcon="@mipmap/ic_launcher_round" android:theme="@style/Theme.App"> @@ -87,6 +88,11 @@ + someActivityResultLauncher; /** * Called when the activity is first created. @@ -69,6 +86,32 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + someActivityResultLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult result) { + if (result.getResultCode() == Activity.RESULT_OK) { + // There are no request codes + Intent data = result.getData(); + String profile = null; + String profile_hash = null; + String sparams = null; + if (data != null && data.hasExtra("PARAMS_VALUES")) { + sparams = data.getExtras().getString("PARAMS_VALUES", ""); + } + if (data != null && data.hasExtra("PROFILE")) { + profile = data.getExtras().getString("PROFILE", ""); + } + if (data != null && data.hasExtra("PROFILE_HASH")) { + profile_hash = data.getExtras().getString("PROFILE_HASH", ""); + } + mBRouterView.configureServiceParams(profile, sparams); + } + + } + }); + ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); int memoryClass = am.getMemoryClass(); @@ -105,70 +148,70 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat showADialog(DIALOG_SELECTPROFILE_ID); } }) - .setNegativeButton("Close", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - finish(); - } - }); + .setNegativeButton("Close", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + finish(); + } + }); return builder.create(); case DIALOG_SHOW_DM_INFO_ID: builder - .setTitle("BRouter Download Manager") - .setMessage( - "*** Attention: ***\n\n" + "The Download Manager is used to download routing-data " - + "files which can be up to 170MB each. Do not start the Download Manager " - + "on a cellular data connection without a data plan! " - + "Download speed is restricted to 16 MBit/s.") - .setPositiveButton("I know", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - Intent intent = new Intent(BRouterActivity.this, BInstallerActivity.class); - startActivity(intent); - showNewDialog(DIALOG_MAINACTION_ID); - } - }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - finish(); - } - }); + .setTitle("BRouter Download Manager") + .setMessage( + "*** Attention: ***\n\n" + "The Download Manager is used to download routing-data " + + "files which can be up to 170MB each. Do not start the Download Manager " + + "on a cellular data connection without a data plan! " + + "Download speed is restricted to 16 MBit/s.") + .setPositiveButton("I know", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Intent intent = new Intent(BRouterActivity.this, BInstallerActivity.class); + startActivity(intent); + showNewDialog(DIALOG_MAINACTION_ID); + } + }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + finish(); + } + }); return builder.create(); case DIALOG_SHOW_REPEAT_TIMEOUT_HELP_ID: builder - .setTitle("Successfully prepared a timeout-free calculation") - .setMessage( - "You successfully repeated a calculation that previously run into a timeout " - + "when started from your map-tool. If you repeat the same request from your " - + "maptool, with the exact same destination point and a close-by starting point, " - + "this request is guaranteed not to time out.") - .setNegativeButton("Exit", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - finish(); - } - }); + .setTitle("Successfully prepared a timeout-free calculation") + .setMessage( + "You successfully repeated a calculation that previously run into a timeout " + + "when started from your map-tool. If you repeat the same request from your " + + "maptool, with the exact same destination point and a close-by starting point, " + + "this request is guaranteed not to time out.") + .setNegativeButton("Exit", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + finish(); + } + }); return builder.create(); case DIALOG_OLDDATAHINT_ID: builder - .setTitle("Local setup needs reset") - .setMessage( - "You are currently using an old version of the lookup-table " - + "together with routing data made for this old table. " - + "Before downloading new datafiles made for the new table, " - + "you have to reset your local setup by 'moving away' (or deleting) " - + "your /brouter directory and start a new setup by calling the " + "BRouter App again.") - .setPositiveButton("OK", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - finish(); - } - }); + .setTitle("Local setup needs reset") + .setMessage( + "You are currently using an old version of the lookup-table " + + "together with routing data made for this old table. " + + "Before downloading new datafiles made for the new table, " + + "you have to reset your local setup by 'moving away' (or deleting) " + + "your /brouter directory and start a new setup by calling the " + "BRouter App again.") + .setPositiveButton("OK", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + finish(); + } + }); return builder.create(); case DIALOG_ROUTINGMODES_ID: builder.setTitle(message); builder.setMultiChoiceItems(routingModes, routingModesChecked, - new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - routingModesChecked[which] = isChecked; - } - }); + new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + routingModesChecked[which] = isChecked; + } + }); builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mBRouterView.configureService(routingModes, routingModesChecked); @@ -177,14 +220,14 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat return builder.create(); case DIALOG_EXCEPTION_ID: builder - .setTitle("An Error occured") - .setMessage(errorMessage) - .setPositiveButton("OK", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - mBRouterView.continueProcessing(); - } - }); + .setTitle("An Error occured") + .setMessage(errorMessage) + .setPositiveButton("OK", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + mBRouterView.continueProcessing(); + } + }); return builder.create(); case DIALOG_TEXTENTRY_ID: builder.setTitle("Enter SDCARD base dir:"); @@ -221,16 +264,16 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat case DIALOG_VIASELECT_ID: builder.setTitle("Check VIA Selection:"); builder.setMultiChoiceItems(availableVias, getCheckedBooleanArray(availableVias.length), - new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - if (isChecked) { - selectedVias.add(availableVias[which]); - } else { - selectedVias.remove(availableVias[which]); - } + new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + if (isChecked) { + selectedVias.add(availableVias[which]); + } else { + selectedVias.remove(availableVias[which]); } - }); + } + }); builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mBRouterView.updateViaList(selectedVias); @@ -245,12 +288,12 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat nogoNames[i] = nogoList.get(i).name; final boolean[] nogoEnabled = getCheckedBooleanArray(nogoList.size()); builder.setMultiChoiceItems(nogoNames, getCheckedBooleanArray(nogoNames.length), - new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - nogoEnabled[which] = isChecked; - } - }); + new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + nogoEnabled[which] = isChecked; + } + }); builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mBRouterView.updateNogoList(nogoEnabled); @@ -263,8 +306,71 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat // -2: Unused? // -1: Route calculated // other: Select waypoints for route calculation - builder.setTitle(title).setMessage(errorMessage); + builder.setTitle(title); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View v = inflater.inflate(R.layout.dialog_message, null); + builder.setView(v); + TextView tv = v.findViewById(R.id.message); + tv.setText(errorMessage); + } else { + // builder.setMessage(errorMessage); + } + List slist = new ArrayList<>(); + // Neutral button + if (wpCount == 0) { + slist.add("Server-Mode"); + } else if (wpCount == -3) { + slist.add("Info"); + } else if (wpCount >= 2) { + slist.add("Calc Route"); + } + + if (wpCount == 0) { + slist.add("Profile Settings"); + } + // Positive button + if (wpCount == -3 || wpCount == -1) { + slist.add("Share GPX"); + } else if (wpCount >= 0) { + String selectLabel = wpCount == 0 ? "Select from" : "Select to/via"; + slist.add(selectLabel); + } + + String[] sArr = new String[slist.size()]; + sArr = slist.toArray(sArr); + builder.setItems( + sArr, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + if (slist.size() > 1 && item == 0) { + if (wpCount == 0) { + mBRouterView.startConfigureService(); + } else if (wpCount == -3) { + showRepeatTimeoutHelp(); + } else if (wpCount >= 2) { + mBRouterView.finishWaypointSelection(); + mBRouterView.startProcessing(selectedProfile); + } + } else { + if (slist.size() == 3 && item == 1) { + showProfileSettings(selectedProfile); + // finish(); + } else { + if (wpCount == -3 || wpCount == -1) { + mBRouterView.shareTrack(); + finish(); + } else if (wpCount >= 0) { + mBRouterView.pickWaypoints(); + } + } + } + } + + }); + + /* // Neutral button if (wpCount == 0) { builder.setNeutralButton("Server-Mode", (dialog, which) -> { @@ -293,6 +399,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat mBRouterView.pickWaypoints(); }); } + */ // Negative button builder.setNegativeButton("Exit", (dialog, which) -> { @@ -302,14 +409,14 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat return builder.create(); case DIALOG_MODECONFIGOVERVIEW_ID: builder - .setTitle("Success") - .setMessage(message) - .setPositiveButton("Exit", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - finish(); - } - }); + .setTitle("Success") + .setMessage(message) + .setPositiveButton("Exit", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + finish(); + } + }); return builder.create(); case DIALOG_PICKWAYPOINT_ID: builder.setTitle(wpCount > 0 ? "Select to/via" : "Select from"); @@ -326,6 +433,49 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat } } + private void showProfileSettings(String selectedProfile) { + List listParams = new ArrayList<>(); + File baseDir = ConfigHelper.getBaseDir(getBaseContext()); + File profile = new File(baseDir, "brouter/profiles2/" + selectedProfile + ".brf"); + if (profile.exists()) { + InputStream fis = null; + try { + fis = new FileInputStream(profile); + listParams = RoutingParameterDialog.getParamsFromProfile(fis); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + } + } + } + } + + String sparams = mBRouterView.getConfigureServiceParams(selectedProfile); + if (sparams != null) { + if (listParams.size() > 0) { + Intent i = new Intent(BRouterActivity.this, RoutingParameterDialog.class); + i.putExtra("PROFILE", selectedProfile); + i.putExtra("PROFILE_HASH", String.format("B%X", profile.getAbsolutePath().hashCode())); + i.putExtra("PARAMS", (Serializable) listParams); + i.putExtra("PARAMS_VALUES", sparams); + //startActivityForResult(i, 100); + someActivityResultLauncher.launch(i); + } else { + Toast.makeText(this, "no profile data", Toast.LENGTH_LONG).show(); + finish(); + } + } else { + Toast.makeText(this, selectedProfile + ", no used profile", Toast.LENGTH_LONG).show(); + finish(); + } + } + private boolean[] getCheckedBooleanArray(int size) { boolean[] checked = new boolean[size]; Arrays.fill(checked, true); @@ -359,7 +509,8 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat size = (long) stat.getAvailableBlocks() * stat.getBlockSize(); } } catch (Exception e) { - /* ignore */ } + /* ignore */ + } dirFreeSizes.add(size); } @@ -373,7 +524,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat DecimalFormat df = new DecimalFormat("###0.00"); for (int idx = 0; idx < availableBasedirs.size(); idx++) { basedirOptions[bdidx++] = availableBasedirs.get(idx) + " (" - + df.format(dirFreeSizes.get(idx) / 1024. / 1024. / 1024.) + " GB free)"; + + df.format(dirFreeSizes.get(idx) / 1024. / 1024. / 1024.) + " GB free)"; } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { basedirOptions[bdidx] = "Enter path manually"; @@ -417,7 +568,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat private void showADialog(int id) { Dialog d = createADialog(id); - if (d!=null) d.show(); + if (d != null) d.show(); } private void showNewDialog(int id) { @@ -505,4 +656,24 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat } } } + + private void onItemClick(AdapterView adapterView, View view, int which, long l) { + if (which == 0) { + if (wpCount == 0) { + mBRouterView.startConfigureService(); + } else if (wpCount == -3) { + showRepeatTimeoutHelp(); + } else if (wpCount >= 2) { + mBRouterView.finishWaypointSelection(); + mBRouterView.startProcessing(selectedProfile); + } + } else { + if (wpCount == -3 || wpCount == -1) { + mBRouterView.shareTrack(); + finish(); + } else if (wpCount >= 0) { + mBRouterView.pickWaypoints(); + } + } + } } diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java index 668b0a9..eb6a129 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java @@ -129,6 +129,7 @@ public class BRouterService extends Service { if (!smc.mode.equals(mode_key)) continue; worker.profileName = smc.profile; + worker.profileParams = (smc.params.equals("noparams") ? null : smc.params); worker.profilePath = baseDir + "/brouter/profiles2/" + smc.profile + ".brf"; worker.rawTrackPath = baseDir + "/brouter/modes/" + mode_key + "_rawtrack.dat"; diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java index 1586c1d..6e6c706 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -829,7 +829,10 @@ public class BRouterView extends View { for (int i = 0; i < 6; i++) { if (checkedModes[i]) { writeRawTrackToMode(routingModes[i]); - ServiceModeConfig smc = new ServiceModeConfig(routingModes[i], profileName); + String s = map.get(routingModes[i]).params; + String p = map.get(routingModes[i]).profile; + if (s == null || !p.equals(profileName)) s = "noparams"; + ServiceModeConfig smc = new ServiceModeConfig(routingModes[i], profileName, s); for (OsmNodeNamed nogo : nogoVetoList) { smc.nogoVetos.add(nogo.ilon + "," + nogo.ilat); } @@ -860,6 +863,81 @@ public class BRouterView extends View { ((BRouterActivity) getContext()).showModeConfigOverview(msg.toString()); } + public void configureServiceParams(String profile, String sparams) { + List map = new ArrayList<>(); + BufferedReader br = null; + String modesFile = modesDir + "/serviceconfig.dat"; + try { + br = new BufferedReader(new FileReader(modesFile)); + for (; ; ) { + String line = br.readLine(); + if (line == null) + break; + ServiceModeConfig smc = new ServiceModeConfig(line); + if (smc.profile.equals(profile)) smc.params = sparams; + map.add(smc); + } + } catch (Exception ignored) { + } finally { + if (br != null) + try { + br.close(); + } catch (Exception ignored) { + } + } + + // now write new config + BufferedWriter bw = null; + StringBuilder msg = new StringBuilder("Mode mapping is now:\n"); + msg.append("( ["); + msg.append(nogoVetoList.size() > 0 ? nogoVetoList.size() : "..").append("] counts nogo-vetos)\n"); + try { + bw = new BufferedWriter(new FileWriter(modesFile)); + for (ServiceModeConfig smc : map) { + bw.write(smc.toLine()); + bw.write('\n'); + msg.append(smc).append('\n'); + } + } catch (Exception ignored) { + } finally { + if (bw != null) + try { + bw.close(); + } catch (Exception ignored) { + } + } + ((BRouterActivity) getContext()).showModeConfigOverview(msg.toString()); + } + + public String getConfigureServiceParams(String profile) { + List map = new ArrayList<>(); + BufferedReader br = null; + String modesFile = modesDir + "/serviceconfig.dat"; + try { + br = new BufferedReader(new FileReader(modesFile)); + for (; ; ) { + String line = br.readLine(); + if (line == null) + break; + ServiceModeConfig smc = new ServiceModeConfig(line); + if (smc.profile.equals(profile)) { + if (!smc.params.equals("noparams")) return smc.params; + else return ""; + } + map.add(smc); + } + } catch (Exception ignored) { + } finally { + if (br != null) + try { + br.close(); + } catch (Exception ignored) { + } + } + // no profile found + return null; + } + public void shareTrack() { File track = new File(trackOutfile); // Copy file to cache to ensure FileProvider allows sharing the file diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java index 738b760..4d74130 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java @@ -1,6 +1,8 @@ package btools.routingapp; +import android.os.Bundle; + import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; @@ -9,8 +11,6 @@ import java.util.HashMap; import java.util.List; import java.util.StringTokenizer; -import android.os.Bundle; - import btools.router.OsmNodeNamed; import btools.router.OsmNogoPolygon; import btools.router.OsmTrack; @@ -30,6 +30,7 @@ public class BRouterWorker { public List waypoints; public List nogoList; public List nogoPolygonsList; + public String profileParams; public String getTrackFromParams(Bundle params) { String pathToFileResult = params.getString("pathToFileResult"); @@ -113,9 +114,18 @@ public class BRouterWorker { } } + String extraParams = null; + if (params.containsKey("extraParams")) { // add user params + extraParams = params.getString("extraParams"); + } + if (extraParams != null && this.profileParams != null) { + // don't overwrite incoming values + extraParams = this.profileParams + "&" + extraParams; + } else if (this.profileParams != null) { + extraParams = this.profileParams; + } if (params.containsKey("extraParams")) { // add user params - String extraParams = params.getString("extraParams"); if (rc.keyValues == null) rc.keyValues = new HashMap(); StringTokenizer tk = new StringTokenizer(extraParams, "?&"); while (tk.hasMoreTokens()) { diff --git a/brouter-routing-app/src/main/java/btools/routingapp/RoutingParam.java b/brouter-routing-app/src/main/java/btools/routingapp/RoutingParam.java new file mode 100644 index 0000000..d16bcc8 --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/RoutingParam.java @@ -0,0 +1,14 @@ +package btools.routingapp; + +import java.io.Serializable; + +public class RoutingParam implements Serializable { + public String name; + public String description; + public String type; + public String value; + + public String toString() { + return "RoutingParam " + name + " = " + value +" type: " + type + " txt: " + description; + } +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java b/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java new file mode 100644 index 0000000..4f892c6 --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/RoutingParameterDialog.java @@ -0,0 +1,394 @@ +package btools.routingapp; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Build; +import android.os.Bundle; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.CheckBoxPreference; +import androidx.preference.EditTextPreference; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceScreen; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This activity is used to define the parameter for the six BRouter routing modes. + * But it could also used from other apps to get parameters for an extern profile + * So the app can send a remote profile and add some parameters for the handling + *

+ * How to use from extern app + *

+ * - copy the class btools.routingapp.RoutingParam to your app java folder without any changes + * - copy the routine 'getParamsFromProfile' below to your favorite setting class + * inside BRouter the getParamsFromProfile is called to handle the internal parameters + *

+ * Call the Parameter Setting Dialog from your app like this: + *

+ * // read the variable parameters into array + * List listParams = new ArrayList<>(); + * String file = "xyz.brf"; + * InputStream fis = new FileInputStream(file); // could be also a stream from DocumentFile + * listParams = getParamsFromProfile(fis); + * fis.close(); + *

+ * // call the BRouter param dialog + * ComponentName cn = new ComponentName("btools.routingapp", "btools.routingapp.RoutingParameterDialog"); + * Intent i = new Intent(); + * i.setComponent(cn); + *

+ * // fill some parameter for the dialog + * String profile_hash = String.format("X%X", file.hashCode()); // some identify code + * i.putExtra("PROFILE_HASH", profile_hash); + * i.putExtra("PROFILE", profile_name); // the profile name, only used for display + * i.putExtra("PARAMS", listParams); // the settings list + * i.putExtra("PARAMS_VALUES", saved_params); // your stored profile parameter or nothing + *

+ * startActivityForResult(i, ROUTE_SETTING_REQUEST); + *

+ * onActivityResult: + * if (requestCode == ROUTE_SETTING_REQUEST) { + * String profile = null; + * String profile_hash = null; + * String sparams = null; + * // get back the selected parameter (only PARAMS_VALUES is needed) + * if (data != null && data.hasExtra("PARAMS_VALUES")) { + * sparams = data.getExtras().getString("PARAMS_VALUES", ""); + * Log.d(TAG, "result sparams " + sparams); + * } + * if (data != null && data.hasExtra("PROFILE")) { + * profile = data.getExtras().getString("PROFILE", ""); + * Log.d(TAG, "result profile " + profile); + * } + * if (data != null && data.hasExtra("PROFILE_HASH")) { + * profile_hash = data.getExtras().getString("PROFILE_HASH", ""); + * Log.d(TAG, "result profile_hash " + profile_hash); + * } + * } + */ + +public class RoutingParameterDialog extends AppCompatActivity { + + static String TAG = "RoutingParameterDialog"; + + static SharedPreferences sharedValues; + static ArrayList listParams; + static String profile; + static String profile_hash; + + /** + * collect a list of parameter in a profile + * + * @param fis - inputstream from profile + * @return - list of variable params in this profile + * @throws IOException if not readable + */ + static public List getParamsFromProfile(final InputStream fis) throws IOException { + List list = null; + + if (fis != null) { + list = new ArrayList<>(); + + // prepare the file for reading + InputStreamReader chapterReader = new InputStreamReader(fis); + BufferedReader buffreader = new BufferedReader(chapterReader); + + String line; + + // read every line of the file into the line-variable, on line at the time + do { + line = buffreader.readLine(); + // do something with the line + if (line != null && + line.contains("#") && + line.contains("%") && + line.lastIndexOf("%") != line.indexOf("%") + ) { + String s = line.substring(line.indexOf("#") + 1); + String v = line.substring(0, line.indexOf("#")); + try { + String[] sa = s.split("\\|"); + RoutingParam p = new RoutingParam(); + p.name = sa[0].trim(); + p.name = p.name.substring(1, p.name.length() - 1); + // turnInstructionMode may transfered from client direct, use only in web client + if (p.name.equals("turnInstructionMode")) continue; + p.description = sa[1].trim(); + p.type = sa[2].trim(); + String[] sav = v.trim().split(" +"); + if (sav[1].equals(p.name)) { + if (sav[0].equals("assign")) { + if (sav[2].equals("=")) { + p.value = sav[3]; + } else { + p.value = sav[2]; + } + } + list.add(p); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } while (line != null); + + } + + return list; + } + + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getSupportFragmentManager().beginTransaction().replace(android.R.id.content, new MyPreferenceFragment()).commit(); + + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, + new OnBackInvokedCallback() { + @Override + public void onBackInvoked() { + StringBuilder sb = null; + if (sharedValues != null) { + // fill preference with used params + // for direct use in the BRouter interface "extraParams" + sb = new StringBuilder(); + for (Map.Entry entry : sharedValues.getAll().entrySet()) { + if (!entry.getKey().equals("params")) { + sb.append(sb.length() > 0 ? "&" : "") + .append(entry.getKey()) + .append("="); + String s = entry.getValue().toString(); + if (s.equals("true")) s = "1"; + else if (s.equals("false")) s = "0"; + sb.append(s); + } + } + } + // and return the array + // one should be enough + Intent i = new Intent(); + // i.putExtra("PARAMS", listParams); + i.putExtra("PROFILE", profile); + i.putExtra("PROFILE_HASH", profile_hash); + if (sb != null) i.putExtra("PARAMS_VALUES", sb.toString()); + + setResult(Activity.RESULT_OK, i); + finish(); + } + } + ); + + + } + } + + + public static class MyPreferenceFragment extends PreferenceFragmentCompat { + + private Activity mActivity; + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + if (context instanceof Activity) { + mActivity = (Activity) context; + } + } + + @Override + public void onDetach() { + super.onDetach(); + mActivity = null; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mActivity.setTitle("Profile Settings"); + listParams = new ArrayList<>(); + String sparams = ""; + try { + Intent i = mActivity.getIntent(); + if (i == null) { + mActivity.finish(); + return; + } + + if (i.hasExtra("PROFILE")) { + profile = i.getStringExtra("PROFILE"); + } + if (i.hasExtra("PROFILE_HASH")) { + profile_hash = i.getStringExtra("PROFILE_HASH"); + } + + if (i.hasExtra("PARAMS")) { + List result; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + result = (List) i.getExtras().getSerializable("PARAMS", ArrayList.class); + } else { + result = (List) i.getExtras().getSerializable("PARAMS"); + } + if (result instanceof ArrayList) { + for (Object o : result) { + if (o instanceof RoutingParam) listParams.add((RoutingParam) o); + } + } + } + if (i.hasExtra("PARAMS_VALUES")) { + sparams = i.getExtras().getString("PARAMS_VALUES", ""); + } + } catch (Exception e) { + e.printStackTrace(); + } + + getPreferenceManager().setSharedPreferencesName("prefs_profile_" + profile_hash); + sharedValues = getPreferenceManager().getSharedPreferences(); + // clear all + // sharedValues.edit().clear().commit(); + + setPreferenceScreen(createPreferenceHierarchy(sparams)); + + } + + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { + + } + + private PreferenceScreen createPreferenceHierarchy(String sparams) { + // Root + Activity a = this.getActivity(); + if (a == null) return null; + + // fill incoming params + Map params = new HashMap<>(); + if (sparams != null && sparams.length() > 0) { + String[] sa = sparams.split("&"); + for (String sar : sa) { + String[] sa2 = sar.split("="); + if (sa2.length == 2) params.put(sa2[0], sa2[1]); + + } + } + + PreferenceScreen root = getPreferenceManager().createPreferenceScreen(a); + + PreferenceCategory gpsPrefCat = new PreferenceCategory(this.getActivity()); + if (profile.length() > 0) { + gpsPrefCat.setTitle(profile); + } else { + gpsPrefCat.setTitle("Profile Settings"); + } + root.addPreference(gpsPrefCat); + + if (listParams != null) { + for (RoutingParam p : listParams) { + if (p.type.equals("number")) { + EditTextPreference numberTextPref = new EditTextPreference(this.getActivity()); + numberTextPref.setDialogTitle(p.name); + numberTextPref.setKey(p.name); + numberTextPref.setSummary(p.description); + String s = (params.get(p.name) != null ? params.get(p.name) : p.value); + if (p.value.equals(s)) sharedValues.edit().remove(p.name).apply(); + numberTextPref.setTitle(p.name + ": " + s); + numberTextPref.setText(s); + //EditText speedEditText = (EditText) speedTextPref.getText(); + //speedEditText.setKeyListener(DigitsKeyListener.getInstance(false, true)); + numberTextPref.setOnPreferenceChangeListener((Preference preference, Object newValue) -> { + p.value = (String) newValue; + numberTextPref.setTitle(p.name + ": " + p.value); + return true; + }); + + gpsPrefCat.addPreference(numberTextPref); + + } else if (p.type.equals("boolean")) { + CheckBoxPreference boolPref = new CheckBoxPreference(this.getActivity()); + boolPref.setKey(p.name); + boolPref.setTitle(p.name); + boolPref.setSummary(p.description); + boolean checked = false; + boolean vchecked = p.value != null && (p.value.equals("1") || p.value.equals("true")); + + if (params.get(p.name) != null) { + checked = params.get(p.name).equals("1") || params.get(p.name).equals("true"); + } else { + checked = vchecked; + } + if (vchecked == checked) sharedValues.edit().remove(p.name).apply(); + + boolPref.setChecked(checked); + //historyPref.setDefaultValue(sharedValues.getBoolean(p.name, p.value != null ? p.value.equals("1") || p.value.equals("true") : false)); + boolPref.setDefaultValue(p.value != null && (p.value.equals("1") || p.value.equals("true"))); + boolPref.setOnPreferenceClickListener((Preference preference) -> { + p.value = (((CheckBoxPreference) preference).isChecked() ? "1" : "0"); + return true; + + }); + gpsPrefCat.addPreference(boolPref); + + } else if (p.type.contains("[") && p.type.contains("]")) { + String[] sa = p.type.substring(p.type.indexOf("[") + 1, p.type.indexOf("]")).split(","); + String[] entryValues = new String[sa.length]; + String[] entries = new String[sa.length]; + int i = 0, ii = 0; + String s = (params.get(p.name) != null ? params.get(p.name) : p.value); //sharedValues.getString(p.name, p.value); + for (String tmp : sa) { + // Add the name and address to the ListPreference enties and entyValues + //L.v("AFTrack", "device: "+device.getName() + " -- " + device.getAddress()); + entryValues[i] = "" + i; + entries[i] = tmp.trim(); + if (entryValues[i].equals(s)) ii = i; + i++; + } + if (p.value.equals(s)) sharedValues.edit().remove(p.name).apply(); + + ListPreference listPref = new ListPreference(this.getActivity()); + listPref.setEntries(entries); + listPref.setEntryValues(entryValues); + listPref.setDialogTitle(p.name); + listPref.setKey(p.name); + listPref.setValueIndex(ii); + listPref.setTitle(p.name + ": " + entries[ii]); + listPref.setSummary(p.description); + listPref.setOnPreferenceChangeListener((Preference preference, Object newValue) -> { + p.value = (String) newValue; + int iii = Integer.decode(p.value); + listPref.setTitle(p.name + ": " + entries[iii]); + + return true; + + }); + + gpsPrefCat.addPreference(listPref); + + } + } + } + return root; + } + + } + +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/ServiceModeConfig.java b/brouter-routing-app/src/main/java/btools/routingapp/ServiceModeConfig.java index e66cda9..63f30ba 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/ServiceModeConfig.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/ServiceModeConfig.java @@ -10,27 +10,32 @@ import java.util.TreeSet; public class ServiceModeConfig { public String mode; public String profile; + public String params; public TreeSet nogoVetos; public ServiceModeConfig(String line) { StringTokenizer tk = new StringTokenizer(line); mode = tk.nextToken(); profile = tk.nextToken(); + if (tk.hasMoreTokens()) params = tk.nextToken(); + else params = "noparams"; nogoVetos = new TreeSet(); while (tk.hasMoreTokens()) { nogoVetos.add(tk.nextToken()); } } - public ServiceModeConfig(String mode, String profile) { + public ServiceModeConfig(String mode, String profile, String params) { this.mode = mode; this.profile = profile; + this.params = params; nogoVetos = new TreeSet(); } public String toLine() { StringBuilder sb = new StringBuilder(100); sb.append(mode).append(' ').append(profile); + sb.append(' ').append(params); for (String veto : nogoVetos) sb.append(' ').append(veto); return sb.toString(); } @@ -38,7 +43,7 @@ public class ServiceModeConfig { public String toString() { StringBuilder sb = new StringBuilder(100); sb.append(mode).append("->").append(profile); - sb.append(" [" + nogoVetos.size() + "]"); + sb.append(" [" + nogoVetos.size() + "]" + (params.equals("noparams")?"":" +p")); return sb.toString(); } } diff --git a/brouter-routing-app/src/main/res/layout/dialog_message.xml b/brouter-routing-app/src/main/res/layout/dialog_message.xml new file mode 100644 index 0000000..ceca354 --- /dev/null +++ b/brouter-routing-app/src/main/res/layout/dialog_message.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/build.gradle b/build.gradle index 32b3748..a44f025 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ allprojects { // app: build.gradle (versionCode only) // OsmTrack (version and versionDate) // docs revisions.md (version and versionDate) - project.version "1.7.0" + project.version "1.7.1-beta-1" group 'org.btools' repositories {