Merge pull request #319 from afischerdev/test-and11

Update for Android 11 part 1
This commit is contained in:
afischerdev 2021-08-03 13:30:02 +02:00 committed by GitHub
commit 14d5a2c4e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 984 additions and 236 deletions

View file

@ -64,6 +64,12 @@ app), use
``` ```
gradlew clean build -x :brouter-routing-app:build gradlew clean build -x :brouter-routing-app:build
```
Then build jars for server and map creator with all dependent classes
```
gradlew farJar
``` ```
@ -85,7 +91,7 @@ binary format (rd5) for improved efficiency of BRouter routing.
#### Download them from brouter.de #### Download them from brouter.de
Segments files from the whole planet are generated weekly at Segments files from the whole planet are generated weekly at
[http://brouter.de/brouter/segments4/](http://brouter.de/brouter/segments4/). [https://brouter.de/brouter/segments4/](http://brouter.de/brouter/segments4/).
You can download one or more segments files, covering the area of the planet You can download one or more segments files, covering the area of the planet
your want to route, into the `misc/segments4` directory. your want to route, into the `misc/segments4` directory.

View file

@ -79,6 +79,7 @@ public final class RoutingContext
public double starttimeoffset; public double starttimeoffset;
public boolean transitonly; public boolean transitonly;
public double waypointCatchingRange;
private void setModel( String className ) private void setModel( String className )
{ {
@ -144,6 +145,8 @@ public final class RoutingContext
bikeMode = 0.f != expctxGlobal.getVariableValue( "validForBikes", 0.f ); bikeMode = 0.f != expctxGlobal.getVariableValue( "validForBikes", 0.f );
footMode = 0.f != expctxGlobal.getVariableValue( "validForFoot", 0.f ); footMode = 0.f != expctxGlobal.getVariableValue( "validForFoot", 0.f );
waypointCatchingRange = expctxGlobal.getVariableValue( "waypointCatchingRange", 250.f );
// turn-restrictions used per default for car profiles // turn-restrictions used per default for car profiles
considerTurnRestrictions = 0.f != expctxGlobal.getVariableValue( "considerTurnRestrictions", carMode ? 1.f : 0.f ); considerTurnRestrictions = 0.f != expctxGlobal.getVariableValue( "considerTurnRestrictions", carMode ? 1.f : 0.f );

View file

@ -452,7 +452,7 @@ public class RoutingEngine extends Thread
private void matchWaypointsToNodes( List<MatchedWaypoint> unmatchedWaypoints ) private void matchWaypointsToNodes( List<MatchedWaypoint> unmatchedWaypoints )
{ {
resetCache( false ); resetCache( false );
nodesCache.matchWaypointsToNodes( unmatchedWaypoints, 250., islandNodePairs ); nodesCache.matchWaypointsToNodes( unmatchedWaypoints, routingContext.waypointCatchingRange, islandNodePairs );
} }
private OsmTrack searchTrack( MatchedWaypoint startWp, MatchedWaypoint endWp, OsmTrack nearbyTrack, OsmTrack refTrack ) private OsmTrack searchTrack( MatchedWaypoint startWp, MatchedWaypoint endWp, OsmTrack nearbyTrack, OsmTrack refTrack )

View file

@ -338,7 +338,7 @@ public final class NodesCache
public void matchWaypointsToNodes( List<MatchedWaypoint> unmatchedWaypoints, double maxDistance, OsmNodePairSet islandNodePairs ) public void matchWaypointsToNodes( List<MatchedWaypoint> unmatchedWaypoints, double maxDistance, OsmNodePairSet islandNodePairs )
{ {
waypointMatcher = new WaypointMatcherImpl( unmatchedWaypoints, 250., islandNodePairs ); waypointMatcher = new WaypointMatcherImpl( unmatchedWaypoints, maxDistance, islandNodePairs );
for( MatchedWaypoint mwp : unmatchedWaypoints ) for( MatchedWaypoint mwp : unmatchedWaypoints )
{ {
preloadPosition( mwp.waypoint ); preloadPosition( mwp.waypoint );

View file

@ -71,7 +71,7 @@ android {
dependencies { dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.appcompat:appcompat:1.3.1'
implementation project(':brouter-mapaccess') implementation project(':brouter-mapaccess')
implementation project(':brouter-core') implementation project(':brouter-core')

View file

@ -8,14 +8,17 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:icon="@drawable/icon" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name" android:label="@string/app_name"
android:allowBackup="true"> android:allowBackup="false">
<activity android:name=".BRouterActivity" <activity android:name=".BRouterActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:screenOrientation="portrait" android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> android:exported="true"
android:screenOrientation="unspecified" android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
@ -24,6 +27,8 @@
<activity android:name=".BInstallerActivity" <activity android:name=".BInstallerActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:screenOrientation="landscape" android:screenOrientation="landscape"
android:launchMode="singleTask"
android:exported="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
</activity> </activity>
<service <service
@ -31,6 +36,11 @@
android:name=".BRouterService" android:name=".BRouterService"
android:enabled="true" android:enabled="true"
android:process=":brouter_service" /> android:process=":brouter_service" />
<service android:name="btools.routingapp.DownloadService"
android:label="Download Service"
android:icon="@drawable/icon"
android:enabled="true"
/>
</application> </application>
</manifest> </manifest>

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><g style="line-height:1.25;-inkscape-font-specification:'Lucida Sans Typewriter, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal"><path style="-inkscape-font-specification:'Lucida Sans Typewriter, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal" d="M52.445 52.51H12.609V39.836q0-3.581.376-6 .566-3.52 3.015-5.632 2.423-2.143 5.518-2.143 2.853 0 5.437 1.898 1.265.949 2.315 2.235 1.05 1.255 2.45 4.867 1.345-5.54 4.225-8.235 2.853-2.694 6.514-2.694 2.503 0 4.737 1.378 2.207 1.347 3.5 3.704 1.237 2.235 1.507 4.867.242 2.633.242 6.582zm-22.26-6.031v-2.112q0-2.572-.323-4.806-.323-2.235-1.427-3.98-.969-1.592-2.476-2.48-1.534-.918-3.23-.918-1.588 0-2.853.796-1.265.796-1.938 2.051-.969 1.714-1.157 3.95-.189 2.203-.189 4.683v2.816zm18.276 0V41.03q0-2.388-.188-4.408-.215-2.051-.996-3.275-.808-1.256-2.234-2.02-1.427-.766-2.988-.766-3.391 0-5.948 3.428-2.584 3.398-2.584 10.286v2.204z" fill="#a46843" aria-label="B" font-weight="400" font-size="58.787" font-family="Lucida Sans Typewriter" letter-spacing="0" word-spacing="0" stroke-width="2.939"/></g><g fill="#520" stroke-width="2"><g style="line-height:1.25;-inkscape-font-specification:'Lucida Sans Typewriter, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal"><path d="M20.337 46.717V7.802h9.934c3.838 0 6.605.342 8.302 1.025 1.698.684 3.026 1.815 3.985 3.392.978 1.56 1.467 3.243 1.467 5.049 0 1.875-.563 3.83-1.688 5.863-1.107 2.034-3.912 4.016-7.177 5.593l12.315 17.904-5.553.089-11.12-16.29-5.014-.354v16.644zm5.451-16.644l4.911.35c-1.762-4.727 3.502-5.083 5.107-6.766 1.605-1.7 2.408-3.69 2.408-5.969 0-1.21-.268-2.26-.803-3.155-.517-.894-1.347-1.586-2.49-2.077-1.144-.508-3.072-.763-5.784-.763h-3.349z" style="-inkscape-font-specification:'Lucida Sans Typewriter, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal" fill="#370d00" aria-label="R" font-weight="400" font-size="55.244" font-family="Lucida Sans Typewriter" letter-spacing="0" word-spacing="0"/></g><path style="mix-blend-mode:normal" d="M25.481 48.856a2.436 2.634 0 01-2.426 2.634 2.436 2.634 0 01-2.446-2.613 2.436 2.634 0 012.407-2.655 2.436 2.634 0 012.465 2.591M47.003 48.803a2.436 2.634 0 01-2.426 2.633 2.436 2.634 0 01-2.446-2.612 2.436 2.634 0 012.406-2.655 2.436 2.634 0 012.466 2.591" fill="none" stroke="#370d00"/></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -6,20 +6,42 @@ import java.util.Set;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.os.Bundle; import android.os.Bundle;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.PowerManager.WakeLock; import android.os.PowerManager.WakeLock;
import android.speech.tts.TextToSpeech.OnInitListener; import android.speech.tts.TextToSpeech.OnInitListener;
import android.util.Log;
public class BInstallerActivity extends Activity implements OnInitListener { 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 static final int DIALOG_CONFIRM_DELETE_ID = 1;
private BInstallerView mBInstallerView; private BInstallerView mBInstallerView;
private PowerManager mPowerManager; private PowerManager mPowerManager;
private WakeLock mWakeLock; 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. */ /** Called when the activity is first created. */
@Override @Override
@ -51,6 +73,12 @@ public class BInstallerActivity extends Activity implements OnInitListener {
*/ */
mWakeLock.acquire(); mWakeLock.acquire();
IntentFilter filter = new IntentFilter();
filter.addAction(DOWNLOAD_ACTION);
myReceiver = new DownloadReceiver();
registerReceiver(myReceiver, filter);
// Start the download manager // Start the download manager
mBInstallerView.startInstaller(); mBInstallerView.startInstaller();
} }
@ -58,6 +86,18 @@ public class BInstallerActivity extends Activity implements OnInitListener {
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
super.onPause();
mWakeLock.release();
}
@Override
public void onDestroy() {
super.onDestroy();
if (myReceiver != null) unregisterReceiver(myReceiver);
System.exit(0); System.exit(0);
} }

View file

@ -7,10 +7,12 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
@ -23,6 +25,7 @@ import android.os.PowerManager;
import android.os.StatFs; import android.os.StatFs;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.widget.Toast; import android.widget.Toast;
@ -63,7 +66,7 @@ public class BInstallerView extends View
private File baseDir; private File baseDir;
private boolean isDownloading = false; private boolean isDownloading = false;
private volatile boolean downloadCanceled = false; public static boolean downloadCanceled = false;
private long currentDownloadSize; private long currentDownloadSize;
private String currentDownloadFile = ""; private String currentDownloadFile = "";
@ -78,6 +81,9 @@ public class BInstallerView extends View
Paint pnt_1 = new Paint(); Paint pnt_1 = new Paint();
Paint pnt_2 = new Paint(); Paint pnt_2 = new Paint();
Paint paint = new Paint(); Paint paint = new Paint();
Activity mActivity;
protected String baseNameForTile( int tileIndex ) protected String baseNameForTile( int tileIndex )
{ {
@ -133,6 +139,7 @@ public class BInstallerView extends View
int tidx_min = -1; int tidx_min = -1;
int min_size = Integer.MAX_VALUE; int min_size = Integer.MAX_VALUE;
ArrayList<Integer> downloadList = new ArrayList<>();
// prepare download list // prepare download list
for( int ix=0; ix<72; ix++ ) for( int ix=0; ix<72; ix++ )
{ {
@ -142,6 +149,7 @@ public class BInstallerView extends View
if ( ( tileStatus[tidx] & MASK_SELECTED_RD5 ) != 0 ) if ( ( tileStatus[tidx] & MASK_SELECTED_RD5 ) != 0 )
{ {
int tilesize = BInstallerSizes.getRd5Size(tidx); int tilesize = BInstallerSizes.getRd5Size(tidx);
downloadList.add(tidx);
if ( tilesize > 0 && tilesize < min_size ) if ( tilesize > 0 && tilesize < min_size )
{ {
tidx_min = tidx; tidx_min = tidx;
@ -150,29 +158,39 @@ public class BInstallerView extends View
} }
} }
} }
if ( tidx_min != -1 )
{ if (downloadList.size()>0) {
tileStatus[tidx_min] ^= tileStatus[tidx_min] & MASK_SELECTED_RD5; isDownloading = true;
startDownload( tidx_min ); downloadAll(downloadList);
for (Integer i : downloadList) {
tileStatus[i.intValue()] ^= tileStatus[i.intValue()] & MASK_SELECTED_RD5;
}
downloadList.clear();
} }
} }
private void startDownload( int tileIndex ) private void downloadAll(ArrayList<Integer> downloadList) {
{ ArrayList<String> urlparts = new ArrayList<>();
for (Integer i: downloadList) {
String namebase = baseNameForTile( tileIndex ); urlparts.add(baseNameForTile( i.intValue() ));
String baseurl = "http://brouter.de/brouter/segments4/"; }
currentDownloadFile = namebase + ".rd5";
currentDownloadOperation = "Checking"; currentDownloadOperation = "Start download ...";
String url = baseurl + currentDownloadFile; downloadAction = "";
isDownloading = true; downloadCanceled = false;
downloadCanceled = false; isDownloading = true;
currentDownloadSize = 0;
downloadAction = "Connecting... "; //final DownloadBackground downloadTask = new DownloadBackground(getContext(), urlparts, baseDir);
final DownloadTask downloadTask = new DownloadTask(getContext()); //downloadTask.execute( );
downloadTask.execute( url ); 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 ) public void downloadDone( boolean success )
{ {
isDownloading = false; isDownloading = false;
@ -184,6 +202,16 @@ public class BInstallerView extends View
invalidate(); invalidate();
} }
public void setState(String txt, boolean b) {
currentDownloadOperation = txt;
downloadAction = "";
isDownloading = b;
if (!b) {
scanExistingFiles();
}
invalidate();
}
private int tileIndex( float x, float y ) private int tileIndex( float x, float y )
{ {
int ix = (int)(72.f * x / bmp.getWidth()); int ix = (int)(72.f * x / bmp.getWidth());
@ -300,6 +328,7 @@ public class BInstallerView extends View
public BInstallerView(Context context) { public BInstallerView(Context context) {
super(context); super(context);
mActivity = (Activity) context;
DisplayMetrics metrics = new DisplayMetrics(); DisplayMetrics metrics = new DisplayMetrics();
((Activity)getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics); ((Activity)getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics);
@ -314,10 +343,15 @@ public class BInstallerView extends View
imgw = (int)(imgwOrig / scaleOrig); imgw = (int)(imgwOrig / scaleOrig);
imgh = (int)(imghOrig / scaleOrig); imgh = (int)(imghOrig / scaleOrig);
totalSize = 0;
rd5Tiles = 0;
delTiles = 0;
} }
@Override @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w,h,oldw,oldh);
} }
private void toast( String msg ) private void toast( String msg )
@ -390,10 +424,11 @@ public class BInstallerView extends View
{ {
String sizeHint = currentDownloadSize > 0 ? " (" + ((currentDownloadSize + mb-1)/mb) + " MB)" : ""; String sizeHint = currentDownloadSize > 0 ? " (" + ((currentDownloadSize + mb-1)/mb) + " MB)" : "";
paint.setTextSize(30); 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); canvas.drawText( downloadAction, 30, (imgh/3)*2, paint);
} }
if ( !tilesVisible ) if ( !tilesVisible && !isDownloading)
{ {
paint.setTextSize(35); paint.setTextSize(35);
canvas.drawText( "Touch region to zoom in!", 30, (imgh/3)*2, paint); canvas.drawText( "Touch region to zoom in!", 30, (imgh/3)*2, paint);
@ -655,209 +690,7 @@ float tx, ty;
return true; 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<String, Integer, String> 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
}

View file

@ -4,12 +4,14 @@ import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.FileReader; import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -76,7 +78,9 @@ public class BRouterView extends View
private String profileName; private String profileName;
private String sourceHint; private String sourceHint;
private boolean waitingForSelection = false; private boolean waitingForSelection = false;
private boolean waitingForMigration = false;
private String rawTrackPath; private String rawTrackPath;
private String oldMigrationPath;
private boolean needsViaSelection; private boolean needsViaSelection;
private boolean needsNogoSelection; private boolean needsNogoSelection;
@ -124,8 +128,18 @@ public class BRouterView extends View
File brd = new File( baseDir, "brouter" ); File brd = new File( baseDir, "brouter" );
if ( brd.isDirectory() ) if ( brd.isDirectory() )
{ {
startSetup( baseDir, false ); if (brd.getAbsolutePath().contains("/Android/data/")) {
return; 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 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"); File inputDir = new File (basedir, "/import");
assertDirectoryExists( "input directory", inputDir, null, version ); 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 deviceLevel = android.os.Build.VERSION.SDK_INT;
int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
canAccessSdCard = deviceLevel < 23 || targetSdkVersion == 10; canAccessSdCard = deviceLevel < 23 || targetSdkVersion == 10;
@ -320,6 +340,67 @@ public class BRouterView extends View
waitingForSelection = true; 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() public boolean hasUpToDateLookups()
{ {

View file

@ -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<String> 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<String> 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;
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.95"
android:scaleY="0.95"
android:translateX="2.7"
android:translateY="2.7">
<group android:scaleX="0.95"
android:scaleY="0.95"
android:translateX="2.7"
android:translateY="2.7">
<group android:scaleX="0.95"
android:scaleY="0.95"
android:translateX="2.7"
android:translateY="2.7">
<group android:scaleX="0.95"
android:scaleY="0.95"
android:translateX="2.7"
android:translateY="2.7">
<group android:scaleX="0.95"
android:scaleY="0.95"
android:translateX="2.7"
android:translateY="2.7">
<group android:scaleX="1.06"
android:scaleY="1.06">
<group android:translateX="-3.0566037"
android:translateY="-3.0566037">
<path android:fillColor="#d5d2c1"
android:pathData="M0,0h108v108h-108z"/>
</group>
</group>
</group>
</group>
</group>
</group>
</group>
</vector>

View file

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="1.096875"
android:scaleY="1.096875"
android:translateX="18.9"
android:translateY="18.9">
<path
android:pathData="M52.445,52.51L12.609,52.51L12.609,39.836q0,-3.581 0.376,-6 0.566,-3.52 3.015,-5.632 2.423,-2.143 5.518,-2.143 2.853,0 5.437,1.898 1.265,0.949 2.315,2.235 1.05,1.255 2.45,4.867 1.345,-5.54 4.225,-8.235 2.853,-2.694 6.514,-2.694 2.503,0 4.737,1.378 2.207,1.347 3.5,3.704 1.237,2.235 1.507,4.867 0.242,2.633 0.242,6.582zM30.185,46.479v-2.112q0,-2.572 -0.323,-4.806 -0.323,-2.235 -1.427,-3.98 -0.969,-1.592 -2.476,-2.48 -1.534,-0.918 -3.23,-0.918 -1.588,0 -2.853,0.796 -1.265,0.796 -1.938,2.051 -0.969,1.714 -1.157,3.95 -0.189,2.203 -0.189,4.683v2.816zM48.461,46.479L48.461,41.03q0,-2.388 -0.188,-4.408 -0.215,-2.051 -0.996,-3.275 -0.808,-1.256 -2.234,-2.02 -1.427,-0.766 -2.988,-0.766 -3.391,0 -5.948,3.428 -2.584,3.398 -2.584,10.286v2.204z"
android:strokeWidth="2.939"
android:fillColor="#a46843"/>
<path
android:pathData="M20.337,46.717L20.337,7.802h9.934c3.838,0 6.605,0.342 8.302,1.025 1.698,0.684 3.026,1.815 3.985,3.392 0.978,1.56 1.467,3.243 1.467,5.049 0,1.875 -0.563,3.83 -1.688,5.863 -1.107,2.034 -3.912,4.016 -7.177,5.593l12.315,17.904 -5.553,0.089 -11.12,-16.29 -5.014,-0.354v16.644zM25.788,30.073l4.911,0.35c-1.762,-4.727 3.502,-5.083 5.107,-6.766 1.605,-1.7 2.408,-3.69 2.408,-5.969 0,-1.21 -0.268,-2.26 -0.803,-3.155 -0.517,-0.894 -1.347,-1.586 -2.49,-2.077 -1.144,-0.508 -3.072,-0.763 -5.784,-0.763h-3.349z"
android:strokeWidth="2"
android:fillColor="#370d00"/>
<path
android:pathData="M25.481,48.856a2.436,2.634 0,0 1,-2.426 2.634,2.436 2.634,0 0,1 -2.446,-2.613 2.436,2.634 0,0 1,2.407 -2.655,2.436 2.634,0 0,1 2.465,2.591M47.003,48.803a2.436,2.634 0,0 1,-2.426 2.633,2.436 2.634,0 0,1 -2.446,-2.612 2.436,2.634 0,0 1,2.406 -2.655,2.436 2.634,0 0,1 2.466,2.591"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#370d00"/>
</group>
</vector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -8,7 +8,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.2' classpath 'com.android.tools.build:gradle:7.0.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
@ -17,6 +17,11 @@ buildscript {
} }
allprojects { allprojects {
// NOTE:
// there are three places to change the version number
// this file
// app: build.gradle (versionCode only)
// OsmTrack (version and versionDate)
project.version "1.6.1" project.version "1.6.1"
repositories { repositories {

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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