diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index 677ed19..5ec912e 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -71,7 +71,7 @@ android { dependencies { - implementation 'androidx.appcompat:appcompat:1.3.0' + implementation 'androidx.appcompat:appcompat:1.3.1' implementation project(':brouter-mapaccess') implementation project(':brouter-core') diff --git a/brouter-routing-app/src/main/AndroidManifest.xml b/brouter-routing-app/src/main/AndroidManifest.xml index f5d4713..90eee53 100644 --- a/brouter-routing-app/src/main/AndroidManifest.xml +++ b/brouter-routing-app/src/main/AndroidManifest.xml @@ -8,14 +8,17 @@ + + android:allowBackup="false"> + android:exported="true" + android:screenOrientation="unspecified" android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> @@ -24,6 +27,8 @@ + \ No newline at end of file diff --git a/brouter-routing-app/src/main/assets/segments4.zip b/brouter-routing-app/src/main/assets/segments4.zip index dd18139..c7671fd 100644 Binary files a/brouter-routing-app/src/main/assets/segments4.zip and b/brouter-routing-app/src/main/assets/segments4.zip differ diff --git a/brouter-routing-app/src/main/assets/serverconfig.txt b/brouter-routing-app/src/main/assets/serverconfig.txt new file mode 100644 index 0000000..ff9eeb7 --- /dev/null +++ b/brouter-routing-app/src/main/assets/serverconfig.txt @@ -0,0 +1,16 @@ +# +# data download parameter +# +# Keep in mind, when profiles downloaded they overwrite old files +# So when modifying an original profile make a copy from it +# + +segment_url=https://brouter.de/brouter/segments4/ +lookup_url=https://brouter.de/brouter/segments4/ +profiles_url=https://brouter.de/brouter/segments4/ + + +# these are comma separated arrays + +check_lookup=lookups.dat +check_profiles=car-eco.brf,car-fast.brf,dummy.brf,fastbike.brf,fastbike-asia-pacific.brf,fastbike-lowtraffic.brf,moped.brf,shortest.brf,trekking.brf,vm-forum-liegerad-schnell.brf,vm-forum-velomobil-schnell.brf \ No newline at end of file diff --git a/brouter-routing-app/src/main/ic_launcher-playstore.png b/brouter-routing-app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..5c716a9 Binary files /dev/null and b/brouter-routing-app/src/main/ic_launcher-playstore.png differ diff --git a/brouter-routing-app/src/main/icon_brouter.svg b/brouter-routing-app/src/main/icon_brouter.svg new file mode 100644 index 0000000..1f9f5dd --- /dev/null +++ b/brouter-routing-app/src/main/icon_brouter.svg @@ -0,0 +1 @@ + \ No newline at end of file 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 52c2c98..8a1c93f 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java @@ -6,20 +6,42 @@ import java.util.Set; import android.app.Activity; 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.os.Bundle; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.speech.tts.TextToSpeech.OnInitListener; +import android.util.Log; public class BInstallerActivity extends Activity implements OnInitListener { + public static final String DOWNLOAD_ACTION = "btools.routingapp.download"; + private static final int DIALOG_CONFIRM_DELETE_ID = 1; private BInstallerView mBInstallerView; private PowerManager mPowerManager; private WakeLock mWakeLock; + private DownloadReceiver myReceiver; + + + 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); + mBInstallerView.setState(txt, ready); + } + } + } + /** Called when the activity is first created. */ @Override @@ -51,6 +73,12 @@ public class BInstallerActivity extends Activity implements OnInitListener { */ mWakeLock.acquire(); + IntentFilter filter = new IntentFilter(); + filter.addAction(DOWNLOAD_ACTION); + + myReceiver = new DownloadReceiver(); + registerReceiver(myReceiver, filter); + // Start the download manager mBInstallerView.startInstaller(); } @@ -58,6 +86,18 @@ public class BInstallerActivity extends Activity implements OnInitListener { @Override protected void onPause() { super.onPause(); + + + super.onPause(); + + mWakeLock.release(); + + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (myReceiver != null) unregisterReceiver(myReceiver); System.exit(0); } diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java index 7c707dd..debd00d 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java @@ -7,10 +7,12 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.util.ArrayList; import java.util.Locale; import android.app.Activity; import android.content.Context; +import android.content.Intent; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -23,6 +25,7 @@ import android.os.PowerManager; import android.os.StatFs; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.Toast; @@ -63,7 +66,7 @@ public class BInstallerView extends View private File baseDir; private boolean isDownloading = false; - private volatile boolean downloadCanceled = false; + public static boolean downloadCanceled = false; private long currentDownloadSize; private String currentDownloadFile = ""; @@ -78,6 +81,9 @@ public class BInstallerView extends View Paint pnt_1 = new Paint(); Paint pnt_2 = new Paint(); Paint paint = new Paint(); + + Activity mActivity; + protected String baseNameForTile( int tileIndex ) { @@ -133,6 +139,7 @@ public class BInstallerView extends View int tidx_min = -1; int min_size = Integer.MAX_VALUE; + ArrayList downloadList = new ArrayList<>(); // prepare download list for( int ix=0; ix<72; ix++ ) { @@ -142,6 +149,7 @@ public class BInstallerView extends View if ( ( tileStatus[tidx] & MASK_SELECTED_RD5 ) != 0 ) { int tilesize = BInstallerSizes.getRd5Size(tidx); + downloadList.add(tidx); if ( tilesize > 0 && tilesize < min_size ) { tidx_min = tidx; @@ -150,29 +158,39 @@ public class BInstallerView extends View } } } - if ( tidx_min != -1 ) - { - tileStatus[tidx_min] ^= tileStatus[tidx_min] & MASK_SELECTED_RD5; - startDownload( tidx_min ); + + if (downloadList.size()>0) { + isDownloading = true; + downloadAll(downloadList); + for (Integer i : downloadList) { + tileStatus[i.intValue()] ^= tileStatus[i.intValue()] & MASK_SELECTED_RD5; + } + downloadList.clear(); } } - - private void startDownload( int tileIndex ) - { - - String namebase = baseNameForTile( tileIndex ); - String baseurl = "http://brouter.de/brouter/segments4/"; - currentDownloadFile = namebase + ".rd5"; - currentDownloadOperation = "Checking"; - String url = baseurl + currentDownloadFile; - isDownloading = true; - downloadCanceled = false; - currentDownloadSize = 0; - downloadAction = "Connecting... "; - final DownloadTask downloadTask = new DownloadTask(getContext()); - downloadTask.execute( url ); + + private void downloadAll(ArrayList downloadList) { + ArrayList urlparts = new ArrayList<>(); + for (Integer i: downloadList) { + urlparts.add(baseNameForTile( i.intValue() )); + } + + currentDownloadOperation = "Start download ..."; + downloadAction = ""; + downloadCanceled = false; + isDownloading = true; + + //final DownloadBackground downloadTask = new DownloadBackground(getContext(), urlparts, baseDir); + //downloadTask.execute( ); + Intent intent = new Intent(mActivity, DownloadService.class); + intent.putExtra("dir", baseDir.getAbsolutePath()+"/brouter/"); + intent.putExtra("urlparts", urlparts); + mActivity.startService(intent); + + deleteRawTracks(); // invalidate raw-tracks after data update } + public void downloadDone( boolean success ) { isDownloading = false; @@ -184,6 +202,16 @@ public class BInstallerView extends View invalidate(); } + public void setState(String txt, boolean b) { + currentDownloadOperation = txt; + downloadAction = ""; + isDownloading = b; + if (!b) { + scanExistingFiles(); + } + invalidate(); + } + private int tileIndex( float x, float y ) { int ix = (int)(72.f * x / bmp.getWidth()); @@ -300,6 +328,7 @@ public class BInstallerView extends View public BInstallerView(Context context) { super(context); + mActivity = (Activity) context; DisplayMetrics metrics = new DisplayMetrics(); ((Activity)getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics); @@ -314,10 +343,15 @@ public class BInstallerView extends View imgw = (int)(imgwOrig / scaleOrig); imgh = (int)(imghOrig / scaleOrig); + + totalSize = 0; + rd5Tiles = 0; + delTiles = 0; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w,h,oldw,oldh); } private void toast( String msg ) @@ -390,10 +424,11 @@ public class BInstallerView extends View { String sizeHint = currentDownloadSize > 0 ? " (" + ((currentDownloadSize + mb-1)/mb) + " MB)" : ""; paint.setTextSize(30); - canvas.drawText( currentDownloadOperation + " " + currentDownloadFile + sizeHint, 30, (imgh/3)*2-30, paint); + canvas.drawText( currentDownloadOperation, 30, (imgh/3)*2-30, paint); + // canvas.drawText( currentDownloadOperation + " " + currentDownloadFile + sizeHint, 30, (imgh/3)*2-30, paint); canvas.drawText( downloadAction, 30, (imgh/3)*2, paint); } - if ( !tilesVisible ) + if ( !tilesVisible && !isDownloading) { paint.setTextSize(35); canvas.drawText( "Touch region to zoom in!", 30, (imgh/3)*2, paint); @@ -655,209 +690,7 @@ float tx, ty; return true; } - - // usually, subclasses of AsyncTask are declared inside the activity class. - // that way, you can easily modify the UI thread from here - private class DownloadTask extends AsyncTask implements ProgressListener { - private Context context; - private PowerManager.WakeLock mWakeLock; - public DownloadTask(Context context) { - this.context = context; - } - @Override - public void updateProgress( String progress ) - { - newDownloadAction = progress; - publishProgress( 0 ); - } - - @Override - public boolean isCanceled() - { - return isDownloadCanceled(); - } - - @Override - protected String doInBackground(String... sUrls) - { - InputStream input = null; - OutputStream output = null; - HttpURLConnection connection = null; - String surl = sUrls[0]; - File fname = null; - File tmp_file = null; - try - { - try - { - int slidx = surl.lastIndexOf( "segments4/" ); - String name = surl.substring( slidx+10 ); - String surlBase = surl.substring( 0, slidx+10 ); - fname = new File (baseDir, "brouter/segments4/" + name); - - boolean delta = true; - - 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); - - updateProgress( "Connecting.." ); - - connection = (HttpURLConnection) urlDelta.openConnection(); - connection.connect(); - - // 404 kind of expected here, means there's no delta file - if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND ) - { - connection = null; - } - } - - if ( connection == null ) - { - delta = false; - URL url = new URL(surl); - connection = (HttpURLConnection) url.openConnection(); - connection.connect(); - } - // 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(); - } - - // this will be useful to display download percentage - // might be -1: server did not report the length - int fileLength = connection.getContentLength(); - currentDownloadSize = fileLength; - if ( availableSize >= 0 && fileLength > availableSize ) return "not enough space on sd-card"; - - currentDownloadOperation = delta ? "Updating" : "Loading"; - - // 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 (isDownloadCanceled()) { - 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 < 2 Mbit/s - long dt = t0 + total/524 - System.currentTimeMillis(); - if ( dt > 0 ) - { - try { Thread.sleep( dt ); } catch( InterruptedException ie ) {} - } - } - 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 (isDownloadCanceled()) - { - return "Canceled!"; - } - if ( tmp_file != null ) - { - updateProgress( "Verifying integrity.." ); - String check_result = PhysicalFile.checkFileIntegrity( tmp_file ); - if ( check_result != null ) return check_result; - - if ( !tmp_file.renameTo( fname ) ) - { - return "Could not rename to " + fname.getAbsolutePath(); - } - deleteRawTracks(); // invalidate raw-tracks after data update - } - 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 - } - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - // take CPU lock to prevent CPU from going off if the user - // presses the power button during download - PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); - mWakeLock.acquire(); - } - - @Override - protected void onProgressUpdate(Integer... progress) { - if ( !newDownloadAction.equals( downloadAction ) ) - { - downloadAction = newDownloadAction; - invalidate(); - } - } - - @Override - protected void onPostExecute(String result) { - mWakeLock.release(); - downloadDone( result == null ); - - if (result != null) - Toast.makeText(context,"Download error: "+result, Toast.LENGTH_LONG).show(); - else - Toast.makeText(context,"File downloaded", Toast.LENGTH_SHORT).show(); - } - - } // download task -} +} 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 5c7c13e..18e71f0 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -4,12 +4,14 @@ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; 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; @@ -76,7 +78,9 @@ public class BRouterView extends View private String profileName; private String sourceHint; private boolean waitingForSelection = false; + private boolean waitingForMigration = false; private String rawTrackPath; + private String oldMigrationPath; private boolean needsViaSelection; private boolean needsNogoSelection; @@ -124,8 +128,18 @@ public class BRouterView extends View File brd = new File( baseDir, "brouter" ); if ( brd.isDirectory() ) { - startSetup( baseDir, false ); - return; + if (brd.getAbsolutePath().contains("/Android/data/")) { + String message = "(previous basedir " + baseDir + " has to migrate )" ; + + ( (BRouterActivity) getContext() ).selectBasedir( getStorageDirectories(), guessBaseDir(), message ); + waitingForSelection = true; + waitingForMigration = true; + oldMigrationPath = brd.getAbsolutePath(); + return; + } else { + startSetup( baseDir, false ); + return; + } } } String message = baseDir == null ? "(no basedir configured previously)" : "(previous basedir " + baseDir @@ -202,6 +216,12 @@ public class BRouterView extends View File inputDir = new File (basedir, "/import"); assertDirectoryExists( "input directory", inputDir, null, version ); + // new init is done move old files + if (waitingForMigration) { + moveFolders(oldMigrationPath, basedir + "/brouter"); + waitingForMigration = false; + } + int deviceLevel = android.os.Build.VERSION.SDK_INT; int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; canAccessSdCard = deviceLevel < 23 || targetSdkVersion == 10; @@ -320,6 +340,67 @@ public class BRouterView extends View waitingForSelection = true; } + private void moveFolders(String oldMigrationPath, String basedir) { + File oldDir = new File(oldMigrationPath); + File[] oldFiles = oldDir.listFiles(); + for (File f: oldFiles) { + if (f.isDirectory()) { + int index = f.getAbsolutePath().lastIndexOf("/"); + String tmpdir = basedir + f.getAbsolutePath().substring(index); + moveFolders(f.getAbsolutePath(), tmpdir); + } else { + if ( ! f.getName().startsWith("v1.6")) { + moveFile(oldMigrationPath, f.getName(), basedir); + } + } + + } + } + + private void moveFile(String inputPath, String inputFile, String outputPath) { + + InputStream in = null; + OutputStream out = null; + try { + + //create output directory if it doesn't exist + File dir = new File (outputPath); + if (!dir.exists()) + { + dir.mkdirs(); + } + + + in = new FileInputStream(inputPath + "/" + inputFile); + out = new FileOutputStream(outputPath + "/" + inputFile); + + byte[] buffer = new byte[1024]; + int read; + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + in.close(); + in = null; + + // write the output file + out.flush(); + out.close(); + out = null; + + // delete the original file + new File(inputPath + "/" + inputFile).delete(); + + + } + + catch (FileNotFoundException fnfe1) { + Log.e("tag", fnfe1.getMessage()); + } + catch (Exception e) { + Log.e("tag", e.getMessage()); + } + + } public boolean hasUpToDateLookups() { diff --git a/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java b/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java new file mode 100644 index 0000000..b026a20 --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java @@ -0,0 +1,544 @@ +package btools.routingapp; + +import android.app.NotificationManager; +import android.app.Service; +import android.content.Intent; +import android.net.TrafficStats; +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.os.StatFs; +import android.util.Log; +import android.widget.Toast; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +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 boolean DEBUG = false; + + String segmenturl = "https://brouter.de/brouter/segments4/"; + String lookupurl = "https://brouter.de/brouter/segments4/"; + String profilesurl = "https://brouter.de/brouter/segments4/"; + String checkLookup = "lookups.dat"; + String checkProfiles = ""; + + private NotificationHelper mNotificationHelper; + private List mUrlList; + private String baseDir; + + private volatile String newDownloadAction = ""; + 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; + + // 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"); + serviceState = true; + + HandlerThread thread = new HandlerThread("ServiceStartArguments", 1); + thread.start(); + + // Get the HandlerThread's Looper and use it for our Handler + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper); + + availableSize = -1; + try + { + StatFs stat = new StatFs(baseDir); + availableSize = (long)stat.getAvailableBlocksLong()*stat.getBlockSizeLong(); + } + catch (Exception e) { /* ignore */ } + + } + + + @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"); + List urlparts = extra.getStringArrayList("urlparts"); + mUrlList = urlparts; + baseDir = dir; + + File configFile = new File (dir, "segments4/serverconfig.txt"); + if ( configFile.exists() ) { + try { + BufferedReader br = new BufferedReader( new FileReader( configFile ) ); + for ( ;; ) + { + String line = br.readLine(); + if ( line == null ) break; + if ( line.trim().startsWith( "segment_url=" ) ) { + segmenturl = line.substring(12); + } + else if ( line.trim().startsWith( "lookup_url=" ) ) { + lookupurl = line.substring(11); + } + else if ( line.trim().startsWith( "profiles_url=" ) ) { + profilesurl = line.substring(13); + } + else if ( line.trim().startsWith( "check_lookup=" ) ) { + checkLookup = line.substring(13); + } + else if ( line.trim().startsWith( "check_profiles=" ) ) { + checkProfiles = line.substring(15); + } + } + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + } + + 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() { + + // first check lookup table and prifles + 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 = segmenturl + 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"); + } + count++; + } + + bIsDownloading = false; + updateProgress( "finished " ); + } + + + public void updateProgress( String progress ) + { + if ( !newDownloadAction.equals( 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); + } + + } + + private String download(int counter, int size, String surl) + { + InputStream input = null; + OutputStream output = null; + HttpURLConnection connection = null; + File fname = null; + File tmp_file = null; + try + { + try + { + TrafficStats.setThreadStatsTag(1); + + int slidx = surl.lastIndexOf( "segments4/" ); + String name = surl.substring( slidx+10 ); + String surlBase = surl.substring( 0, slidx+10 ); + 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.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 ( connection == null ) + { + updateProgress( "Connecting.." + surl ); + + delta = false; + URL url = new URL(surl); + connection = (HttpURLConnection) url.openConnection(); + connection.connect(); + } + + updateProgress( "Connecting.." + counter + "/"+size ); + + // 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(); + } + + // 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"; + 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 ( dt > 0 ) + { + try { Thread.sleep( dt ); } catch( InterruptedException ie ) {} + } + } + 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 ( !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 = checkLookup.split(","); + for (String f: sa) { + if (f.length()>0) { + File file = new File(baseDir + "profiles2", f); + checkOrDownloadLookup(f, file); + } + } + + sa = checkProfiles.split(","); + 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; + } + } + } + } + return null; + } + + private String checkOrDownloadLookup(String fileName, File f) { + String url = lookupurl + fileName; + return downloadScript(url, f); + } + + private String checkOrDownloadScript(String fileName, File f) { + String url = profilesurl + 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; + File targetFile = f; + + try + { + try + { + TrafficStats.setThreadStatsTag(1); + + if ( connection == null ) + { + URL url = new URL(surl); + connection = (HttpURLConnection) url.openConnection(); + 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) { + long currentDownloadSize = fileLength; + 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 < 2 Mbit/s + long dt = t0 + total / 524 - 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() { + return BInstallerView.downloadCanceled; + } + +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/NotificationHelper.java b/brouter-routing-app/src/main/java/btools/routingapp/NotificationHelper.java new file mode 100644 index 0000000..ddbf4a1 --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/NotificationHelper.java @@ -0,0 +1,135 @@ +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); + } +} \ No newline at end of file diff --git a/brouter-routing-app/src/main/res/drawable/ic_launcher_background.xml b/brouter-routing-app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..7c8c867 --- /dev/null +++ b/brouter-routing-app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + diff --git a/brouter-routing-app/src/main/res/drawable/ic_launcher_foreground.xml b/brouter-routing-app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2393404 --- /dev/null +++ b/brouter-routing-app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/brouter-routing-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/brouter-routing-app/src/main/res/mipmap-hdpi/ic_launcher.png b/brouter-routing-app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cea3d79 Binary files /dev/null and b/brouter-routing-app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/brouter-routing-app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/brouter-routing-app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..0042e71 Binary files /dev/null and b/brouter-routing-app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/brouter-routing-app/src/main/res/mipmap-mdpi/ic_launcher.png b/brouter-routing-app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..7944fa0 Binary files /dev/null and b/brouter-routing-app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/brouter-routing-app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/brouter-routing-app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..1266e2a Binary files /dev/null and b/brouter-routing-app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/brouter-routing-app/src/main/res/mipmap-xhdpi/ic_launcher.png b/brouter-routing-app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..f71fcae Binary files /dev/null and b/brouter-routing-app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/brouter-routing-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/brouter-routing-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..9648664 Binary files /dev/null and b/brouter-routing-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/brouter-routing-app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/brouter-routing-app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..2140746 Binary files /dev/null and b/brouter-routing-app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/brouter-routing-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/brouter-routing-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..9ba382b Binary files /dev/null and b/brouter-routing-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/brouter-routing-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/brouter-routing-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..5c86dd0 Binary files /dev/null and b/brouter-routing-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/brouter-routing-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/brouter-routing-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..3ac14a4 Binary files /dev/null and b/brouter-routing-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1d288b1..6df5e88 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip