Merge pull request #546 from afischerdev/app-params-update

Add an app params dialog
This commit is contained in:
afischerdev 2023-05-08 16:30:25 +02:00 committed by GitHub
commit 0c32770cfd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 798 additions and 98 deletions

View file

@ -11,7 +11,7 @@ android {
namespace 'btools.routingapp' namespace 'btools.routingapp'
applicationId "btools.routingapp" applicationId "btools.routingapp"
versionCode 47 versionCode 48
versionName project.version versionName project.version
resValue('string', 'app_version', defaultConfig.versionName) resValue('string', 'app_version', defaultConfig.versionName)
@ -89,19 +89,20 @@ android {
dependencies { dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation "androidx.constraintlayout:constraintlayout:2.1.4" 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 'com.google.android.material:material:1.8.0'
implementation project(':brouter-mapaccess') implementation project(':brouter-mapaccess')
implementation project(':brouter-core') implementation project(':brouter-core')
implementation project(':brouter-expressions') implementation project(':brouter-expressions')
implementation project(':brouter-util') implementation project(':brouter-util')
implementation 'androidx.preference:preference:1.2.0'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 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 { gradle.projectsEvaluated {

View file

@ -18,6 +18,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:preserveLegacyExternalStorage="true" android:preserveLegacyExternalStorage="true"
android:hasFragileUserData="true" android:hasFragileUserData="true"
android:enableOnBackInvokedCallback="true"
android:largeHeap="true" android:largeHeap="true"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.App"> android:theme="@style/Theme.App">
@ -87,6 +88,11 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.brf" /> <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.brf" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".RoutingParameterDialog"
android:exported="true"
android:launchMode="singleTask"
/>
<service <service
android:name=".BRouterService" android:name=".BRouterService"

View file

@ -1,8 +1,10 @@
package btools.routingapp; package btools.routingapp;
import android.app.Activity;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@ -10,14 +12,28 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.os.StatFs; import android.os.StatFs;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.os.EnvironmentCompat; import androidx.core.os.EnvironmentCompat;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -61,6 +77,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat
private String errorMessage; private String errorMessage;
private String title; private String title;
private int wpCount; private int wpCount;
ActivityResultLauncher<Intent> someActivityResultLauncher;
/** /**
* Called when the activity is first created. * Called when the activity is first created.
@ -69,6 +86,32 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
someActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@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); ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass(); int memoryClass = am.getMemoryClass();
@ -105,70 +148,70 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat
showADialog(DIALOG_SELECTPROFILE_ID); showADialog(DIALOG_SELECTPROFILE_ID);
} }
}) })
.setNegativeButton("Close", new DialogInterface.OnClickListener() { .setNegativeButton("Close", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
finish(); finish();
} }
}); });
return builder.create(); return builder.create();
case DIALOG_SHOW_DM_INFO_ID: case DIALOG_SHOW_DM_INFO_ID:
builder builder
.setTitle("BRouter Download Manager") .setTitle("BRouter Download Manager")
.setMessage( .setMessage(
"*** Attention: ***\n\n" + "The Download Manager is used to download routing-data " "*** 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 " + "files which can be up to 170MB each. Do not start the Download Manager "
+ "on a cellular data connection without a data plan! " + "on a cellular data connection without a data plan! "
+ "Download speed is restricted to 16 MBit/s.") + "Download speed is restricted to 16 MBit/s.")
.setPositiveButton("I know", new DialogInterface.OnClickListener() { .setPositiveButton("I know", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
Intent intent = new Intent(BRouterActivity.this, BInstallerActivity.class); Intent intent = new Intent(BRouterActivity.this, BInstallerActivity.class);
startActivity(intent); startActivity(intent);
showNewDialog(DIALOG_MAINACTION_ID); showNewDialog(DIALOG_MAINACTION_ID);
} }
}).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
finish(); finish();
} }
}); });
return builder.create(); return builder.create();
case DIALOG_SHOW_REPEAT_TIMEOUT_HELP_ID: case DIALOG_SHOW_REPEAT_TIMEOUT_HELP_ID:
builder builder
.setTitle("Successfully prepared a timeout-free calculation") .setTitle("Successfully prepared a timeout-free calculation")
.setMessage( .setMessage(
"You successfully repeated a calculation that previously run into a timeout " "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 " + "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, " + "maptool, with the exact same destination point and a close-by starting point, "
+ "this request is guaranteed not to time out.") + "this request is guaranteed not to time out.")
.setNegativeButton("Exit", new DialogInterface.OnClickListener() { .setNegativeButton("Exit", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
finish(); finish();
} }
}); });
return builder.create(); return builder.create();
case DIALOG_OLDDATAHINT_ID: case DIALOG_OLDDATAHINT_ID:
builder builder
.setTitle("Local setup needs reset") .setTitle("Local setup needs reset")
.setMessage( .setMessage(
"You are currently using an old version of the lookup-table " "You are currently using an old version of the lookup-table "
+ "together with routing data made for this old table. " + "together with routing data made for this old table. "
+ "Before downloading new datafiles made for the new table, " + "Before downloading new datafiles made for the new table, "
+ "you have to reset your local setup by 'moving away' (or deleting) " + "you have to reset your local setup by 'moving away' (or deleting) "
+ "your <basedir>/brouter directory and start a new setup by calling the " + "BRouter App again.") + "your <basedir>/brouter directory and start a new setup by calling the " + "BRouter App again.")
.setPositiveButton("OK", new DialogInterface.OnClickListener() { .setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
finish(); finish();
} }
}); });
return builder.create(); return builder.create();
case DIALOG_ROUTINGMODES_ID: case DIALOG_ROUTINGMODES_ID:
builder.setTitle(message); builder.setTitle(message);
builder.setMultiChoiceItems(routingModes, routingModesChecked, builder.setMultiChoiceItems(routingModes, routingModesChecked,
new DialogInterface.OnMultiChoiceClickListener() { new DialogInterface.OnMultiChoiceClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) { public void onClick(DialogInterface dialog, int which, boolean isChecked) {
routingModesChecked[which] = isChecked; routingModesChecked[which] = isChecked;
} }
}); });
builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) { public void onClick(DialogInterface dialog, int whichButton) {
mBRouterView.configureService(routingModes, routingModesChecked); mBRouterView.configureService(routingModes, routingModesChecked);
@ -177,14 +220,14 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat
return builder.create(); return builder.create();
case DIALOG_EXCEPTION_ID: case DIALOG_EXCEPTION_ID:
builder builder
.setTitle("An Error occured") .setTitle("An Error occured")
.setMessage(errorMessage) .setMessage(errorMessage)
.setPositiveButton("OK", .setPositiveButton("OK",
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
mBRouterView.continueProcessing(); mBRouterView.continueProcessing();
} }
}); });
return builder.create(); return builder.create();
case DIALOG_TEXTENTRY_ID: case DIALOG_TEXTENTRY_ID:
builder.setTitle("Enter SDCARD base dir:"); builder.setTitle("Enter SDCARD base dir:");
@ -221,16 +264,16 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat
case DIALOG_VIASELECT_ID: case DIALOG_VIASELECT_ID:
builder.setTitle("Check VIA Selection:"); builder.setTitle("Check VIA Selection:");
builder.setMultiChoiceItems(availableVias, getCheckedBooleanArray(availableVias.length), builder.setMultiChoiceItems(availableVias, getCheckedBooleanArray(availableVias.length),
new DialogInterface.OnMultiChoiceClickListener() { new DialogInterface.OnMultiChoiceClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) { public void onClick(DialogInterface dialog, int which, boolean isChecked) {
if (isChecked) { if (isChecked) {
selectedVias.add(availableVias[which]); selectedVias.add(availableVias[which]);
} else { } else {
selectedVias.remove(availableVias[which]); selectedVias.remove(availableVias[which]);
}
} }
}); }
});
builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) { public void onClick(DialogInterface dialog, int whichButton) {
mBRouterView.updateViaList(selectedVias); mBRouterView.updateViaList(selectedVias);
@ -245,12 +288,12 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat
nogoNames[i] = nogoList.get(i).name; nogoNames[i] = nogoList.get(i).name;
final boolean[] nogoEnabled = getCheckedBooleanArray(nogoList.size()); final boolean[] nogoEnabled = getCheckedBooleanArray(nogoList.size());
builder.setMultiChoiceItems(nogoNames, getCheckedBooleanArray(nogoNames.length), builder.setMultiChoiceItems(nogoNames, getCheckedBooleanArray(nogoNames.length),
new DialogInterface.OnMultiChoiceClickListener() { new DialogInterface.OnMultiChoiceClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) { public void onClick(DialogInterface dialog, int which, boolean isChecked) {
nogoEnabled[which] = isChecked; nogoEnabled[which] = isChecked;
} }
}); });
builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) { public void onClick(DialogInterface dialog, int whichButton) {
mBRouterView.updateNogoList(nogoEnabled); mBRouterView.updateNogoList(nogoEnabled);
@ -263,8 +306,71 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat
// -2: Unused? // -2: Unused?
// -1: Route calculated // -1: Route calculated
// other: Select waypoints for route calculation // 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<String> 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 // Neutral button
if (wpCount == 0) { if (wpCount == 0) {
builder.setNeutralButton("Server-Mode", (dialog, which) -> { builder.setNeutralButton("Server-Mode", (dialog, which) -> {
@ -293,6 +399,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat
mBRouterView.pickWaypoints(); mBRouterView.pickWaypoints();
}); });
} }
*/
// Negative button // Negative button
builder.setNegativeButton("Exit", (dialog, which) -> { builder.setNegativeButton("Exit", (dialog, which) -> {
@ -302,14 +409,14 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat
return builder.create(); return builder.create();
case DIALOG_MODECONFIGOVERVIEW_ID: case DIALOG_MODECONFIGOVERVIEW_ID:
builder builder
.setTitle("Success") .setTitle("Success")
.setMessage(message) .setMessage(message)
.setPositiveButton("Exit", .setPositiveButton("Exit",
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
finish(); finish();
} }
}); });
return builder.create(); return builder.create();
case DIALOG_PICKWAYPOINT_ID: case DIALOG_PICKWAYPOINT_ID:
builder.setTitle(wpCount > 0 ? "Select to/via" : "Select from"); 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<RoutingParam> 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) { private boolean[] getCheckedBooleanArray(int size) {
boolean[] checked = new boolean[size]; boolean[] checked = new boolean[size];
Arrays.fill(checked, true); Arrays.fill(checked, true);
@ -359,7 +509,8 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat
size = (long) stat.getAvailableBlocks() * stat.getBlockSize(); size = (long) stat.getAvailableBlocks() * stat.getBlockSize();
} }
} catch (Exception e) { } catch (Exception e) {
/* ignore */ } /* ignore */
}
dirFreeSizes.add(size); dirFreeSizes.add(size);
} }
@ -373,7 +524,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat
DecimalFormat df = new DecimalFormat("###0.00"); DecimalFormat df = new DecimalFormat("###0.00");
for (int idx = 0; idx < availableBasedirs.size(); idx++) { for (int idx = 0; idx < availableBasedirs.size(); idx++) {
basedirOptions[bdidx++] = availableBasedirs.get(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) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
basedirOptions[bdidx] = "Enter path manually"; basedirOptions[bdidx] = "Enter path manually";
@ -417,7 +568,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat
private void showADialog(int id) { private void showADialog(int id) {
Dialog d = createADialog(id); Dialog d = createADialog(id);
if (d!=null) d.show(); if (d != null) d.show();
} }
private void showNewDialog(int id) { 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();
}
}
}
} }

View file

@ -129,6 +129,7 @@ public class BRouterService extends Service {
if (!smc.mode.equals(mode_key)) if (!smc.mode.equals(mode_key))
continue; continue;
worker.profileName = smc.profile; worker.profileName = smc.profile;
worker.profileParams = (smc.params.equals("noparams") ? null : smc.params);
worker.profilePath = baseDir + "/brouter/profiles2/" + smc.profile + ".brf"; worker.profilePath = baseDir + "/brouter/profiles2/" + smc.profile + ".brf";
worker.rawTrackPath = baseDir + "/brouter/modes/" + mode_key + "_rawtrack.dat"; worker.rawTrackPath = baseDir + "/brouter/modes/" + mode_key + "_rawtrack.dat";

View file

@ -562,7 +562,10 @@ public class BRouterView extends View {
} }
String name = ze.getName(); String name = ze.getName();
File outfile = new File(path, name); File outfile = new File(path, name);
if (!outfile.exists() && outfile.getParentFile() != null) { String canonicalPath = outfile.getCanonicalPath();
if (canonicalPath.startsWith(path.getCanonicalPath()) &&
!outfile.exists() &&
outfile.getParentFile() != null) {
outfile.getParentFile().mkdirs(); outfile.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(outfile); FileOutputStream fos = new FileOutputStream(outfile);
@ -575,6 +578,7 @@ public class BRouterView extends View {
fos.close(); fos.close();
} }
} }
zis.close();
is.close(); is.close();
return true; return true;
} catch (IOException io) { } catch (IOException io) {
@ -825,7 +829,10 @@ public class BRouterView extends View {
for (int i = 0; i < 6; i++) { for (int i = 0; i < 6; i++) {
if (checkedModes[i]) { if (checkedModes[i]) {
writeRawTrackToMode(routingModes[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) { for (OsmNodeNamed nogo : nogoVetoList) {
smc.nogoVetos.add(nogo.ilon + "," + nogo.ilat); smc.nogoVetos.add(nogo.ilon + "," + nogo.ilat);
} }
@ -856,6 +863,81 @@ public class BRouterView extends View {
((BRouterActivity) getContext()).showModeConfigOverview(msg.toString()); ((BRouterActivity) getContext()).showModeConfigOverview(msg.toString());
} }
public void configureServiceParams(String profile, String sparams) {
List<ServiceModeConfig> 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<ServiceModeConfig> 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() { public void shareTrack() {
File track = new File(trackOutfile); File track = new File(trackOutfile);
// Copy file to cache to ensure FileProvider allows sharing the file // Copy file to cache to ensure FileProvider allows sharing the file

View file

@ -1,6 +1,8 @@
package btools.routingapp; package btools.routingapp;
import android.os.Bundle;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileWriter; import java.io.FileWriter;
@ -9,8 +11,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import android.os.Bundle;
import btools.router.OsmNodeNamed; import btools.router.OsmNodeNamed;
import btools.router.OsmNogoPolygon; import btools.router.OsmNogoPolygon;
import btools.router.OsmTrack; import btools.router.OsmTrack;
@ -30,6 +30,7 @@ public class BRouterWorker {
public List<OsmNodeNamed> waypoints; public List<OsmNodeNamed> waypoints;
public List<OsmNodeNamed> nogoList; public List<OsmNodeNamed> nogoList;
public List<OsmNodeNamed> nogoPolygonsList; public List<OsmNodeNamed> nogoPolygonsList;
public String profileParams;
public String getTrackFromParams(Bundle params) { public String getTrackFromParams(Bundle params) {
String pathToFileResult = params.getString("pathToFileResult"); 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 if (params.containsKey("extraParams")) { // add user params
String extraParams = params.getString("extraParams");
if (rc.keyValues == null) rc.keyValues = new HashMap<String, String>(); if (rc.keyValues == null) rc.keyValues = new HashMap<String, String>();
StringTokenizer tk = new StringTokenizer(extraParams, "?&"); StringTokenizer tk = new StringTokenizer(extraParams, "?&");
while (tk.hasMoreTokens()) { while (tk.hasMoreTokens()) {

View file

@ -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;
}
}

View file

@ -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
* <p>
* How to use from extern app
* <p>
* - 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
* <p>
* Call the Parameter Setting Dialog from your app like this:
* <p>
* // read the variable parameters into array
* List<RoutingParam> listParams = new ArrayList<>();
* String file = "xyz.brf";
* InputStream fis = new FileInputStream(file); // could be also a stream from DocumentFile
* listParams = getParamsFromProfile(fis);
* fis.close();
* <p>
* // call the BRouter param dialog
* ComponentName cn = new ComponentName("btools.routingapp", "btools.routingapp.RoutingParameterDialog");
* Intent i = new Intent();
* i.setComponent(cn);
* <p>
* // 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
* <p>
* startActivityForResult(i, ROUTE_SETTING_REQUEST);
* <p>
* 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<RoutingParam> 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<RoutingParam> getParamsFromProfile(final InputStream fis) throws IOException {
List<RoutingParam> 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<String, ?> 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<String, String> 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;
}
}
}

View file

@ -105,7 +105,10 @@ public class ServerConfig {
String name = ze.getName(); String name = ze.getName();
if (name.equals(mServerConfigName)) { if (name.equals(mServerConfigName)) {
File outfile = new File(path, name + ".tmp"); File outfile = new File(path, name + ".tmp");
if (!outfile.exists() && outfile.getParentFile() != null) { String canonicalPath = outfile.getCanonicalPath();
if (canonicalPath.startsWith(path.getCanonicalPath()) &&
!outfile.exists() &&
outfile.getParentFile() != null) {
outfile.getParentFile().mkdirs(); outfile.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(outfile); FileOutputStream fos = new FileOutputStream(outfile);

View file

@ -10,27 +10,32 @@ import java.util.TreeSet;
public class ServiceModeConfig { public class ServiceModeConfig {
public String mode; public String mode;
public String profile; public String profile;
public String params;
public TreeSet<String> nogoVetos; public TreeSet<String> nogoVetos;
public ServiceModeConfig(String line) { public ServiceModeConfig(String line) {
StringTokenizer tk = new StringTokenizer(line); StringTokenizer tk = new StringTokenizer(line);
mode = tk.nextToken(); mode = tk.nextToken();
profile = tk.nextToken(); profile = tk.nextToken();
if (tk.hasMoreTokens()) params = tk.nextToken();
else params = "noparams";
nogoVetos = new TreeSet<String>(); nogoVetos = new TreeSet<String>();
while (tk.hasMoreTokens()) { while (tk.hasMoreTokens()) {
nogoVetos.add(tk.nextToken()); nogoVetos.add(tk.nextToken());
} }
} }
public ServiceModeConfig(String mode, String profile) { public ServiceModeConfig(String mode, String profile, String params) {
this.mode = mode; this.mode = mode;
this.profile = profile; this.profile = profile;
this.params = params;
nogoVetos = new TreeSet<String>(); nogoVetos = new TreeSet<String>();
} }
public String toLine() { public String toLine() {
StringBuilder sb = new StringBuilder(100); StringBuilder sb = new StringBuilder(100);
sb.append(mode).append(' ').append(profile); sb.append(mode).append(' ').append(profile);
sb.append(' ').append(params);
for (String veto : nogoVetos) sb.append(' ').append(veto); for (String veto : nogoVetos) sb.append(' ').append(veto);
return sb.toString(); return sb.toString();
} }
@ -38,7 +43,7 @@ public class ServiceModeConfig {
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(100); StringBuilder sb = new StringBuilder(100);
sb.append(mode).append("->").append(profile); sb.append(mode).append("->").append(profile);
sb.append(" [" + nogoVetos.size() + "]"); sb.append(" [" + nogoVetos.size() + "]" + (params.equals("noparams")?"":" +p"));
return sb.toString(); return sb.toString();
} }
} }

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/system_profile_dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:id="@+id/message" />
</LinearLayout>

View file

@ -22,7 +22,7 @@ allprojects {
// app: build.gradle (versionCode only) // app: build.gradle (versionCode only)
// OsmTrack (version and versionDate) // OsmTrack (version and versionDate)
// docs revisions.md (version and versionDate) // docs revisions.md (version and versionDate)
project.version "1.7.0" project.version "1.7.1-beta-1"
group 'org.btools' group 'org.btools'
repositories { repositories {