diff --git a/.gitignore b/.gitignore index 32b0ae5..ddd96e8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,5 +19,4 @@ misc/customprofiles *.BAK .classpath .project -*.iml !/.idea/runConfigurations diff --git a/brouter-routing-app/src/main/AndroidManifest.xml b/brouter-routing-app/src/main/AndroidManifest.xml index 9c5a766..f5d4713 100644 --- a/brouter-routing-app/src/main/AndroidManifest.xml +++ b/brouter-routing-app/src/main/AndroidManifest.xml @@ -1,12 +1,18 @@ - - + + - + @@ -17,13 +23,14 @@ + android:screenOrientation="landscape" + android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> - + android:process=":brouter_service" /> + \ No newline at end of file diff --git a/brouter-routing-app/src/main/aidl/btools/routingapp/IBRouterService.aidl b/brouter-routing-app/src/main/aidl/btools/routingapp/IBRouterService.aidl index 119d20e..ef249b1 100644 --- a/brouter-routing-app/src/main/aidl/btools/routingapp/IBRouterService.aidl +++ b/brouter-routing-app/src/main/aidl/btools/routingapp/IBRouterService.aidl @@ -6,10 +6,10 @@ interface IBRouterService { //param params--> Map of params: // "pathToFileResult"-->String with the path to where the result must be saved, including file name and extension - // -->if null, the track is passed via the return argument + // -->if null, the track is passed via the return argument, this should be default when Android Q or later // "maxRunningTime"-->String with a number of seconds for the routing timeout, default = 60 // "turnInstructionFormat"-->String selecting the format for turn-instructions values: osmand, locus - // "trackFormat"-->[kml|gpx] default = gpx + // "trackFormat"-->[kml|gpx|json] default = gpx // "lats"-->double[] array of latitudes; 2 values at least. // "lons"-->double[] array of longitudes; 2 values at least. // "nogoLats"-->double[] array of nogo latitudes; may be null. @@ -18,8 +18,21 @@ interface IBRouterService { // "fast"-->[0|1] // "v"-->[motorcar|bicycle|foot] // "remoteProfile"--> (String), net-content of a profile. If remoteProfile != null, v+fast are ignored - //return null if all ok and no path given, the track if ok and path given, an error message if it was wrong - //call in a background thread, heavy task! + // + // "lonlats" = lon,lat|... (unlimited list of lon,lat waypoints separated by |) + // "nogos" = lon,lat,radius|... (optional, radius in meters) + // "polylines" = lon,lat,weight|... (unlimited list of lon,lat and weight (optional) separated by |) + // "polygons" = lon,lat,weight|... (unlimited list of lon,lat and weight (optional) separated by |) + // "profile" = profile file name without .brf + // "alternativeidx" = [0|1|2|3] (optional, default 0) + // "exportWaypoints" = 1 to export them (optional, default is no export) + // "pois" = lon,lat,name|... (optional) + // "extraParams" = Bundle key=value list for a profile setup (like "profile:") + // "timode" = turnInstructionMode [0=none, 1=auto-choose, 2=locus-style, 3=osmand-style, 4=comment-style, 5=gpsies-style, 6=orux-style] default 0 + + // return null if all ok and no path given, the track if ok and path given, an error message if it was wrong + // the resultas string when 'pathToFileResult' is null, this should be default when Android Q or later + // call in a background thread, heavy task! String getTrackFromParams(in Bundle params); } diff --git a/brouter-routing-app/src/main/java/btools/routingapp/AppLogger.java b/brouter-routing-app/src/main/java/btools/routingapp/AppLogger.java index 6d1eda8..9e84b8f 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/AppLogger.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/AppLogger.java @@ -25,7 +25,7 @@ public class AppLogger // open logfile if existing File sd = Environment.getExternalStorageDirectory(); if ( sd == null ) return; - File debugLog = new File( sd, "Android/data/btools.routingapp/files/brouterapp.txt" ); + File debugLog = new File( sd, "Android/media/btools.routingapp/brouter/brouterapp.txt" ); if ( debugLog.exists() ) { debugLogWriter = new FileWriter( debugLog, true ); diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java index 1e30a30..7c707dd 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java @@ -7,6 +7,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.util.Locale; import android.app.Activity; import android.content.Context; @@ -20,6 +21,7 @@ import android.graphics.Paint; import android.os.AsyncTask; import android.os.PowerManager; import android.os.StatFs; +import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.MotionEvent; import android.view.View; @@ -73,6 +75,10 @@ public class BInstallerView extends View private long rd5Tiles = 0; private long delTiles = 0; + Paint pnt_1 = new Paint(); + Paint pnt_2 = new Paint(); + Paint paint = new Paint(); + protected String baseNameForTile( int tileIndex ) { int lon = (tileIndex % 72 ) * 5 - 180; @@ -89,15 +95,15 @@ public class BInstallerView extends View private int tileForBaseName( String basename ) { - String uname = basename.toUpperCase(); + String uname = basename.toUpperCase(Locale.ROOT); int idx = uname.indexOf( "_" ); if ( idx < 0 ) return -1; String slon = uname.substring( 0, idx ); String slat = uname.substring( idx+1 ); - int ilon = slon.charAt(0) == 'W' ? -Integer.valueOf( slon.substring(1) ) : - ( slon.charAt(0) == 'E' ? Integer.valueOf( slon.substring(1) ) : -1 ); - int ilat = slat.charAt(0) == 'S' ? -Integer.valueOf( slat.substring(1) ) : - ( slat.charAt(0) == 'N' ? Integer.valueOf( slat.substring(1) ) : -1 ); + int ilon = slon.charAt(0) == 'W' ? -Integer.parseInt( slon.substring(1) ) : + ( slon.charAt(0) == 'E' ? Integer.parseInt( slon.substring(1) ) : -1 ); + int ilat = slat.charAt(0) == 'S' ? -Integer.parseInt( slat.substring(1) ) : + ( slat.charAt(0) == 'N' ? Integer.parseInt( slat.substring(1) ) : -1 ); if ( ilon < -180 || ilon >= 180 || ilon % 5 != 0 ) return -1; if ( ilat < - 90 || ilat >= 90 || ilat % 5 != 0 ) return -1; return (ilon+180) / 5 + 72*((ilat+90)/5); @@ -236,7 +242,7 @@ public class BInstallerView extends View try { StatFs stat = new StatFs(baseDir.getAbsolutePath ()); - availableSize = (long)stat.getAvailableBlocks()*stat.getBlockSize(); + availableSize = (long)stat.getAvailableBlocksLong()*stat.getBlockSizeLong(); } catch (Exception e) { /* ignore */ } } @@ -339,7 +345,6 @@ public class BInstallerView extends View if ( drawGrid ) { - Paint pnt_1 = new Paint(); pnt_1.setColor(Color.GREEN); for( int ix=1; ix<72; ix++ ) @@ -358,7 +363,7 @@ public class BInstallerView extends View totalSize = 0; int mask2 = MASK_SELECTED_RD5 | MASK_DELETED_RD5 | MASK_INSTALLED_RD5; int mask3 = mask2 | MASK_CURRENT_RD5; - Paint pnt_2 = new Paint(); + pnt_2.setColor(Color.GRAY); pnt_2.setStrokeWidth(1); drawSelectedTiles( canvas, pnt_2, fw, fh, MASK_INSTALLED_RD5, mask3, false, false, drawGrid ); @@ -377,7 +382,6 @@ public class BInstallerView extends View canvas.setMatrix( matText ); - Paint paint = new Paint(); paint.setColor(Color.RED); long mb = 1024*1024; @@ -408,7 +412,8 @@ public class BInstallerView extends View if ( isDownloading ) btnText = "Cancel Download"; else if ( delTiles > 0 ) btnText = "Delete " + delTiles + " tiles"; else if ( rd5Tiles > 0 ) btnText = "Start Download"; - + else if ( tilesVisible && rd5Tiles == 0) btnText = "Update all"; + if ( btnText != null ) { canvas.drawLine( imgw-btnw, imgh-btnh, imgw-btnw, imgh-2, paint); @@ -482,7 +487,7 @@ float tx, ty; @Override public boolean onTouchEvent(MotionEvent event) { - + // get pointer index from the event object int pointerIndex = event.getActionIndex(); @@ -569,8 +574,23 @@ float tx, ty; } // download button? - if ( ( delTiles > 0 || rd5Tiles > 0 || isDownloading ) && event.getX() > imgwOrig - btnw*scaleOrig && event.getY() > imghOrig-btnh*scaleOrig ) + if ( ( delTiles > 0 || rd5Tiles >= 0 || isDownloading ) && event.getX() > imgwOrig - btnw*scaleOrig && event.getY() > imghOrig-btnh*scaleOrig ) { + if (rd5Tiles == 0) { + for ( int ix = 0; ix < 72; ix++ ) + { + for ( int iy = 0; iy < 36; iy++ ) + { + int tidx = gridPos2Tileindex( ix, iy ); + if (tidx != -1) { + if ( ( tileStatus[tidx] & MASK_INSTALLED_RD5 ) != 0 ) { + tileStatus[tidx] |= MASK_SELECTED_RD5; + } + } + + } + } + } toggleDownload(); invalidate(); break; @@ -731,7 +751,7 @@ float tx, ty; tmp_file = new File( fname.getAbsolutePath() + ( delta ? "_diff" : "_tmp" ) ); output = new FileOutputStream( tmp_file ); - byte data[] = new byte[4096]; + byte[] data = new byte[4096]; long total = 0; long t0 = System.currentTimeMillis(); int count; diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java index aba0eab..b6dd6ca 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java @@ -23,6 +23,7 @@ import android.os.PowerManager.WakeLock; import android.os.StatFs; import android.speech.tts.TextToSpeech.OnInitListener; import android.util.Log; +import android.view.KeyEvent; import android.widget.EditText; import androidx.core.app.ActivityCompat; @@ -81,10 +82,12 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit protected Dialog onCreateDialog( int id ) { AlertDialog.Builder builder; + builder = new AlertDialog.Builder( this ); + builder.setCancelable(false); + switch ( id ) { case DIALOG_SELECTPROFILE_ID: - builder = new AlertDialog.Builder( this ); builder.setTitle( "Select a routing profile" ); builder.setItems( availableProfiles, new DialogInterface.OnClickListener() { @@ -96,22 +99,30 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_MAINACTION_ID: - builder = new AlertDialog.Builder( this ); builder.setTitle( "Select Main Action" ); - builder.setItems( new String[] - { "Download Manager", "BRouter App" }, new DialogInterface.OnClickListener() - { - public void onClick( DialogInterface dialog, int item ) - { - if ( item == 0 ) - startDownloadManager(); - else - showDialog( DIALOG_SELECTPROFILE_ID ); - } - } ); + builder + .setItems( new String[] + { "Download Manager", "BRouter App" }, new DialogInterface.OnClickListener() + { + public void onClick( DialogInterface dialog, int item ) + { + if ( item == 0 ) + startDownloadManager(); + else + showDialog( DIALOG_SELECTPROFILE_ID ); + } + } + ) + .setNegativeButton( "Close", new DialogInterface.OnClickListener() + { + public void onClick( DialogInterface dialog, int id ) + { + finish(); + } + } + ); return builder.create(); case DIALOG_SHOW_DM_INFO_ID: - builder = new AlertDialog.Builder( this ); builder .setTitle( "BRouter Download Manager" ) .setMessage( @@ -134,7 +145,6 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_SHOW_WP_HELP_ID: - builder = new AlertDialog.Builder( this ); builder .setTitle( "No Waypoint Database found" ) .setMessage( @@ -160,7 +170,6 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_SHOW_API23_HELP_ID: - builder = new AlertDialog.Builder( this ); builder .setTitle( "Android >=6 limitations" ) .setMessage( @@ -183,7 +192,6 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_SHOW_REPEAT_TIMEOUT_HELP_ID: - builder = new AlertDialog.Builder( this ); builder .setTitle( "Successfully prepared a timeout-free calculation" ) .setMessage( @@ -199,7 +207,6 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_SHOW_WP_SCANRESULT_ID: - builder = new AlertDialog.Builder( this ); builder .setTitle( "Waypoint Database " ) .setMessage( "Found Waypoint-Database(s) for maptool-dir: " + maptoolDirCandidate @@ -218,7 +225,6 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_OLDDATAHINT_ID: - builder = new AlertDialog.Builder( this ); builder .setTitle( "Local setup needs reset" ) .setMessage( @@ -235,7 +241,6 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_ROUTINGMODES_ID: - builder = new AlertDialog.Builder( this ); builder.setTitle( message ); builder.setMultiChoiceItems( routingModes, routingModesChecked, new DialogInterface.OnMultiChoiceClickListener() { @@ -254,7 +259,6 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_EXCEPTION_ID: - builder = new AlertDialog.Builder( this ); builder.setTitle( "An Error occured" ).setMessage( errorMessage ).setPositiveButton( "OK", new DialogInterface.OnClickListener() { public void onClick( DialogInterface dialog, int id ) @@ -264,7 +268,6 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_TEXTENTRY_ID: - builder = new AlertDialog.Builder( this ); builder.setTitle( "Enter SDCARD base dir:" ); builder.setMessage( message ); final EditText input = new EditText( this ); @@ -280,7 +283,6 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_SELECTBASEDIR_ID: - builder = new AlertDialog.Builder( this ); builder.setTitle( "Choose brouter data base dir:" ); // builder.setMessage( message ); builder.setSingleChoiceItems( basedirOptions, 0, new DialogInterface.OnClickListener() @@ -307,7 +309,6 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_VIASELECT_ID: - builder = new AlertDialog.Builder( this ); builder.setTitle( "Check VIA Selection:" ); builder.setMultiChoiceItems( availableVias, getCheckedBooleanArray( availableVias.length ), new DialogInterface.OnMultiChoiceClickListener() { @@ -334,7 +335,6 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_NOGOSELECT_ID: - builder = new AlertDialog.Builder( this ); builder.setTitle( "Check NoGo Selection:" ); String[] nogoNames = new String[nogoList.size()]; for ( int i = 0; i < nogoList.size(); i++ ) @@ -360,7 +360,7 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit case DIALOG_SHOWRESULT_ID: String leftLabel = wpCount < 0 ? ( wpCount != -2 ? "Exit" : "Help") : ( wpCount == 0 ? "Select from" : "Select to/via" ); String rightLabel = wpCount < 2 ? ( wpCount == -3 ? "Help" : "Server-Mode" ) : "Calc Route"; - builder = new AlertDialog.Builder( this ); + builder.setTitle( title ).setMessage( errorMessage ).setPositiveButton( leftLabel, new DialogInterface.OnClickListener() { public void onClick( DialogInterface dialog, int id ) @@ -399,7 +399,6 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_MODECONFIGOVERVIEW_ID: - builder = new AlertDialog.Builder( this ); builder.setTitle( "Success" ).setMessage( message ).setPositiveButton( "Exit", new DialogInterface.OnClickListener() { public void onClick( DialogInterface dialog, int id ) @@ -409,7 +408,6 @@ public class BRouterActivity extends Activity implements OnInitListener, Activit } ); return builder.create(); case DIALOG_PICKWAYPOINT_ID: - builder = new AlertDialog.Builder( this ); builder.setTitle( wpCount > 0 ? "Select to/via" : "Select from" ); builder.setItems( availableWaypoints, new DialogInterface.OnClickListener() { 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 93d2c86..fbe5d09 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java @@ -12,6 +12,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.zip.GZIPOutputStream; import java.util.ArrayList; @@ -68,9 +69,24 @@ public class BRouterService extends Service remoteProfile = checkForTestDummy( baseDir ); } - String errMsg = remoteProfile == null - ? getConfigFromMode( worker, baseDir, params.getString( "v" ), params.getString( "fast" ) ) - : getConfigForRemoteProfile( worker, baseDir, remoteProfile ); + String errMsg = null; + if (remoteProfile != null ) { + errMsg = getConfigForRemoteProfile(worker, baseDir, remoteProfile); + } else if (params.containsKey("profile")) { + String profile = params.getString( "profile" ); + worker.profileName = profile; + worker.profilePath = baseDir + "/brouter/profiles2/" + profile + ".brf"; + worker.rawTrackPath = baseDir + "/brouter/modes/" + profile + "_rawtrack.dat"; + if (!new File(worker.profilePath).exists()) errMsg = "Profile " + profile + " does not exists"; + try { + readNogos(worker, baseDir); + } catch (Exception e) { + errMsg = e.getLocalizedMessage(); + } + } + else { + errMsg = getConfigFromMode(worker, baseDir, params.getString("v"), params.getString("fast")); + } if ( errMsg != null ) { @@ -131,24 +147,8 @@ public class BRouterService extends Service worker.profilePath = baseDir + "/brouter/profiles2/" + smc.profile + ".brf"; worker.rawTrackPath = baseDir + "/brouter/modes/" + mode_key + "_rawtrack.dat"; - worker.nogoList = new ArrayList(); + readNogos(worker, baseDir); - int deviceLevel = android.os.Build.VERSION.SDK_INT; - int targetSdkVersion = getApplicationInfo().targetSdkVersion; - boolean canAccessSdCard = deviceLevel < 23 || targetSdkVersion == 10; - AppLogger.log( "dev/target=" + deviceLevel + "/" + targetSdkVersion + " canAccessSdCard=" + canAccessSdCard ); - if ( canAccessSdCard ) - { - CoordinateReader cor = CoordinateReader.obtainValidReader( baseDir, worker.segmentDir, true ); - // veto nogos by profiles veto list - for ( OsmNodeNamed nogo : cor.nogopoints ) - { - if ( !smc.nogoVetos.contains( nogo.ilon + "," + nogo.ilat ) ) - { - worker.nogoList.add( nogo ); - } - } - } return null; } } @@ -175,20 +175,7 @@ public class BRouterService extends Service try { - // add nogos from waypoint database - int deviceLevel = android.os.Build.VERSION.SDK_INT; - int targetSdkVersion = getApplicationInfo().targetSdkVersion; - boolean canAccessSdCard = deviceLevel < 23 || targetSdkVersion == 10; - AppLogger.log( "dev/target=" + deviceLevel + "/" + targetSdkVersion + " canAccessSdCard=" + canAccessSdCard ); - if ( canAccessSdCard ) - { - CoordinateReader cor = CoordinateReader.obtainValidReader( baseDir, worker.segmentDir, true ); - worker.nogoList = new ArrayList( cor.nogopoints ); - } - else - { - worker.nogoList = new ArrayList(); - } + readNogos(worker, baseDir); if ( !fileEqual( profileBytes, profileFile ) ) { @@ -211,6 +198,34 @@ public class BRouterService extends Service return null; } + private void readNogos(BRouterWorker worker, String baseDir) throws Exception { + // add nogos from waypoint database + int deviceLevel = android.os.Build.VERSION.SDK_INT; + int targetSdkVersion = getApplicationInfo().targetSdkVersion; + boolean canAccessSdCard = deviceLevel < 23 || targetSdkVersion == 10; + AppLogger.log( "dev/target=" + deviceLevel + "/" + targetSdkVersion + " canAccessSdCard=" + canAccessSdCard ); + if ( canAccessSdCard ) + { + CoordinateReader cor = CoordinateReader.obtainValidReader( baseDir, worker.segmentDir, true ); + worker.nogoList = new ArrayList( cor.nogopoints ); + worker.nogoPolygonsList = new ArrayList(); + } + else if (deviceLevel >= android.os.Build.VERSION_CODES.Q) { + CoordinateReader cor = new CoordinateReaderInternal( baseDir ); + cor.readFromTo(); + + worker.nogoList = new ArrayList( cor.nogopoints ); + worker.nogoPolygonsList = new ArrayList(); + } + else + { + worker.nogoList = new ArrayList(); + worker.nogoPolygonsList = new ArrayList(); + } + + } + + private boolean fileEqual( byte[] fileBytes, File file ) throws Exception { if ( !file.exists() ) 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 146f28c..5c7c13e 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -28,6 +28,7 @@ import android.content.res.AssetManager; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.os.Build; import android.os.Environment; import android.util.DisplayMetrics; import android.util.Log; @@ -181,7 +182,7 @@ public class BRouterView extends View String basedir = baseDir.getAbsolutePath(); AppLogger.log( "using basedir: " + basedir ); - String version = "v1.6.1"; + String version = "v" + getContext().getString(R.string.app_version); // create missing directories assertDirectoryExists( "project directory", new File (basedir, "brouter"), null, null ); @@ -198,6 +199,9 @@ public class BRouterView extends View assertDirectoryExists( "modes directory", modesDir, "modes.zip", version ); assertDirectoryExists( "readmes directory", new File (basedir, "brouter/readmes"), "readmes.zip", version ); + File inputDir = new File (basedir, "/import"); + assertDirectoryExists( "input directory", inputDir, null, version ); + int deviceLevel = android.os.Build.VERSION.SDK_INT; int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; canAccessSdCard = deviceLevel < 23 || targetSdkVersion == 10; @@ -207,7 +211,11 @@ public class BRouterView extends View } else { - cor = new CoordinateReaderNone(); + if (deviceLevel >= android.os.Build.VERSION_CODES.Q) { + cor = new CoordinateReaderInternal(basedir); + } else { + cor = new CoordinateReaderNone(); + } cor.readFromTo(); } @@ -342,7 +350,7 @@ public class BRouterView extends View { for ( int i = nogoList.size() - 1; i >= 0; i-- ) { - if ( !enabled[i] ) + if ( enabled[i] ) { nogoVetoList.add( nogoList.get( i ) ); nogoList.remove( i ); @@ -830,7 +838,7 @@ public class BRouterView extends View else { String memstat = memoryClass + "mb pathPeak " + ((cr.getPathPeak()+500)/1000) + "k"; - String result = "version = BRouter-1.6.1\n" + "mem = " + memstat + "\ndistance = " + cr.getDistance() / 1000. + " km\n" + "filtered ascend = " + cr.getAscend() + String result = "version = BRouter-" + getContext().getString(R.string.app_version) + "\n" + "mem = " + memstat + "\ndistance = " + cr.getDistance() / 1000. + " km\n" + "filtered ascend = " + cr.getAscend() + " m\n" + "plain ascend = " + cr.getPlainAscend() + " m\n" + "estimated time = " + cr.getTime(); rawTrack = cr.getFoundRawTrack(); @@ -1044,7 +1052,8 @@ public class BRouterView extends View // no write new config BufferedWriter bw = null; StringBuilder msg = new StringBuilder( "Mode mapping is now:\n" ); - msg.append( "( [..] counts nogo-vetos)\n" ); + msg.append("( ["); + msg.append(nogoVetoList.size()>0?nogoVetoList.size():"..").append( "] counts nogo-vetos)\n" ); try { bw = new BufferedWriter( new FileWriter( modesFile ) ); @@ -1086,7 +1095,12 @@ public class BRouterView extends View } private ArrayList getStorageDirectories () { - ArrayList list = new ArrayList(Arrays.asList(getContext().getExternalFilesDirs(null))); + ArrayList list = null; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { + list = new ArrayList(Arrays.asList(getContext().getExternalMediaDirs())); + } else { + list = new ArrayList(Arrays.asList(getContext().getExternalFilesDirs(null))); + } ArrayList res = new ArrayList(); for (File f : list) { 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 c40ff9c..a1770fd 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java @@ -10,8 +10,9 @@ import java.util.List; import java.util.StringTokenizer; import android.os.Bundle; + import btools.router.OsmNodeNamed; -import btools.router.OsmPathElement; +import btools.router.OsmNogoPolygon; import btools.router.OsmTrack; import btools.router.RoutingContext; import btools.router.RoutingEngine; @@ -29,70 +30,93 @@ public class BRouterWorker public String rawTrackPath; public List waypoints; public List nogoList; + public List nogoPolygonsList; - public String getTrackFromParams(Bundle params) - { - String pathToFileResult = params.getString("pathToFileResult"); - - if (pathToFileResult != null) - { - File f = new File (pathToFileResult); - File dir = f.getParentFile(); - if (!dir.exists() || !dir.canWrite()){ - return "file folder does not exists or can not be written!"; - } - } + public String getTrackFromParams(Bundle params) { + String pathToFileResult = params.getString("pathToFileResult"); - long maxRunningTime = 60000; - String sMaxRunningTime = params.getString( "maxRunningTime" ); - if ( sMaxRunningTime != null ) - { - maxRunningTime = Integer.parseInt( sMaxRunningTime ) * 1000; - } - - RoutingContext rc = new RoutingContext(); - rc.rawTrackPath = rawTrackPath; - rc.localFunction = profilePath; - - String tiFormat = params.getString( "turnInstructionFormat" ); - if ( tiFormat != null ) - { - if ( "osmand".equalsIgnoreCase( tiFormat ) ) - { - rc.turnInstructionMode = 3; + if (pathToFileResult != null) { + File f = new File(pathToFileResult); + File dir = f.getParentFile(); + if (!dir.exists() || !dir.canWrite()) { + return "file folder does not exists or can not be written!"; + } } - else if ( "locus".equalsIgnoreCase( tiFormat ) ) - { - rc.turnInstructionMode = 2; + + long maxRunningTime = 60000; + String sMaxRunningTime = params.getString("maxRunningTime"); + if (sMaxRunningTime != null) { + maxRunningTime = Integer.parseInt(sMaxRunningTime) * 1000; } - } + + RoutingContext rc = new RoutingContext(); + rc.rawTrackPath = rawTrackPath; + rc.localFunction = profilePath; + + String tiFormat = params.getString("turnInstructionFormat"); + if (tiFormat != null) { + if ("osmand".equalsIgnoreCase(tiFormat)) { + rc.turnInstructionMode = 3; + } else if ("locus".equalsIgnoreCase(tiFormat)) { + rc.turnInstructionMode = 2; + } + } + if (params.containsKey("timode")) { + rc.turnInstructionMode = params.getInt("timode"); + } + if ( params.containsKey( "direction" ) ) { - rc.startDirection = Integer.valueOf( params.getInt( "direction" ) ); + rc.startDirection = params.getInt( "direction" ); } - 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() ) { - String t = tk.nextToken(); - StringTokenizer tk2 = new StringTokenizer( t, "=" ); - if ( tk2.hasMoreTokens() ) { - String key = tk2.nextToken(); - if ( tk2.hasMoreTokens() ) { - String value = tk2.nextToken(); - rc.keyValues.put( key, value ); - } - } - } + if ( params.containsKey( "alternativeidx" ) ) { + rc.alternativeIdx = params.getInt( "alternativeidx" ); } readNogos( params ); // add interface provided nogos - RoutingContext.prepareNogoPoints( nogoList ); - rc.nogopoints = nogoList; + if ( nogoList != null ) { + RoutingContext.prepareNogoPoints(nogoList); + if (rc.nogopoints == null) { + rc.nogopoints = nogoList; + } else { + rc.nogopoints.addAll(nogoList); + } + } + if (rc.nogopoints == null) { + rc.nogopoints = nogoPolygonsList; + } else if ( nogoPolygonsList != null ) { + rc.nogopoints.addAll(nogoPolygonsList); + } + List poisList = readPoisList(params); + rc.poipoints = poisList; + + if (params.containsKey("lats")) { + waypoints = readPositions(params); + } + if (params.containsKey("lonlats")) { + waypoints = readLonlats(params); + } + + if (waypoints == null) return "no pts "; + + 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() ) { + String t = tk.nextToken(); + StringTokenizer tk2 = new StringTokenizer( t, "=" ); + if ( tk2.hasMoreTokens() ) { + String key = tk2.nextToken(); + if ( tk2.hasMoreTokens() ) { + String value = tk2.nextToken(); + rc.keyValues.put( key, value ); + } + } + } + } - waypoints = readPositions(params); try { @@ -130,6 +154,9 @@ public class BRouterWorker OsmTrack track = cr.getFoundTrack(); if ( track != null ) { + if (params.containsKey("exportWaypoints")) { + track.exportWaypoints = (params.getInt("exportWaypoints",0) == 1 ); + } if ( pathToFileResult == null ) { switch ( writeFromat ) { @@ -183,27 +210,225 @@ public class BRouterWorker return wplist; } + private List readLonlats(Bundle params ) + { + List wplist = new ArrayList(); + + String lonLats = params.getString( "lonlats" ); + if (lonLats == null) throw new IllegalArgumentException( "lonlats parameter not set" ); + + String[] coords = lonLats.split("\\|"); + if (coords.length < 2) + throw new IllegalArgumentException( "we need two lat/lon points at least!" ); + + for (int i = 0; i < coords.length; i++) + { + String[] lonLat = coords[i].split(","); + if (lonLat.length < 2) + throw new IllegalArgumentException( "we need two lat/lon points at least!" ); + wplist.add( readPosition( lonLat[0], lonLat[1], "via" + i ) ); + } + + wplist.get(0).name = "from"; + wplist.get(wplist.size()-1).name = "to"; + + return wplist; + } + + private static OsmNodeNamed readPosition( String vlon, String vlat, String name ) + { + if ( vlon == null ) throw new IllegalArgumentException( "lon " + name + " not found in input" ); + if ( vlat == null ) throw new IllegalArgumentException( "lat " + name + " not found in input" ); + + return readPosition(Double.parseDouble( vlon ), Double.parseDouble( vlat ), name); + } + + private static OsmNodeNamed readPosition( double lon, double lat, String name ) + { + OsmNodeNamed n = new OsmNodeNamed(); + n.name = name; + n.ilon = (int)( ( lon + 180. ) *1000000. + 0.5); + n.ilat = (int)( ( lat + 90. ) *1000000. + 0.5); + return n; + } + + private void readNogos( Bundle params ) { - double[] lats = params.getDoubleArray("nogoLats"); - double[] lons = params.getDoubleArray("nogoLons"); - double[] radi = params.getDoubleArray("nogoRadi"); - - if ( lats == null || lons == null || radi == null ) return; - - for( int i=0; i readNogoList(Bundle params) + { + // lon,lat,radius|... + String nogos = params.getString( "nogos" ); + if ( nogos == null ) return null; + + String[] lonLatRadList = nogos.split("\\|"); + + List nogoList = new ArrayList(); + for (int i = 0; i < lonLatRadList.length; i++) + { + String[] lonLatRad = lonLatRadList[i].split(","); + String nogoWeight = "NaN"; + if (lonLatRad.length > 3) { + nogoWeight = lonLatRad[3]; + } + nogoList.add(readNogo(lonLatRad[0], lonLatRad[1], lonLatRad[2], nogoWeight)); + } + + return nogoList; + } + + private static OsmNodeNamed readNogo( String lon, String lat, String radius, String nogoWeight ) + { + double weight = "undefined".equals( nogoWeight ) ? Double.NaN : Double.parseDouble( nogoWeight ); + return readNogo(Double.parseDouble( lon ), Double.parseDouble( lat ), Integer.parseInt( radius ), weight ); + } + + private static OsmNodeNamed readNogo( double lon, double lat, int radius, double nogoWeight ) + { + OsmNodeNamed n = new OsmNodeNamed(); + n.name = "nogo" + radius; + n.ilon = (int)( ( lon + 180. ) *1000000. + 0.5); + n.ilat = (int)( ( lat + 90. ) *1000000. + 0.5); + n.isNogo = true; + n.nogoWeight = nogoWeight; + return n; + } + + private List readNogoPolygons(Bundle params) + { + List result = new ArrayList(); + parseNogoPolygons( params.getString("polylines"), result, false ); + parseNogoPolygons( params.getString("polygons"), result, true ); + return result.size() > 0 ? result : null; + } + + private static void parseNogoPolygons(String polygons, List result, boolean closed ) + { + if ( polygons != null ) + { + OsmNogoPolygon polygon = new OsmNogoPolygon(closed); + polygon.name = "nogopoly" ; + String nogoWeight = "NaN"; + String[] polygonList = polygons.split("\\|"); + for (int i = 0; i < polygonList.length; i++) + { + String[] lonLatList = polygonList[i].split(","); + if ( lonLatList.length > 1 ) + { + int j; + for (j = 0; j < 2 * (lonLatList.length / 2) - 1;) + { + String slon = lonLatList[j++]; + String slat = lonLatList[j++]; + int lon = (int)( ( Double.parseDouble(slon) + 180. ) *1000000. + 0.5); + int lat = (int)( ( Double.parseDouble(slat) + 90. ) *1000000. + 0.5); + polygon.addVertex(lon, lat); + } + + + if (j < lonLatList.length) { + nogoWeight = lonLatList[j]; + } + } + } + polygon.nogoWeight = Double.parseDouble( nogoWeight ); + + if ( polygon.points.size() > 0 ) + { + polygon.calcBoundingCircle(); + result.add(polygon); + } + } + } + + private static void parseNogoPolygons_alt(String polygons, List result, boolean closed ) + { + if ( polygons != null ) + { + String[] polygonList = polygons.split("\\|"); + for (int i = 0; i < polygonList.length; i++) + { + String[] lonLatList = polygonList[i].split(","); + if ( lonLatList.length > 1 ) + { + OsmNogoPolygon polygon = new OsmNogoPolygon(closed); + polygon.name = "nogo" + i; + int j; + for (j = 0; j < 2 * (lonLatList.length / 2) - 1;) + { + String slon = lonLatList[j++]; + String slat = lonLatList[j++]; + int lon = (int)( ( Double.parseDouble(slon) + 180. ) *1000000. + 0.5); + int lat = (int)( ( Double.parseDouble(slat) + 90. ) *1000000. + 0.5); + polygon.addVertex(lon, lat); + } + + String nogoWeight = "NaN"; + if (j < lonLatList.length) { + nogoWeight = lonLatList[j]; + } + polygon.nogoWeight = Double.parseDouble( nogoWeight ); + + if ( polygon.points.size() > 0 ) + { + polygon.calcBoundingCircle(); + result.add(polygon); + } + } + } + } + } + + private List readPoisList(Bundle params ) + { + // lon,lat,name|... + String pois = params.getString( "pois", null ); + if ( pois == null ) return null; + + String[] lonLatNameList = pois.split("\\|"); + + List poisList = new ArrayList(); + for (int i = 0; i < lonLatNameList.length; i++) + { + String[] lonLatName = lonLatNameList[i].split(","); + + OsmNodeNamed n = new OsmNodeNamed(); + n.ilon = (int)( ( Double.parseDouble(lonLatName[0]) + 180. ) *1000000. + 0.5); + n.ilat = (int)( ( Double.parseDouble(lonLatName[1]) + 90. ) *1000000. + 0.5); + n.name = lonLatName[2]; + poisList.add(n); + } + + return poisList; + } + private void writeTimeoutData( RoutingContext rc ) throws Exception { String timeoutFile = baseDir + "/brouter/modes/timeoutdata.txt"; diff --git a/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderInternal.java b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderInternal.java new file mode 100644 index 0000000..cfe5b38 --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderInternal.java @@ -0,0 +1,211 @@ +package btools.routingapp; + +import android.graphics.Point; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import btools.router.OsmNodeNamed; +import btools.router.OsmNogoPolygon; + +/** + * Read coordinates from a gpx-file + */ +public class CoordinateReaderInternal extends CoordinateReader +{ + private String internalDir; + + public CoordinateReaderInternal(String basedir ) + { + this( basedir, false ); + } + + public CoordinateReaderInternal(String basedir, boolean shortPath ) + { + super( basedir ); + if ( shortPath ) + { + internalDir = basedir; + tracksdir = "/tracks"; + rootdir = ""; + } + else + { + internalDir = basedir + "/import"; + tracksdir = "/import/tracks"; + rootdir = "/import"; + } + } + + @Override + public long getTimeStamp() throws Exception + { + long t1 = new File( internalDir + "/favourites_bak.gpx" ).lastModified(); + long t2 = new File( internalDir + "/favourites.gpx" ).lastModified(); + return t1 > t2 ? t1 : t2; + } + + @Override + public int getTurnInstructionMode() + { + return 4; // comment style + } + + /* + * read the from and to position from a gpx-file + * (with hardcoded name for now) + */ + @Override + public void readPointmap() throws Exception + { + if (! _readPointmap( internalDir + "/favourites_bak.gpx" ) ) { + _readPointmap( internalDir + "/favourites.gpx" ); + } + + try + { + _readNogoLines( basedir+tracksdir ); + } + catch( IOException ioe ) + { + } + } + + private boolean _readPointmap( String filename ) throws Exception + { + BufferedReader br = null; + try { + br = new BufferedReader( + new InputStreamReader( + new FileInputStream( filename ) ) ); + } catch (FileNotFoundException e) { + // ignore until it's reading error + return false; + } + OsmNodeNamed n = null; + + for(;;) + { + String line = br.readLine(); + if ( line == null ) break; + + int idx0 = line.indexOf( " lat=\"" ); + int idx10 = line.indexOf( "" ); + if ( idx0 >= 0 ) + { + n = new OsmNodeNamed(); + idx0 += 6; + int idx1 = line.indexOf( '"', idx0 ); + n.ilat = (int)( (Double.parseDouble( line.substring( idx0, idx1 ) ) + 90. )*1000000. + 0.5); + int idx2 = line.indexOf( " lon=\"" ); + if ( idx2 < 0 ) continue; + idx2 += 6; + int idx3 = line.indexOf( '"', idx2 ); + n.ilon = (int)( ( Double.parseDouble( line.substring( idx2, idx3 ) ) + 180. )*1000000. + 0.5); + if ( idx3 < 0 ) continue; + } + if ( n != null && idx10 >= 0 ) + { + idx10 += 6; + int idx11 = line.indexOf( "", idx10 ); + if ( idx11 >= 0 ) + { + n.name = line.substring( idx10, idx11 ).trim(); + checkAddPoint( "(one-for-all)", n ); + } + } + } + br.close(); + return true; + } + + private void _readNogoLines( String dirname ) throws IOException + { + + File dir = new File( dirname ); + + if (dir.exists() && dir.isDirectory()) + { + for (final File file : dir.listFiles()) + { + final String name = file.getName(); + if (name.startsWith("nogo") && name.endsWith(".gpx")) + { + try + { + _readNogoLine(file); + } + catch (Exception e) + { + } + } + } + } + } + + private void _readNogoLine( File file ) throws Exception + { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(false); + XmlPullParser xpp = factory.newPullParser(); + + xpp.setInput(new FileReader(file)); + + List tmpPts = new ArrayList(); + int eventType = xpp.getEventType(); + int numSeg = 0; + while (eventType != XmlPullParser.END_DOCUMENT) { + switch(eventType) { + case XmlPullParser.START_TAG: { + if (xpp.getName().equals("trkpt") || xpp.getName().equals("rtept")) { + final String lon = xpp.getAttributeValue(null,"lon"); + final String lat = xpp.getAttributeValue(null,"lat"); + if (lon != null && lat != null) { + tmpPts.add(new Point( + (int)( ( Double.parseDouble(lon) + 180. ) *1000000. + 0.5), + (int)( ( Double.parseDouble(lat) + 90. ) *1000000. + 0.5)) ); + } + } + break; + } + case XmlPullParser.END_TAG: { + if (xpp.getName().equals("trkseg") || xpp.getName().equals("rte")) { // rte has no segment + OsmNogoPolygon nogo = null; + if (tmpPts.size() >= 0) { + if (tmpPts.get(0).x == tmpPts.get(tmpPts.size()-1).x && + tmpPts.get(0).y == tmpPts.get(tmpPts.size()-1).y) { + nogo = new OsmNogoPolygon(true); + } else { + nogo = new OsmNogoPolygon(false); + } + for (Point p : tmpPts) { + nogo.addVertex(p.x, p.y); + } + nogo.calcBoundingCircle(); + final String name = file.getName(); + nogo.name = name.substring(0, name.length() - 4); + if (numSeg > 0) { + nogo.name += Integer.toString(numSeg + 1); + } + numSeg++; + checkAddPoint("(one-for-all)", nogo); + } + tmpPts.clear(); + } + break; + } + } + eventType = xpp.next(); + } + } +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderLocus.java b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderLocus.java index 8c33746..9238f2b 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderLocus.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderLocus.java @@ -21,8 +21,11 @@ public class CoordinateReaderLocus extends CoordinateReader @Override public long getTimeStamp() throws Exception { - long t1 = new File( basedir + "/Locus/data/database/waypoints.db" ).lastModified(); - return t1; + File f = new File( basedir + "/Locus/data/database/waypoints.db" ); + long t1 = f.lastModified(); + // Android 10 delivers file size but can't read it + boolean canRead = f.canRead(); + return canRead ? t1 : 0L; } @Override @@ -43,9 +46,19 @@ public class CoordinateReaderLocus extends CoordinateReader private void _readPointmap( String filename ) throws Exception { - SQLiteDatabase myDataBase = SQLiteDatabase.openDatabase( filename, null, SQLiteDatabase.OPEN_READONLY); + SQLiteDatabase myDataBase = null; + try { + myDataBase = SQLiteDatabase.openDatabase( filename, null, SQLiteDatabase.OPEN_READONLY); + } catch (Exception e) { + // not open, do not produce an error + return; + } Cursor c = myDataBase.rawQuery("SELECT c.name, w.name, w.longitude, w.latitude FROM waypoints w, categories c where w.parent_id = c._id", null); + if (c.getCount() == 0) { + c.close(); + c = myDataBase.rawQuery("SELECT c.name, w.name, w.longitude, w.latitude FROM waypoints w, groups c where w.parent_id = c._id;", null ); + } while (c.moveToNext()) { OsmNodeNamed n = new OsmNodeNamed(); @@ -55,6 +68,7 @@ public class CoordinateReaderLocus extends CoordinateReader n.ilat = (int)( ( c.getDouble(3) + 90. )*1000000. + 0.5); checkAddPoint( category, n ); } + c.close(); myDataBase.close(); } } diff --git a/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOrux.java b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOrux.java index 9e8e14c..3d4d44b 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOrux.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOrux.java @@ -21,8 +21,11 @@ public class CoordinateReaderOrux extends CoordinateReader @Override public long getTimeStamp() throws Exception { - long t1 = new File( basedir + "/oruxmaps/tracklogs/oruxmapstracks.db" ).lastModified(); - return t1; + File f = new File( basedir + "/oruxmaps/tracklogs/oruxmapstracks.db" ); + long t1 = f.lastModified(); + // Android 10 delivers file size but can't read it + boolean canRead = f.canRead(); + return canRead ? t1 : 0L; } @Override @@ -43,7 +46,13 @@ public class CoordinateReaderOrux extends CoordinateReader private void _readPointmap( String filename ) throws Exception { - SQLiteDatabase myDataBase = SQLiteDatabase.openDatabase( filename, null, SQLiteDatabase.OPEN_READONLY); + SQLiteDatabase myDataBase = null; + try { + myDataBase = SQLiteDatabase.openDatabase( filename, null, SQLiteDatabase.OPEN_READONLY); + } catch (Exception e) { + // not open, do not produce an error + return; + } Cursor c = myDataBase.rawQuery("SELECT poiname, poilon, poilat, poifolder FROM pois", null); while (c.moveToNext()) { @@ -54,6 +63,7 @@ public class CoordinateReaderOrux extends CoordinateReader String category = c.getString(3); checkAddPoint( category, n ); } + c.close(); myDataBase.close(); } } diff --git a/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOsmAnd.java b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOsmAnd.java index b4b059f..7e9ef94 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOsmAnd.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOsmAnd.java @@ -45,8 +45,10 @@ public class CoordinateReaderOsmAnd extends CoordinateReader @Override public long getTimeStamp() throws Exception { - long t1 = new File( osmandDir + "/favourites_bak.gpx" ).lastModified(); - long t2 = new File( osmandDir + "/favourites.gpx" ).lastModified(); + File f1 = new File( osmandDir + "/favourites_bak.gpx" ); + File f2 = new File( osmandDir + "/favourites.gpx" ); + long t1 = f1.canRead()?f1.lastModified():0L; + long t2 = f2.canRead()?f2.lastModified():0L; return t1 > t2 ? t1 : t2; }