From cde460676068bdc1b00e97bd583dd38d7353363d Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Sat, 2 Apr 2022 18:22:19 +0200 Subject: [PATCH 01/16] Reformat and fix warnings in AndroidManifest --- .../src/main/AndroidManifest.xml | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/brouter-routing-app/src/main/AndroidManifest.xml b/brouter-routing-app/src/main/AndroidManifest.xml index c1ffbdf..e4030e4 100644 --- a/brouter-routing-app/src/main/AndroidManifest.xml +++ b/brouter-routing-app/src/main/AndroidManifest.xml @@ -3,7 +3,9 @@ - @@ -11,14 +13,13 @@ + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:preserveLegacyExternalStorage="true" + android:roundIcon="@mipmap/ic_launcher_round"> @@ -29,20 +30,20 @@ + android:launchMode="singleTask" + android:screenOrientation="landscape" + android:theme="@android:style/Theme.NoTitleBar.Fullscreen" /> - + + @@ -88,15 +89,15 @@ + android:label="Download Service" /> From d92c3beb3eff4c8789a9dfe605a15f136685a770 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Sat, 2 Apr 2022 18:31:11 +0200 Subject: [PATCH 02/16] Switch activities to AppCompat and adapt themes No longer uses fullscreen, statusbar shall be visible to check connection status Closes #57 --- .../src/main/AndroidManifest.xml | 12 ++--- .../btools/routingapp/BImportActivity.java | 4 +- .../btools/routingapp/BInstallerActivity.java | 5 +- .../btools/routingapp/BRouterActivity.java | 5 +- .../java/btools/routingapp/BRouterView.java | 46 ++++++++----------- 5 files changed, 31 insertions(+), 41 deletions(-) diff --git a/brouter-routing-app/src/main/AndroidManifest.xml b/brouter-routing-app/src/main/AndroidManifest.xml index e4030e4..0d50b10 100644 --- a/brouter-routing-app/src/main/AndroidManifest.xml +++ b/brouter-routing-app/src/main/AndroidManifest.xml @@ -17,12 +17,12 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:preserveLegacyExternalStorage="true" - android:roundIcon="@mipmap/ic_launcher_round"> + android:roundIcon="@mipmap/ic_launcher_round" + android:theme="@style/Theme.AppCompat.Light.NoActionBar"> + android:screenOrientation="unspecified"> @@ -32,13 +32,11 @@ android:name=".BInstallerActivity" android:exported="true" android:launchMode="singleTask" - android:screenOrientation="landscape" - android:theme="@android:style/Theme.NoTitleBar.Fullscreen" /> + android:screenOrientation="landscape" /> + android:label="Import Profile"> diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java index 3d5f085..8bd33de 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java @@ -1,6 +1,5 @@ package btools.routingapp; -import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -12,6 +11,7 @@ import android.widget.EditText; import android.widget.Toast; import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; import java.io.BufferedReader; import java.io.File; @@ -20,7 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -public class BImportActivity extends Activity { +public class BImportActivity extends AppCompatActivity { // profile size is generally < 30 kb, so set max size to 100 kb private static final int MAX_PROFILE_SIZE = 100000; private EditText mTextFilename; diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java index 286142e..a7b2134 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java @@ -5,7 +5,6 @@ import static btools.routingapp.BInstallerView.MASK_DELETED_RD5; import static btools.routingapp.BInstallerView.MASK_INSTALLED_RD5; import static btools.routingapp.BInstallerView.MASK_SELECTED_RD5; -import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.BroadcastReceiver; @@ -23,13 +22,15 @@ import android.view.View; import android.widget.Button; import android.widget.TextView; +import androidx.appcompat.app.AppCompatActivity; + import java.io.File; import java.util.ArrayList; import java.util.Locale; import btools.router.RoutingHelper; -public class BInstallerActivity extends Activity { +public class BInstallerActivity extends AppCompatActivity { public static final String DOWNLOAD_ACTION = "btools.routingapp.download"; private static final int DIALOG_CONFIRM_DELETE_ID = 1; 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 5fb8e04..0d70ed5 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java @@ -32,13 +32,13 @@ import android.util.Log; import android.view.KeyEvent; import android.widget.EditText; - +import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.os.EnvironmentCompat; import btools.router.OsmNodeNamed; -public class BRouterActivity extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback { +public class BRouterActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback { private static final int DIALOG_SELECTPROFILE_ID = 1; private static final int DIALOG_EXCEPTION_ID = 2; @@ -626,6 +626,7 @@ public class BRouterActivity extends Activity implements ActivityCompat.OnReques @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 0) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { mBRouterView.startSetup(null, true); 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 fa02aae..c21af97 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -1,5 +1,21 @@ package btools.routingapp; +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +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.Log; +import android.view.View; +import android.widget.Toast; + +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -12,36 +28,13 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Set; -import java.util.StringTokenizer; import java.util.TreeMap; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageManager; -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; -import android.view.View; -import android.widget.Toast; - - -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - -import btools.expressions.BExpressionContextWay; import btools.expressions.BExpressionMetaData; import btools.mapaccess.OsmNode; import btools.router.OsmNodeNamed; @@ -108,11 +101,6 @@ public class BRouterView extends View { public void init() { try { - DisplayMetrics metrics = new DisplayMetrics(); - ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics); - imgw = metrics.widthPixels; - imgh = metrics.heightPixels; - // get base dir from private file File baseDir = ConfigHelper.getBaseDir(getContext()); // check if valid @@ -703,6 +691,8 @@ public class BRouterView extends View { @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { + imgw = w; + imgh = h; } private void toast(String msg) { From c80ad5f03badb7a3c3cdc89ffa76b899a111a3da Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Sun, 3 Apr 2022 17:50:13 +0200 Subject: [PATCH 03/16] Update sdk and dependencies --- brouter-routing-app/build.gradle | 7 +++---- .../src/main/java/btools/routingapp/BImportActivity.java | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index 4c88be6..535bf68 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -5,7 +5,7 @@ plugins { } android { - compileSdkVersion 30 + compileSdkVersion 31 defaultConfig { applicationId "btools.routingapp" @@ -93,9 +93,8 @@ android { } dependencies { - - implementation 'androidx.appcompat:appcompat:1.3.1' - implementation "androidx.constraintlayout:constraintlayout:2.1.2" + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation "androidx.constraintlayout:constraintlayout:2.1.3" implementation project(':brouter-mapaccess') implementation project(':brouter-core') diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java index 8bd33de..2550bf9 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java @@ -76,8 +76,8 @@ public class BImportActivity extends AppCompatActivity { try (Cursor cursor = this.getContentResolver().query(intent.getData(), new String[]{ OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { - filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); - filesize = cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE)); + filename = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)); + filesize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)); } } // is the file extention ".brf" in the file name From db42ae9f33a30016dc913ea5ac4aef98ba92c79a Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Tue, 11 Jan 2022 14:49:27 +0100 Subject: [PATCH 04/16] Always show main dialog (with Download Manager) It can be confusing when the dialog is shown only sometimes and there is no indication why the dialog isn't shown. The connection status can also change after the start of the download manager so it has to handle those errors anyway. Closes #389 --- .../btools/routingapp/BRouterActivity.java | 56 ++++--------------- 1 file changed, 10 insertions(+), 46 deletions(-) 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 0d70ed5..8c2cd13 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java @@ -1,41 +1,32 @@ package btools.routingapp; -import java.io.File; -import java.lang.reflect.Method; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - 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.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkInfo; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.PowerManager; 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.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.os.EnvironmentCompat; +import java.io.File; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import btools.router.OsmNodeNamed; public class BRouterActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback { @@ -391,39 +382,12 @@ public class BRouterActivity extends AppCompatActivity implements ActivityCompat private String maptoolDirCandidate; - public boolean isOnline(Context context) { - boolean result = false; - ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - Network nw = connectivityManager.getActiveNetwork(); - if (nw == null) return false; - NetworkCapabilities nwc = connectivityManager.getNetworkCapabilities(nw); - if (nwc == null) return false; - result = nwc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) | - nwc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) | - nwc.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET); - - } else { - NetworkInfo ni = connectivityManager.getActiveNetworkInfo(); - if (ni == null) return false; - result = ni.getType() == ConnectivityManager.TYPE_WIFI || - ni.getType() == ConnectivityManager.TYPE_MOBILE || - ni.getType() == ConnectivityManager.TYPE_ETHERNET; - } - - return result; - } - @SuppressWarnings("deprecation") public void selectProfile(String[] items) { availableProfiles = items; - // if we have internet access, first show the main action dialog - if (isOnline(this)) { - showDialog(DIALOG_MAINACTION_ID); - } else { - showDialog(DIALOG_SELECTPROFILE_ID); - } + // show main dialog + showDialog(DIALOG_MAINACTION_ID); } @SuppressWarnings("deprecation") From 13f5ad0bcffc7678bd05509593a29407eafd6d14 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Sun, 9 Jan 2022 08:39:00 +0100 Subject: [PATCH 05/16] Small cleanup of DownloadService --- .../btools/routingapp/DownloadService.java | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java b/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java index 7457a84..14b54c1 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java @@ -1,10 +1,8 @@ package btools.routingapp; -import android.app.NotificationManager; import android.app.Service; import android.content.Intent; import android.net.TrafficStats; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; @@ -42,10 +40,7 @@ public class DownloadService extends Service implements ProgressListener { private volatile String currentDownloadOperation = ""; private long availableSize; - private Looper mServiceLooper; private ServiceHandler mServiceHandler; - private NotificationManager mNM; - String downloadUrl; public static boolean serviceState = false; private boolean bIsDownloading; @@ -77,14 +72,12 @@ public class DownloadService extends Service implements ProgressListener { thread.start(); // Get the HandlerThread's Looper and use it for our Handler - mServiceLooper = thread.getLooper(); - mServiceHandler = new ServiceHandler(mServiceLooper); + Looper serviceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(serviceLooper); availableSize = -1; try { availableSize = BInstallerActivity.getAvailableSpace(baseDir); - //StatFs stat = new StatFs(baseDir); - //availableSize = (long)stat.getAvailableBlocksLong()*stat.getBlockSizeLong(); } catch (Exception e) { /* ignore */ } } @@ -98,8 +91,7 @@ public class DownloadService extends Service implements ProgressListener { Bundle extra = intent.getExtras(); if (extra != null) { String dir = extra.getString("dir"); - List urlparts = extra.getStringArrayList("urlparts"); - mUrlList = urlparts; + mUrlList = extra.getStringArrayList("urlparts"); baseDir = dir; } @@ -171,7 +163,6 @@ public class DownloadService extends Service implements ProgressListener { intent.putExtra("txt", progress); intent.putExtra("ready", bIsDownloading); sendBroadcast(intent); - ; newDownloadAction = progress; mNotificationHelper.progressUpdate(newDownloadAction); } @@ -182,7 +173,6 @@ public class DownloadService extends Service implements ProgressListener { InputStream input = null; OutputStream output = null; HttpURLConnection connection = null; - File fname = null; File tmp_file = null; try { try { @@ -191,7 +181,7 @@ public class DownloadService extends Service implements ProgressListener { int slidx = surl.lastIndexOf("segments4/"); String name = surl.substring(slidx + 10); String surlBase = surl.substring(0, slidx + 10); - fname = new File(baseDir, "segments4/" + name); + File fname = new File(baseDir, "segments4/" + name); boolean delta = true; @@ -239,7 +229,6 @@ public class DownloadService extends Service implements ProgressListener { // this will be useful to display download percentage // might be -1: server did not report the length int fileLength = connection.getContentLength(); - long currentDownloadSize = fileLength; if (availableSize >= 0 && fileLength > availableSize) return "not enough space on sd-card"; currentDownloadOperation = delta ? "Updating" : "Loading"; @@ -276,7 +265,7 @@ public class DownloadService extends Service implements ProgressListener { if (dt > 0) { try { Thread.sleep(dt); - } catch (InterruptedException ie) { + } catch (InterruptedException ignored) { } } } @@ -374,7 +363,6 @@ public class DownloadService extends Service implements ProgressListener { OutputStream output = null; HttpURLConnection connection = null; File tmp_file = null; - File targetFile = f; try { try { @@ -401,7 +389,6 @@ public class DownloadService extends Service implements ProgressListener { long fileLength = (long) connection.getContentLength(); if (DEBUG) Log.d("BR", "file size " + size + " == " + fileLength + " " + f.getName()); if (fileLength != size) { - long currentDownloadSize = fileLength; if (availableSize >= 0 && fileLength > availableSize) return "not enough space on sd-card"; @@ -413,7 +400,7 @@ public class DownloadService extends Service implements ProgressListener { tmp_file = new File(f.getAbsolutePath() + "_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; From 0a8d4dd1f2d84599119825ac002ca5c48d3f329b Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Tue, 11 Jan 2022 08:04:03 +0100 Subject: [PATCH 06/16] Rd5Diff: Specify IOException instead of generic Exception --- .../main/java/btools/codec/MicroCache2.java | 4 +- .../java/btools/mapaccess/Rd5DiffManager.java | 43 ++++++++++--------- .../java/btools/mapaccess/Rd5DiffTool.java | 17 ++++---- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/brouter-codec/src/main/java/btools/codec/MicroCache2.java b/brouter-codec/src/main/java/btools/codec/MicroCache2.java index 8cd8f30..16223a1 100644 --- a/brouter-codec/src/main/java/btools/codec/MicroCache2.java +++ b/brouter-codec/src/main/java/btools/codec/MicroCache2.java @@ -15,7 +15,7 @@ public final class MicroCache2 extends MicroCache private int latBase; private int cellsize; - public MicroCache2( int size, byte[] databuffer, int lonIdx, int latIdx, int divisor ) throws Exception + public MicroCache2( int size, byte[] databuffer, int lonIdx, int latIdx, int divisor ) { super( databuffer ); // sets ab=databuffer, aboffset=0 @@ -34,7 +34,7 @@ public final class MicroCache2 extends MicroCache return b; } - public MicroCache2( StatCoderContext bc, DataBuffers dataBuffers, int lonIdx, int latIdx, int divisor, TagValueValidator wayValidator, WaypointMatcher waypointMatcher ) throws Exception + public MicroCache2( StatCoderContext bc, DataBuffers dataBuffers, int lonIdx, int latIdx, int divisor, TagValueValidator wayValidator, WaypointMatcher waypointMatcher ) { super( null ); cellsize = 1000000 / divisor; diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffManager.java b/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffManager.java index 86ff392..3b33904 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffManager.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffManager.java @@ -8,8 +8,10 @@ package btools.mapaccess; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; +import java.io.IOException; import java.security.DigestInputStream; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; final public class Rd5DiffManager { @@ -93,30 +95,31 @@ final public class Rd5DiffManager } } - public static String getMD5( File f ) throws Exception + public static String getMD5( File f ) throws IOException { - MessageDigest md = MessageDigest.getInstance("MD5"); - BufferedInputStream bis = new BufferedInputStream( new FileInputStream( f ) ); - DigestInputStream dis = new DigestInputStream(bis, md); - byte[] buf = new byte[8192]; - for(;;) - { - int len = dis.read( buf ); - if ( len <= 0 ) - { - break; + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f)); + DigestInputStream dis = new DigestInputStream(bis, md); + byte[] buf = new byte[8192]; + for (; ; ) { + int len = dis.read(buf); + if (len <= 0) { + break; + } } - } - dis.close(); - byte[] bytes = md.digest(); + dis.close(); + byte[] bytes = md.digest(); - StringBuilder sb = new StringBuilder(); - for (int j = 0; j < bytes.length; j++) - { - int v = bytes[j] & 0xff; - sb.append( hexChar( v >>> 4 ) ).append( hexChar( v & 0xf ) ); + StringBuilder sb = new StringBuilder(); + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xff; + sb.append(hexChar(v >>> 4)).append(hexChar(v & 0xf)); + } + return sb.toString(); + } catch (NoSuchAlgorithmException e) { + throw new IOException("MD5 algorithm not available", e); } - return sb.toString(); } private static char hexChar( int v ) diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java b/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java index ac2f1d9..e1e48c6 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java @@ -12,6 +12,7 @@ import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.util.Arrays; import btools.codec.DataBuffers; @@ -60,7 +61,7 @@ final public class Rd5DiffTool implements ProgressListener return false; } - private static long[] readFileIndex( DataInputStream dis, DataOutputStream dos ) throws Exception + private static long[] readFileIndex( DataInputStream dis, DataOutputStream dos ) throws IOException { long[] fileIndex = new long[25]; for( int i=0; i<25; i++ ) @@ -85,7 +86,7 @@ final public class Rd5DiffTool implements ProgressListener return index[tileIndex]; } - private static int[] readPosIndex( DataInputStream dis, DataOutputStream dos ) throws Exception + private static int[] readPosIndex( DataInputStream dis, DataOutputStream dos ) throws IOException { int[] posIndex = new int[1024]; for( int i=0; i<1024; i++ ) @@ -105,7 +106,7 @@ final public class Rd5DiffTool implements ProgressListener return idx == -1 ? 4096 : posIdx[idx]; } - private static byte[] createMicroCache( int[] posIdx, int tileIdx, DataInputStream dis, boolean deltaMode ) throws Exception + private static byte[] createMicroCache( int[] posIdx, int tileIdx, DataInputStream dis, boolean deltaMode ) throws IOException { if ( posIdx == null ) { @@ -125,7 +126,7 @@ final public class Rd5DiffTool implements ProgressListener return ab; } - private static MicroCache createMicroCache( byte[] ab, DataBuffers dataBuffers ) throws Exception + private static MicroCache createMicroCache( byte[] ab, DataBuffers dataBuffers ) { if ( ab == null || ab.length == 0 ) { @@ -286,7 +287,7 @@ final public class Rd5DiffTool implements ProgressListener } - public static void recoverFromDelta( File f1, File f2, File outFile, ProgressListener progress /* , File cmpFile */ ) throws Exception + public static void recoverFromDelta( File f1, File f2, File outFile, ProgressListener progress /* , File cmpFile */ ) throws IOException { if ( f2.length() == 0L ) { @@ -468,7 +469,7 @@ final public class Rd5DiffTool implements ProgressListener } } - public static void copyFile( File f1, File outFile, ProgressListener progress ) throws Exception + public static void copyFile( File f1, File outFile, ProgressListener progress ) throws IOException { boolean canceled = false; DataInputStream dis1 = new DataInputStream( new BufferedInputStream( new FileInputStream( f1 ) ) ); @@ -756,7 +757,7 @@ final public class Rd5DiffTool implements ProgressListener this.dataBuffers = dataBuffers; } - public MicroCache readMC() throws Exception + public MicroCache readMC() throws IOException { if (skips < 0 ) { @@ -775,7 +776,7 @@ final public class Rd5DiffTool implements ProgressListener return mc; } - public void finish() throws Exception + public void finish() { skips = -1; } From a091b07cb607eef072cb3b8bdcf6baa4802f1de8 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Sat, 2 Apr 2022 14:22:08 +0200 Subject: [PATCH 07/16] Reformat DownloadService --- .../btools/routingapp/DownloadService.java | 47 +++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java b/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java index 14b54c1..c4d8639 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java @@ -29,39 +29,17 @@ import btools.util.ProgressListener; public class DownloadService extends Service implements ProgressListener { private static final boolean DEBUG = false; - + public static boolean serviceState = false; private ServerConfig mServerConfig; - private NotificationHelper mNotificationHelper; private List mUrlList; private String baseDir; - private volatile String newDownloadAction = ""; private volatile String currentDownloadOperation = ""; private long availableSize; - private ServiceHandler mServiceHandler; - public static boolean serviceState = false; private boolean bIsDownloading; - // Handler that receives messages from the thread - private final class ServiceHandler extends Handler { - public ServiceHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - bIsDownloading = true; - downloadFiles(); - - stopForeground(true); - stopSelf(msg.arg1); - mNotificationHelper.stopNotification(); - } - } - - @Override public void onCreate() { if (DEBUG) Log.d("SERVICE", "onCreate"); @@ -82,7 +60,6 @@ public class DownloadService extends Service implements ProgressListener { } - @Override public int onStartCommand(Intent intent, int flags, int startId) { if (DEBUG) Log.d("SERVICE", "onStartCommand"); @@ -105,7 +82,6 @@ public class DownloadService extends Service implements ProgressListener { return START_STICKY; } - @Override public void onDestroy() { if (DEBUG) Log.d("SERVICE", "onDestroy"); @@ -113,13 +89,11 @@ public class DownloadService extends Service implements ProgressListener { super.onDestroy(); } - @Override public IBinder onBind(Intent intent) { return null; } - public void downloadFiles() { // first check lookup table and profiles @@ -155,7 +129,6 @@ public class DownloadService extends Service implements ProgressListener { updateProgress("finished "); } - public void updateProgress(String progress) { if (!newDownloadAction.equals(progress)) { if (DEBUG) Log.d("BR", "up " + progress); @@ -466,9 +439,25 @@ public class DownloadService extends Service implements ProgressListener { } - public boolean isCanceled() { return BInstallerActivity.downloadCanceled; } + // Handler that receives messages from the thread + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + bIsDownloading = true; + downloadFiles(); + + stopForeground(true); + stopSelf(msg.arg1); + mNotificationHelper.stopNotification(); + } + } + } From d74d0af687c082d8541f03c2e649b3a4f7a2b2fe Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Sat, 2 Apr 2022 15:52:43 +0200 Subject: [PATCH 08/16] Rewrite DownloadService Split code into smaller pieces and remove duplication which already caused confusion (492d79d42e52b9f6ffd5af327736125d1232c472 changed wrong download speed limit) --- .../btools/routingapp/DownloadService.java | 461 ++++++------------ 1 file changed, 136 insertions(+), 325 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java b/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java index c4d8639..7e608fc 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java @@ -2,7 +2,6 @@ package btools.routingapp; import android.app.Service; import android.content.Intent; -import android.net.TrafficStats; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; @@ -27,18 +26,22 @@ import btools.mapaccess.Rd5DiffTool; import btools.util.ProgressListener; public class DownloadService extends Service implements ProgressListener { + private 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"; private static final boolean DEBUG = false; public static boolean serviceState = false; private ServerConfig mServerConfig; private NotificationHelper mNotificationHelper; - private List mUrlList; + private List mSegmentNames; private String baseDir; - private volatile String newDownloadAction = ""; - private volatile String currentDownloadOperation = ""; - private long availableSize; + private volatile String lastProgress = ""; private ServiceHandler mServiceHandler; private boolean bIsDownloading; + private int mSegmentIndex; + private int mSegmentCount; @Override public void onCreate() { @@ -52,12 +55,6 @@ public class DownloadService extends Service implements ProgressListener { // Get the HandlerThread's Looper and use it for our Handler Looper serviceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(serviceLooper); - - availableSize = -1; - try { - availableSize = BInstallerActivity.getAvailableSpace(baseDir); - } catch (Exception e) { /* ignore */ } - } @Override @@ -68,7 +65,7 @@ public class DownloadService extends Service implements ProgressListener { Bundle extra = intent.getExtras(); if (extra != null) { String dir = extra.getString("dir"); - mUrlList = extra.getStringArrayList("urlparts"); + mSegmentNames = extra.getStringArrayList("urlparts"); baseDir = dir; } @@ -95,348 +92,162 @@ public class DownloadService extends Service implements ProgressListener { } public void downloadFiles() { + try { + mSegmentIndex = 0; + downloadLookupAndProfiles(); - // first check lookup table and profiles - String result = checkScripts(); - if (result != null) { - if (DEBUG) Log.d("BR", "error: " + result); - bIsDownloading = false; - updateProgress("finished "); - - Toast.makeText(this, result, Toast.LENGTH_LONG).show(); - return; - } - - - int count = 1; - int size = mUrlList.size(); - for (String part : mUrlList) { - String url = mServerConfig.getSegmentUrl() + part + ".rd5"; - if (DEBUG) Log.d("BR", "downlaod " + url); - - result = download(count, size, url); - if (result != null) { - if (DEBUG) Log.d("BR", "" + result); - Toast.makeText(this, result, Toast.LENGTH_LONG).show(); - break; - } else { - updateProgress("Download " + part + " " + count + "/" + size + " finshed"); + mSegmentIndex = 1; + mSegmentCount = mSegmentNames.size(); + for (String segmentName : mSegmentNames) { + downloadSegment(mServerConfig.getSegmentUrl(), segmentName + SEGMENT_SUFFIX); + mSegmentIndex++; } - count++; + } catch (IOException e) { + Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show(); + } catch (InterruptedException e) { + updateProgress("canceled"); + } finally { + bIsDownloading = false; + updateProgress("finished"); } - - bIsDownloading = false; - updateProgress("finished "); } public void updateProgress(String progress) { - if (!newDownloadAction.equals(progress)) { + if (!lastProgress.equals(progress)) { + if (mSegmentIndex > 0) { + progress = mSegmentIndex + "/" + mSegmentCount + " " + progress; + } if (DEBUG) Log.d("BR", "up " + progress); Intent intent = new Intent(BInstallerActivity.DOWNLOAD_ACTION); intent.putExtra("txt", progress); intent.putExtra("ready", bIsDownloading); sendBroadcast(intent); - newDownloadAction = progress; - mNotificationHelper.progressUpdate(newDownloadAction); + mNotificationHelper.progressUpdate(progress); + lastProgress = progress; } - } - private String download(int counter, int size, String surl) { - InputStream input = null; - OutputStream output = null; - HttpURLConnection connection = null; - File tmp_file = null; + private void downloadLookupAndProfiles() 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); + downloadFile(lookupUrl, lookupFile, false); + } + } + + String[] profiles = mServerConfig.getProfiles(); + for (String fileName : profiles) { + if (fileName.length() > 0) { + File profileFile = new File(baseDir, PROFILES_DIR + fileName); + if (profileFile.exists()) { + String profileLocation = mServerConfig.getProfilesUrl() + fileName; + URL profileUrl = new URL(profileLocation); + downloadFile(profileUrl, profileFile, false); + } + } + } + } + + 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"); try { - try { - TrafficStats.setThreadStatsTag(1); - - int slidx = surl.lastIndexOf("segments4/"); - String name = surl.substring(slidx + 10); - String surlBase = surl.substring(0, slidx + 10); - File fname = new File(baseDir, "segments4/" + name); - - boolean delta = true; - - // if (!targetFile.getParentFile().exists()) targetFile.getParentFile().mkdirs(); - if (fname.exists()) { - updateProgress("Calculating local checksum.."); - - // first check for a delta file - String md5 = Rd5DiffManager.getMD5(fname); - String surlDelta = surlBase + "diff/" + name.replace(".rd5", "/" + md5 + ".df5"); - - URL urlDelta = new URL(surlDelta); - - connection = (HttpURLConnection) urlDelta.openConnection(); - connection.setConnectTimeout(5000); - connection.connect(); - - // 404 kind of expected here, means there's no delta file - if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) { - connection = null; - } else { - updateProgress("Connecting.." + surlDelta); + if (segmentFile.exists()) { + updateProgress("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); + updateProgress("Applying delta..."); + Rd5DiffTool.recoverFromDelta(segmentFile, segmentDeltaFile, segmentFileTemp, this); + } catch (IOException e) { + throw new IOException("Failed to download & apply delta update", e); + } finally { + segmentDeltaFile.delete(); } } + } - if (connection == null) { - updateProgress("Connecting.." + name); + if (!segmentFileTemp.exists()) { + URL segmentUrl = new URL(segmentBaseUrl + segmentName); + downloadFile(segmentUrl, segmentFileTemp, true); + } - delta = false; - URL url = new URL(surl); - connection = (HttpURLConnection) url.openConnection(); - connection.setConnectTimeout(5000); - connection.connect(); + PhysicalFile.checkFileIntegrity(segmentFileTemp); + if (segmentFile.exists()) { + if (!segmentFile.delete()) { + throw new IOException("Failed to delete existing " + segmentFile.getAbsolutePath()); } + } - updateProgress("Connecting.." + counter + "/" + size); + if (!segmentFileTemp.renameTo(segmentFile)) { + throw new IOException("Failed to write " + segmentFile.getAbsolutePath()); + } + } finally { + segmentFileTemp.delete(); + } + } - // expect HTTP 200 OK, so we don't mistakenly save error report - // instead of the file - if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { - return "Server returned HTTP " + connection.getResponseCode() - + " " + connection.getResponseMessage(); + private boolean httpFileExists(URL downloadUrl) throws IOException { + HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); + connection.setConnectTimeout(5000); + connection.setRequestMethod("HEAD"); + connection.connect(); + + return connection.getResponseCode() == HttpURLConnection.HTTP_OK; + } + + private void downloadFile(URL downloadUrl, File outputFile, boolean limitDownloadSpeed) throws IOException, InterruptedException { + HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); + connection.setConnectTimeout(5000); + connection.connect(); + + updateProgress("Connecting..."); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new IOException("HTTP Request failed"); + } + int fileLength = connection.getContentLength(); + updateProgress("Loading"); + + try ( + InputStream input = connection.getInputStream(); + OutputStream output = new FileOutputStream(outputFile) + ) { + byte[] buffer = new byte[4096]; + long total = 0; + long t0 = System.currentTimeMillis(); + int count; + while ((count = input.read(buffer)) != -1) { + if (isCanceled()) { + throw new InterruptedException(); } + total += count; + // publishing the progress.... + if (fileLength > 0) // only if total length is known + { + int pct = (int) (total * 100 / fileLength); + updateProgress("Progress " + pct + "%"); + } else { + updateProgress("Progress (unknown size)"); + } + output.write(buffer, 0, count); - // this will be useful to display download percentage - // might be -1: server did not report the length - int fileLength = connection.getContentLength(); - if (availableSize >= 0 && fileLength > availableSize) return "not enough space on sd-card"; - - currentDownloadOperation = delta ? "Updating" : "Loading"; - updateProgress(currentDownloadOperation); - - // download the file - input = connection.getInputStream(); - - tmp_file = new File(fname.getAbsolutePath() + (delta ? "_diff" : "_tmp")); - output = new FileOutputStream(tmp_file); - - byte[] data = new byte[4096]; - long total = 0; - long t0 = System.currentTimeMillis(); - int count; - while ((count = input.read(data)) != -1) { - if (isCanceled()) { - return "Download canceled!"; - } - total += count; - // publishing the progress.... - if (fileLength > 0) // only if total length is known - { - int pct = (int) (total * 100 / fileLength); - updateProgress("Progress " + counter + "/" + size + " .. " + pct + "%"); - } else { - updateProgress("Progress (unnown size)"); - } - - output.write(data, 0, count); - - // enforce < 2 Mbit/s - long dt = t0 + total / 524 - System.currentTimeMillis(); + if (limitDownloadSpeed) { + // enforce < 16 Mbit/s + long dt = t0 + total / 2096 - System.currentTimeMillis(); if (dt > 0) { - try { - Thread.sleep(dt); - } catch (InterruptedException ignored) { - } - } - } - output.close(); - output = null; - - if (delta) { - updateProgress("Applying delta.."); - File diffFile = tmp_file; - tmp_file = new File(fname + "_tmp"); - Rd5DiffTool.recoverFromDelta(fname, diffFile, tmp_file, this); - diffFile.delete(); - } - if (isCanceled()) { - return "Canceled!"; - } - if (tmp_file != null) { - updateProgress("Verifying integrity.."); - String check_result = PhysicalFile.checkFileIntegrity(tmp_file); - if (check_result != null) { - if (check_result.startsWith("version old lookups.dat")) { - - } - return check_result; - } - if (fname.exists()) fname.delete(); - if (!tmp_file.renameTo(fname)) { - return "Could not rename to " + fname.getAbsolutePath(); - } - - } - return null; - } catch (Exception e) { - //e.printStackTrace(); ; - return e.toString(); - } finally { - try { - if (output != null) - output.close(); - if (input != null) - input.close(); - } catch (IOException ignored) { - } - - if (connection != null) - connection.disconnect(); - } - } finally { - if (tmp_file != null) tmp_file.delete(); // just to be sure - } - } - - private String checkScripts() { - - String[] sa = mServerConfig.getLookups(); - for (String f : sa) { - if (f.length() > 0) { - File file = new File(baseDir + "profiles2", f); - checkOrDownloadLookup(f, file); - } - } - - sa = mServerConfig.getProfiles(); - for (String f : sa) { - if (f.length() > 0) { - File file = new File(baseDir + "profiles2", f); - if (file.exists()) { - String result = checkOrDownloadScript(f, file); - if (result != null) { - return result; + Thread.sleep(dt); } } } } - return null; - } - - private String checkOrDownloadLookup(String fileName, File f) { - String url = mServerConfig.getLookupUrl() + fileName; - return downloadScript(url, f); - } - - private String checkOrDownloadScript(String fileName, File f) { - String url = mServerConfig.getProfilesUrl() + fileName; - return downloadScript(url, f); - } - - private String downloadScript(String surl, File f) { - long size = 0L; - if (f.exists()) { - size = f.length(); - } - - InputStream input = null; - OutputStream output = null; - HttpURLConnection connection = null; - File tmp_file = null; - - try { - try { - TrafficStats.setThreadStatsTag(1); - - URL url = new URL(surl); - connection = (HttpURLConnection) url.openConnection(); - connection.setConnectTimeout(5000); - connection.connect(); - - // expect HTTP 200 OK, so we don't mistakenly save error report - // instead of the file - if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) { - return null; - } - if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { - return "Server returned HTTP " + connection.getResponseCode() - + " " + connection.getResponseMessage() + " " + f.getName(); - } - - - // this will be useful to display download percentage - // might be -1: server did not report the length - long fileLength = (long) connection.getContentLength(); - if (DEBUG) Log.d("BR", "file size " + size + " == " + fileLength + " " + f.getName()); - if (fileLength != size) { - if (availableSize >= 0 && fileLength > availableSize) - return "not enough space on sd-card"; - - currentDownloadOperation = "Updating"; - - // download the file - input = connection.getInputStream(); - - tmp_file = new File(f.getAbsolutePath() + "_tmp"); - output = new FileOutputStream(tmp_file); - - byte[] data = new byte[4096]; - long total = 0; - long t0 = System.currentTimeMillis(); - int count; - while ((count = input.read(data)) != -1) { - if (isCanceled()) { - return "Download canceled!"; - } - total += count; - // publishing the progress.... - if (fileLength > 0) // only if total length is known - { - int pct = (int) (total * 100 / fileLength); - updateProgress("Progress " + pct + "%"); - } else { - updateProgress("Progress (unnown size)"); - } - - output.write(data, 0, count); - - // enforce < 16 Mbit/s - long dt = t0 + total / 2096 - System.currentTimeMillis(); - if (dt > 0) { - try { - Thread.sleep(dt); - } catch (InterruptedException ie) { - } - } - } - output.close(); - output = null; - } - - if (isCanceled()) { - return "Canceled!"; - } - if (tmp_file != null) { - f.delete(); - - if (!tmp_file.renameTo(f)) { - return "Could not rename to " + f.getName(); - } - if (DEBUG) Log.d("BR", "update " + f.getName()); - } - return null; - } catch (Exception e) { - return e.toString(); - } finally { - try { - if (output != null) - output.close(); - if (input != null) - input.close(); - } catch (IOException ignored) { - } - - if (connection != null) - connection.disconnect(); - - } - } finally { - if (tmp_file != null) tmp_file.delete(); // just to be sure - } - } public boolean isCanceled() { From 21abce0139bc8a8abba2fde04390259e03e53cde Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Sat, 2 Apr 2022 16:19:47 +0200 Subject: [PATCH 09/16] Hacky way to disable reporting for small files --- .../main/java/btools/routingapp/DownloadService.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java b/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java index 7e608fc..f1ec4bf 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java @@ -203,18 +203,21 @@ public class DownloadService extends Service implements ProgressListener { return connection.getResponseCode() == HttpURLConnection.HTTP_OK; } + private void downloadFile(URL downloadUrl, File outputFile, boolean limitDownloadSpeed) throws IOException, InterruptedException { + // For all those small files the progress reporting is really noisy + boolean reportDownloadProgress = limitDownloadSpeed; HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); connection.setConnectTimeout(5000); connection.connect(); - updateProgress("Connecting..."); + if (reportDownloadProgress) updateProgress("Connecting..."); if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { throw new IOException("HTTP Request failed"); } int fileLength = connection.getContentLength(); - updateProgress("Loading"); + if (reportDownloadProgress) updateProgress("Loading"); try ( InputStream input = connection.getInputStream(); @@ -233,9 +236,9 @@ public class DownloadService extends Service implements ProgressListener { if (fileLength > 0) // only if total length is known { int pct = (int) (total * 100 / fileLength); - updateProgress("Progress " + pct + "%"); + if (reportDownloadProgress) updateProgress("Progress " + pct + "%"); } else { - updateProgress("Progress (unknown size)"); + if (reportDownloadProgress) updateProgress("Progress (unknown size)"); } output.write(buffer, 0, count); From ecc4def40cb2d09028a65da101f892b0860654b5 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Sat, 2 Apr 2022 16:58:05 +0200 Subject: [PATCH 10/16] Use WorkManager for downloads --- brouter-routing-app/build.gradle | 2 + .../btools/routingapp/DownloadWorkerTest.java | 71 +++++ .../btools/routingapp/BInstallerActivity.java | 96 +++--- .../btools/routingapp/DownloadWorker.java | 299 ++++++++++++++++++ .../src/main/res/values/strings.xml | 3 + 5 files changed, 428 insertions(+), 43 deletions(-) create mode 100644 brouter-routing-app/src/androidTest/java/btools/routingapp/DownloadWorkerTest.java create mode 100644 brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index 535bf68..ff83e75 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -95,6 +95,7 @@ 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 project(':brouter-mapaccess') implementation project(':brouter-core') @@ -105,6 +106,7 @@ dependencies { 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' } task generateProfiles(type: Exec) { diff --git a/brouter-routing-app/src/androidTest/java/btools/routingapp/DownloadWorkerTest.java b/brouter-routing-app/src/androidTest/java/btools/routingapp/DownloadWorkerTest.java new file mode 100644 index 0000000..a9004c0 --- /dev/null +++ b/brouter-routing-app/src/androidTest/java/btools/routingapp/DownloadWorkerTest.java @@ -0,0 +1,71 @@ +package btools.routingapp; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.work.Data; +import androidx.work.ListenableWorker.Result; +import androidx.work.testing.TestWorkerBuilder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +@RunWith(AndroidJUnit4.class) +public class DownloadWorkerTest { + private Context context; + private Executor executor; + + @Before + public void setUp() { + context = ApplicationProvider.getApplicationContext(); + executor = Executors.newSingleThreadExecutor(); + } + + @Test + public void testDownloadNewFile() { + Data inputData = new Data.Builder() + .putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, new String[]{"E105_N50"}) + .build(); + + DownloadWorker worker = + TestWorkerBuilder.from(context, DownloadWorker.class, executor) + .setInputData(inputData) + .build(); + + Result result = worker.doWork(); + assertThat(result, is(Result.success())); + } + + @Test + public void testDownloadInvalidSegment() { + Data inputData = new Data.Builder() + .putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, new String[]{"X00"}) + .build(); + + DownloadWorker worker = + TestWorkerBuilder.from(context, DownloadWorker.class, executor) + .setInputData(inputData) + .build(); + + Result result = worker.doWork(); + assertThat(result, is(Result.failure())); + } + + @Test + public void testDownloadNoSegments() { + DownloadWorker worker = + TestWorkerBuilder.from(context, DownloadWorker.class, executor) + .build(); + + Result result = worker.doWork(); + assertThat(result, is(Result.failure())); + } +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java index a7b2134..63aeb08 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java @@ -7,11 +7,7 @@ import static btools.routingapp.BInstallerView.MASK_SELECTED_RD5; import android.app.AlertDialog; import android.app.Dialog; -import android.content.BroadcastReceiver; -import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.res.Resources; import android.os.Build; @@ -23,6 +19,13 @@ import android.widget.Button; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; +import androidx.work.Constraints; +import androidx.work.Data; +import androidx.work.NetworkType; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkInfo; +import androidx.work.WorkManager; +import androidx.work.WorkRequest; import java.io.File; import java.util.ArrayList; @@ -37,7 +40,6 @@ public class BInstallerActivity extends AppCompatActivity { public static boolean downloadCanceled = false; private File mBaseDir; private BInstallerView mBInstallerView; - private DownloadReceiver downloadReceiver; private View mDownloadInfo; private TextView mDownloadInfoText; private Button mButtonDownloadCancel; @@ -153,34 +155,59 @@ public class BInstallerActivity extends AppCompatActivity { downloadCanceled = false; mDownloadInfoText.setText(R.string.download_info_start); - Intent intent = new Intent(this, DownloadService.class); - intent.putExtra("dir", mBaseDir.getAbsolutePath() + "/brouter/"); - intent.putExtra("urlparts", urlparts); - startService(intent); + Data inputData = new Data.Builder() + .putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, urlparts.toArray(new String[0])) + .build(); + + Constraints constraints = new Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build(); + + WorkRequest downloadWorkRequest = + new OneTimeWorkRequest.Builder(DownloadWorker.class) + .setInputData(inputData) + .setConstraints(constraints) + .build(); + + WorkManager workManager = WorkManager.getInstance(getApplicationContext()); + workManager.enqueue(downloadWorkRequest); + + mButtonDownloadCancel.setOnClickListener(view -> { + mDownloadInfoText.setText("Cancelling..."); + workManager.cancelWorkById(downloadWorkRequest.getId()); + }); + + workManager + .getWorkInfoByIdLiveData(downloadWorkRequest.getId()) + .observe(this, workInfo -> { + if (workInfo != null) { + if (workInfo.getState() == WorkInfo.State.ENQUEUED) { + mDownloadInfoText.setText("Waiting for download to start. Check internet connection if it takes too long"); + } + + 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 (segmentName != null) { + mDownloadInfoText.setText(String.format("Download %s - %d%%", segmentName, percent)); + } + } + + if (workInfo.getState().isFinished()) { + mSegmentsView.setVisibility(View.VISIBLE); + mDownloadInfo.setVisibility(View.GONE); + scanExistingFiles(); + } + } + }); deleteRawTracks(); // invalidate raw-tracks after data update } - @Override - protected void onResume() { - super.onResume(); - - IntentFilter filter = new IntentFilter(); - filter.addAction(DOWNLOAD_ACTION); - - downloadReceiver = new DownloadReceiver(); - registerReceiver(downloadReceiver, filter); - } - - @Override - protected void onPause() { - super.onPause(); - } - @Override public void onDestroy() { super.onDestroy(); - if (downloadReceiver != null) unregisterReceiver(downloadReceiver); System.exit(0); } @@ -279,21 +306,4 @@ public class BInstallerActivity extends AppCompatActivity { String slat = lat < 0 ? "S" + (-lat) : "N" + lat; return slon + "_" + slat; } - - public class DownloadReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - if (intent.hasExtra("txt")) { - String txt = intent.getStringExtra("txt"); - boolean ready = intent.getBooleanExtra("ready", false); - if (!ready) { - mSegmentsView.setVisibility(View.VISIBLE); - mDownloadInfo.setVisibility(View.GONE); - scanExistingFiles(); - } - mDownloadInfoText.setText(txt); - } - } - } } diff --git a/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java b/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java new file mode 100644 index 0000000..d675e3d --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java @@ -0,0 +1,299 @@ +package btools.routingapp; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import androidx.core.app.NotificationCompat; +import androidx.work.Data; +import androidx.work.ForegroundInfo; +import androidx.work.WorkManager; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import java.io.File; +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 btools.mapaccess.PhysicalFile; +import btools.mapaccess.Rd5DiffManager; +import btools.mapaccess.Rd5DiffTool; +import btools.util.ProgressListener; + +public class DownloadWorker extends Worker { + public static final String KEY_INPUT_SEGMENT_NAMES = "SEGMENT_NAMES"; + public static final String PROGRESS_SEGMENT_NAME = "PROGRESS_SEGMENT_NAME"; + public static final String PROGRESS_SEGMENT_PERCENT = "PROGRESS_SEGMENT_PERCENT"; + + private static final int NOTIFICATION_ID = 1; + private 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"; + + private NotificationManager notificationManager; + private ServerConfig mServerConfig; + private File baseDir; + private ProgressListener diffProgressListener; + private DownloadProgressListener downloadProgressListener; + private Data.Builder progressBuilder = new Data.Builder(); + private NotificationCompat.Builder notificationBuilder; + + public DownloadWorker( + @NonNull Context context, + @NonNull WorkerParameters parameters) { + super(context, parameters); + notificationManager = (NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); + mServerConfig = new ServerConfig(context); + baseDir = new File(ConfigHelper.getBaseDir(context), "brouter"); + + notificationBuilder = createNotificationBuilder(); + + diffProgressListener = new ProgressListener() { + @Override + public void updateProgress(String progress) { + notificationManager.notify(NOTIFICATION_ID, createNotification(progress)); + } + + @Override + public boolean isCanceled() { + return isStopped(); + } + }; + + downloadProgressListener = new DownloadProgressListener() { + @Override + public void updateProgress(int max, int progress) { + if (max > 0) { + notificationBuilder.setProgress(max, progress, false); + notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); + progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, progress * 100 / max); + setProgressAsync(progressBuilder.build()); + } else { + notificationBuilder.setProgress(0, 0, true); + notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); + progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, -1); + setProgressAsync(progressBuilder.build()); + } + } + + @Override + public void updateProgress(String content) { + notificationBuilder.setContentText(content); + notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); + } + }; + + progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, 0); + setProgressAsync(progressBuilder.build()); + } + + @NonNull + @Override + public Result doWork() { + Data inputData = getInputData(); + String[] segmentNames = inputData.getStringArray(KEY_INPUT_SEGMENT_NAMES); + if (segmentNames == null) { + return Result.failure(); + } + // Mark the Worker as important + setForegroundAsync(new ForegroundInfo(NOTIFICATION_ID, createNotification("Starting Download"))); + try { + notificationManager.notify(NOTIFICATION_ID, createNotification("Updating profiles")); + downloadLookupAndProfiles(); + + int segmentIndex = 1; + for (String segmentName : segmentNames) { + notificationManager.notify(NOTIFICATION_ID, createNotification(String.format("%s (%d/%d)", segmentName, segmentIndex, segmentNames.length))); + progressBuilder.putString(PROGRESS_SEGMENT_NAME, segmentName); + setProgressAsync(progressBuilder.build()); + downloadSegment(mServerConfig.getSegmentUrl(), segmentName + SEGMENT_SUFFIX); + segmentIndex++; + } + } catch (IOException e) { + return Result.failure(); + } catch (InterruptedException e) { + return Result.failure(); + } + return Result.success(); + } + + private void downloadLookupAndProfiles() 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); + downloadFile(lookupUrl, lookupFile, false); + } + } + + String[] profiles = mServerConfig.getProfiles(); + for (String fileName : profiles) { + if (fileName.length() > 0) { + File profileFile = new File(baseDir, PROFILES_DIR + fileName); + if (profileFile.exists()) { + String profileLocation = mServerConfig.getProfilesUrl() + fileName; + URL profileUrl = new URL(profileLocation); + downloadFile(profileUrl, profileFile, false); + } + } + } + } + + 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"); + try { + if (segmentFile.exists()) { + downloadProgressListener.updateProgress("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); + downloadProgressListener.updateProgress("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 (!segmentFileTemp.exists()) { + URL segmentUrl = new URL(segmentBaseUrl + segmentName); + downloadFile(segmentUrl, segmentFileTemp, true); + } + + PhysicalFile.checkFileIntegrity(segmentFileTemp); + if (segmentFile.exists()) { + if (!segmentFile.delete()) { + throw new IOException("Failed to delete existing " + segmentFile.getAbsolutePath()); + } + } + + if (!segmentFileTemp.renameTo(segmentFile)) { + throw new IOException("Failed to write " + segmentFile.getAbsolutePath()); + } + } finally { + segmentFileTemp.delete(); + } + } + + private boolean httpFileExists(URL downloadUrl) throws IOException { + HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); + connection.setConnectTimeout(5000); + connection.setRequestMethod("HEAD"); + connection.connect(); + + return connection.getResponseCode() == HttpURLConnection.HTTP_OK; + } + + private void downloadFile(URL downloadUrl, File outputFile, boolean limitDownloadSpeed) throws IOException, InterruptedException { + // For all those small files the progress reporting is really noisy + boolean reportDownloadProgress = limitDownloadSpeed; + HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); + connection.setConnectTimeout(5000); + connection.connect(); + + if (reportDownloadProgress) downloadProgressListener.updateProgress("Connecting..."); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new IOException("HTTP Request failed"); + } + int fileLength = connection.getContentLength(); + try ( + InputStream input = connection.getInputStream(); + OutputStream output = new FileOutputStream(outputFile) + ) { + byte[] buffer = new byte[4096]; + int total = 0; + long t0 = System.currentTimeMillis(); + int count; + while ((count = input.read(buffer)) != -1) { + if (isStopped()) { + throw new InterruptedException(); + } + total += count; + output.write(buffer, 0, count); + + // publishing the progress.... + downloadProgressListener.updateProgress(fileLength, total); + + if (limitDownloadSpeed) { + // enforce < 16 Mbit/s + long dt = t0 + total / 2096 - System.currentTimeMillis(); + if (dt > 0) { + Thread.sleep(dt); + } + } + } + } + + setProgressAsync(new Data.Builder().putInt(PROGRESS_SEGMENT_PERCENT, 100).build()); + } + + @NonNull + private NotificationCompat.Builder createNotificationBuilder() { + Context context = getApplicationContext(); + String id = context.getString(R.string.notification_channel_id); + String title = context.getString(R.string.notification_title); + String cancel = context.getString(R.string.cancel_download); + // This PendingIntent can be used to cancel the worker + PendingIntent intent = WorkManager.getInstance(context) + .createCancelPendingIntent(getId()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createChannel(); + } + + return new NotificationCompat.Builder(context, id) + .setContentTitle(title) + .setTicker(title) + .setOnlyAlertOnce(true) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setSmallIcon(android.R.drawable.stat_sys_download) + .setOngoing(true) + // Add the cancel action to the notification which can + // be used to cancel the worker + .addAction(android.R.drawable.ic_delete, cancel, intent); + } + + @NonNull + private Notification createNotification(@NonNull String content) { + notificationBuilder.setContentText(content); + // Reset progress from previous download + notificationBuilder.setProgress(0, 0, false); + return notificationBuilder.build(); + } + + @RequiresApi(Build.VERSION_CODES.O) + private void createChannel() { + CharSequence name = getApplicationContext().getString(R.string.channel_name); + int importance = NotificationManager.IMPORTANCE_LOW; + NotificationChannel channel = new NotificationChannel(getApplicationContext().getString(R.string.notification_channel_id), name, importance); + // Register the channel with the system; you can't change the importance + // or other notification behaviors after this + notificationManager.createNotificationChannel(channel); + } + + interface DownloadProgressListener { + void updateProgress(int max, int progress); + void updateProgress(String content); + } +} diff --git a/brouter-routing-app/src/main/res/values/strings.xml b/brouter-routing-app/src/main/res/values/strings.xml index b7d0d46..6663c01 100644 --- a/brouter-routing-app/src/main/res/values/strings.xml +++ b/brouter-routing-app/src/main/res/values/strings.xml @@ -30,4 +30,7 @@ Update %s Select segments Size=%s\nFree=%s + brouter_download + Download Segments + Downloads From 3a2c109ded4f35b3fde09cbaa05a5b5c82e358a5 Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Sat, 2 Apr 2022 16:58:55 +0200 Subject: [PATCH 11/16] Remove DownloadService --- .../src/main/AndroidManifest.xml | 5 - .../btools/routingapp/BInstallerActivity.java | 1 - .../btools/routingapp/DownloadService.java | 277 ------------------ .../btools/routingapp/NotificationHelper.java | 134 --------- 4 files changed, 417 deletions(-) delete mode 100644 brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java delete mode 100644 brouter-routing-app/src/main/java/btools/routingapp/NotificationHelper.java diff --git a/brouter-routing-app/src/main/AndroidManifest.xml b/brouter-routing-app/src/main/AndroidManifest.xml index 0d50b10..8efa1af 100644 --- a/brouter-routing-app/src/main/AndroidManifest.xml +++ b/brouter-routing-app/src/main/AndroidManifest.xml @@ -91,11 +91,6 @@ android:enabled="true" android:exported="true" android:process=":brouter_service" /> - diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java index 63aeb08..8781696 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java @@ -35,7 +35,6 @@ import btools.router.RoutingHelper; public class BInstallerActivity extends AppCompatActivity { - public static final String DOWNLOAD_ACTION = "btools.routingapp.download"; private static final int DIALOG_CONFIRM_DELETE_ID = 1; public static boolean downloadCanceled = false; private File mBaseDir; diff --git a/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java b/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java deleted file mode 100644 index f1ec4bf..0000000 --- a/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java +++ /dev/null @@ -1,277 +0,0 @@ -package btools.routingapp; - -import android.app.Service; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.util.Log; -import android.widget.Toast; - -import java.io.File; -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.List; - -import btools.mapaccess.PhysicalFile; -import btools.mapaccess.Rd5DiffManager; -import btools.mapaccess.Rd5DiffTool; -import btools.util.ProgressListener; - -public class DownloadService extends Service implements ProgressListener { - private 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"; - - private static final boolean DEBUG = false; - public static boolean serviceState = false; - private ServerConfig mServerConfig; - private NotificationHelper mNotificationHelper; - private List mSegmentNames; - private String baseDir; - private volatile String lastProgress = ""; - private ServiceHandler mServiceHandler; - private boolean bIsDownloading; - private int mSegmentIndex; - private int mSegmentCount; - - @Override - public void onCreate() { - if (DEBUG) Log.d("SERVICE", "onCreate"); - serviceState = true; - mServerConfig = new ServerConfig(getApplicationContext()); - - HandlerThread thread = new HandlerThread("ServiceStartArguments", 1); - thread.start(); - - // Get the HandlerThread's Looper and use it for our Handler - Looper serviceLooper = thread.getLooper(); - mServiceHandler = new ServiceHandler(serviceLooper); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (DEBUG) Log.d("SERVICE", "onStartCommand"); - - mNotificationHelper = new NotificationHelper(this); - Bundle extra = intent.getExtras(); - if (extra != null) { - String dir = extra.getString("dir"); - mSegmentNames = extra.getStringArrayList("urlparts"); - baseDir = dir; - } - - mNotificationHelper.startNotification(this); - - Message msg = mServiceHandler.obtainMessage(); - msg.arg1 = startId; - mServiceHandler.sendMessage(msg); - - // If we get killed, after returning from here, restart - return START_STICKY; - } - - @Override - public void onDestroy() { - if (DEBUG) Log.d("SERVICE", "onDestroy"); - serviceState = false; - super.onDestroy(); - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - public void downloadFiles() { - try { - mSegmentIndex = 0; - downloadLookupAndProfiles(); - - mSegmentIndex = 1; - mSegmentCount = mSegmentNames.size(); - for (String segmentName : mSegmentNames) { - downloadSegment(mServerConfig.getSegmentUrl(), segmentName + SEGMENT_SUFFIX); - mSegmentIndex++; - } - } catch (IOException e) { - Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show(); - } catch (InterruptedException e) { - updateProgress("canceled"); - } finally { - bIsDownloading = false; - updateProgress("finished"); - } - } - - public void updateProgress(String progress) { - if (!lastProgress.equals(progress)) { - if (mSegmentIndex > 0) { - progress = mSegmentIndex + "/" + mSegmentCount + " " + progress; - } - if (DEBUG) Log.d("BR", "up " + progress); - Intent intent = new Intent(BInstallerActivity.DOWNLOAD_ACTION); - intent.putExtra("txt", progress); - intent.putExtra("ready", bIsDownloading); - sendBroadcast(intent); - mNotificationHelper.progressUpdate(progress); - lastProgress = progress; - } - } - - private void downloadLookupAndProfiles() 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); - downloadFile(lookupUrl, lookupFile, false); - } - } - - String[] profiles = mServerConfig.getProfiles(); - for (String fileName : profiles) { - if (fileName.length() > 0) { - File profileFile = new File(baseDir, PROFILES_DIR + fileName); - if (profileFile.exists()) { - String profileLocation = mServerConfig.getProfilesUrl() + fileName; - URL profileUrl = new URL(profileLocation); - downloadFile(profileUrl, profileFile, false); - } - } - } - } - - 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"); - try { - if (segmentFile.exists()) { - updateProgress("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); - updateProgress("Applying delta..."); - Rd5DiffTool.recoverFromDelta(segmentFile, segmentDeltaFile, segmentFileTemp, this); - } catch (IOException e) { - throw new IOException("Failed to download & apply delta update", e); - } finally { - segmentDeltaFile.delete(); - } - } - } - - if (!segmentFileTemp.exists()) { - URL segmentUrl = new URL(segmentBaseUrl + segmentName); - downloadFile(segmentUrl, segmentFileTemp, true); - } - - PhysicalFile.checkFileIntegrity(segmentFileTemp); - if (segmentFile.exists()) { - if (!segmentFile.delete()) { - throw new IOException("Failed to delete existing " + segmentFile.getAbsolutePath()); - } - } - - if (!segmentFileTemp.renameTo(segmentFile)) { - throw new IOException("Failed to write " + segmentFile.getAbsolutePath()); - } - } finally { - segmentFileTemp.delete(); - } - } - - private boolean httpFileExists(URL downloadUrl) throws IOException { - HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); - connection.setConnectTimeout(5000); - connection.setRequestMethod("HEAD"); - connection.connect(); - - return connection.getResponseCode() == HttpURLConnection.HTTP_OK; - } - - - private void downloadFile(URL downloadUrl, File outputFile, boolean limitDownloadSpeed) throws IOException, InterruptedException { - // For all those small files the progress reporting is really noisy - boolean reportDownloadProgress = limitDownloadSpeed; - HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); - connection.setConnectTimeout(5000); - connection.connect(); - - if (reportDownloadProgress) updateProgress("Connecting..."); - - if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { - throw new IOException("HTTP Request failed"); - } - int fileLength = connection.getContentLength(); - if (reportDownloadProgress) updateProgress("Loading"); - - try ( - InputStream input = connection.getInputStream(); - OutputStream output = new FileOutputStream(outputFile) - ) { - byte[] buffer = new byte[4096]; - long total = 0; - long t0 = System.currentTimeMillis(); - int count; - while ((count = input.read(buffer)) != -1) { - if (isCanceled()) { - throw new InterruptedException(); - } - total += count; - // publishing the progress.... - if (fileLength > 0) // only if total length is known - { - int pct = (int) (total * 100 / fileLength); - if (reportDownloadProgress) updateProgress("Progress " + pct + "%"); - } else { - if (reportDownloadProgress) updateProgress("Progress (unknown size)"); - } - output.write(buffer, 0, count); - - if (limitDownloadSpeed) { - // enforce < 16 Mbit/s - long dt = t0 + total / 2096 - System.currentTimeMillis(); - if (dt > 0) { - Thread.sleep(dt); - } - } - } - } - } - - public boolean isCanceled() { - return BInstallerActivity.downloadCanceled; - } - - // Handler that receives messages from the thread - private final class ServiceHandler extends Handler { - public ServiceHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - bIsDownloading = true; - downloadFiles(); - - stopForeground(true); - stopSelf(msg.arg1); - mNotificationHelper.stopNotification(); - } - } - -} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/NotificationHelper.java b/brouter-routing-app/src/main/java/btools/routingapp/NotificationHelper.java deleted file mode 100644 index a97f296..0000000 --- a/brouter-routing-app/src/main/java/btools/routingapp/NotificationHelper.java +++ /dev/null @@ -1,134 +0,0 @@ -package btools.routingapp; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.media.AudioAttributes; -import android.os.Build; -import android.util.Log; - -import androidx.core.app.NotificationCompat; - - -import static android.content.Context.NOTIFICATION_SERVICE; - -public class NotificationHelper { - - private static final boolean DEBUG = false; - - public static String BRouterNotificationChannel1 = "brouter_channel_01"; - - private Context mContext; - private int NOTIFICATION_ID = 111; - private Notification mNotification; - private NotificationManager mNotificationManager; - private PendingIntent mContentIntent; - private CharSequence mContentTitle; - - public NotificationHelper(Context context) { - if (DEBUG) Log.d("NH", "init "); - mContext = context; - createNotificationChannels(); - } - - public void startNotification(Service service) { - if (DEBUG) Log.d("NH", "startNotification "); - - mNotification = createNotification("BRouter Download", "Download some files"); - - if (service != null) service.startForeground(NOTIFICATION_ID, mNotification); - - mNotificationManager.notify(NOTIFICATION_ID, mNotification); - - } - - public void progressUpdate(String text) { - mNotification = createNotification("BRouter Download", text); - mNotification.flags = Notification.FLAG_NO_CLEAR | - Notification.FLAG_ONGOING_EVENT; - - mNotificationManager.notify(NOTIFICATION_ID, mNotification); - } - - - public Notification createNotification(String title, String desc) { - - Intent resultIntent = new Intent(mContext, BInstallerActivity.class); - - Intent notificationIntent = new Intent(); - mContentIntent = PendingIntent.getActivity(mContext, 0, resultIntent, PendingIntent.FLAG_IMMUTABLE); - - mNotificationManager = (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - - - final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, BRouterNotificationChannel1); - builder.setSmallIcon(android.R.drawable.stat_sys_download) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setContentTitle(title) - .setContentText(desc) - .setTicker(desc) - .setOngoing(true) - .setAutoCancel(true) - .setOnlyAlertOnce(true) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .setContentIntent(mContentIntent); - - return builder.build(); - - } else { - final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); - builder.setSmallIcon(android.R.drawable.stat_sys_download) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setContentTitle(title) - .setContentText(desc) - .setOnlyAlertOnce(true) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .setContentIntent(mContentIntent); - - return builder.build(); - } - - } - - /** - * create notification channels - */ - public void createNotificationChannels() { - if (DEBUG) Log.d("NH", "createNotificationChannels "); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - - NotificationManager sNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - // Sound channel - CharSequence name = "BRouter Download"; - // The user-visible description of the channel. - String description = "BRouter Download Channel"; //getString(R.string.channel_description); - - NotificationChannel channel = new NotificationChannel(BRouterNotificationChannel1, name, NotificationManager.IMPORTANCE_LOW); - channel.setDescription(description); - AudioAttributes att = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_UNKNOWN) - .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN) - .build(); - channel.setSound(null, null); - channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); - - sNotificationManager.createNotificationChannel(channel); - - } - } - - public void stopNotification() { - if (DEBUG) Log.d("NH", "stopNotification "); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mNotificationManager.deleteNotificationChannel(BRouterNotificationChannel1); - } - mNotificationManager.cancel(NOTIFICATION_ID); - } -} From 952ea803b2033bcd5c43bcae7ab95c4de44ee96e Mon Sep 17 00:00:00 2001 From: Manuel Fuhr Date: Sat, 2 Apr 2022 17:34:41 +0200 Subject: [PATCH 12/16] Use LinarProgessIndicator instead of sub-view --- .../java/btools/mapaccess/Rd5DiffTool.java | 9 +- brouter-routing-app/build.gradle | 33 ++--- .../src/main/AndroidManifest.xml | 2 +- .../btools/routingapp/BInstallerActivity.java | 58 +++++---- .../btools/routingapp/DownloadWorker.java | 116 +++++++++++------- .../main/res/layout/activity_binstaller.xml | 104 ++++++---------- .../src/main/res/values/styles.xml | 10 ++ .../java/btools/util/ProgressListener.java | 3 +- 8 files changed, 173 insertions(+), 162 deletions(-) create mode 100644 brouter-routing-app/src/main/res/values/styles.xml diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java b/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java index e1e48c6..c5ffbf1 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java @@ -50,9 +50,8 @@ final public class Rd5DiffTool implements ProgressListener } @Override - public void updateProgress( String progress ) - { - System.out.println( progress ); + public void updateProgress(String task, int progress) { + System.out.println(task + ": " + progress + "%"); } @Override @@ -342,7 +341,7 @@ final public class Rd5DiffTool implements ProgressListener int pct = (int)(100. * bytesProcessed / getTileEnd( fileIndex1, 24 ) + 0.5 ); if ( pct != lastPct ) { - progress.updateProgress( "Applying delta: " + pct + "%" ); + progress.updateProgress("Applying delta", pct); lastPct = pct; } @@ -490,7 +489,7 @@ final public class Rd5DiffTool implements ProgressListener int pct = (int)( (100. * sizeRead) / (sizeTotal+1) + 0.5 ); if ( pct != lastPct ) { - progress.updateProgress( "Copying: " + pct + "%" ); + progress.updateProgress("Copying", pct); lastPct = pct; } int len = dis1.read( buf ); diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index ff83e75..d254021 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -14,16 +14,16 @@ android { versionName project.version resValue('string', 'app_version', defaultConfig.versionName) - setProperty("archivesBaseName","BRouterApp." + defaultConfig.versionName) + setProperty("archivesBaseName", "BRouterApp." + defaultConfig.versionName) minSdkVersion 14 - + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } sourceSets.main.assets.srcDirs += new File(project.buildDir, 'assets') - if(project.hasProperty("RELEASE_STORE_FILE") && RELEASE_STORE_FILE.length() > 0) { + if (project.hasProperty("RELEASE_STORE_FILE") && RELEASE_STORE_FILE.length() > 0) { signingConfigs { // this uses a file ~/.gradle/gradle.properties // with content: @@ -33,17 +33,17 @@ android { // RELEASE_KEY_PASSWORD=***** // release { - // enable signingConfig in buildTypes to get a signed apk file - storeFile file(RELEASE_STORE_FILE) - storePassword RELEASE_STORE_PASSWORD - keyAlias RELEASE_KEY_ALIAS - keyPassword RELEASE_KEY_PASSWORD + // enable signingConfig in buildTypes to get a signed apk file + storeFile file(RELEASE_STORE_FILE) + storePassword RELEASE_STORE_PASSWORD + keyAlias RELEASE_KEY_ALIAS + keyPassword RELEASE_KEY_PASSWORD - // Optional, specify signing versions used - v1SigningEnabled true - v2SigningEnabled true + // Optional, specify signing versions used + v1SigningEnabled true + v2SigningEnabled true - } + } } } @@ -51,7 +51,7 @@ android { release { minifyEnabled false debuggable false - if(project.hasProperty("RELEASE_STORE_FILE") && RELEASE_STORE_FILE.length() > 0) { + if (project.hasProperty("RELEASE_STORE_FILE") && RELEASE_STORE_FILE.length() > 0) { signingConfig signingConfigs.release } proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' @@ -64,7 +64,8 @@ android { } lintOptions { disable 'InvalidPackage' - checkReleaseBuilds false //added this line to the build.gradle under the /android/app/build.gradle + checkReleaseBuilds false + //added this line to the build.gradle under the /android/app/build.gradle } compileOptions { @@ -96,6 +97,7 @@ 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 project(':brouter-mapaccess') implementation project(':brouter-core') @@ -116,8 +118,7 @@ task generateProfiles(type: Exec) { task generateProfilesZip(type: Zip) { if (DefaultNativePlatform.getCurrentOperatingSystem().isWindows()) { logger.warn("Note: On Windows run script '../misc/scripts/generate_profile_variants.sh' manually to include all profiles") - } - else { + } else { dependsOn generateProfiles } archiveFileName = "profiles2.zip" diff --git a/brouter-routing-app/src/main/AndroidManifest.xml b/brouter-routing-app/src/main/AndroidManifest.xml index 8efa1af..76f6b51 100644 --- a/brouter-routing-app/src/main/AndroidManifest.xml +++ b/brouter-routing-app/src/main/AndroidManifest.xml @@ -18,7 +18,7 @@ android:label="@string/app_name" android:preserveLegacyExternalStorage="true" android:roundIcon="@mipmap/ic_launcher_round" - android:theme="@style/Theme.AppCompat.Light.NoActionBar"> + android:theme="@style/Theme.App"> { @@ -84,12 +82,7 @@ public class BInstallerActivity extends AppCompatActivity { } } ); - mDownloadInfo = findViewById(R.id.view_download_progress); - mDownloadInfoText = findViewById(R.id.textViewDownloadProgress); - mButtonDownloadCancel = findViewById(R.id.buttonDownloadCancel); - mButtonDownloadCancel.setOnClickListener(view -> { - cancelDownload(); - }); + mProgressIndicator = findViewById(R.id.progressDownload); mBaseDir = ConfigHelper.getBaseDir(this); scanExistingFiles(); @@ -138,21 +131,13 @@ public class BInstallerActivity extends AppCompatActivity { } } - private void cancelDownload() { - downloadCanceled = true; - mDownloadInfoText.setText(getString(R.string.download_info_cancel)); - } - public void downloadAll(ArrayList downloadList) { ArrayList urlparts = new ArrayList<>(); for (Integer i : downloadList) { urlparts.add(baseNameForTile(i)); } - mSegmentsView.setVisibility(View.GONE); - mDownloadInfo.setVisibility(View.VISIBLE); downloadCanceled = false; - mDownloadInfoText.setText(R.string.download_info_start); Data inputData = new Data.Builder() .putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, urlparts.toArray(new String[0])) @@ -171,31 +156,44 @@ public class BInstallerActivity extends AppCompatActivity { WorkManager workManager = WorkManager.getInstance(getApplicationContext()); workManager.enqueue(downloadWorkRequest); - mButtonDownloadCancel.setOnClickListener(view -> { - mDownloadInfoText.setText("Cancelling..."); - workManager.cancelWorkById(downloadWorkRequest.getId()); - }); - workManager .getWorkInfoByIdLiveData(downloadWorkRequest.getId()) .observe(this, workInfo -> { if (workInfo != null) { if (workInfo.getState() == WorkInfo.State.ENQUEUED) { - mDownloadInfoText.setText("Waiting for download to start. Check internet connection if it takes too long"); + 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 (segmentName != null) { - mDownloadInfoText.setText(String.format("Download %s - %d%%", segmentName, percent)); + if (percent > 0) { + mProgressIndicator.setIndeterminate(false); } + mProgressIndicator.setProgress(percent); } if (workInfo.getState().isFinished()) { - mSegmentsView.setVisibility(View.VISIBLE); - mDownloadInfo.setVisibility(View.GONE); + String result; + switch (workInfo.getState()) { + case FAILED: + result = "failed."; + break; + case CANCELLED: + result = "cancelled"; + break; + case SUCCEEDED: + result = "succeeded"; + break; + default: + result = ""; + } + Toast.makeText(this, "Download " + result + ".", Toast.LENGTH_SHORT).show(); + mProgressIndicator.hide(); scanExistingFiles(); } } 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 d675e3d..378e431 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java @@ -59,10 +59,59 @@ public class DownloadWorker extends Worker { notificationBuilder = createNotificationBuilder(); + downloadProgressListener = new DownloadProgressListener() { + private String currentDownloadName; + private DownloadType currentDownloadType; + private int lastProgressPercent; + + @Override + public void onDownloadStart(String downloadName, DownloadType downloadType) { + currentDownloadName = downloadName; + currentDownloadType = downloadType; + if (downloadType == DownloadType.SEGMENT) { + progressBuilder.putString(PROGRESS_SEGMENT_NAME, downloadName); + notificationBuilder.setContentText(downloadName); + } + } + + @Override + public void onDownloadInfo(String info) { + notificationBuilder.setContentText(info); + notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); + } + + @Override + public void onDownloadProgress(int max, int progress) { + int progressPercent = (int) (progress * 100L / max); + + // Only report segments and update if it changed to avoid hammering NotificationManager + if (currentDownloadType != DownloadType.SEGMENT || progressPercent == lastProgressPercent) { + return; + } + + if (max > 0) { + notificationBuilder.setProgress(max, progress, false); + progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, progressPercent); + } else { + notificationBuilder.setProgress(0, 0, true); + progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, -1); + } + notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); + setProgressAsync(progressBuilder.build()); + + lastProgressPercent = progressPercent; + } + + @Override + public void onDownloadFinished() { + } + }; + diffProgressListener = new ProgressListener() { @Override - public void updateProgress(String progress) { - notificationManager.notify(NOTIFICATION_ID, createNotification(progress)); + public void updateProgress(String task, int progress) { + downloadProgressListener.onDownloadInfo(task); + downloadProgressListener.onDownloadProgress(100, progress); } @Override @@ -70,32 +119,6 @@ public class DownloadWorker extends Worker { return isStopped(); } }; - - downloadProgressListener = new DownloadProgressListener() { - @Override - public void updateProgress(int max, int progress) { - if (max > 0) { - notificationBuilder.setProgress(max, progress, false); - notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); - progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, progress * 100 / max); - setProgressAsync(progressBuilder.build()); - } else { - notificationBuilder.setProgress(0, 0, true); - notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); - progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, -1); - setProgressAsync(progressBuilder.build()); - } - } - - @Override - public void updateProgress(String content) { - notificationBuilder.setContentText(content); - notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); - } - }; - - progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, 0); - setProgressAsync(progressBuilder.build()); } @NonNull @@ -109,14 +132,11 @@ public class DownloadWorker extends Worker { // Mark the Worker as important setForegroundAsync(new ForegroundInfo(NOTIFICATION_ID, createNotification("Starting Download"))); try { - notificationManager.notify(NOTIFICATION_ID, createNotification("Updating profiles")); downloadLookupAndProfiles(); int segmentIndex = 1; for (String segmentName : segmentNames) { - notificationManager.notify(NOTIFICATION_ID, createNotification(String.format("%s (%d/%d)", segmentName, segmentIndex, segmentNames.length))); - progressBuilder.putString(PROGRESS_SEGMENT_NAME, segmentName); - setProgressAsync(progressBuilder.build()); + downloadProgressListener.onDownloadStart(segmentName, DownloadType.SEGMENT); downloadSegment(mServerConfig.getSegmentUrl(), segmentName + SEGMENT_SUFFIX); segmentIndex++; } @@ -135,7 +155,9 @@ public class DownloadWorker extends Worker { 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(); } } @@ -146,7 +168,9 @@ public class DownloadWorker extends Worker { if (profileFile.exists()) { String profileLocation = mServerConfig.getProfilesUrl() + fileName; URL profileUrl = new URL(profileLocation); + downloadProgressListener.onDownloadStart(fileName, DownloadType.PROFILE); downloadFile(profileUrl, profileFile, false); + downloadProgressListener.onDownloadFinished(); } } } @@ -157,7 +181,7 @@ public class DownloadWorker extends Worker { File segmentFileTemp = new File(segmentFile.getAbsolutePath() + "_tmp"); try { if (segmentFile.exists()) { - downloadProgressListener.updateProgress("Calculating local checksum..."); + downloadProgressListener.onDownloadInfo("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); @@ -165,7 +189,7 @@ public class DownloadWorker extends Worker { File segmentDeltaFile = new File(segmentFile.getAbsolutePath() + "_diff"); try { downloadFile(segmentDeltaUrl, segmentDeltaFile, true); - downloadProgressListener.updateProgress("Applying delta..."); + downloadProgressListener.onDownloadInfo("Applying delta..."); Rd5DiffTool.recoverFromDelta(segmentFile, segmentDeltaFile, segmentFileTemp, diffProgressListener); } catch (IOException e) { throw new IOException("Failed to download & apply delta update", e); @@ -205,14 +229,10 @@ public class DownloadWorker extends Worker { } private void downloadFile(URL downloadUrl, File outputFile, boolean limitDownloadSpeed) throws IOException, InterruptedException { - // For all those small files the progress reporting is really noisy - boolean reportDownloadProgress = limitDownloadSpeed; HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); connection.setConnectTimeout(5000); connection.connect(); - if (reportDownloadProgress) downloadProgressListener.updateProgress("Connecting..."); - if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { throw new IOException("HTTP Request failed"); } @@ -232,8 +252,7 @@ public class DownloadWorker extends Worker { total += count; output.write(buffer, 0, count); - // publishing the progress.... - downloadProgressListener.updateProgress(fileLength, total); + downloadProgressListener.onDownloadProgress(fileLength, total); if (limitDownloadSpeed) { // enforce < 16 Mbit/s @@ -244,8 +263,6 @@ public class DownloadWorker extends Worker { } } } - - setProgressAsync(new Data.Builder().putInt(PROGRESS_SEGMENT_PERCENT, 100).build()); } @NonNull @@ -292,8 +309,19 @@ public class DownloadWorker extends Worker { notificationManager.createNotificationChannel(channel); } + enum DownloadType { + LOOKUP, + PROFILE, + SEGMENT + } + interface DownloadProgressListener { - void updateProgress(int max, int progress); - void updateProgress(String content); + void onDownloadStart(String downloadName, DownloadType downloadType); + + void onDownloadInfo(String info); + + void onDownloadProgress(int max, int progress); + + void onDownloadFinished(); } } 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 11e4a84..5f0e7c1 100644 --- a/brouter-routing-app/src/main/res/layout/activity_binstaller.xml +++ b/brouter-routing-app/src/main/res/layout/activity_binstaller.xml @@ -1,71 +1,45 @@ - + android:layout_height="match_parent"> - + +