package btools.routingapp; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.StatFs; import android.view.LayoutInflater; import android.view.View; import android.widget.AdapterView; 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.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.os.EnvironmentCompat; 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.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import btools.router.OsmNodeNamed; public class BRouterActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback { private static final int DIALOG_SELECTPROFILE_ID = 1; private static final int DIALOG_EXCEPTION_ID = 2; private static final int DIALOG_SHOW_DM_INFO_ID = 3; private static final int DIALOG_TEXTENTRY_ID = 4; private static final int DIALOG_VIASELECT_ID = 5; private static final int DIALOG_NOGOSELECT_ID = 6; private static final int DIALOG_SHOWRESULT_ID = 7; private static final int DIALOG_ROUTINGMODES_ID = 8; private static final int DIALOG_MODECONFIGOVERVIEW_ID = 9; private static final int DIALOG_PICKWAYPOINT_ID = 10; private static final int DIALOG_SELECTBASEDIR_ID = 11; private static final int DIALOG_MAINACTION_ID = 12; //private static final int DIALOG_OLDDATAHINT_ID = 13; private static final int DIALOG_SHOW_REPEAT_TIMEOUT_HELP_ID = 16; private final Set dialogIds = new HashSet<>(); private BRouterView mBRouterView; private String[] availableProfiles; private String selectedProfile = null; private List availableBasedirs; private String[] basedirOptions; private int selectedBasedir; private String[] availableWaypoints; private String[] routingModes; private boolean[] routingModesChecked; private String message = null; private String[] availableVias; private Set selectedVias; private List nogoList; private String errorMessage; private String title; private int wpCount; private boolean startSilent; private String configuredParams = null; ActivityResultLauncher someActivityResultLauncher; /** * Called when the activity is first created. */ @Override 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", ""); } if (mBRouterView.getConfigureServiceParams(profile) == null) { mBRouterView.startConfigureService(sparams); } else { mBRouterView.configureServiceParams(profile, sparams); } } } }); ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); int memoryClass = am.getMemoryClass(); Intent i = getIntent(); if (i.hasExtra("runsilent")) startSilent = true; // startQuiete = true; // instantiate our simulation view and set it as the activity's content mBRouterView = new BRouterView(this, memoryClass); mBRouterView.init(startSilent); setContentView(mBRouterView); } protected Dialog createADialog(int id) { AlertDialog.Builder builder; builder = new AlertDialog.Builder(this); builder.setCancelable(false); switch (id) { case DIALOG_SELECTPROFILE_ID: builder.setTitle(R.string.action_select_profile); builder.setItems(availableProfiles, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { selectedProfile = availableProfiles[item]; mBRouterView.startProcessing(selectedProfile); } }); return builder.create(); case DIALOG_MAINACTION_ID: builder.setTitle(R.string.main_action); builder.setItems( new String[]{getString(R.string.main_action_1), getString(R.string.main_action_2)}, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { if (item == 0) startDownloadManager(); else showADialog(DIALOG_SELECTPROFILE_ID); } }) .setNegativeButton(getString(R.string.close), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { finish(); } }); return builder.create(); case DIALOG_SHOW_DM_INFO_ID: builder .setTitle(R.string.title_download) .setMessage(R.string.summary_download) .setPositiveButton(R.string.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(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { finish(); } }); return builder.create(); case DIALOG_SHOW_REPEAT_TIMEOUT_HELP_ID: builder .setTitle(R.string.title_timeoutfree) .setMessage(R.string.summary_timeoutfree) .setNegativeButton(R.string.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(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { finish(); } }); return builder.create(); */ case DIALOG_ROUTINGMODES_ID: String configuredParams = this.configuredParams; builder.setTitle(message); builder.setMultiChoiceItems(routingModes, routingModesChecked, new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { routingModesChecked[which] = isChecked; } }); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mBRouterView.configureService(routingModes, routingModesChecked, configuredParams); } }); return builder.create(); case DIALOG_EXCEPTION_ID: builder .setTitle(R.string.error) .setMessage(errorMessage) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { mBRouterView.continueProcessing(); } }); return builder.create(); case DIALOG_TEXTENTRY_ID: builder.setTitle(R.string.title_sdcard); builder.setMessage(message); final EditText input = new EditText(this); // input.setText(defaultbasedir); builder.setView(input); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String basedir = input.getText().toString(); mBRouterView.startSetup(new File(basedir), true, false); } }); return builder.create(); case DIALOG_SELECTBASEDIR_ID: builder.setTitle(getString(R.string.action_choose_folder)); // builder.setMessage( message ); builder.setSingleChoiceItems(basedirOptions, 0, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { selectedBasedir = item; } }); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { if (selectedBasedir < availableBasedirs.size()) { mBRouterView.startSetup(availableBasedirs.get(selectedBasedir), true, false); } else { showADialog(DIALOG_TEXTENTRY_ID); } } }); return builder.create(); case DIALOG_VIASELECT_ID: builder.setTitle(R.string.action_via_select); 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]); } } }); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mBRouterView.updateViaList(selectedVias); mBRouterView.startProcessing(selectedProfile); } }); return builder.create(); case DIALOG_NOGOSELECT_ID: builder.setTitle(R.string.action_nogo_select); String[] nogoNames = new String[nogoList.size()]; for (int i = 0; i < nogoList.size(); i++) 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; } }); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mBRouterView.updateNogoList(nogoEnabled); mBRouterView.startProcessing(selectedProfile); } }); return builder.create(); case DIALOG_SHOWRESULT_ID: // -3: Repeated route calculation // -2: Unused? // -1: Route calculated // other: Select waypoints for route calculation 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(getString(R.string.action_servermode)); } else if (wpCount == -3) { slist.add(getString(R.string.action_info)); } else if (wpCount >= 2) { slist.add(getString(R.string.action_calc_route)); } if (wpCount == 0) { slist.add(getString(R.string.action_profile_settings)); } // Positive button if (wpCount == -3 || wpCount == -1) { slist.add(getString(R.string.action_share)); } else if (wpCount >= 0) { String selectLabel = wpCount == 0 ? getString(R.string.action_select_from) : getString(R.string.action_select_to); 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(null); } 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) -> { mBRouterView.startConfigureService(); }); } else if (wpCount == -3) { builder.setNeutralButton("Info", (dialog, which) -> { showRepeatTimeoutHelp(); }); } else if (wpCount >= 2) { builder.setNeutralButton("Calc Route", (dialog, which) -> { mBRouterView.finishWaypointSelection(); mBRouterView.startProcessing(selectedProfile); }); } // Positive button if (wpCount == -3 || wpCount == -1) { builder.setPositiveButton("Share GPX", (dialog, which) -> { mBRouterView.shareTrack(); finish(); }); } else if (wpCount >= 0) { String selectLabel = wpCount == 0 ? "Select from" : "Select to/via"; builder.setPositiveButton(selectLabel, (dialog, which) -> { mBRouterView.pickWaypoints(); }); } */ // Negative button builder.setNegativeButton(R.string.exit, (dialog, which) -> { finish(); }); return builder.create(); case DIALOG_MODECONFIGOVERVIEW_ID: builder .setTitle(R.string.success) .setMessage(message) .setPositiveButton(R.string.exit, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { finish(); } }); return builder.create(); case DIALOG_PICKWAYPOINT_ID: builder.setTitle(wpCount == 0 ? getString(R.string.action_select_from) : getString(R.string.action_select_to)); builder.setItems(availableWaypoints, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { mBRouterView.updateWaypointList(availableWaypoints[item]); mBRouterView.startProcessing(selectedProfile); } }); return builder.create(); default: return null; } } 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) { // profile is not used yet sparams = ""; } 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, R.string.msg_no_profile, Toast.LENGTH_LONG).show(); finish(); } } private boolean[] getCheckedBooleanArray(int size) { boolean[] checked = new boolean[size]; Arrays.fill(checked, true); return checked; } public void selectProfile(String[] items, boolean showDialog) { availableProfiles = items; Arrays.sort(availableProfiles); // show main dialog if (showDialog) showADialog(DIALOG_MAINACTION_ID); } public void startDownloadManager() { showADialog(DIALOG_SHOW_DM_INFO_ID); } @SuppressWarnings("deprecation") public void selectBasedir(ArrayList items, String message) { this.message = message; availableBasedirs = items; ArrayList dirFreeSizes = new ArrayList<>(); for (File f : items) { long size = 0L; try { StatFs stat = new StatFs(f.getAbsolutePath()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { size = stat.getAvailableBlocksLong() * stat.getBlockSizeLong(); } else { size = (long) stat.getAvailableBlocks() * stat.getBlockSize(); } } catch (Exception e) { /* ignore */ } dirFreeSizes.add(size); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { basedirOptions = new String[items.size()]; } else { basedirOptions = new String[items.size() + 1]; } int bdidx = 0; 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)"; } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { basedirOptions[bdidx] = "Enter path manually"; } if (startSilent) { mBRouterView.startSetup(availableBasedirs.get(0), true, startSilent); Intent intent = new Intent(BRouterActivity.this, BInstallerActivity.class); startActivity(intent); finish(); } else { showADialog(DIALOG_SELECTBASEDIR_ID); } } public void selectRoutingModes(String[] modes, boolean[] modesChecked, String message, String sparams) { routingModes = modes; routingModesChecked = modesChecked; this.message = message; this.configuredParams = sparams; showADialog(DIALOG_ROUTINGMODES_ID); } public void showModeConfigOverview(String message) { this.message = message; showADialog(DIALOG_MODECONFIGOVERVIEW_ID); } public void selectVias(String[] items) { availableVias = items; selectedVias = new HashSet<>(availableVias.length); Collections.addAll(selectedVias, items); showADialog(DIALOG_VIASELECT_ID); } public void selectWaypoint(String[] items) { availableWaypoints = items; showNewDialog(DIALOG_PICKWAYPOINT_ID); } public void showRepeatTimeoutHelp() { showNewDialog(DIALOG_SHOW_REPEAT_TIMEOUT_HELP_ID); } public void selectNogos(List nogoList) { this.nogoList = nogoList; showADialog(DIALOG_NOGOSELECT_ID); } private void showADialog(int id) { Dialog d = createADialog(id); if (d != null) d.show(); } private void showNewDialog(int id) { if (dialogIds.contains(id)) { // removeDialog(id); } dialogIds.add(id); showADialog(id); } public void showErrorMessage(String msg) { errorMessage = msg; showNewDialog(DIALOG_EXCEPTION_ID); } public void showResultMessage(String title, String msg, int wpCount) { errorMessage = msg; this.title = title; this.wpCount = wpCount; showNewDialog(DIALOG_SHOWRESULT_ID); } @Override protected void onPause() { super.onPause(); // When the activity is paused, we make sure to stop the router mBRouterView.stopRouting(); } public ArrayList getStorageDirectories() { ArrayList list = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { list = new ArrayList<>(Arrays.asList(getExternalMediaDirs())); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { list = new ArrayList<>(Arrays.asList(getExternalFilesDirs(null))); } ArrayList res = new ArrayList<>(); if (list != null) { for (File f : list) { if (f != null) { if (EnvironmentCompat.getStorageState(f).equals(Environment.MEDIA_MOUNTED)) res.add(f); } } } if (checkExternalStorageWritable()) { res.add(Environment.getExternalStorageDirectory()); } return res; } private boolean checkExternalStorageWritable() { boolean isWritable = false; try { File sd = Environment.getExternalStorageDirectory(); File testDir = new File(sd, "brouter"); boolean didExist = testDir.isDirectory(); if (!didExist) { testDir.mkdir(); } File testFile = new File(testDir, "test" + System.currentTimeMillis()); testFile.createNewFile(); if (testFile.exists()) { testFile.delete(); isWritable = true; } } catch (Throwable t) { // ignore } return isWritable; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 0) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { mBRouterView.startSetup(null, true, false); } else { mBRouterView.init(false); } } } private void onItemClick(AdapterView adapterView, View view, int which, long l) { if (which == 0) { if (wpCount == 0) { mBRouterView.startConfigureService(null); } 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(); } } } }