diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index fcef247..34a331e 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -352,7 +352,7 @@ public class RoutingEngine extends Thread { } private void logException(Throwable t) { - errorMessage = t instanceof IllegalArgumentException ? t.getMessage() : t.toString(); + errorMessage = t instanceof RuntimeException ? t.getMessage() : t.toString(); logInfo("Error (linksProcessed=" + linksProcessed + " open paths: " + openSet.getSize() + "): " + errorMessage); } diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/NodesCache.java b/brouter-mapaccess/src/main/java/btools/mapaccess/NodesCache.java index ab89a68..ba594b9 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/NodesCache.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/NodesCache.java @@ -187,6 +187,8 @@ public final class NodesCache { ghostWakeup += segment.getDataSize(); } return segment; + } catch (IOException re) { + throw new RuntimeException(re.getMessage()); } catch (RuntimeException re) { throw re; } catch (Exception e) { @@ -294,14 +296,14 @@ public final class NodesCache { for (int i = 0; i < len; i++) { MatchedWaypoint mwp = unmatchedWaypoints.get(i); if (mwp.crosspoint == null) { - if (unmatchedWaypoints.size() > 1 && i == unmatchedWaypoints.size()-1 && unmatchedWaypoints.get(i-1).direct) { + if (unmatchedWaypoints.size() > 1 && i == unmatchedWaypoints.size() - 1 && unmatchedWaypoints.get(i - 1).direct) { mwp.crosspoint = new OsmNode(mwp.waypoint.ilon, mwp.waypoint.ilat); mwp.direct = true; } else { throw new IllegalArgumentException(mwp.name + "-position not mapped in existing datafile"); } } - if (unmatchedWaypoints.size() > 1 && i == unmatchedWaypoints.size()-1 && unmatchedWaypoints.get(i-1).direct) { + if (unmatchedWaypoints.size() > 1 && i == unmatchedWaypoints.size() - 1 && unmatchedWaypoints.get(i - 1).direct) { mwp.crosspoint = new OsmNode(mwp.waypoint.ilon, mwp.waypoint.ilat); mwp.direct = true; } diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java b/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java index 921bd31..6b043bd 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java @@ -37,6 +37,27 @@ final public class PhysicalFile { } } + public static int checkVersionIntegrity(File f) { + int version = -1; + RandomAccessFile raf = null; + try { + byte[] iobuffer = new byte[200]; + raf = new RandomAccessFile(f, "r"); + raf.readFully(iobuffer, 0, 200); + ByteDataReader dis = new ByteDataReader(iobuffer); + long lv = dis.readLong(); + version = (int) (lv >> 48); + } catch (IOException e) { + } finally { + try { + if (raf != null) raf.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return version; + } + /** * Checks the integrity of the file using the build-in checksums * @@ -81,7 +102,7 @@ final public class PhysicalFile { short readVersion = (short) (lv >> 48); if (i == 0 && lookupVersion != -1 && readVersion != lookupVersion) { throw new IOException("lookup version mismatch (old rd5?) lookups.dat=" - + lookupVersion + " " + f.getAbsolutePath() + "=" + readVersion); + + lookupVersion + " " + f.getName() + "=" + readVersion); } fileIndex[i] = lv & 0xffffffffffffL; } diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index 71dea8e..16e3ba3 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -5,9 +5,10 @@ plugins { } android { - compileSdkVersion 31 + compileSdkVersion 33 defaultConfig { + namespace 'btools.routingapp' applicationId "btools.routingapp" versionCode 45 @@ -17,7 +18,7 @@ android { setProperty("archivesBaseName", "BRouterApp." + defaultConfig.versionName) minSdkVersion 14 - targetSdkVersion 30 + targetSdkVersion 33 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -84,10 +85,10 @@ android { } dependencies { - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation "androidx.constraintlayout:constraintlayout:2.1.3" - implementation 'androidx.work:work-runtime:2.7.1' - implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation "androidx.constraintlayout:constraintlayout:2.1.4" + implementation 'androidx.work:work-runtime:2.8.0' + implementation 'com.google.android.material:material:1.8.0' implementation project(':brouter-mapaccess') implementation project(':brouter-core') @@ -96,9 +97,13 @@ dependencies { testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - androidTestImplementation 'androidx.work:work-testing:2.7.1' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.work:work-testing:2.8.0' +} + +gradle.projectsEvaluated { + preBuild.dependsOn(generateProfilesZip, generateReadmesZip) } check.dependsOn 'checkstyle' diff --git a/brouter-routing-app/src/main/AndroidManifest.xml b/brouter-routing-app/src/main/AndroidManifest.xml index 17957da..01dd8db 100644 --- a/brouter-routing-app/src/main/AndroidManifest.xml +++ b/brouter-routing-app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ + > + selectedTiles; + BInstallerView.OnSelectListener onSelectListener; + + @SuppressWarnings("deprecation") public static long getAvailableSpace(String baseDir) { StatFs stat = new StatFs(baseDir); @@ -60,32 +81,78 @@ public class BInstallerActivity extends AppCompatActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + boolean running = isDownloadRunning(DownloadWorker.class); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); setContentView(R.layout.activity_binstaller); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.POST_NOTIFICATIONS) + == PackageManager.PERMISSION_GRANTED) { + // nothing to do + } + if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { + // + } else { + // You can directly ask for the permission. + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, MY_PERMISSIONS_REQUEST_NITIFICATION); + } + } + mSummaryInfo = findViewById(R.id.textViewSegmentSummary); mBInstallerView = findViewById(R.id.BInstallerView); - mBInstallerView.setOnSelectListener( - () -> { + onSelectListener = new BInstallerView.OnSelectListener() { + @Override + public void onSelect() { + //if (!isDownloadRunning(DownloadWorker.class)) updateDownloadButton(); } - ); + }; + + mBInstallerView.setOnSelectListener(onSelectListener); + mButtonDownload = findViewById(R.id.buttonDownload); mButtonDownload.setOnClickListener( view -> { - if (mBInstallerView.getSelectedTiles(MASK_DELETED_RD5).size() > 0) { + if (isDownloadRunning(DownloadWorker.class)) { + stopDownload(); + } else if (mBInstallerView.getSelectedTiles(MASK_DELETED_RD5).size() > 0) { showConfirmDelete(); } else if (mBInstallerView.getSelectedTiles(MASK_SELECTED_RD5).size() > 0) { + mBInstallerView.setOnSelectListener(null); downloadSelectedTiles(); } else { + mBInstallerView.setOnSelectListener(null); downloadInstalledTiles(); } } ); mProgressIndicator = findViewById(R.id.progressDownload); + mDownloadSummaryInfo = findViewById(R.id.textViewDownloadSummary); + mDownloadSummaryInfo.setVisibility(View.INVISIBLE); mBaseDir = ConfigHelper.getBaseDir(this); - scanExistingFiles(); + + if (running) { + mProgressIndicator.show(); + mButtonDownload.setEnabled(false); + WorkManager instance = WorkManager.getInstance(getApplicationContext()); + LiveData> ld = instance.getWorkInfosForUniqueWorkLiveData(DownloadWorker.WORKER_NAME); + ld.observe(this, listOfWorkInfo -> { + // If there are no matching work info, do nothing + if (listOfWorkInfo == null || listOfWorkInfo.isEmpty()) { + return; + } + for (WorkInfo workInfo : listOfWorkInfo) { + startObserver(workInfo); + } + + }); + + } else { + scanExistingFiles(); + } } private String getSegmentsPlural(int count) { @@ -131,19 +198,24 @@ public class BInstallerActivity extends AppCompatActivity { } } - public void downloadAll(ArrayList downloadList) { + public void downloadAll(ArrayList downloadList, int all) { ArrayList urlparts = new ArrayList<>(); for (Integer i : downloadList) { urlparts.add(baseNameForTile(i)); } downloadCanceled = false; + mProgressIndicator.show(); + mButtonDownload.setEnabled(false); Data inputData = new Data.Builder() .putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, urlparts.toArray(new String[0])) + .putInt(DownloadWorker.KEY_INPUT_SEGMENT_ALL, all) .build(); Constraints constraints = new Constraints.Builder() + .setRequiresBatteryNotLow(true) + .setRequiresStorageNotLow(true) .setRequiredNetworkType(NetworkType.CONNECTED) .build(); @@ -154,65 +226,112 @@ public class BInstallerActivity extends AppCompatActivity { .build(); WorkManager workManager = WorkManager.getInstance(getApplicationContext()); - workManager.enqueue(downloadWorkRequest); + workManager.enqueueUniqueWork(DownloadWorker.WORKER_NAME, ExistingWorkPolicy.KEEP, (OneTimeWorkRequest) downloadWorkRequest); + + try { + WorkInfo wi = WorkManager.getInstance(getApplicationContext()).getWorkInfoById(downloadWorkRequest.getId()).get(); + if (wi != null && (wi.getState() == WorkInfo.State.ENQUEUED || wi.getState() == WorkInfo.State.BLOCKED)) { + Log.d("worker", "cancel " + wi.getState()); + //WorkManager.getInstance(getApplicationContext()).cancelWorkById(downloadWorkRequest.getId()); + } + } catch (ExecutionException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + Log.d("worker", "canceled " + e.getMessage()); + //e.printStackTrace(); + } workManager .getWorkInfoByIdLiveData(downloadWorkRequest.getId()) .observe(this, workInfo -> { - if (workInfo != null) { - if (workInfo.getState() == WorkInfo.State.ENQUEUED) { - Toast.makeText(this, "Download scheduled. Check internet connection if it doesn't start.", Toast.LENGTH_LONG).show(); - mProgressIndicator.hide(); - mProgressIndicator.setIndeterminate(true); - mProgressIndicator.show(); - } - - if (workInfo.getState() == WorkInfo.State.RUNNING) { - Data progress = workInfo.getProgress(); - String segmentName = progress.getString(DownloadWorker.PROGRESS_SEGMENT_NAME); - int percent = progress.getInt(DownloadWorker.PROGRESS_SEGMENT_PERCENT, 0); - if (percent > 0) { - mProgressIndicator.setIndeterminate(false); - } - mProgressIndicator.setProgress(percent); - } - - if (workInfo.getState().isFinished()) { - String result; - switch (workInfo.getState()) { - case FAILED: - result = "Download failed"; - break; - case CANCELLED: - result = "Download cancelled"; - break; - case SUCCEEDED: - result = "Download succeeded"; - break; - default: - result = ""; - } - if (workInfo.getState() != WorkInfo.State.FAILED) { - Toast.makeText(this, result, Toast.LENGTH_SHORT).show(); - } else { - String error = workInfo.getOutputData().getString(DownloadWorker.KEY_OUTPUT_ERROR); - Toast.makeText(this, result + ": " + error, Toast.LENGTH_LONG).show(); - } - mProgressIndicator.hide(); - scanExistingFiles(); - } - } + startObserver(workInfo); }); deleteRawTracks(); // invalidate raw-tracks after data update } - @Override - protected Dialog onCreateDialog(int id) { + private void startObserver(WorkInfo workInfo) { + if (workInfo != null) { + if (workInfo.getState() == WorkInfo.State.ENQUEUED || workInfo.getState() == WorkInfo.State.BLOCKED) { + Log.d("worker", "cancel " + workInfo.getState()); + //WorkManager.getInstance(getApplicationContext()).cancelWorkById(downloadWorkRequest.getId()); + } + + if (workInfo.getState() == WorkInfo.State.ENQUEUED) { + Toast.makeText(this, "Download scheduled. Check internet connection if it doesn't start.", Toast.LENGTH_LONG).show(); + mProgressIndicator.hide(); + mProgressIndicator.setIndeterminate(true); + mProgressIndicator.show(); + } + + if (workInfo.getState() == WorkInfo.State.RUNNING) { + mDownloadSummaryInfo.setVisibility(View.VISIBLE); + Data progress = workInfo.getProgress(); + String segmentName = progress.getString(DownloadWorker.PROGRESS_SEGMENT_NAME); + int percent = progress.getInt(DownloadWorker.PROGRESS_SEGMENT_PERCENT, 0); + if (percent > 0) { + mDownloadSummaryInfo.setText("Downloading .. " + segmentName); + } + if (percent > 0) { + mProgressIndicator.setIndeterminate(false); + } + mProgressIndicator.setProgress(percent); + + mButtonDownload.setText(getString(R.string.action_cancel)); + mButtonDownload.setEnabled(true); + + } + + if (workInfo.getState().isFinished()) { + String result; + switch (workInfo.getState()) { + case FAILED: + result = "Download failed"; + break; + case CANCELLED: + result = "Download cancelled"; + break; + case SUCCEEDED: + result = "Download succeeded"; + break; + default: + result = ""; + } + String error = null; + if (workInfo.getState() != WorkInfo.State.FAILED) { + Toast.makeText(this, result, Toast.LENGTH_SHORT).show(); + } else { + error = workInfo.getOutputData().getString(DownloadWorker.KEY_OUTPUT_ERROR); + if (error != null && !error.startsWith("Version")) { + Toast.makeText(this, result + ": " + error, Toast.LENGTH_LONG).show(); + } + } + + if (error != null && error.startsWith("Version error")) { + showConfirmNextSteps(); + } else if (error != null && error.startsWith("Version diffs")) { + showConfirmGetDiffs(); + } else { + mBInstallerView.setOnSelectListener(onSelectListener); + mBInstallerView.clearAllTilesStatus(MASK_SELECTED_RD5); + scanExistingFiles(); + } + mProgressIndicator.hide(); + mDownloadSummaryInfo.setVisibility(View.INVISIBLE); + mButtonDownload.setEnabled(true); + } + } + + } + + + protected Dialog createADialog(int id) { AlertDialog.Builder builder; + builder = new AlertDialog.Builder(this); + builder.setCancelable(false); + switch (id) { case DIALOG_CONFIRM_DELETE_ID: - builder = new AlertDialog.Builder(this); builder .setTitle("Confirm Delete") .setMessage("Really delete?").setPositiveButton("Yes", new DialogInterface.OnClickListener() { @@ -225,15 +344,77 @@ public class BInstallerActivity extends AppCompatActivity { }); return builder.create(); + case DIALOG_CONFIRM_NEXTSTEPS_ID: + builder + .setTitle("Version Problem") + .setMessage("The base version for tiles has changed. What to do?") + .setPositiveButton("Continue with current download, delete other old data", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + + ArrayList allTiles = mBInstallerView.getSelectedTiles(MASK_INSTALLED_RD5); + for (Integer sel : allTiles) { + if (!selectedTiles.contains(sel)) { + mBInstallerView.toggleTileStatus(sel, 0); + new File(mBaseDir, "brouter/segments4/" + baseNameForTile(sel) + ".rd5").delete(); + } + } + downloadSelectedTiles(); + } + }).setNegativeButton("Select all for download and start", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + downloadInstalledTiles(); + } + }).setNeutralButton("Cancel now, complete on an other day", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + File tmplookupFile = new File(mBaseDir, "brouter/profiles2/lookups.dat.tmp"); + tmplookupFile.delete(); + finish(); + } + }); + return builder.create(); + + case DIALOG_CONFIRM_GETDIFFS_ID: + builder + .setTitle("Version Differences") + .setMessage("The base version for some tiles is different. What to do?") + .setPositiveButton("Download all different tiles", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + downloadDiffVersionTiles(); + } + }).setNegativeButton("Drop all different tiles", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dropDiffVersionTiles(); + } + }).setNeutralButton("Cancel now, complete on an other day", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + finish(); + } + }); + return builder.create(); + default: return null; } } - public void showConfirmDelete() { - showDialog(DIALOG_CONFIRM_DELETE_ID); + void showADialog(int id) { + Dialog d = createADialog(id); + if (d != null) d.show(); } + public void showConfirmDelete() { + showADialog(DIALOG_CONFIRM_DELETE_ID); + } + + public void showConfirmNextSteps() { + showADialog(DIALOG_CONFIRM_NEXTSTEPS_ID); + } + + private void showConfirmGetDiffs() { + showADialog(DIALOG_CONFIRM_GETDIFFS_ID); + } + + private void scanExistingFiles() { mBInstallerView.clearAllTilesStatus(MASK_CURRENT_RD5 | MASK_INSTALLED_RD5 | MASK_DELETED_RD5 | MASK_SELECTED_RD5); @@ -270,14 +451,51 @@ public class BInstallerActivity extends AppCompatActivity { } private void downloadSelectedTiles() { - ArrayList selectedTiles = mBInstallerView.getSelectedTiles(MASK_SELECTED_RD5); - downloadAll(selectedTiles); - mBInstallerView.clearAllTilesStatus(MASK_SELECTED_RD5); + selectedTiles = mBInstallerView.getSelectedTiles(MASK_SELECTED_RD5); + downloadAll(selectedTiles, DownloadWorker.VALUE_SEGMENT_PARTS); } private void downloadInstalledTiles() { ArrayList selectedTiles = mBInstallerView.getSelectedTiles(MASK_INSTALLED_RD5); - downloadAll(selectedTiles); + ArrayList tmpSelectedTiles = mBInstallerView.getSelectedTiles(MASK_SELECTED_RD5); + if (tmpSelectedTiles.size() > 0) { + selectedTiles.addAll(tmpSelectedTiles); + } + downloadAll(selectedTiles, DownloadWorker.VALUE_SEGMENT_ALL); + } + + private void downloadDiffVersionTiles() { + downloadAll(new ArrayList(), DownloadWorker.VALUE_SEGMENT_DIFFS); + } + + private void dropDiffVersionTiles() { + downloadAll(new ArrayList(), DownloadWorker.VALUE_SEGMENT_DROPDIFFS); + } + + private boolean isDownloadRunning(Class serviceClass) { + WorkManager instance = WorkManager.getInstance(getApplicationContext()); + + ListenableFuture> statuses = instance.getWorkInfosForUniqueWork(DownloadWorker.WORKER_NAME); + try { + boolean running = false; + List workInfoList = statuses.get(); + for (WorkInfo workInfo : workInfoList) { + WorkInfo.State state = workInfo.getState(); + running = state == WorkInfo.State.RUNNING | state == WorkInfo.State.ENQUEUED; + } + return running; + } catch (ExecutionException e) { + e.printStackTrace(); + return false; + } catch (InterruptedException e) { + e.printStackTrace(); + return false; + } + } + + void stopDownload() { + WorkManager workManager = WorkManager.getInstance(getApplicationContext()); + workManager.cancelAllWork(); } private int tileForBaseName(String basename) { 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 ffe75c2..ef8dcca 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java @@ -75,18 +75,20 @@ public class BInstallerView extends View { } public void setTileStatus(int tileIndex, int tileMask) { - tileStatus[tileIndex] |= tileMask; - if (mOnSelectListener != null) { - mOnSelectListener.onSelect(); + if (mOnSelectListener == null) { + return; } + tileStatus[tileIndex] |= tileMask; + mOnSelectListener.onSelect(); invalidate(); } public void toggleTileStatus(int tileIndex, int tileMask) { - tileStatus[tileIndex] ^= tileMask; - if (mOnSelectListener != null) { - mOnSelectListener.onSelect(); + if (mOnSelectListener == null) { + return; } + tileStatus[tileIndex] ^= tileMask; + mOnSelectListener.onSelect(); invalidate(); } 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 f093ec5..f044a2c 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java @@ -78,8 +78,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat setContentView(mBRouterView); } - @Override - protected Dialog onCreateDialog(int id) { + protected Dialog createADialog(int id) { AlertDialog.Builder builder; builder = new AlertDialog.Builder(this); builder.setCancelable(false); @@ -103,7 +102,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat if (item == 0) startDownloadManager(); else - showDialog(DIALOG_SELECTPROFILE_ID); + showADialog(DIALOG_SELECTPROFILE_ID); } }) .setNegativeButton("Close", new DialogInterface.OnClickListener() { @@ -214,7 +213,7 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat if (selectedBasedir < availableBasedirs.size()) { mBRouterView.startSetup(availableBasedirs.get(selectedBasedir), true); } else { - showDialog(DIALOG_TEXTENTRY_ID); + showADialog(DIALOG_TEXTENTRY_ID); } } }); @@ -338,17 +337,14 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat Arrays.sort(availableProfiles); // show main dialog - showDialog(DIALOG_MAINACTION_ID); + showADialog(DIALOG_MAINACTION_ID); } public void startDownloadManager() { - if (!mBRouterView.hasUpToDateLookups()) { - showDialog(DIALOG_OLDDATAHINT_ID); - } else { - showDialog(DIALOG_SHOW_DM_INFO_ID); - } + showADialog(DIALOG_SHOW_DM_INFO_ID); } + @SuppressWarnings("deprecation") public void selectBasedir(ArrayList items, String message) { this.message = message; availableBasedirs = items; @@ -357,7 +353,11 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat long size = 0L; try { StatFs stat = new StatFs(f.getAbsolutePath()); - size = (long) stat.getAvailableBlocks() * stat.getBlockSize(); + 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); @@ -379,26 +379,26 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat basedirOptions[bdidx] = "Enter path manually"; } - showDialog(DIALOG_SELECTBASEDIR_ID); + showADialog(DIALOG_SELECTBASEDIR_ID); } public void selectRoutingModes(String[] modes, boolean[] modesChecked, String message) { routingModes = modes; routingModesChecked = modesChecked; this.message = message; - showDialog(DIALOG_ROUTINGMODES_ID); + showADialog(DIALOG_ROUTINGMODES_ID); } public void showModeConfigOverview(String message) { this.message = message; - showDialog(DIALOG_MODECONFIGOVERVIEW_ID); + showADialog(DIALOG_MODECONFIGOVERVIEW_ID); } public void selectVias(String[] items) { availableVias = items; selectedVias = new HashSet<>(availableVias.length); Collections.addAll(selectedVias, items); - showDialog(DIALOG_VIASELECT_ID); + showADialog(DIALOG_VIASELECT_ID); } public void selectWaypoint(String[] items) { @@ -412,15 +412,20 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat public void selectNogos(List nogoList) { this.nogoList = nogoList; - showDialog(DIALOG_NOGOSELECT_ID); + 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); + // removeDialog(id); } dialogIds.add(id); - showDialog(id); + showADialog(id); } public void showErrorMessage(String msg) { 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 412ab93..fa0ca70 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java @@ -279,7 +279,7 @@ public class BRouterService extends Service { private void logBundle(Bundle params) { if (AppLogger.isLogging()) { for (String k : params.keySet()) { - Object val = "remoteProfile".equals(k) ? "<..cut..>" : params.get(k); + Object val = "remoteProfile".equals(k) ? "<..cut..>" : params.getString(k); String desc = "key=" + k + (val == null ? "" : " class=" + val.getClass() + " val=" + val.toString()); AppLogger.log(desc); } 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 8c6f52e..f243039 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -5,9 +5,11 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.AssetManager; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.Rect; import android.util.Log; import android.view.View; import android.widget.Toast; @@ -740,7 +742,8 @@ public class BRouterView extends View { paintPosition(n.ilon, n.ilat, color, minradius); } - canvas.drawBitmap(imgPixels, 0, imgw, (float) 0., (float) 0., imgw, imgh, false, null); + Bitmap bmp = Bitmap.createBitmap(imgPixels, imgw, imgh, Bitmap.Config.RGB_565); + canvas.drawBitmap(bmp, 0, 0, null); // nogo circles if any for (int ngi = 0; ngi < nogoList.size(); ngi++) { diff --git a/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java b/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java index e6c0409..3453924 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java @@ -17,28 +17,42 @@ import androidx.work.Worker; import androidx.work.WorkerParameters; import java.io.File; +import java.io.FileFilter; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.util.ArrayList; +import java.util.List; import java.util.Random; +import btools.expressions.BExpressionMetaData; import btools.mapaccess.PhysicalFile; import btools.mapaccess.Rd5DiffManager; import btools.mapaccess.Rd5DiffTool; import btools.util.ProgressListener; public class DownloadWorker extends Worker { + public static final String WORKER_NAME = "BRouterWorker"; + + private final static boolean DEBUG = false; + public static final String KEY_INPUT_SEGMENT_NAMES = "SEGMENT_NAMES"; + public static final String KEY_INPUT_SEGMENT_ALL = "SEGMENT_ALL"; public static final String KEY_OUTPUT_ERROR = "ERROR"; + + public static final int VALUE_SEGMENT_PARTS = 0; + public static final int VALUE_SEGMENT_ALL = 1; + public static final int VALUE_SEGMENT_DIFFS = 2; + public static final int VALUE_SEGMENT_DROPDIFFS = 3; + public static final String PROGRESS_SEGMENT_NAME = "PROGRESS_SEGMENT_NAME"; public static final String PROGRESS_SEGMENT_PERCENT = "PROGRESS_SEGMENT_PERCENT"; - private final static boolean DEBUG = false; private static final int NOTIFICATION_ID = new Random().nextInt(); - private static final String PROFILES_DIR = "profiles2/"; + public static final String PROFILES_DIR = "profiles2/"; private static final String SEGMENTS_DIR = "segments4/"; private static final String SEGMENT_DIFF_SUFFIX = ".df5"; private static final String SEGMENT_SUFFIX = ".rd5"; @@ -51,6 +65,11 @@ public class DownloadWorker extends Worker { private final DownloadProgressListener downloadProgressListener; private final Data.Builder progressBuilder = new Data.Builder(); private final NotificationCompat.Builder notificationBuilder; + private int downloadAll; + private boolean versionChanged; + private List done = new ArrayList<>(); + + int version = -1; public DownloadWorker( @NonNull Context context, @@ -70,16 +89,21 @@ public class DownloadWorker extends Worker { @Override public void onDownloadStart(String downloadName, DownloadType downloadType) { + if (DEBUG) Log.d(LOG_TAG, "onDownloadStart " + downloadName); currentDownloadName = downloadName; currentDownloadType = downloadType; if (downloadType == DownloadType.SEGMENT) { progressBuilder.putString(PROGRESS_SEGMENT_NAME, downloadName); notificationBuilder.setContentText(downloadName); + } else { + progressBuilder.putString(PROGRESS_SEGMENT_NAME, "check profiles"); } + setProgressAsync(progressBuilder.build()); } @Override public void onDownloadInfo(String info) { + if (DEBUG) Log.d(LOG_TAG, "onDownloadInfo " + info); notificationBuilder.setContentText(currentDownloadName + ": " + info); notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); } @@ -100,6 +124,7 @@ public class DownloadWorker extends Worker { notificationBuilder.setProgress(0, 0, true); progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, -1); } + progressBuilder.putString(PROGRESS_SEGMENT_NAME, currentDownloadName); notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); setProgressAsync(progressBuilder.build()); @@ -108,12 +133,14 @@ public class DownloadWorker extends Worker { @Override public void onDownloadFinished() { + if (DEBUG) Log.d(LOG_TAG, "onDownloadFinished "); } }; diffProgressListener = new ProgressListener() { @Override public void updateProgress(String task, int progress) { + if (DEBUG) Log.d(LOG_TAG, "updateProgress " + task + " " + progress); downloadProgressListener.onDownloadInfo(task); downloadProgressListener.onDownloadProgress(100, progress); } @@ -131,6 +158,9 @@ public class DownloadWorker extends Worker { Data inputData = getInputData(); Data.Builder output = new Data.Builder(); String[] segmentNames = inputData.getStringArray(KEY_INPUT_SEGMENT_NAMES); + downloadAll = inputData.getInt(KEY_INPUT_SEGMENT_ALL, 0); + if (DEBUG) + Log.d(LOG_TAG, "doWork done " + done.size() + " segs " + segmentNames.length + " " + this); if (segmentNames == null) { if (DEBUG) Log.d(LOG_TAG, "Failure: no segmentNames"); return Result.failure(); @@ -140,49 +170,127 @@ public class DownloadWorker extends Worker { setForegroundAsync(new ForegroundInfo(NOTIFICATION_ID, notificationBuilder.build())); try { if (DEBUG) Log.d(LOG_TAG, "Download lookup & profiles"); - downloadLookupAndProfiles(); + if (!downloadLookup()) { + output.putString(KEY_OUTPUT_ERROR, "Version error"); + return Result.failure(output.build()); + } + + if (!versionChanged && downloadAll != VALUE_SEGMENT_ALL) { + List tmpSegementNames = new ArrayList<>(); + File segmentFolder = new File(baseDir, SEGMENTS_DIR); + File[] files = segmentFolder.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + return (file.getPath().endsWith(SEGMENT_SUFFIX)); + } + }); + for (File f : files) { + int thePFversion = PhysicalFile.checkVersionIntegrity(f); + if (DEBUG) Log.d("worker", "check " + f.getName() + " " + thePFversion + "=" + version); + if (thePFversion != -1 && thePFversion != version) { + tmpSegementNames.add(f.getName().substring(0, f.getName().indexOf("."))); + versionChanged = true; + } + } + if (tmpSegementNames.size() > 0 && (downloadAll != VALUE_SEGMENT_DIFFS && downloadAll != VALUE_SEGMENT_DROPDIFFS)) { + output.putString(KEY_OUTPUT_ERROR, "Version diffs"); + return Result.failure(output.build()); + } + if (downloadAll == VALUE_SEGMENT_DIFFS) { + segmentNames = tmpSegementNames.toArray(new String[0]); + } else if (downloadAll == VALUE_SEGMENT_DROPDIFFS) { + for (String segmentName : tmpSegementNames) { + File segmentFile = new File(baseDir, SEGMENTS_DIR + segmentName + SEGMENT_SUFFIX); + segmentFile.delete(); + } + return Result.success(); + } + } + + downloadProfiles(); for (String segmentName : segmentNames) { + if (isStopped()) break; downloadProgressListener.onDownloadStart(segmentName, DownloadType.SEGMENT); if (DEBUG) Log.d(LOG_TAG, "Download segment " + segmentName); downloadSegment(mServerConfig.getSegmentUrl(), segmentName + SEGMENT_SUFFIX); } } catch (IOException e) { - Log.w(LOG_TAG, e); - output.putString(KEY_OUTPUT_ERROR, e.toString()); + output.putString(KEY_OUTPUT_ERROR, e.getMessage()); return Result.failure(output.build()); } catch (InterruptedException e) { - Log.w(LOG_TAG, e); - output.putString(KEY_OUTPUT_ERROR, e.toString()); + output.putString(KEY_OUTPUT_ERROR, e.getMessage()); return Result.failure(output.build()); } if (DEBUG) Log.d(LOG_TAG, "doWork finished"); return Result.success(); } - private void downloadLookupAndProfiles() throws IOException, InterruptedException { + private boolean downloadLookup() throws IOException, InterruptedException { String[] lookups = mServerConfig.getLookups(); for (String fileName : lookups) { if (fileName.length() > 0) { File lookupFile = new File(baseDir, PROFILES_DIR + fileName); - String lookupLocation = mServerConfig.getLookupUrl() + fileName; - URL lookupUrl = new URL(lookupLocation); - downloadProgressListener.onDownloadStart(fileName, DownloadType.LOOKUP); - downloadFile(lookupUrl, lookupFile, false); - downloadProgressListener.onDownloadFinished(); + BExpressionMetaData meta = new BExpressionMetaData(); + meta.readMetaData(lookupFile); + version = meta.lookupVersion; + + int size = (int) (lookupFile.exists() ? lookupFile.length() : 0); + File tmplookupFile = new File(baseDir, PROFILES_DIR + fileName + ".tmp"); + boolean changed = false; + if (tmplookupFile.exists()) { + lookupFile.delete(); + tmplookupFile.renameTo(lookupFile); + versionChanged = true; + meta.readMetaData(lookupFile); + version = meta.lookupVersion; + } else { + String lookupLocation = mServerConfig.getLookupUrl() + fileName; + URL lookupUrl = new URL(lookupLocation); + downloadProgressListener.onDownloadStart(fileName, DownloadType.LOOKUP); + changed = downloadFile(lookupUrl, tmplookupFile, size, false, DownloadType.LOOKUP); + downloadProgressListener.onDownloadFinished(); + done.add(lookupUrl); + } + if (changed && downloadAll == VALUE_SEGMENT_PARTS) { + meta = new BExpressionMetaData(); + meta.readMetaData(tmplookupFile); + int newversion = meta.lookupVersion; + if (DEBUG) Log.d(LOG_TAG, "version old " + version + " new " + newversion); + if (version != newversion) { + return false; + } + } else if (changed) { + lookupFile.delete(); + tmplookupFile.renameTo(lookupFile); + versionChanged = changed; + meta.readMetaData(lookupFile); + version = meta.lookupVersion; + } else { + if (tmplookupFile.exists()) tmplookupFile.delete(); + } + } } + return true; + } + + private void downloadProfiles() throws IOException, InterruptedException { String[] profiles = mServerConfig.getProfiles(); for (String fileName : profiles) { + if (isStopped()) break; if (fileName.length() > 0) { File profileFile = new File(baseDir, PROFILES_DIR + fileName); - if (profileFile.exists()) { + //if (profileFile.exists()) + { String profileLocation = mServerConfig.getProfilesUrl() + fileName; URL profileUrl = new URL(profileLocation); + int size = (int) (profileFile.exists() ? profileFile.length() : 0); downloadProgressListener.onDownloadStart(fileName, DownloadType.PROFILE); - downloadFile(profileUrl, profileFile, false); + downloadFile(profileUrl, profileFile, size, false, DownloadType.PROFILE); downloadProgressListener.onDownloadFinished(); + done.add(profileUrl); } } } @@ -191,29 +299,38 @@ public class DownloadWorker extends Worker { private void downloadSegment(String segmentBaseUrl, String segmentName) throws IOException, InterruptedException { File segmentFile = new File(baseDir, SEGMENTS_DIR + segmentName); File segmentFileTemp = new File(segmentFile.getAbsolutePath() + "_tmp"); + if (DEBUG) Log.d(LOG_TAG, "Download " + segmentName + " " + version + " " + versionChanged); try { if (segmentFile.exists()) { - if (DEBUG) Log.d(LOG_TAG, "Calculating local checksum"); - String md5 = Rd5DiffManager.getMD5(segmentFile); - String segmentDeltaLocation = segmentBaseUrl + "diff/" + segmentName.replace(SEGMENT_SUFFIX, "/" + md5 + SEGMENT_DIFF_SUFFIX); - URL segmentDeltaUrl = new URL(segmentDeltaLocation); - if (httpFileExists(segmentDeltaUrl)) { - File segmentDeltaFile = new File(segmentFile.getAbsolutePath() + "_diff"); - try { - downloadFile(segmentDeltaUrl, segmentDeltaFile, true); - if (DEBUG) Log.d(LOG_TAG, "Applying delta"); - Rd5DiffTool.recoverFromDelta(segmentFile, segmentDeltaFile, segmentFileTemp, diffProgressListener); - } catch (IOException e) { - throw new IOException("Failed to download & apply delta update", e); - } finally { - segmentDeltaFile.delete(); + if (!versionChanged) { // no diff file on version change + String md5 = Rd5DiffManager.getMD5(segmentFile); + if (DEBUG) Log.d(LOG_TAG, "Calculating local checksum " + md5); + String segmentDeltaLocation = segmentBaseUrl + "diff/" + segmentName.replace(SEGMENT_SUFFIX, "/" + md5 + SEGMENT_DIFF_SUFFIX); + URL segmentDeltaUrl = new URL(segmentDeltaLocation); + if (httpFileExists(segmentDeltaUrl)) { + File segmentDeltaFile = new File(segmentFile.getAbsolutePath() + "_diff"); + try { + downloadFile(segmentDeltaUrl, segmentDeltaFile, 0, true, DownloadType.SEGMENT); + done.add(segmentDeltaUrl); + if (DEBUG) Log.d(LOG_TAG, "Applying delta"); + Rd5DiffTool.recoverFromDelta(segmentFile, segmentDeltaFile, segmentFileTemp, diffProgressListener); + } catch (IOException e) { + throw new IOException("Failed to download & apply delta update", e); + } finally { + segmentDeltaFile.delete(); + } + } + } else { + if (segmentFileTemp.exists()) { + segmentFileTemp.delete(); } } } if (!segmentFileTemp.exists()) { URL segmentUrl = new URL(segmentBaseUrl + segmentName); - downloadFile(segmentUrl, segmentFileTemp, true); + downloadFile(segmentUrl, segmentFileTemp, 0, true, DownloadType.SEGMENT); + done.add(segmentUrl); } PhysicalFile.checkFileIntegrity(segmentFileTemp); @@ -235,12 +352,14 @@ public class DownloadWorker extends Worker { HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); connection.setConnectTimeout(5000); connection.setRequestMethod("HEAD"); + connection.setDoInput(false); connection.connect(); return connection.getResponseCode() == HttpURLConnection.HTTP_OK; } - private void downloadFile(URL downloadUrl, File outputFile, boolean limitDownloadSpeed) throws IOException, InterruptedException { + private boolean downloadFile(URL downloadUrl, File outputFile, int fileSize, boolean limitDownloadSpeed, DownloadType type) throws IOException, InterruptedException { + if (DEBUG) Log.d(LOG_TAG, "download " + outputFile.getAbsolutePath()); HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); connection.setConnectTimeout(5000); connection.connect(); @@ -248,11 +367,25 @@ public class DownloadWorker extends Worker { if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { throw new IOException("HTTP Request failed: " + downloadUrl + " returned " + connection.getResponseCode()); } - int fileLength = connection.getContentLength(); - try ( - InputStream input = connection.getInputStream(); - OutputStream output = new FileOutputStream(outputFile) - ) { + int dataLength = connection.getContentLength(); + // no need of download when size equal + // file size not the best coice but easy to handle, date is not available + switch (type) { + case LOOKUP: + if (fileSize == dataLength) return false; + break; + case PROFILE: + if (fileSize == dataLength) return false; + break; + default: + break; + } + InputStream input = null; + OutputStream output = null; + try { + input = connection.getInputStream(); + output = new FileOutputStream(outputFile); + byte[] buffer = new byte[4096]; int total = 0; long t0 = System.currentTimeMillis(); @@ -264,7 +397,7 @@ public class DownloadWorker extends Worker { total += count; output.write(buffer, 0, count); - downloadProgressListener.onDownloadProgress(fileLength, total); + downloadProgressListener.onDownloadProgress(dataLength, total); if (limitDownloadSpeed) { // enforce < 16 Mbit/s @@ -274,7 +407,12 @@ public class DownloadWorker extends Worker { } } } + } finally { + if (input != null) input.close(); + if (output != null) output.close(); + connection.disconnect(); } + return true; } @NonNull @@ -313,7 +451,7 @@ public class DownloadWorker extends Worker { notificationManager.createNotificationChannel(channel); } - enum DownloadType { + public enum DownloadType { LOOKUP, PROFILE, SEGMENT diff --git a/brouter-routing-app/src/main/res/layout/activity_binstaller.xml b/brouter-routing-app/src/main/res/layout/activity_binstaller.xml index 5f0e7c1..9cd3b99 100644 --- a/brouter-routing-app/src/main/res/layout/activity_binstaller.xml +++ b/brouter-routing-app/src/main/res/layout/activity_binstaller.xml @@ -42,4 +42,17 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + diff --git a/brouter-routing-app/src/main/res/values/strings.xml b/brouter-routing-app/src/main/res/values/strings.xml index 6663c01..168a26a 100644 --- a/brouter-routing-app/src/main/res/values/strings.xml +++ b/brouter-routing-app/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ Delete %s Update %s Select segments + Stop Download Size=%s\nFree=%s brouter_download Download Segments diff --git a/brouter-server/build.gradle b/brouter-server/build.gradle index c52d524..7f97475 100644 --- a/brouter-server/build.gradle +++ b/brouter-server/build.gradle @@ -13,7 +13,7 @@ application { attributes "Main-Class": getMainClass(), "Implementation-Version": project.version } } - + task fatJar(type: Jar) { archiveFileName = 'brouter-' + project.version + '-all.jar' @@ -34,7 +34,7 @@ application { } distZip { - dependsOn fatJar + dependsOn fatJar, ':brouter-routing-app:packageRelease' archiveFileName = 'brouter-' + project.version + '.zip' } diff --git a/build.gradle b/build.gradle index 8d58199..22579a5 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.2' + classpath 'com.android.tools.build:gradle:7.4.2' // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle.properties b/gradle.properties index 199d16e..147c25a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,5 +16,6 @@ org.gradle.jvmargs=-Xmx1536m # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true +android.enableJetifier=false + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6df5e88..cdbfc5f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip