diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 88ce852..0dab983 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -12,6 +12,7 @@ jobs: build: runs-on: ubuntu-latest + environment: BRouter permissions: contents: read packages: write @@ -27,10 +28,19 @@ jobs: server-id: github # Value of the distributionManagement/repository/id field of the pom.xml settings-path: ${{ github.workspace }} # location for the settings.xml file + - name: Setup keystore + env: + BROUTER_KEYSTORE_BASE64: ${{ secrets.BROUTER_KEYSTORE_BASE64 }} + run: | + echo $BROUTER_KEYSTORE_BASE64 | base64 -di > ${{ github.workspace }}/brouter.jks - name: Build with Gradle + env: + ORG_GRADLE_PROJECT_RELEASE_STORE_FILE: ${{ secrets.BROUTER_KEYSTORE_FILE }} + ORG_GRADLE_PROJECT_RELEASE_KEY_ALIAS: ${{ secrets.BROUTER_KEY_ALIAS }} + ORG_GRADLE_PROJECT_RELEASE_KEY_PASSWORD: ${{ secrets.BROUTER_KEY_PASSWORD }} + ORG_GRADLE_PROJECT_RELEASE_STORE_PASSWORD: ${{ secrets.BROUTER_STORE_PASSWORD }} run: gradle build - # The USERNAME and TOKEN need to correspond to the credentials environment variables used in # the publishing section of your build.gradle - name: Publish to GitHub Packages diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index f27ddf3..354677c 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -13,7 +13,7 @@ jobs: build: runs-on: ubuntu-latest - + environment: BRouter steps: - uses: actions/checkout@v2 - name: Set up JDK 11 @@ -24,7 +24,17 @@ jobs: cache: gradle - name: Create local.properties run: touch local.properties + - name: Setup keystore + env: + BROUTER_KEYSTORE_BASE64: ${{ secrets.BROUTER_KEYSTORE_BASE64 }} + run: | + echo $BROUTER_KEYSTORE_BASE64 | base64 -di > ${{ github.workspace }}/brouter.jks - name: Build with Gradle + env: + ORG_GRADLE_PROJECT_RELEASE_STORE_FILE: ${{ secrets.BROUTER_KEYSTORE_FILE }} + ORG_GRADLE_PROJECT_RELEASE_KEY_ALIAS: ${{ secrets.BROUTER_KEY_ALIAS }} + ORG_GRADLE_PROJECT_RELEASE_KEY_PASSWORD: ${{ secrets.BROUTER_KEY_PASSWORD }} + ORG_GRADLE_PROJECT_RELEASE_STORE_PASSWORD: ${{ secrets.BROUTER_STORE_PASSWORD }} run: ./gradlew build - name: Upload ZIP uses: actions/upload-artifact@v2 diff --git a/README.md b/README.md index bef1cfa..b1fcbc2 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ Segments files from the whole planet are generated weekly at [https://brouter.de/brouter/segments4/](http://brouter.de/brouter/segments4/). You can download one or more segments files, covering the area of the planet -your want to route, into the `misc/segments4` directory. +you want to route, into the `misc/segments4` directory. #### Generate your own segments files diff --git a/brouter-codec/src/main/java/btools/codec/MicroCache2.java b/brouter-codec/src/main/java/btools/codec/MicroCache2.java index 8cd8f30..16223a1 100644 --- a/brouter-codec/src/main/java/btools/codec/MicroCache2.java +++ b/brouter-codec/src/main/java/btools/codec/MicroCache2.java @@ -15,7 +15,7 @@ public final class MicroCache2 extends MicroCache private int latBase; private int cellsize; - public MicroCache2( int size, byte[] databuffer, int lonIdx, int latIdx, int divisor ) throws Exception + public MicroCache2( int size, byte[] databuffer, int lonIdx, int latIdx, int divisor ) { super( databuffer ); // sets ab=databuffer, aboffset=0 @@ -34,7 +34,7 @@ public final class MicroCache2 extends MicroCache return b; } - public MicroCache2( StatCoderContext bc, DataBuffers dataBuffers, int lonIdx, int latIdx, int divisor, TagValueValidator wayValidator, WaypointMatcher waypointMatcher ) throws Exception + public MicroCache2( StatCoderContext bc, DataBuffers dataBuffers, int lonIdx, int latIdx, int divisor, TagValueValidator wayValidator, WaypointMatcher waypointMatcher ) { super( null ); cellsize = 1000000 / divisor; diff --git a/brouter-core/src/main/java/btools/router/OsmPath.java b/brouter-core/src/main/java/btools/router/OsmPath.java index 4a42b0c..edbf738 100644 --- a/brouter-core/src/main/java/btools/router/OsmPath.java +++ b/brouter-core/src/main/java/btools/router/OsmPath.java @@ -255,8 +255,8 @@ abstract class OsmPath implements OsmLinkHolder if ( nsection == 0 && rc.considerTurnRestrictions && !detailMode&& !isStartpoint ) { if ( rc.inverseDirection - ? TurnRestriction.isTurnForbidden( sourceNode.firstRestriction, lon2, lat2, lon0, lat0, rc.bikeMode, rc.carMode ) - : TurnRestriction.isTurnForbidden( sourceNode.firstRestriction, lon0, lat0, lon2, lat2, rc.bikeMode, rc.carMode ) ) + ? TurnRestriction.isTurnForbidden( sourceNode.firstRestriction, lon2, lat2, lon0, lat0, rc.bikeMode || rc.footMode, rc.carMode ) + : TurnRestriction.isTurnForbidden( sourceNode.firstRestriction, lon0, lat0, lon2, lat2, rc.bikeMode || rc.footMode, rc.carMode ) ) { cost = -1; return; diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index fde92a6..b094551 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -33,8 +33,8 @@ import btools.util.StringUtils; public final class OsmTrack { - final public static String version = "1.6.2"; - final public static String versionDate = "10102021"; + final public static String version = "1.6.3"; + final public static String versionDate = "21122021"; // csv-header-line private static final String MESSAGES_HEADER = "Longitude\tLatitude\tElevation\tDistance\tCostPerKm\tElevCost\tTurnCost\tNodeCost\tInitialCost\tWayTags\tNodeTags\tTime\tEnergy"; @@ -591,8 +591,10 @@ public final class OsmTrack if ( turnInstructionMode == 2 ) { - sb.append( " " ).append( "" + voiceHints.getLocusRouteType() ).append( "\n" ); - sb.append( " 1\n" ); + sb.append( " \n" ); + sb.append( " " ).append( "" + voiceHints.getLocusRouteType() ).append( "\n" ); + sb.append( " 1\n" ); + sb.append( " \n" ); } sb.append( " \n" ); diff --git a/brouter-core/src/main/java/btools/router/RoutingContext.java b/brouter-core/src/main/java/btools/router/RoutingContext.java index 2076484..1da65d4 100644 --- a/brouter-core/src/main/java/btools/router/RoutingContext.java +++ b/brouter-core/src/main/java/btools/router/RoutingContext.java @@ -147,8 +147,8 @@ public final class RoutingContext waypointCatchingRange = expctxGlobal.getVariableValue( "waypointCatchingRange", 250.f ); - // turn-restrictions used per default for car profiles - considerTurnRestrictions = 0.f != expctxGlobal.getVariableValue( "considerTurnRestrictions", 1.f ); + // turn-restrictions not used per default for foot profiles + considerTurnRestrictions = 0.f != expctxGlobal.getVariableValue( "considerTurnRestrictions", footMode ? 0.f : 1.f ); // process tags not used in the profile (to have them in the data-tab) processUnusedTags = 0.f != expctxGlobal.getVariableValue( "processUnusedTags", 0.f ); diff --git a/brouter-core/src/main/java/btools/router/SuspectInfo.java b/brouter-core/src/main/java/btools/router/SuspectInfo.java new file mode 100644 index 0000000..db86592 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/SuspectInfo.java @@ -0,0 +1,66 @@ +package btools.router; + +import java.util.Map; + +public class SuspectInfo +{ + public static final int TRIGGER_DEAD_END = 1; + public static final int TRIGGER_DEAD_START = 2; + public static final int TRIGGER_NODE_BLOCK = 4; + public static final int TRIGGER_BAD_ACCESS = 8; + public static final int TRIGGER_UNK_ACCESS = 16; + public static final int TRIGGER_SHARP_EXIT = 32; + public static final int TRIGGER_SHARP_ENTRY = 64; + public static final int TRIGGER_SHARP_LINK = 128; + public static final int TRIGGER_BAD_TR = 256; + + public int prio; + public int triggers; + + public static void addSuspect( Map map, long id, int prio, int trigger ) + { + Long iD = Long.valueOf( id ); + SuspectInfo info = map.get( iD ); + if ( info == null ) + { + info = new SuspectInfo(); + map.put( iD, info ); + } + info.prio = Math.max( info.prio, prio ); + info.triggers |= trigger; + } + + public static SuspectInfo addTrigger( SuspectInfo old, int prio, int trigger ) + { + if ( old == null ) + { + old = new SuspectInfo(); + } + old.prio = Math.max( old.prio, prio ); + old.triggers |= trigger; + return old; + } + + public static String getTriggerText( int triggers ) + { + StringBuilder sb = new StringBuilder(); + addText( sb, "dead-end" , triggers, TRIGGER_DEAD_END ); + addText( sb, "dead-start" , triggers, TRIGGER_DEAD_START ); + addText( sb, "node-block" , triggers, TRIGGER_NODE_BLOCK ); + addText( sb, "bad-access" , triggers, TRIGGER_BAD_ACCESS ); + addText( sb, "unkown-access", triggers, TRIGGER_UNK_ACCESS ); + addText( sb, "sharp-exit" , triggers, TRIGGER_SHARP_EXIT ); + addText( sb, "sharp-entry" , triggers, TRIGGER_SHARP_ENTRY ); + addText( sb, "sharp-link" , triggers, TRIGGER_SHARP_LINK ); + addText( sb, "bad-tr" , triggers, TRIGGER_BAD_TR ); + return sb.toString(); + } + + private static void addText( StringBuilder sb, String text, int mask, int bit ) + { + if ( ( bit & mask ) == 0 ) return; + if ( sb.length() > 0 ) sb.append( "," ); + sb.append( text ); + } + +} diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index 2c7679e..8620e12 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -622,8 +622,8 @@ public abstract class BExpressionContext implements IByteArrayUnifier if ( lookupData2 != null ) { // do not create unknown value for external data array, - // record as 'other' instead - lookupData2[inum] = 0; + // record as 'unknown' instead + lookupData2[inum] = 1; // 1 == unknown if (bFoundAsterix) { // found value for lookup * //System.out.println( "add unknown " + name + " " + value ); diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/OsmCutter.java b/brouter-map-creator/src/main/java/btools/mapcreator/OsmCutter.java index 749950c..ab5b050 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/OsmCutter.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/OsmCutter.java @@ -260,30 +260,11 @@ public class OsmCutter extends MapCreatorBase @Override public void nextRestriction( RelationData r, long fromWid, long toWid, long viaNid ) throws Exception { - if ( fromWid == 0 || toWid == 0 || viaNid == 0 ) - { - return; - } String type = r.getTag( "type" ); if ( type == null || !"restriction".equals( type ) ) { return; } - String restriction = r.getTag( "restriction" ); - if ( restriction == null ) - { - return; - } - boolean isPositive = true; - if ( restriction.startsWith( "no_" ) ) - { - isPositive = false; - } - else if ( !restriction.startsWith( "only_" ) ) - { - return; - } - short exceptions = 0; String except = r.getTag( "except" ); if ( except != null ) @@ -296,23 +277,31 @@ public class OsmCutter extends MapCreatorBase exceptions |= toBit( "hgv" , 4, except ); } - // System.out.println( "restriction id = " + r.rid + " isPositive=" + isPositive + " fromWid = " + fromWid + " toWid = " + toWid+ " viaNid = " + viaNid ); - RestrictionData res = new RestrictionData(); - res.isPositive = isPositive; - res.exceptions = exceptions; - res.fromWid = fromWid; - res.toWid = toWid; - res.viaNid = viaNid; - - if ( restrictionsDos != null ) + for( String restrictionKey : r.getTagsOrNull().keySet() ) { - res.writeTo( restrictionsDos ); - } - if ( restrictionCutter != null ) - { - restrictionCutter.nextRestriction( res ); - } + if ( !( restrictionKey.equals( "restriction" ) || restrictionKey.startsWith( "restriction:" ) ) ) + { + continue; + } + String restriction = r.getTag( restrictionKey ); + + RestrictionData res = new RestrictionData(); + res.restrictionKey = restrictionKey; + res.restriction = restriction; + res.exceptions = exceptions; + res.fromWid = fromWid; + res.toWid = toWid; + res.viaNid = viaNid; + if ( restrictionsDos != null ) + { + res.writeTo( restrictionsDos ); + } + if ( restrictionCutter != null ) + { + restrictionCutter.nextRestriction( res ); + } + } } private static short toBit( String tag, int bitpos, String s ) diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java b/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java index 936072f..92b1b54 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java @@ -175,11 +175,11 @@ public class OsmNodeP extends OsmLinkP RestrictionData r = getFirstRestriction(); while( r != null ) { - if ( r.fromLon != 0 && r.toLon != 0 ) + if ( r.isValid() && r.fromLon != 0 && r.toLon != 0 ) { mc.writeBoolean( true ); // restriction follows mc.writeShort( r.exceptions ); - mc.writeBoolean( r.isPositive ); + mc.writeBoolean( r.isPositive() ); mc.writeInt( r.fromLon ); mc.writeInt( r.fromLat ); mc.writeInt( r.toLon ); diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/RestrictionData.java b/brouter-map-creator/src/main/java/btools/mapcreator/RestrictionData.java index 72ac0d4..50f3130 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/RestrictionData.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/RestrictionData.java @@ -1,9 +1,14 @@ package btools.mapcreator; +import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.TreeSet; -import btools.util.LongList; +import btools.util.CheapAngleMeter; /** * Container for a turn restriction @@ -12,26 +17,125 @@ import btools.util.LongList; */ public class RestrictionData extends MapCreatorBase { - public boolean isPositive; + public String restrictionKey; + public String restriction; public short exceptions; public long fromWid; public long toWid; public long viaNid; public RestrictionData next; + public int viaLon; + public int viaLat; + public int fromLon; public int fromLat; public int toLon; public int toLat; + + public boolean badWayMatch; + + private static HashMap names = new HashMap<>(); + private static TreeSet badTRs = new TreeSet<>(); public RestrictionData() { } + + public boolean isPositive() + { + return restriction.startsWith( "only_" ); + } + + public boolean isValid() + { + boolean valid = fromLon != 0 && toLon != 0 && ( restriction.startsWith( "only_" ) || restriction.startsWith( "no_" ) ); + if ( (!valid) || badWayMatch || !(checkGeometry()) ) + { + synchronized( badTRs ) + { + badTRs.add( ( (long) viaLon ) << 32 | viaLat ); + } + } + return valid && "restriction".equals( restrictionKey ); + } + + private boolean checkGeometry() + { + double a = (new CheapAngleMeter()).calcAngle( fromLon, fromLat, viaLon, viaLat, toLon, toLat ); + String t; + if ( restriction.startsWith( "only_" ) ) + { + t = restriction.substring( "only_".length() ); + } + else if ( restriction.startsWith( "no_" ) ) + { + t = restriction.substring( "no_".length() ); + } + else throw new RuntimeException( "ups" ); + + if ( restrictionKey.endsWith( ":conditional" ) ) + { + int idx = t.indexOf( '@' ); + if ( idx >= 0 ) + { + t = t.substring(0, idx ).trim(); + } + } + + if ( "left_turn".equals( t ) ) + { + return a < -5. && a > -175.; + } + if ( "right_turn".equals( t ) ) + { + return a > 5. && a < 175.; + } + if ( "straight_on".equals( t ) ) + { + return a > -85. && a < 85.; + } + if ( "u_turn".equals( t ) ) + { + return a < - 95. || a > 95.; + } + return "entry".equals( t ) || "exit".equals( t ); + } + + private static String unifyName( String name ) + { + synchronized( names ) + { + String n = names.get(name); + if ( n == null ) + { + names.put( name, name ); + n = name; + } + return n; + } + } + + public static void dumpBadTRs() + { + try( BufferedWriter bw = new BufferedWriter( new FileWriter( "badtrs.txt" ) ) ) + { + for( Long id : badTRs ) + { + bw.write( "" + id + " 26\n" ); + } + } + catch( IOException ioe ) + { + throw new RuntimeException( ioe ); + } + } public RestrictionData( DataInputStream di ) throws Exception { - isPositive = di.readBoolean(); + restrictionKey = unifyName( di.readUTF() ); + restriction = unifyName( di.readUTF() ); exceptions = di.readShort(); fromWid = readId( di ); toWid = readId( di ); @@ -40,7 +144,8 @@ public class RestrictionData extends MapCreatorBase public void writeTo( DataOutputStream dos ) throws Exception { - dos.writeBoolean( isPositive ); + dos.writeUTF( restrictionKey ); + dos.writeUTF( restriction ); dos.writeShort( exceptions ); writeId( dos, fromWid ); writeId( dos, toWid ); diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java b/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java index 99e127c..ffb491d 100644 --- a/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java +++ b/brouter-map-creator/src/main/java/btools/mapcreator/WayLinker.java @@ -138,6 +138,9 @@ public class WayLinker extends MapCreatorBase implements Runnable new WayLinker().process( new File( args[0] ), new File( args[1] ), new File( args[2] ), new File( args[3] ), new File( args[4] ), new File( args[5] ), new File( args[6] ), args[7] ); + + System.out.println( "dumping bad TRs" ); + RestrictionData.dumpBadTRs(); } public void process( File nodeTilesIn, File wayTilesIn, File borderFileIn, File restrictionsFileIn, File lookupFile, File profileFile, File dataTilesOut, @@ -287,6 +290,8 @@ public class WayLinker extends MapCreatorBase implements Runnable nodesMap.put( res.viaNid, n ); } OsmNodePT nt = (OsmNodePT) n; + res.viaLon = nt.ilon; + res.viaLat = nt.ilat; res.next = nt.firstRestriction; nt.firstRestriction = res; ntr++; @@ -351,36 +356,49 @@ public class WayLinker extends MapCreatorBase implements Runnable // the leg according to the mapped direction private void checkRestriction( OsmNodeP n1, OsmNodeP n2, WayData w ) { - checkRestriction( n1, n2, w.wid, true ); - checkRestriction( n2, n1, w.wid, false ); + checkRestriction( n1, n2, w, true ); + checkRestriction( n2, n1, w, false ); } - private void checkRestriction( OsmNodeP n1, OsmNodeP n2, long wid, boolean checkFrom ) + private void checkRestriction( OsmNodeP n1, OsmNodeP n2, WayData w, boolean checkFrom ) { RestrictionData r = n2.getFirstRestriction(); while ( r != null ) { - if ( r.fromWid == wid ) + if ( r.fromWid == w.wid ) { if ( r.fromLon == 0 || checkFrom ) { r.fromLon = n1.ilon; r.fromLat = n1.ilat; n1.bits |= OsmNodeP.DP_SURVIVOR_BIT; + if ( !isEndNode( n2, w ) ) + { + r.badWayMatch = true; + } } } - if ( r.toWid == wid ) + if ( r.toWid == w.wid ) { if ( r.toLon == 0 || !checkFrom ) { r.toLon = n1.ilon; r.toLat = n1.ilat; n1.bits |= OsmNodeP.DP_SURVIVOR_BIT; + if ( !isEndNode( n2, w ) ) + { + r.badWayMatch = true; + } } } r = r.next; } } + + private boolean isEndNode( OsmNodeP n, WayData w ) + { + return n == nodesMap.get( w.nodes.get( 0 ) ) || n == nodesMap.get( w.nodes.get( w.nodes.size() - 1 ) ); + } @Override public void nextWay( WayData way ) throws Exception diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/DirectWeaver.java b/brouter-mapaccess/src/main/java/btools/mapaccess/DirectWeaver.java index ed25fd8..dc7191e 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/DirectWeaver.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/DirectWeaver.java @@ -20,7 +20,7 @@ public final class DirectWeaver extends ByteDataWriter private int size = 0; - public DirectWeaver( StatCoderContext bc, DataBuffers dataBuffers, int lonIdx, int latIdx, int divisor, TagValueValidator wayValidator, WaypointMatcher waypointMatcher, OsmNodesMap hollowNodes ) throws Exception + public DirectWeaver( StatCoderContext bc, DataBuffers dataBuffers, int lonIdx, int latIdx, int divisor, TagValueValidator wayValidator, WaypointMatcher waypointMatcher, OsmNodesMap hollowNodes ) { super( null ); int cellsize = 1000000 / divisor; diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java index bf03490..f2c481b 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java @@ -35,7 +35,7 @@ final class OsmFile private int ncaches; private int indexsize; - public OsmFile( PhysicalFile rafile, int lonDegree, int latDegree, DataBuffers dataBuffers ) throws Exception + public OsmFile( PhysicalFile rafile, int lonDegree, int latDegree, DataBuffers dataBuffers ) throws IOException { this.lonDegree = lonDegree; this.latDegree = latDegree; @@ -111,7 +111,7 @@ final class OsmFile return idx == -1 ? indexsize : posIdx[idx]; } - public int getDataInputForSubIdx( int subIdx, byte[] iobuffer ) throws Exception + public int getDataInputForSubIdx( int subIdx, byte[] iobuffer ) throws IOException { int startPos = getPosIdx( subIdx - 1 ); int endPos = getPosIdx( subIdx ); @@ -128,7 +128,7 @@ final class OsmFile } public MicroCache createMicroCache( int lonIdx, int latIdx, DataBuffers dataBuffers, TagValueValidator wayValidator, - WaypointMatcher waypointMatcher, boolean reallyDecode, OsmNodesMap hollowNodes ) throws Exception + WaypointMatcher waypointMatcher, boolean reallyDecode, OsmNodesMap hollowNodes ) throws IOException { int subIdx = ( latIdx - divisor * latDegree ) * divisor + ( lonIdx - divisor * lonDegree ); diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java b/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java index 1c378e0..1798fd3 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/PhysicalFile.java @@ -31,13 +31,12 @@ final public class PhysicalFile { MicroCache.debug = true; - String message = checkFileIntegrity( new File( args[0] ) ); - - if ( message != null ) - { - System.out.println( "************************************" ); - System.out.println( message ); - System.out.println( "************************************" ); + try { + checkFileIntegrity( new File( args[0] ) ); + } catch (IOException e) { + System.err.println( "************************************" ); + e.printStackTrace(); + System.err.println( "************************************" ); } } @@ -46,7 +45,7 @@ final public class PhysicalFile * * @return the error message if file corrupt, else null */ - public static String checkFileIntegrity( File f ) + public static String checkFileIntegrity( File f ) throws IOException { PhysicalFile pf = null; try @@ -66,14 +65,6 @@ final public class PhysicalFile } } } - catch (IllegalArgumentException iae) - { - return iae.getMessage(); - } - catch (Exception e) - { - return e.toString(); - } finally { if ( pf != null ) @@ -88,7 +79,7 @@ final public class PhysicalFile return null; } - public PhysicalFile( File f, DataBuffers dataBuffers, int lookupVersion, int lookupMinorVersion ) throws Exception + public PhysicalFile( File f, DataBuffers dataBuffers, int lookupVersion, int lookupMinorVersion ) throws IOException { fileName = f.getName(); byte[] iobuffer = dataBuffers.iobuffer; @@ -102,7 +93,7 @@ final public class PhysicalFile short readVersion = (short)(lv >> 48); if ( i == 0 && lookupVersion != -1 && readVersion != lookupVersion ) { - throw new IllegalArgumentException( "lookup version mismatch (old rd5?) lookups.dat=" + throw new IOException( "lookup version mismatch (old rd5?) lookups.dat=" + lookupVersion + " " + f. getAbsolutePath() + "=" + readVersion ); } fileIndex[i] = lv & 0xffffffffffffL; diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffManager.java b/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffManager.java index 86ff392..3b33904 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffManager.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffManager.java @@ -8,8 +8,10 @@ package btools.mapaccess; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; +import java.io.IOException; import java.security.DigestInputStream; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; final public class Rd5DiffManager { @@ -93,30 +95,31 @@ final public class Rd5DiffManager } } - public static String getMD5( File f ) throws Exception + public static String getMD5( File f ) throws IOException { - MessageDigest md = MessageDigest.getInstance("MD5"); - BufferedInputStream bis = new BufferedInputStream( new FileInputStream( f ) ); - DigestInputStream dis = new DigestInputStream(bis, md); - byte[] buf = new byte[8192]; - for(;;) - { - int len = dis.read( buf ); - if ( len <= 0 ) - { - break; + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f)); + DigestInputStream dis = new DigestInputStream(bis, md); + byte[] buf = new byte[8192]; + for (; ; ) { + int len = dis.read(buf); + if (len <= 0) { + break; + } } - } - dis.close(); - byte[] bytes = md.digest(); + dis.close(); + byte[] bytes = md.digest(); - StringBuilder sb = new StringBuilder(); - for (int j = 0; j < bytes.length; j++) - { - int v = bytes[j] & 0xff; - sb.append( hexChar( v >>> 4 ) ).append( hexChar( v & 0xf ) ); + StringBuilder sb = new StringBuilder(); + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xff; + sb.append(hexChar(v >>> 4)).append(hexChar(v & 0xf)); + } + return sb.toString(); + } catch (NoSuchAlgorithmException e) { + throw new IOException("MD5 algorithm not available", e); } - return sb.toString(); } private static char hexChar( int v ) diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java b/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java index ac2f1d9..c5ffbf1 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java @@ -12,6 +12,7 @@ import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.util.Arrays; import btools.codec.DataBuffers; @@ -49,9 +50,8 @@ final public class Rd5DiffTool implements ProgressListener } @Override - public void updateProgress( String progress ) - { - System.out.println( progress ); + public void updateProgress(String task, int progress) { + System.out.println(task + ": " + progress + "%"); } @Override @@ -60,7 +60,7 @@ final public class Rd5DiffTool implements ProgressListener return false; } - private static long[] readFileIndex( DataInputStream dis, DataOutputStream dos ) throws Exception + private static long[] readFileIndex( DataInputStream dis, DataOutputStream dos ) throws IOException { long[] fileIndex = new long[25]; for( int i=0; i<25; i++ ) @@ -85,7 +85,7 @@ final public class Rd5DiffTool implements ProgressListener return index[tileIndex]; } - private static int[] readPosIndex( DataInputStream dis, DataOutputStream dos ) throws Exception + private static int[] readPosIndex( DataInputStream dis, DataOutputStream dos ) throws IOException { int[] posIndex = new int[1024]; for( int i=0; i<1024; i++ ) @@ -105,7 +105,7 @@ final public class Rd5DiffTool implements ProgressListener return idx == -1 ? 4096 : posIdx[idx]; } - private static byte[] createMicroCache( int[] posIdx, int tileIdx, DataInputStream dis, boolean deltaMode ) throws Exception + private static byte[] createMicroCache( int[] posIdx, int tileIdx, DataInputStream dis, boolean deltaMode ) throws IOException { if ( posIdx == null ) { @@ -125,7 +125,7 @@ final public class Rd5DiffTool implements ProgressListener return ab; } - private static MicroCache createMicroCache( byte[] ab, DataBuffers dataBuffers ) throws Exception + private static MicroCache createMicroCache( byte[] ab, DataBuffers dataBuffers ) { if ( ab == null || ab.length == 0 ) { @@ -286,7 +286,7 @@ final public class Rd5DiffTool implements ProgressListener } - public static void recoverFromDelta( File f1, File f2, File outFile, ProgressListener progress /* , File cmpFile */ ) throws Exception + public static void recoverFromDelta( File f1, File f2, File outFile, ProgressListener progress /* , File cmpFile */ ) throws IOException { if ( f2.length() == 0L ) { @@ -341,7 +341,7 @@ final public class Rd5DiffTool implements ProgressListener int pct = (int)(100. * bytesProcessed / getTileEnd( fileIndex1, 24 ) + 0.5 ); if ( pct != lastPct ) { - progress.updateProgress( "Applying delta: " + pct + "%" ); + progress.updateProgress("Applying delta", pct); lastPct = pct; } @@ -468,7 +468,7 @@ final public class Rd5DiffTool implements ProgressListener } } - public static void copyFile( File f1, File outFile, ProgressListener progress ) throws Exception + public static void copyFile( File f1, File outFile, ProgressListener progress ) throws IOException { boolean canceled = false; DataInputStream dis1 = new DataInputStream( new BufferedInputStream( new FileInputStream( f1 ) ) ); @@ -489,7 +489,7 @@ final public class Rd5DiffTool implements ProgressListener int pct = (int)( (100. * sizeRead) / (sizeTotal+1) + 0.5 ); if ( pct != lastPct ) { - progress.updateProgress( "Copying: " + pct + "%" ); + progress.updateProgress("Copying", pct); lastPct = pct; } int len = dis1.read( buf ); @@ -756,7 +756,7 @@ final public class Rd5DiffTool implements ProgressListener this.dataBuffers = dataBuffers; } - public MicroCache readMC() throws Exception + public MicroCache readMC() throws IOException { if (skips < 0 ) { @@ -775,7 +775,7 @@ final public class Rd5DiffTool implements ProgressListener return mc; } - public void finish() throws Exception + public void finish() { skips = -1; } diff --git a/brouter-routing-app/build.gradle b/brouter-routing-app/build.gradle index dee8277..aa4c375 100644 --- a/brouter-routing-app/build.gradle +++ b/brouter-routing-app/build.gradle @@ -5,23 +5,25 @@ plugins { } android { - compileSdkVersion 30 + compileSdkVersion 31 defaultConfig { applicationId "btools.routingapp" - versionCode 42 + versionCode 45 versionName project.version resValue('string', 'app_version', defaultConfig.versionName) setProperty("archivesBaseName", "BRouterApp." + defaultConfig.versionName) minSdkVersion 14 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } sourceSets.main.assets.srcDirs += new File(project.buildDir, 'assets') - if (project.hasProperty("RELEASE_STORE_FILE")) { + if (project.hasProperty("RELEASE_STORE_FILE") && RELEASE_STORE_FILE.length() > 0) { signingConfigs { // this uses a file ~/.gradle/gradle.properties // with content: @@ -49,7 +51,7 @@ android { release { minifyEnabled false debuggable false - if (project.hasProperty("RELEASE_STORE_FILE")) { + if (project.hasProperty("RELEASE_STORE_FILE") && RELEASE_STORE_FILE.length() > 0) { signingConfig signingConfigs.release } proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' @@ -96,14 +98,21 @@ android { } dependencies { - - implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation "androidx.constraintlayout:constraintlayout:2.1.3" + implementation 'androidx.work:work-runtime:2.7.1' + implementation 'com.google.android.material:material:1.5.0' implementation project(':brouter-mapaccess') implementation project(':brouter-core') implementation project(':brouter-expressions') implementation project(':brouter-util') + testImplementation 'junit:junit:4.13.2' + + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.work:work-testing:2.7.1' } task generateProfiles(type: Exec) { diff --git a/brouter-routing-app/src/androidTest/java/btools/routingapp/BRouterActivityTest.java b/brouter-routing-app/src/androidTest/java/btools/routingapp/BRouterActivityTest.java new file mode 100644 index 0000000..6a6a9cd --- /dev/null +++ b/brouter-routing-app/src/androidTest/java/btools/routingapp/BRouterActivityTest.java @@ -0,0 +1,47 @@ +package btools.routingapp; + +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +import android.os.Build; +import android.os.Environment; + +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class BRouterActivityTest { + @Rule + public ActivityScenarioRule rule = new ActivityScenarioRule<>(BRouterActivity.class); + + @Test + public void storageDirectories() { + ActivityScenario scenario = rule.getScenario(); + scenario.onActivity(activity -> { + List storageDirectories = activity.getStorageDirectories(); + + // Before Android Q (10) legacy storage access is possible + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + assertThat(storageDirectories, hasItem(Environment.getExternalStorageDirectory())); + } + + // When targeting older SDK we can access legacy storage on any android version + if (activity.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.Q) { + assertThat(storageDirectories, hasItem(Environment.getExternalStorageDirectory())); + } + + assertThat(storageDirectories, not(empty())); + }); + } + +} diff --git a/brouter-routing-app/src/androidTest/java/btools/routingapp/DownloadWorkerTest.java b/brouter-routing-app/src/androidTest/java/btools/routingapp/DownloadWorkerTest.java new file mode 100644 index 0000000..a9004c0 --- /dev/null +++ b/brouter-routing-app/src/androidTest/java/btools/routingapp/DownloadWorkerTest.java @@ -0,0 +1,71 @@ +package btools.routingapp; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.work.Data; +import androidx.work.ListenableWorker.Result; +import androidx.work.testing.TestWorkerBuilder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +@RunWith(AndroidJUnit4.class) +public class DownloadWorkerTest { + private Context context; + private Executor executor; + + @Before + public void setUp() { + context = ApplicationProvider.getApplicationContext(); + executor = Executors.newSingleThreadExecutor(); + } + + @Test + public void testDownloadNewFile() { + Data inputData = new Data.Builder() + .putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, new String[]{"E105_N50"}) + .build(); + + DownloadWorker worker = + TestWorkerBuilder.from(context, DownloadWorker.class, executor) + .setInputData(inputData) + .build(); + + Result result = worker.doWork(); + assertThat(result, is(Result.success())); + } + + @Test + public void testDownloadInvalidSegment() { + Data inputData = new Data.Builder() + .putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, new String[]{"X00"}) + .build(); + + DownloadWorker worker = + TestWorkerBuilder.from(context, DownloadWorker.class, executor) + .setInputData(inputData) + .build(); + + Result result = worker.doWork(); + assertThat(result, is(Result.failure())); + } + + @Test + public void testDownloadNoSegments() { + DownloadWorker worker = + TestWorkerBuilder.from(context, DownloadWorker.class, executor) + .build(); + + Result result = worker.doWork(); + assertThat(result, is(Result.failure())); + } +} diff --git a/brouter-routing-app/src/main/AndroidManifest.xml b/brouter-routing-app/src/main/AndroidManifest.xml index c1ffbdf..76f6b51 100644 --- a/brouter-routing-app/src/main/AndroidManifest.xml +++ b/brouter-routing-app/src/main/AndroidManifest.xml @@ -3,7 +3,9 @@ - @@ -11,17 +13,16 @@ + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:preserveLegacyExternalStorage="true" + android:roundIcon="@mipmap/ic_launcher_round" + android:theme="@style/Theme.App"> + android:screenOrientation="unspecified"> @@ -29,20 +30,18 @@ + android:launchMode="singleTask" + android:screenOrientation="landscape" /> - + android:label="Import Profile"> + + @@ -88,15 +87,10 @@ - diff --git a/brouter-routing-app/src/main/assets/segments4.zip b/brouter-routing-app/src/main/assets/segments4.zip index 310b75f..515c7ff 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 index 9128b11..dab2409 100644 --- a/brouter-routing-app/src/main/assets/serverconfig.txt +++ b/brouter-routing-app/src/main/assets/serverconfig.txt @@ -13,4 +13,4 @@ profiles_url=https://brouter.de/brouter/profiles2/ # these are comma separated arrays check_lookup=lookups.dat -check_profiles=car-eco.brf,car-eco-de.brf,car-eco-suspect_scan.brf,car-fast.brf,fastbike.brf,fastbike-asia-pacific.brf,fastbike-lowtraffic.brf,fastbike-verylowtraffic.brf,hiking-beta.brf,moped.brf,rail.brf,river.brf,safety.brf,shortest.brf,trekking.brf,trekking-ignore-cr.brf,trekking-noferries.brf,trekking-nosteps.brf,trekking-steep.brf,vm-forum-liegerad-schnell.brf,vm-forum-velomobil-schnell.brf \ No newline at end of file +check_profiles=car-eco.brf,car-eco-de.brf,car-fast.brf,fastbike.brf,fastbike-asia-pacific.brf,fastbike-lowtraffic.brf,fastbike-verylowtraffic.brf,hiking-mountain.brf,moped.brf,rail.brf,river.brf,safety.brf,shortest.brf,trekking.brf,trekking-ignore-cr.brf,trekking-noferries.brf,trekking-nosteps.brf,trekking-steep.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/java/btools/routingapp/BImportActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java index 3d5f085..2550bf9 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BImportActivity.java @@ -1,6 +1,5 @@ package btools.routingapp; -import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -12,6 +11,7 @@ import android.widget.EditText; import android.widget.Toast; import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; import java.io.BufferedReader; import java.io.File; @@ -20,7 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -public class BImportActivity extends Activity { +public class BImportActivity extends AppCompatActivity { // profile size is generally < 30 kb, so set max size to 100 kb private static final int MAX_PROFILE_SIZE = 100000; private EditText mTextFilename; @@ -76,8 +76,8 @@ public class BImportActivity extends Activity { try (Cursor cursor = this.getContentResolver().query(intent.getData(), new String[]{ OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { - filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); - filesize = cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE)); + filename = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)); + filesize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)); } } // is the file extention ".brf" in the file name 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 9fdbc76..c12ba62 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerActivity.java @@ -1,112 +1,208 @@ package btools.routingapp; -import java.util.HashSet; -import java.util.Set; +import static btools.routingapp.BInstallerView.MASK_CURRENT_RD5; +import static btools.routingapp.BInstallerView.MASK_DELETED_RD5; +import static btools.routingapp.BInstallerView.MASK_INSTALLED_RD5; +import static btools.routingapp.BInstallerView.MASK_SELECTED_RD5; -import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; -import android.content.BroadcastReceiver; -import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.ActivityInfo; +import android.content.res.Resources; import android.os.Build; import android.os.Bundle; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.speech.tts.TextToSpeech.OnInitListener; import android.os.StatFs; -import android.util.Log; +import android.text.format.Formatter; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; -public class BInstallerActivity extends Activity { +import androidx.appcompat.app.AppCompatActivity; +import androidx.work.Constraints; +import androidx.work.Data; +import androidx.work.NetworkType; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkInfo; +import androidx.work.WorkManager; +import androidx.work.WorkRequest; - public static final String DOWNLOAD_ACTION = "btools.routingapp.download"; +import com.google.android.material.progressindicator.LinearProgressIndicator; + +import java.io.File; +import java.util.ArrayList; +import java.util.Locale; + +import btools.router.RoutingHelper; + +public class BInstallerActivity extends AppCompatActivity { private static final int DIALOG_CONFIRM_DELETE_ID = 1; - + public static boolean downloadCanceled = false; + private File mBaseDir; private BInstallerView mBInstallerView; - private PowerManager mPowerManager; - private WakeLock mWakeLock; - private DownloadReceiver myReceiver; + private Button mButtonDownload; + private TextView mSummaryInfo; + private LinearProgressIndicator mProgressIndicator; + public static long getAvailableSpace(String baseDir) { + StatFs stat = new StatFs(baseDir); - public class DownloadReceiver extends BroadcastReceiver { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + return stat.getAvailableBlocksLong() * stat.getBlockSizeLong(); + } else { + //noinspection deprecation + return (long) stat.getAvailableBlocks() * stat.getBlockSize(); + } + } - @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); + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + + setContentView(R.layout.activity_binstaller); + mSummaryInfo = findViewById(R.id.textViewSegmentSummary); + mBInstallerView = findViewById(R.id.BInstallerView); + mBInstallerView.setOnSelectListener( + () -> { + updateDownloadButton(); + } + ); + mButtonDownload = findViewById(R.id.buttonDownload); + mButtonDownload.setOnClickListener( + view -> { + if (mBInstallerView.getSelectedTiles(MASK_DELETED_RD5).size() > 0) { + showConfirmDelete(); + } else if (mBInstallerView.getSelectedTiles(MASK_SELECTED_RD5).size() > 0) { + downloadSelectedTiles(); + } else { + downloadInstalledTiles(); + } + } + ); + mProgressIndicator = findViewById(R.id.progressDownload); + + mBaseDir = ConfigHelper.getBaseDir(this); + scanExistingFiles(); + } + + private String getSegmentsPlural(int count) { + Resources res = getResources(); + return res.getQuantityString(R.plurals.numberOfSegments, count, count); + } + + private void updateDownloadButton() { + final ArrayList selectedTilesDownload = mBInstallerView.getSelectedTiles(MASK_SELECTED_RD5); + final ArrayList selectedTilesUpdate = mBInstallerView.getSelectedTiles(MASK_INSTALLED_RD5); + final ArrayList selectedTilesDelete = mBInstallerView.getSelectedTiles(MASK_DELETED_RD5); + mSummaryInfo.setText(""); + + if (selectedTilesDelete.size() > 0) { + mButtonDownload.setText(getString(R.string.action_delete, getSegmentsPlural(selectedTilesDelete.size()))); + mButtonDownload.setEnabled(true); + } else if (selectedTilesDownload.size() > 0) { + long tileSize = 0; + for (int tileIndex : selectedTilesDownload) { + tileSize += BInstallerSizes.getRd5Size(tileIndex); + } + mButtonDownload.setText(getString(R.string.action_download, getSegmentsPlural(selectedTilesDownload.size()))); + mButtonDownload.setEnabled(true); + mSummaryInfo.setText(getString(R.string.summary_segments, Formatter.formatFileSize(this, tileSize), Formatter.formatFileSize(this, getAvailableSpace(mBaseDir.getAbsolutePath())))); + } else if (selectedTilesUpdate.size() > 0) { + mButtonDownload.setText(getString(R.string.action_update, getSegmentsPlural(selectedTilesUpdate.size()))); + mButtonDownload.setEnabled(true); + } else { + mButtonDownload.setText(getString(R.string.action_select)); + mButtonDownload.setEnabled(false); + } + } + + private void deleteRawTracks() { + File modeDir = new File(mBaseDir, "brouter/modes"); + String[] fileNames = modeDir.list(); + if (fileNames == null) return; + for (String fileName : fileNames) { + if (fileName.endsWith("_rawtrack.dat")) { + File f = new File(modeDir, fileName); + f.delete(); } } } + public void downloadAll(ArrayList downloadList) { + ArrayList urlparts = new ArrayList<>(); + for (Integer i : downloadList) { + urlparts.add(baseNameForTile(i)); + } - /** - * Called when the activity is first created. - */ - @Override - @SuppressWarnings("deprecation") - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + downloadCanceled = false; - // Get an instance of the PowerManager - mPowerManager = (PowerManager) getSystemService(POWER_SERVICE); + Data inputData = new Data.Builder() + .putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, urlparts.toArray(new String[0])) + .build(); - // Create a bright wake lock - mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass() - .getName()); + Constraints constraints = new Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build(); - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + WorkRequest downloadWorkRequest = + new OneTimeWorkRequest.Builder(DownloadWorker.class) + .setInputData(inputData) + .setConstraints(constraints) + .build(); - // instantiate our simulation view and set it as the activity's content - mBInstallerView = new BInstallerView(this); - setContentView(mBInstallerView); + WorkManager workManager = WorkManager.getInstance(getApplicationContext()); + workManager.enqueue(downloadWorkRequest); + + workManager + .getWorkInfoByIdLiveData(downloadWorkRequest.getId()) + .observe(this, workInfo -> { + if (workInfo != null) { + if (workInfo.getState() == WorkInfo.State.ENQUEUED) { + Toast.makeText(this, "Download scheduled. Check internet connection if it doesn't start.", Toast.LENGTH_LONG).show(); + mProgressIndicator.hide(); + mProgressIndicator.setIndeterminate(true); + mProgressIndicator.show(); + } + + if (workInfo.getState() == WorkInfo.State.RUNNING) { + Data progress = workInfo.getProgress(); + String segmentName = progress.getString(DownloadWorker.PROGRESS_SEGMENT_NAME); + int percent = progress.getInt(DownloadWorker.PROGRESS_SEGMENT_PERCENT, 0); + if (percent > 0) { + mProgressIndicator.setIndeterminate(false); + } + mProgressIndicator.setProgress(percent); + } + + if (workInfo.getState().isFinished()) { + String result; + switch (workInfo.getState()) { + case FAILED: + result = "failed."; + break; + case CANCELLED: + result = "cancelled"; + break; + case SUCCEEDED: + result = "succeeded"; + break; + default: + result = ""; + } + Toast.makeText(this, "Download " + result + ".", Toast.LENGTH_SHORT).show(); + mProgressIndicator.hide(); + scanExistingFiles(); + } + } + }); + + deleteRawTracks(); // invalidate raw-tracks after data update } @Override - protected void onResume() { - super.onResume(); - /* - * when the activity is resumed, we acquire a wake-lock so that the - * screen stays on, since the user will likely not be fiddling with the - * screen or buttons. - */ - mWakeLock.acquire(); - - IntentFilter filter = new IntentFilter(); - filter.addAction(DOWNLOAD_ACTION); - - myReceiver = new DownloadReceiver(); - registerReceiver(myReceiver, filter); - - // Start the download manager - mBInstallerView.startInstaller(); - } - - @Override - protected void onPause() { - super.onPause(); - - - super.onPause(); - - mWakeLock.release(); - - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (myReceiver != null) unregisterReceiver(myReceiver); - System.exit(0); - } - - @Override - @SuppressWarnings("deprecation") protected Dialog onCreateDialog(int id) { AlertDialog.Builder builder; switch (id) { @@ -116,7 +212,7 @@ public class BInstallerActivity extends Activity { .setTitle("Confirm Delete") .setMessage("Really delete?").setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { - mBInstallerView.deleteSelectedTiles(); + deleteSelectedTiles(); } }).setNegativeButton("No", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { @@ -129,29 +225,76 @@ public class BInstallerActivity extends Activity { } } - @SuppressWarnings("deprecation") public void showConfirmDelete() { showDialog(DIALOG_CONFIRM_DELETE_ID); } - private Set dialogIds = new HashSet(); + private void scanExistingFiles() { + mBInstallerView.clearAllTilesStatus(MASK_CURRENT_RD5 | MASK_INSTALLED_RD5 | MASK_DELETED_RD5 | MASK_SELECTED_RD5); - private void showNewDialog(int id) { - if (dialogIds.contains(Integer.valueOf(id))) { - removeDialog(id); + scanExistingFiles(new File(mBaseDir, "brouter/segments4")); + + File secondary = RoutingHelper.getSecondarySegmentDir(new File(mBaseDir, "brouter/segments4")); + if (secondary != null) { + scanExistingFiles(secondary); } - dialogIds.add(Integer.valueOf(id)); - showDialog(id); } + private void scanExistingFiles(File dir) { + String[] fileNames = dir.list(); + if (fileNames == null) return; + String suffix = ".rd5"; + for (String fileName : fileNames) { + if (fileName.endsWith(suffix)) { + String basename = fileName.substring(0, fileName.length() - suffix.length()); + int tileIndex = tileForBaseName(basename); + mBInstallerView.setTileStatus(tileIndex, MASK_INSTALLED_RD5); - static public long getAvailableSpace(String baseDir) { - StatFs stat = new StatFs(baseDir); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - return stat.getAvailableBlocksLong() * stat.getBlockSizeLong(); - } else { - return stat.getAvailableBlocks() * stat.getBlockSize(); + long age = System.currentTimeMillis() - new File(dir, fileName).lastModified(); + if (age < 10800000) mBInstallerView.setTileStatus(tileIndex, MASK_CURRENT_RD5); // 3 hours + } } } + + private void deleteSelectedTiles() { + ArrayList selectedTiles = mBInstallerView.getSelectedTiles(MASK_DELETED_RD5); + for (int tileIndex : selectedTiles) { + new File(mBaseDir, "brouter/segments4/" + baseNameForTile(tileIndex) + ".rd5").delete(); + } + scanExistingFiles(); + } + + private void downloadSelectedTiles() { + ArrayList selectedTiles = mBInstallerView.getSelectedTiles(MASK_SELECTED_RD5); + downloadAll(selectedTiles); + mBInstallerView.clearAllTilesStatus(MASK_SELECTED_RD5); + } + + private void downloadInstalledTiles() { + ArrayList selectedTiles = mBInstallerView.getSelectedTiles(MASK_INSTALLED_RD5); + downloadAll(selectedTiles); + } + + private int tileForBaseName(String basename) { + String uname = basename.toUpperCase(Locale.ROOT); + int idx = uname.indexOf("_"); + if (idx < 0) return -1; + String slon = uname.substring(0, idx); + String slat = uname.substring(idx + 1); + int ilon = slon.charAt(0) == 'W' ? -Integer.parseInt(slon.substring(1)) : + (slon.charAt(0) == 'E' ? Integer.parseInt(slon.substring(1)) : -1); + int ilat = slat.charAt(0) == 'S' ? -Integer.parseInt(slat.substring(1)) : + (slat.charAt(0) == 'N' ? Integer.parseInt(slat.substring(1)) : -1); + if (ilon < -180 || ilon >= 180 || ilon % 5 != 0) return -1; + if (ilat < -90 || ilat >= 90 || ilat % 5 != 0) return -1; + return (ilon + 180) / 5 + 72 * ((ilat + 90) / 5); + } + + private String baseNameForTile(int tileIndex) { + int lon = (tileIndex % 72) * 5 - 180; + int lat = (tileIndex / 72) * 5 - 90; + String slon = lon < 0 ? "W" + (-lon) : "E" + lon; + String slat = lat < 0 ? "S" + (-lat) : "N" + lat; + return slon + "_" + slat; + } } 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 f262cfc..ffe75c2 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java @@ -1,18 +1,7 @@ package btools.routingapp; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.ArrayList; -import java.util.Locale; - -import android.app.Activity; +import android.annotation.SuppressLint; import android.content.Context; -import android.content.Intent; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -20,262 +9,37 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; -import android.os.AsyncTask; -import android.os.PowerManager; -import android.os.StatFs; import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.util.Log; +import android.view.GestureDetector; import android.view.MotionEvent; +import android.view.ScaleGestureDetector; import android.view.View; -import android.widget.Toast; -import btools.mapaccess.PhysicalFile; -import btools.mapaccess.Rd5DiffManager; -import btools.mapaccess.Rd5DiffTool; -import btools.router.RoutingHelper; -import btools.util.ProgressListener; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; public class BInstallerView extends View { - private static final int MASK_SELECTED_RD5 = 1; - private static final int MASK_DELETED_RD5 = 2; - private static final int MASK_INSTALLED_RD5 = 4; - private static final int MASK_CURRENT_RD5 = 8; - - private int imgwOrig; - private int imghOrig; - private float scaleOrig; - - private int imgw; - private int imgh; - - private float lastDownX; - private float lastDownY; - - private Bitmap bmp; - + public static final int MASK_SELECTED_RD5 = 1; + public static final int MASK_DELETED_RD5 = 2; + public static final int MASK_INSTALLED_RD5 = 4; + public static final int MASK_CURRENT_RD5 = 8; + private static final float SCALE_GRID_VISIBLE = 3; + private final Bitmap bmp; + private final float[] testVector = new float[2]; + private final int[] tileStatus; + private final Matrix mat; + private final GestureDetector mGestureDetector; + private final ScaleGestureDetector mScaleGestureDetector; + Paint paintGrid = new Paint(); + Paint paintTiles = new Paint(); private float viewscale; - - private float[] testVector = new float[2]; - - private int[] tileStatus; - private boolean tilesVisible = false; + private OnSelectListener mOnSelectListener; - private long availableSize; - private File baseDir; - private File segmentDir; + public BInstallerView(Context context, AttributeSet attrs) { + super(context, attrs); - private boolean isDownloading = false; - public static boolean downloadCanceled = false; - - private long currentDownloadSize; - private String currentDownloadFile = ""; - private volatile String currentDownloadOperation = ""; - private String downloadAction = ""; - private volatile String newDownloadAction = ""; - - private long totalSize = 0; - private long rd5Tiles = 0; - private long delTiles = 0; - - Paint pnt_1 = new Paint(); - Paint pnt_2 = new Paint(); - Paint paint = new Paint(); - - Activity mActivity; - - - protected String baseNameForTile(int tileIndex) { - int lon = (tileIndex % 72) * 5 - 180; - int lat = (tileIndex / 72) * 5 - 90; - String slon = lon < 0 ? "W" + (-lon) : "E" + lon; - String slat = lat < 0 ? "S" + (-lat) : "N" + lat; - return slon + "_" + slat; - } - - private int gridPos2Tileindex(int ix, int iy) { - return (35 - iy) * 72 + (ix >= 70 ? ix - 70 : ix + 2); - } - - private int tileForBaseName(String basename) { - String uname = basename.toUpperCase(Locale.ROOT); - int idx = uname.indexOf("_"); - if (idx < 0) return -1; - String slon = uname.substring(0, idx); - String slat = uname.substring(idx + 1); - int ilon = slon.charAt(0) == 'W' ? -Integer.parseInt(slon.substring(1)) : - (slon.charAt(0) == 'E' ? Integer.parseInt(slon.substring(1)) : -1); - int ilat = slat.charAt(0) == 'S' ? -Integer.parseInt(slat.substring(1)) : - (slat.charAt(0) == 'N' ? Integer.parseInt(slat.substring(1)) : -1); - if (ilon < -180 || ilon >= 180 || ilon % 5 != 0) return -1; - if (ilat < -90 || ilat >= 90 || ilat % 5 != 0) return -1; - return (ilon + 180) / 5 + 72 * ((ilat + 90) / 5); - } - - - public boolean isDownloadCanceled() { - return downloadCanceled; - } - - private void toggleDownload() { - if (isDownloading) { - downloadCanceled = true; - downloadAction = "Canceling..."; - return; - } - - if (delTiles > 0) { - ((BInstallerActivity) getContext()).showConfirmDelete(); - return; - } - - int tidx_min = -1; - int min_size = Integer.MAX_VALUE; - - ArrayList downloadList = new ArrayList<>(); - // prepare download list - for (int ix = 0; ix < 72; ix++) { - for (int iy = 0; iy < 36; iy++) { - int tidx = gridPos2Tileindex(ix, iy); - if ((tileStatus[tidx] & MASK_SELECTED_RD5) != 0) { - int tilesize = BInstallerSizes.getRd5Size(tidx); - downloadList.add(tidx); - if (tilesize > 0 && tilesize < min_size) { - tidx_min = tidx; - min_size = tilesize; - } - } - } - } - - 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 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; - if (success) { - scanExistingFiles(); - toggleDownload(); // keep on if no error - } - 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()); - int iy = (int) (36.f * y / bmp.getHeight()); - if (ix >= 0 && ix < 72 && iy >= 0 && iy < 36) return gridPos2Tileindex(ix, iy); - return -1; - } - - private void clearTileSelection(int mask) { - // clear selection if zooming out - for (int ix = 0; ix < 72; ix++) - for (int iy = 0; iy < 36; iy++) { - int tidx = gridPos2Tileindex(ix, iy); - tileStatus[tidx] ^= tileStatus[tidx] & mask; - } - } - - // get back the current image scale - private float currentScale() { - testVector[1] = 1.f; - mat.mapVectors(testVector); - return testVector[1] / viewscale; - } - - private void deleteRawTracks() { - File modeDir = new File(baseDir, "brouter/modes"); - String[] fileNames = modeDir.list(); - if (fileNames == null) return; - for (String fileName : fileNames) { - if (fileName.endsWith("_rawtrack.dat")) { - File f = new File(modeDir, fileName); - f.delete(); - } - } - } - - private void scanExistingFiles() { - clearTileSelection(MASK_INSTALLED_RD5 | MASK_CURRENT_RD5); - - scanExistingFiles(new File(baseDir, "brouter/segments4")); - - File secondary = RoutingHelper.getSecondarySegmentDir(new File(baseDir, "brouter/segments4")); - if (secondary != null) { - scanExistingFiles(secondary); - } - - availableSize = -1; - try { - availableSize = (long) ((BInstallerActivity) getContext()).getAvailableSpace(baseDir.getAbsolutePath()); - //StatFs stat = new StatFs(baseDir.getAbsolutePath ()); - //availableSize = (long)stat.getAvailableBlocksLong()*stat.getBlockSizeLong(); - } catch (Exception e) { /* ignore */ } - } - - private void scanExistingFiles(File dir) { - String[] fileNames = dir.list(); - if (fileNames == null) return; - String suffix = ".rd5"; - for (String fileName : fileNames) { - if (fileName.endsWith(suffix)) { - String basename = fileName.substring(0, fileName.length() - suffix.length()); - int tidx = tileForBaseName(basename); - tileStatus[tidx] |= MASK_INSTALLED_RD5; - - long age = System.currentTimeMillis() - new File(dir, fileName).lastModified(); - if (age < 10800000) tileStatus[tidx] |= MASK_CURRENT_RD5; // 3 hours - } - } - } - - private Matrix mat; - private Matrix matText; - - public void startInstaller() { - - baseDir = ConfigHelper.getBaseDir(getContext()); - segmentDir = new File(baseDir, "brouter/segments4"); try { AssetManager assetManager = getContext().getAssets(); InputStream istr = assetManager.open("world.png"); @@ -286,56 +50,111 @@ public class BInstallerView extends View { } tileStatus = new int[72 * 36]; - scanExistingFiles(); - - float scaleX = imgwOrig / ((float) bmp.getWidth()); - float scaley = imghOrig / ((float) bmp.getHeight()); - - viewscale = scaleX < scaley ? scaleX : scaley; - mat = new Matrix(); - mat.postScale(viewscale, viewscale); - tilesVisible = false; + mGestureDetector = new GestureDetector(context, new GestureListener()); + mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener()); } - public BInstallerView(Context context) { - super(context); - mActivity = (Activity) context; + public void setOnSelectListener(OnSelectListener listener) { + mOnSelectListener = listener; + } - DisplayMetrics metrics = new DisplayMetrics(); - ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics); - imgwOrig = metrics.widthPixels; - imghOrig = metrics.heightPixels; - int im = imgwOrig > imghOrig ? imgwOrig : imghOrig; + private void setRatio(float ratio, float focusX, float focusY) { + if (currentScale() * ratio >= 1) { + mat.postScale(ratio, ratio, focusX, focusY); + fitBounds(); + tilesVisible = currentScale() >= SCALE_GRID_VISIBLE; - scaleOrig = im / 480.f; + invalidate(); + } + } - matText = new Matrix(); - matText.preScale(scaleOrig, scaleOrig); + private void setScale(float scale, float focusX, float focusY) { + float ratio = scale / currentScale(); + setRatio(ratio, focusX, focusY); + } - imgw = (int) (imgwOrig / scaleOrig); - imgh = (int) (imghOrig / scaleOrig); + public void setTileStatus(int tileIndex, int tileMask) { + tileStatus[tileIndex] |= tileMask; + if (mOnSelectListener != null) { + mOnSelectListener.onSelect(); + } + invalidate(); + } - totalSize = 0; - rd5Tiles = 0; - delTiles = 0; + public void toggleTileStatus(int tileIndex, int tileMask) { + tileStatus[tileIndex] ^= tileMask; + if (mOnSelectListener != null) { + mOnSelectListener.onSelect(); + } + invalidate(); + } + + public void clearAllTilesStatus(int tileMask) { + for (int ix = 0; ix < 72; ix++) { + for (int iy = 0; iy < 36; iy++) { + int tileIndex = gridPos2Tileindex(ix, iy); + tileStatus[tileIndex] ^= tileStatus[tileIndex] & tileMask; + } + } + if (mOnSelectListener != null) { + mOnSelectListener.onSelect(); + } + invalidate(); + } + + public ArrayList getSelectedTiles(int tileMask) { + ArrayList selectedTiles = new ArrayList<>(); + for (int ix = 0; ix < 72; ix++) { + for (int iy = 0; iy < 36; iy++) { + int tileIndex = gridPos2Tileindex(ix, iy); + if ((tileStatus[tileIndex] & tileMask) != 0 && BInstallerSizes.getRd5Size(tileIndex) > 0) { + selectedTiles.add(tileIndex); + } + } + } + + return selectedTiles; + } + + private int gridPos2Tileindex(int ix, int iy) { + return (35 - iy) * 72 + (ix >= 70 ? ix - 70 : ix + 2); + } + + private int tileIndex(float x, float y) { + int ix = (int) (72.f * x / bmp.getWidth()); + int iy = (int) (36.f * y / bmp.getHeight()); + if (ix >= 0 && ix < 72 && iy >= 0 && iy < 36) return gridPos2Tileindex(ix, iy); + return -1; + } + + // get back the current image scale + private float currentScale() { + testVector[0] = 1.f; + mat.mapVectors(testVector); + return testVector[0] / viewscale; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); - } - private void toast(String msg) { - Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show(); + int imgwOrig = getWidth(); + int imghOrig = getHeight(); + + float scaleX = imgwOrig / ((float) bmp.getWidth()); + float scaleY = imghOrig / ((float) bmp.getHeight()); + + viewscale = Math.max(scaleX, scaleY); + + mat.postScale(viewscale, viewscale); + tilesVisible = false; } @Override protected void onDraw(Canvas canvas) { - if (!isDownloading) { - canvas.setMatrix(mat); - canvas.drawBitmap(bmp, 0, 0, null); - } + canvas.setMatrix(mat); + canvas.drawBitmap(bmp, 0, 0, null); // draw 5*5 lattice starting at scale=3 int iw = bmp.getWidth(); @@ -343,290 +162,164 @@ public class BInstallerView extends View { float fw = iw / 72.f; float fh = ih / 36.f; - boolean drawGrid = tilesVisible && !isDownloading; - - if (drawGrid) { - - pnt_1.setColor(Color.GREEN); - - for (int ix = 1; ix < 72; ix++) { - float fx = fw * ix; - canvas.drawLine(fx, 0, fx, ih, pnt_1); + if (tilesVisible) { + paintGrid.setColor(Color.GREEN); + paintGrid.setStyle(Paint.Style.STROKE); + for (int ix = 0; ix < 72; ix++) { + for (int iy = 0; iy < 36; iy++) { + int tidx = gridPos2Tileindex(ix, iy); + int tilesize = BInstallerSizes.getRd5Size(tidx); + if (tilesize > 0) { + canvas.drawRect(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, paintGrid); + } + } } - for (int iy = 1; iy < 36; iy++) { - float fy = fh * iy; - canvas.drawLine(0, fy, iw, fy, pnt_1); - } - } - rd5Tiles = 0; - delTiles = 0; - totalSize = 0; - int mask2 = MASK_SELECTED_RD5 | MASK_DELETED_RD5 | MASK_INSTALLED_RD5; - int mask3 = mask2 | MASK_CURRENT_RD5; - pnt_2.setColor(Color.GRAY); - pnt_2.setStrokeWidth(1); - drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_INSTALLED_RD5, mask3, false, false, drawGrid); - pnt_2.setColor(Color.BLUE); - pnt_2.setStrokeWidth(1); - drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_INSTALLED_RD5 | MASK_CURRENT_RD5, mask3, false, false, drawGrid); - pnt_2.setColor(Color.GREEN); - pnt_2.setStrokeWidth(2); - drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_SELECTED_RD5, mask2, true, false, drawGrid); - pnt_2.setColor(Color.YELLOW); - pnt_2.setStrokeWidth(2); - drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_SELECTED_RD5 | MASK_INSTALLED_RD5, mask2, true, false, drawGrid); - pnt_2.setColor(Color.RED); - pnt_2.setStrokeWidth(2); - drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_DELETED_RD5 | MASK_INSTALLED_RD5, mask2, false, true, drawGrid); + int mask2 = MASK_SELECTED_RD5 | MASK_DELETED_RD5 | MASK_INSTALLED_RD5; + int mask3 = mask2 | MASK_CURRENT_RD5; - canvas.setMatrix(matText); - - paint.setColor(Color.RED); - - long mb = 1024 * 1024; - - if (isDownloading) { - String sizeHint = currentDownloadSize > 0 ? " (" + ((currentDownloadSize + mb - 1) / mb) + " MB)" : ""; - paint.setTextSize(30); - 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 && !isDownloading) { - paint.setTextSize(35); - canvas.drawText("Touch region to zoom in!", 30, (imgh / 3) * 2, paint); - } - paint.setTextSize(20); - - - String totmb = ((totalSize + mb - 1) / mb) + " MB"; - String freemb = availableSize >= 0 ? ((availableSize + mb - 1) / mb) + " MB" : "?"; - canvas.drawText("Selected segments=" + rd5Tiles, 10, 25, paint); - canvas.drawText("Size=" + totmb + " Free=" + freemb, 10, 45, paint); - - - String btnText = null; - if (isDownloading) btnText = "Cancel Download"; - else if (delTiles > 0) btnText = "Delete " + delTiles + " tiles"; - else if (rd5Tiles > 0) btnText = "Start Download"; - else if (tilesVisible && - rd5Tiles == 0 && - RoutingHelper.hasDirectoryAnyDatafiles(segmentDir)) btnText = "Update all"; - - if (btnText != null) { - canvas.drawLine(imgw - btnw, imgh - btnh, imgw - btnw, imgh - 2, paint); - canvas.drawLine(imgw - btnw, imgh - btnh, imgw - 2, imgh - btnh, paint); - canvas.drawLine(imgw - btnw, imgh - btnh, imgw - btnw, imgh - 2, paint); - canvas.drawLine(imgw - 2, imgh - btnh, imgw - 2, imgh - 2, paint); - canvas.drawLine(imgw - btnw, imgh - 2, imgw - 2, imgh - 2, paint); - canvas.drawText(btnText, imgw - btnw + 5, imgh - 10, paint); + paintTiles.setStyle(Paint.Style.STROKE); + paintTiles.setColor(Color.GRAY); + paintTiles.setStrokeWidth(1); + drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_INSTALLED_RD5, mask3); + paintTiles.setColor(Color.BLUE); + paintTiles.setStrokeWidth(1); + drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_INSTALLED_RD5 | MASK_CURRENT_RD5, mask3); + paintTiles.setColor(Color.GREEN); + paintTiles.setStrokeWidth(2); + drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_SELECTED_RD5, mask2); + paintTiles.setColor(Color.YELLOW); + paintTiles.setStrokeWidth(2); + drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_SELECTED_RD5 | MASK_INSTALLED_RD5, mask2); + paintTiles.setColor(Color.RED); + paintTiles.setStrokeWidth(2); + drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_DELETED_RD5 | MASK_INSTALLED_RD5, mask2); } } - int btnh = 40; - int btnw = 160; - - - float tx, ty; - - private void drawSelectedTiles(Canvas canvas, Paint pnt, float fw, float fh, int status, int mask, boolean doCount, boolean cntDel, boolean doDraw) { + private void drawSelectedTiles(Canvas canvas, Paint pnt, float fw, float fh, int status, int mask) { for (int ix = 0; ix < 72; ix++) for (int iy = 0; iy < 36; iy++) { int tidx = gridPos2Tileindex(ix, iy); if ((tileStatus[tidx] & mask) == status) { int tilesize = BInstallerSizes.getRd5Size(tidx); if (tilesize > 0) { - if (doCount) { - rd5Tiles++; - totalSize += BInstallerSizes.getRd5Size(tidx); - } - if (cntDel) { - delTiles++; - totalSize += BInstallerSizes.getRd5Size(tidx); - } - if (!doDraw) - continue; // draw cross canvas.drawLine(fw * ix, fh * iy, fw * (ix + 1), fh * (iy + 1), pnt); canvas.drawLine(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, pnt); // draw frame - canvas.drawLine(fw * ix, fh * iy, fw * (ix + 1), fh * iy, pnt); - canvas.drawLine(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * (iy + 1), pnt); - canvas.drawLine(fw * ix, fh * iy, fw * ix, fh * (iy + 1), pnt); - canvas.drawLine(fw * (ix + 1), fh * iy, fw * (ix + 1), fh * (iy + 1), pnt); + canvas.drawRect(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, pnt); } } } } - public void deleteSelectedTiles() { - for (int ix = 0; ix < 72; ix++) { - for (int iy = 0; iy < 36; iy++) { - int tidx = gridPos2Tileindex(ix, iy); - if ((tileStatus[tidx] & MASK_DELETED_RD5) != 0) { - new File(baseDir, "brouter/segments4/" + baseNameForTile(tidx) + ".rd5").delete(); - } - } + private void fitBounds() { + float[] srcPoints = new float[]{ + 0, 0, + bmp.getWidth(), bmp.getHeight() + }; + float[] dstPoints = new float[srcPoints.length]; + float transX = 0; + float transY = 0; + mat.mapPoints(dstPoints, srcPoints); + if (dstPoints[0] > 0) { + transX = -dstPoints[0]; + } else if (dstPoints[2] < getWidth()) { + transX = getWidth() - dstPoints[2]; + } + if (dstPoints[1] > 0) { + transY = -dstPoints[1]; + } else if (dstPoints[3] < getHeight()) { + transY = getHeight() - dstPoints[3]; + } + if (transX != 0 || transY != 0) { + mat.postTranslate(transX, transY); } - scanExistingFiles(); - invalidate(); } + @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { - - // get pointer index from the event object - int pointerIndex = event.getActionIndex(); - - // get pointer ID - int pointerId = event.getPointerId(pointerIndex); - - // get masked (not specific to a pointer) action - int maskedAction = event.getActionMasked(); - - switch (maskedAction) { - - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: { - lastDownX = event.getX(); - lastDownY = event.getY(); - - break; - } - case MotionEvent.ACTION_MOVE: { // a pointer was moved - - if (isDownloading) break; - int np = event.getPointerCount(); - int nh = event.getHistorySize(); - if (nh == 0) break; - - float x0 = event.getX(0); - float y0 = event.getY(0); - float hx0 = event.getHistoricalX(0, 0); - float hy0 = event.getHistoricalY(0, 0); - - if (np > 1) // multi-touch - { - float x1 = event.getX(1); - float y1 = event.getY(1); - float hx1 = event.getHistoricalX(1, 0); - float hy1 = event.getHistoricalY(1, 0); - - float r = (float) Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); - float hr = (float) Math.sqrt((hx1 - hx0) * (hx1 - hx0) + (hy1 - hy0) * (hy1 - hy0)); - - if (hr > 0.) { - float ratio = r / hr; - - float mx = (x1 + x0) / 2.f; - float my = (y1 + y0) / 2.f; - - float scale = currentScale(); - float newscale = scale * ratio; - - if (newscale > 10.f) ratio *= (10.f / newscale); - if (newscale < 0.5f) ratio *= (0.5f / newscale); - - mat.postScale(ratio, ratio, mx, my); - - mat.postScale(ratio, ratio, mx, my); - - boolean tilesv = currentScale() >= 3.f; - if (tilesVisible && !tilesv) { - clearTileSelection(MASK_SELECTED_RD5 | MASK_DELETED_RD5); - } - tilesVisible = tilesv; - } - - break; - } - mat.postTranslate(x0 - hx0, y0 - hy0); - - break; - } - case MotionEvent.ACTION_UP: - - long downTime = event.getEventTime() - event.getDownTime(); - - if (downTime < 5 || downTime > 500) { - break; - } - - if (Math.abs(lastDownX - event.getX()) > 10 || Math.abs(lastDownY - event.getY()) > 10) { - break; - } - - // download button? - if ((delTiles > 0 || rd5Tiles >= 0 || isDownloading) && event.getX() > imgwOrig - btnw * scaleOrig && event.getY() > imghOrig - btnh * scaleOrig) { - if (rd5Tiles == 0) { - for (int ix = 0; ix < 72; ix++) { - for (int iy = 0; iy < 36; iy++) { - int tidx = gridPos2Tileindex(ix, iy); - if (tidx != -1) { - if ((tileStatus[tidx] & MASK_INSTALLED_RD5) != 0) { - tileStatus[tidx] |= MASK_SELECTED_RD5; - } - } - - } - } - } - toggleDownload(); - invalidate(); - break; - } - - if (!tilesVisible) { - float scale = currentScale(); - if (scale > 0f && scale < 5f) { - float ratio = 5f / scale; - mat.postScale(ratio, ratio, event.getX(), event.getY()); - tilesVisible = true; - } - break; - } - - if (isDownloading) break; - - Matrix imat = new Matrix(); - if (mat.invert(imat)) { - float[] touchpoint = new float[2]; - touchpoint[0] = event.getX(); - touchpoint[1] = event.getY(); - imat.mapPoints(touchpoint); - - int tidx = tileIndex(touchpoint[0], touchpoint[1]); - if (tidx != -1) { - if ((tileStatus[tidx] & MASK_SELECTED_RD5) != 0) { - tileStatus[tidx] ^= MASK_SELECTED_RD5; - if ((tileStatus[tidx] & MASK_INSTALLED_RD5) != 0) { - tileStatus[tidx] |= MASK_DELETED_RD5; - } - } else if ((tileStatus[tidx] & MASK_DELETED_RD5) != 0) { - tileStatus[tidx] ^= MASK_DELETED_RD5; - } else { - tileStatus[tidx] ^= MASK_SELECTED_RD5; - } - } - - tx = touchpoint[0]; - ty = touchpoint[1]; - } - - - break; - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_CANCEL: { - // TODO use data - break; - } - } - invalidate(); - - return true; + boolean retVal = mScaleGestureDetector.onTouchEvent(event); + retVal = mGestureDetector.onTouchEvent(event) || retVal; + return retVal || super.onTouchEvent(event); } + interface OnSelectListener { + void onSelect(); + } + + class GestureListener extends GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + if (tilesVisible) { + Matrix imat = new Matrix(); + if (mat.invert(imat)) { + float[] touchPoint = {e.getX(), e.getY()}; + imat.mapPoints(touchPoint); + + int tidx = tileIndex(touchPoint[0], touchPoint[1]); + if (tidx != -1) { + if ((tileStatus[tidx] & MASK_SELECTED_RD5) != 0) { + toggleTileStatus(tidx, MASK_SELECTED_RD5); + if ((tileStatus[tidx] & MASK_INSTALLED_RD5) != 0) { + setTileStatus(tidx, MASK_DELETED_RD5); + } + } else if ((tileStatus[tidx] & MASK_DELETED_RD5) != 0) { + toggleTileStatus(tidx, MASK_DELETED_RD5); + } else { + toggleTileStatus(tidx, MASK_SELECTED_RD5); + } + } + } + invalidate(); + } + return true; + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + if (!tilesVisible) { + setScale(5, e.getX(), e.getY()); + } else { + setScale(1, e.getX(), e.getY()); + } + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + mat.postTranslate(-distanceX, -distanceY); + fitBounds(); + invalidate(); + return true; + } + } + + class ScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener { + + @Override + public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) { + return true; + } + + @Override + public boolean onScale(ScaleGestureDetector scaleGestureDetector) { + float focusX = scaleGestureDetector.getFocusX(); + float focusY = scaleGestureDetector.getFocusY(); + float ratio = scaleGestureDetector.getScaleFactor(); + + setRatio(ratio, focusX, focusY); + + return true; + } + + @Override + public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) { + } + } } diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java index 40bcb5f..c3253ce 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java @@ -1,7 +1,24 @@ package btools.routingapp; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.os.StatFs; +import android.widget.EditText; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.os.EnvironmentCompat; + import java.io.File; -import java.lang.reflect.Method; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; @@ -9,36 +26,9 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import android.app.Activity; -import android.app.ActivityManager; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkInfo; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.os.StatFs; -import android.speech.tts.TextToSpeech.OnInitListener; -import android.util.Log; -import android.view.KeyEvent; -import android.widget.EditText; - - -import androidx.core.app.ActivityCompat; -import androidx.core.os.EnvironmentCompat; - import btools.router.OsmNodeNamed; -public class BRouterActivity extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback { +public class BRouterActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback { private static final int DIALOG_SELECTPROFILE_ID = 1; private static final int DIALOG_EXCEPTION_ID = 2; @@ -129,11 +119,11 @@ public class BRouterActivity extends Activity implements ActivityCompat.OnReques .setMessage( "*** Attention: ***\n\n" + "The Download Manager is used to download routing-data " + "files which can be up to 170MB each. Do not start the Download Manager " + "on a cellular data connection without a data plan! " - + "Download speed is restricted to 4 MBit/s.").setPositiveButton("I know", new DialogInterface.OnClickListener() { + + "Download speed is restricted to 16 MBit/s.").setPositiveButton("I know", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { Intent intent = new Intent(BRouterActivity.this, BInstallerActivity.class); startActivity(intent); - // finish(); + showNewDialog(DIALOG_MAINACTION_ID); } }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { @@ -391,39 +381,12 @@ public class BRouterActivity extends Activity implements ActivityCompat.OnReques private String maptoolDirCandidate; - public boolean isOnline(Context context) { - boolean result = false; - ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - Network nw = connectivityManager.getActiveNetwork(); - if (nw == null) return false; - NetworkCapabilities nwc = connectivityManager.getNetworkCapabilities(nw); - if (nwc == null) return false; - result = nwc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) | - nwc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) | - nwc.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET); - - } else { - NetworkInfo ni = connectivityManager.getActiveNetworkInfo(); - if (ni == null) return false; - result = ni.getType() == ConnectivityManager.TYPE_WIFI || - ni.getType() == ConnectivityManager.TYPE_MOBILE || - ni.getType() == ConnectivityManager.TYPE_ETHERNET; - } - - return result; - } - @SuppressWarnings("deprecation") public void selectProfile(String[] items) { availableProfiles = items; - // if we have internet access, first show the main action dialog - if (isOnline(this)) { - showDialog(DIALOG_MAINACTION_ID); - } else { - showDialog(DIALOG_SELECTPROFILE_ID); - } + // show main dialog + showDialog(DIALOG_MAINACTION_ID); } @SuppressWarnings("deprecation") @@ -626,6 +589,7 @@ public class BRouterActivity extends Activity implements ActivityCompat.OnReques @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 0) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { mBRouterView.startSetup(null, true); diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java index 9b04dba..c21af97 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -1,5 +1,21 @@ package btools.routingapp; +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.os.Build; +import android.os.Environment; +import android.util.Log; +import android.view.View; +import android.widget.Toast; + +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -12,36 +28,13 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Set; -import java.util.StringTokenizer; import java.util.TreeMap; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.AssetManager; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.os.Build; -import android.os.Environment; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.View; -import android.widget.Toast; - - -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - -import btools.expressions.BExpressionContextWay; import btools.expressions.BExpressionMetaData; import btools.mapaccess.OsmNode; import btools.router.OsmNodeNamed; @@ -108,11 +101,6 @@ public class BRouterView extends View { public void init() { try { - DisplayMetrics metrics = new DisplayMetrics(); - ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics); - imgw = metrics.widthPixels; - imgh = metrics.heightPixels; - // get base dir from private file File baseDir = ConfigHelper.getBaseDir(getContext()); // check if valid @@ -123,6 +111,15 @@ public class BRouterView extends View { if (brd.isDirectory()) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q && !brd.getAbsolutePath().contains("/Android/media/btools.routingapp")) { + + // don't ask twice + String version = "v" + getContext().getString(R.string.app_version); + File vFile = new File(brd, "profiles2/"+version ); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q + && vFile.exists()) { + startSetup(baseDir, false); + return; + } String message = "(previous basedir " + baseDir + " has to migrate )"; ((BRouterActivity) getContext()).selectBasedir(((BRouterActivity) getContext()).getStorageDirectories(), guessBaseDir(), message); @@ -204,7 +201,9 @@ public class BRouterView extends View { // new init is done move old files if (waitingForMigration) { - moveFolders(oldMigrationPath, basedir + "/brouter"); + Log.d("BR", "path " + oldMigrationPath + " " + basedir); + if (!oldMigrationPath.equals(basedir + "/brouter")) + moveFolders(oldMigrationPath, basedir + "/brouter"); waitingForMigration = false; } @@ -254,7 +253,7 @@ public class BRouterView extends View { } } if (tracksDir == null) { - tracksDir = new File(basedir, "router"); // fallback + tracksDir = new File(basedir, "brouter"); // fallback } String[] fileNames = profileDir.list(); @@ -569,7 +568,7 @@ public class BRouterView extends View { // for profile remote, use ref-track logic same as service interface rc.rawTrackPath = rawTrackPath; - cr = new RoutingEngine(tracksDir + "/brouter", null, segmentDir, wpList, rc); + cr = new RoutingEngine(tracksDir.getAbsolutePath()+"/brouter", null, segmentDir, wpList, rc); cr.start(); invalidate(); @@ -606,16 +605,18 @@ public class BRouterView extends View { break; String name = ze.getName(); File outfile = new File(path, name); - outfile.getParentFile().mkdirs(); - FileOutputStream fos = new FileOutputStream(outfile); + if (!outfile.exists()) { + outfile.getParentFile().mkdirs(); + FileOutputStream fos = new FileOutputStream(outfile); - for (; ; ) { - int len = zis.read(data, 0, 1024); - if (len < 0) - break; - fos.write(data, 0, len); + for (; ; ) { + int len = zis.read(data, 0, 1024); + if (len < 0) + break; + fos.write(data, 0, len); + } + fos.close(); } - fos.close(); } is.close(); return true; @@ -690,6 +691,8 @@ public class BRouterView extends View { @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { + imgw = w; + imgh = h; } private void toast(String msg) { diff --git a/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java b/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java deleted file mode 100644 index c1a0c0a..0000000 --- a/brouter-routing-app/src/main/java/btools/routingapp/DownloadService.java +++ /dev/null @@ -1,487 +0,0 @@ -package btools.routingapp; - -import android.app.NotificationManager; -import android.app.Service; -import android.content.Intent; -import android.net.TrafficStats; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.util.Log; -import android.widget.Toast; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.List; - -import btools.mapaccess.PhysicalFile; -import btools.mapaccess.Rd5DiffManager; -import btools.mapaccess.Rd5DiffTool; -import btools.util.ProgressListener; - -public class DownloadService extends Service implements ProgressListener { - - private static final boolean DEBUG = false; - - private ServerConfig mServerConfig; - - private NotificationHelper mNotificationHelper; - private List mUrlList; - private String baseDir; - - private volatile String newDownloadAction = ""; - private volatile String currentDownloadOperation = ""; - private long availableSize; - - private 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; - mServerConfig = new ServerConfig(getApplicationContext()); - - 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 { - availableSize = BInstallerActivity.getAvailableSpace(baseDir); - //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; - } - - 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 profiles - String result = checkScripts(); - if (result != null) { - if (DEBUG) Log.d("BR", "error: " + result); - bIsDownloading = false; - updateProgress("finished "); - - Toast.makeText(this, result, Toast.LENGTH_LONG).show(); - return; - } - - - int count = 1; - int size = mUrlList.size(); - for (String part : mUrlList) { - String url = mServerConfig.getSegmentUrl() + part + ".rd5"; - if (DEBUG) Log.d("BR", "downlaod " + url); - - result = download(count, size, url); - if (result != null) { - if (DEBUG) Log.d("BR", "" + result); - Toast.makeText(this, result, Toast.LENGTH_LONG).show(); - break; - } else { - updateProgress("Download " + part + " " + count + "/" + size + " finshed"); - } - 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.setConnectTimeout(5000); - connection.connect(); - - // 404 kind of expected here, means there's no delta file - if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) { - connection = null; - } else { - updateProgress("Connecting.." + surlDelta); - } - } - - if (connection == null) { - updateProgress("Connecting.." + name); - - delta = false; - URL url = new URL(surl); - connection = (HttpURLConnection) url.openConnection(); - connection.setConnectTimeout(5000); - 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 (fname.exists()) fname.delete(); - if (!tmp_file.renameTo(fname)) { - return "Could not rename to " + fname.getAbsolutePath(); - } - - } - return null; - } catch (Exception e) { - //e.printStackTrace(); ; - return e.toString(); - } finally { - try { - if (output != null) - output.close(); - if (input != null) - input.close(); - } catch (IOException ignored) { - } - - if (connection != null) - connection.disconnect(); - } - } finally { - if (tmp_file != null) tmp_file.delete(); // just to be sure - } - } - - private String checkScripts() { - - String[] sa = mServerConfig.getLookups(); - for (String f : sa) { - if (f.length() > 0) { - File file = new File(baseDir + "profiles2", f); - checkOrDownloadLookup(f, file); - } - } - - sa = mServerConfig.getProfiles(); - for (String f : sa) { - if (f.length() > 0) { - File file = new File(baseDir + "profiles2", f); - if (file.exists()) { - String result = checkOrDownloadScript(f, file); - if (result != null) { - return result; - } - } - } - } - return null; - } - - private String checkOrDownloadLookup(String fileName, File f) { - String url = mServerConfig.getLookupUrl() + fileName; - return downloadScript(url, f); - } - - private String checkOrDownloadScript(String fileName, File f) { - String url = mServerConfig.getProfilesUrl() + fileName; - return downloadScript(url, f); - } - - private String downloadScript(String surl, File f) { - long size = 0L; - if (f.exists()) { - size = f.length(); - } - - InputStream input = null; - OutputStream output = null; - HttpURLConnection connection = null; - File tmp_file = null; - File targetFile = f; - - try { - try { - TrafficStats.setThreadStatsTag(1); - - URL url = new URL(surl); - connection = (HttpURLConnection) url.openConnection(); - connection.setConnectTimeout(5000); - connection.connect(); - - // expect HTTP 200 OK, so we don't mistakenly save error report - // instead of the file - if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) { - return null; - } - if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { - return "Server returned HTTP " + connection.getResponseCode() - + " " + connection.getResponseMessage() + " " + f.getName(); - } - - - // this will be useful to display download percentage - // might be -1: server did not report the length - long fileLength = (long) connection.getContentLength(); - if (DEBUG) Log.d("BR", "file size " + size + " == " + fileLength + " " + f.getName()); - if (fileLength != size) { - 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/DownloadWorker.java b/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java new file mode 100644 index 0000000..0b493fe --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/DownloadWorker.java @@ -0,0 +1,327 @@ +package btools.routingapp; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.os.Build; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import androidx.core.app.NotificationCompat; +import androidx.work.Data; +import androidx.work.ForegroundInfo; +import androidx.work.WorkManager; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Random; + +import btools.mapaccess.PhysicalFile; +import btools.mapaccess.Rd5DiffManager; +import btools.mapaccess.Rd5DiffTool; +import btools.util.ProgressListener; + +public class DownloadWorker extends Worker { + public static final String KEY_INPUT_SEGMENT_NAMES = "SEGMENT_NAMES"; + public static final String PROGRESS_SEGMENT_NAME = "PROGRESS_SEGMENT_NAME"; + public static final String PROGRESS_SEGMENT_PERCENT = "PROGRESS_SEGMENT_PERCENT"; + + private final static boolean DEBUG = false; + private static final int NOTIFICATION_ID = new Random().nextInt(); + private static final String PROFILES_DIR = "profiles2/"; + private static final String SEGMENTS_DIR = "segments4/"; + private static final String SEGMENT_DIFF_SUFFIX = ".df5"; + private static final String SEGMENT_SUFFIX = ".rd5"; + private static final String LOG_TAG = "DownloadWorker"; + + private final NotificationManager notificationManager; + private final ServerConfig mServerConfig; + private final File baseDir; + private final ProgressListener diffProgressListener; + private final DownloadProgressListener downloadProgressListener; + private final Data.Builder progressBuilder = new Data.Builder(); + private final NotificationCompat.Builder notificationBuilder; + + public DownloadWorker( + @NonNull Context context, + @NonNull WorkerParameters parameters) { + super(context, parameters); + notificationManager = (NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); + mServerConfig = new ServerConfig(context); + baseDir = new File(ConfigHelper.getBaseDir(context), "brouter"); + + notificationBuilder = createNotificationBuilder(); + + downloadProgressListener = new DownloadProgressListener() { + private String currentDownloadName; + private DownloadType currentDownloadType; + private int lastProgressPercent; + + @Override + public void onDownloadStart(String downloadName, DownloadType downloadType) { + currentDownloadName = downloadName; + currentDownloadType = downloadType; + if (downloadType == DownloadType.SEGMENT) { + progressBuilder.putString(PROGRESS_SEGMENT_NAME, downloadName); + notificationBuilder.setContentText(downloadName); + } + } + + @Override + public void onDownloadInfo(String info) { + notificationBuilder.setContentText(currentDownloadName + ": " + info); + notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); + } + + @Override + public void onDownloadProgress(int max, int progress) { + int progressPercent = (int) (progress * 100L / max); + + // Only report segments and update if it changed to avoid hammering NotificationManager + if (currentDownloadType != DownloadType.SEGMENT || progressPercent == lastProgressPercent) { + return; + } + + if (max > 0) { + notificationBuilder.setProgress(max, progress, false); + progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, progressPercent); + } else { + notificationBuilder.setProgress(0, 0, true); + progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, -1); + } + notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); + setProgressAsync(progressBuilder.build()); + + lastProgressPercent = progressPercent; + } + + @Override + public void onDownloadFinished() { + } + }; + + diffProgressListener = new ProgressListener() { + @Override + public void updateProgress(String task, int progress) { + downloadProgressListener.onDownloadInfo(task); + downloadProgressListener.onDownloadProgress(100, progress); + } + + @Override + public boolean isCanceled() { + return isStopped(); + } + }; + } + + @NonNull + @Override + public Result doWork() { + Data inputData = getInputData(); + String[] segmentNames = inputData.getStringArray(KEY_INPUT_SEGMENT_NAMES); + if (segmentNames == null) { + if (DEBUG) Log.d(LOG_TAG, "Failure: no segmentNames"); + return Result.failure(); + } + notificationBuilder.setContentText("Starting Download"); + // Mark the Worker as important + setForegroundAsync(new ForegroundInfo(NOTIFICATION_ID, notificationBuilder.build())); + try { + if (DEBUG) Log.d(LOG_TAG, "Download lookup & profiles"); + downloadLookupAndProfiles(); + + for (String segmentName : segmentNames) { + downloadProgressListener.onDownloadStart(segmentName, DownloadType.SEGMENT); + if (DEBUG) Log.d(LOG_TAG, "Download segment " + segmentName); + downloadSegment(mServerConfig.getSegmentUrl(), segmentName + SEGMENT_SUFFIX); + } + } catch (IOException e) { + Log.w(LOG_TAG, e); + return Result.failure(); + } catch (InterruptedException e) { + Log.w(LOG_TAG, e); + return Result.failure(); + } + if (DEBUG) Log.d(LOG_TAG, "doWork finished"); + return Result.success(); + } + + private void downloadLookupAndProfiles() throws IOException, InterruptedException { + String[] lookups = mServerConfig.getLookups(); + for (String fileName : lookups) { + if (fileName.length() > 0) { + File lookupFile = new File(baseDir, PROFILES_DIR + fileName); + String lookupLocation = mServerConfig.getLookupUrl() + fileName; + URL lookupUrl = new URL(lookupLocation); + downloadProgressListener.onDownloadStart(fileName, DownloadType.LOOKUP); + downloadFile(lookupUrl, lookupFile, false); + downloadProgressListener.onDownloadFinished(); + } + } + + String[] profiles = mServerConfig.getProfiles(); + for (String fileName : profiles) { + if (fileName.length() > 0) { + File profileFile = new File(baseDir, PROFILES_DIR + fileName); + if (profileFile.exists()) { + String profileLocation = mServerConfig.getProfilesUrl() + fileName; + URL profileUrl = new URL(profileLocation); + downloadProgressListener.onDownloadStart(fileName, DownloadType.PROFILE); + downloadFile(profileUrl, profileFile, false); + downloadProgressListener.onDownloadFinished(); + } + } + } + } + + private void downloadSegment(String segmentBaseUrl, String segmentName) throws IOException, InterruptedException { + File segmentFile = new File(baseDir, SEGMENTS_DIR + segmentName); + File segmentFileTemp = new File(segmentFile.getAbsolutePath() + "_tmp"); + try { + if (segmentFile.exists()) { + if (DEBUG) Log.d(LOG_TAG, "Calculating local checksum"); + String md5 = Rd5DiffManager.getMD5(segmentFile); + String segmentDeltaLocation = segmentBaseUrl + "diff/" + segmentName.replace(SEGMENT_SUFFIX, "/" + md5 + SEGMENT_DIFF_SUFFIX); + URL segmentDeltaUrl = new URL(segmentDeltaLocation); + if (httpFileExists(segmentDeltaUrl)) { + File segmentDeltaFile = new File(segmentFile.getAbsolutePath() + "_diff"); + try { + downloadFile(segmentDeltaUrl, segmentDeltaFile, true); + if (DEBUG) Log.d(LOG_TAG, "Applying delta"); + Rd5DiffTool.recoverFromDelta(segmentFile, segmentDeltaFile, segmentFileTemp, diffProgressListener); + } catch (IOException e) { + throw new IOException("Failed to download & apply delta update", e); + } finally { + segmentDeltaFile.delete(); + } + } + } + + if (!segmentFileTemp.exists()) { + URL segmentUrl = new URL(segmentBaseUrl + segmentName); + downloadFile(segmentUrl, segmentFileTemp, true); + } + + PhysicalFile.checkFileIntegrity(segmentFileTemp); + if (segmentFile.exists()) { + if (!segmentFile.delete()) { + throw new IOException("Failed to delete existing " + segmentFile.getAbsolutePath()); + } + } + + if (!segmentFileTemp.renameTo(segmentFile)) { + throw new IOException("Failed to write " + segmentFile.getAbsolutePath()); + } + } finally { + segmentFileTemp.delete(); + } + } + + private boolean httpFileExists(URL downloadUrl) throws IOException { + HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); + connection.setConnectTimeout(5000); + connection.setRequestMethod("HEAD"); + connection.connect(); + + return connection.getResponseCode() == HttpURLConnection.HTTP_OK; + } + + private void downloadFile(URL downloadUrl, File outputFile, boolean limitDownloadSpeed) throws IOException, InterruptedException { + HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection(); + connection.setConnectTimeout(5000); + connection.connect(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new IOException("HTTP Request failed"); + } + int fileLength = connection.getContentLength(); + try ( + InputStream input = connection.getInputStream(); + OutputStream output = new FileOutputStream(outputFile) + ) { + byte[] buffer = new byte[4096]; + int total = 0; + long t0 = System.currentTimeMillis(); + int count; + while ((count = input.read(buffer)) != -1) { + if (isStopped()) { + throw new InterruptedException(); + } + total += count; + output.write(buffer, 0, count); + + downloadProgressListener.onDownloadProgress(fileLength, total); + + if (limitDownloadSpeed) { + // enforce < 16 Mbit/s + long dt = t0 + total / 2096 - System.currentTimeMillis(); + if (dt > 0) { + Thread.sleep(dt); + } + } + } + } + } + + @NonNull + private NotificationCompat.Builder createNotificationBuilder() { + Context context = getApplicationContext(); + String id = context.getString(R.string.notification_channel_id); + String title = context.getString(R.string.notification_title); + String cancel = context.getString(R.string.cancel_download); + // This PendingIntent can be used to cancel the worker + PendingIntent intent = WorkManager.getInstance(context) + .createCancelPendingIntent(getId()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createChannel(); + } + + return new NotificationCompat.Builder(context, id) + .setContentTitle(title) + .setTicker(title) + .setOnlyAlertOnce(true) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setSmallIcon(android.R.drawable.stat_sys_download) + .setOngoing(true) + // Add the cancel action to the notification which can + // be used to cancel the worker + .addAction(android.R.drawable.ic_delete, cancel, intent); + } + + @RequiresApi(Build.VERSION_CODES.O) + private void createChannel() { + CharSequence name = getApplicationContext().getString(R.string.channel_name); + int importance = NotificationManager.IMPORTANCE_LOW; + NotificationChannel channel = new NotificationChannel(getApplicationContext().getString(R.string.notification_channel_id), name, importance); + // Register the channel with the system; you can't change the importance + // or other notification behaviors after this + notificationManager.createNotificationChannel(channel); + } + + enum DownloadType { + LOOKUP, + PROFILE, + SEGMENT + } + + interface DownloadProgressListener { + void onDownloadStart(String downloadName, DownloadType downloadType); + + void onDownloadInfo(String info); + + void onDownloadProgress(int max, int progress); + + void onDownloadFinished(); + } +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/NotificationHelper.java b/brouter-routing-app/src/main/java/btools/routingapp/NotificationHelper.java deleted file mode 100644 index a97f296..0000000 --- a/brouter-routing-app/src/main/java/btools/routingapp/NotificationHelper.java +++ /dev/null @@ -1,134 +0,0 @@ -package btools.routingapp; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.media.AudioAttributes; -import android.os.Build; -import android.util.Log; - -import androidx.core.app.NotificationCompat; - - -import static android.content.Context.NOTIFICATION_SERVICE; - -public class NotificationHelper { - - private static final boolean DEBUG = false; - - public static String BRouterNotificationChannel1 = "brouter_channel_01"; - - private Context mContext; - private int NOTIFICATION_ID = 111; - private Notification mNotification; - private NotificationManager mNotificationManager; - private PendingIntent mContentIntent; - private CharSequence mContentTitle; - - public NotificationHelper(Context context) { - if (DEBUG) Log.d("NH", "init "); - mContext = context; - createNotificationChannels(); - } - - public void startNotification(Service service) { - if (DEBUG) Log.d("NH", "startNotification "); - - mNotification = createNotification("BRouter Download", "Download some files"); - - if (service != null) service.startForeground(NOTIFICATION_ID, mNotification); - - mNotificationManager.notify(NOTIFICATION_ID, mNotification); - - } - - public void progressUpdate(String text) { - mNotification = createNotification("BRouter Download", text); - mNotification.flags = Notification.FLAG_NO_CLEAR | - Notification.FLAG_ONGOING_EVENT; - - mNotificationManager.notify(NOTIFICATION_ID, mNotification); - } - - - public Notification createNotification(String title, String desc) { - - Intent resultIntent = new Intent(mContext, BInstallerActivity.class); - - Intent notificationIntent = new Intent(); - mContentIntent = PendingIntent.getActivity(mContext, 0, resultIntent, PendingIntent.FLAG_IMMUTABLE); - - mNotificationManager = (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - - - final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, BRouterNotificationChannel1); - builder.setSmallIcon(android.R.drawable.stat_sys_download) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setContentTitle(title) - .setContentText(desc) - .setTicker(desc) - .setOngoing(true) - .setAutoCancel(true) - .setOnlyAlertOnce(true) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .setContentIntent(mContentIntent); - - return builder.build(); - - } else { - final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); - builder.setSmallIcon(android.R.drawable.stat_sys_download) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setContentTitle(title) - .setContentText(desc) - .setOnlyAlertOnce(true) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .setContentIntent(mContentIntent); - - return builder.build(); - } - - } - - /** - * create notification channels - */ - public void createNotificationChannels() { - if (DEBUG) Log.d("NH", "createNotificationChannels "); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - - NotificationManager sNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - // Sound channel - CharSequence name = "BRouter Download"; - // The user-visible description of the channel. - String description = "BRouter Download Channel"; //getString(R.string.channel_description); - - NotificationChannel channel = new NotificationChannel(BRouterNotificationChannel1, name, NotificationManager.IMPORTANCE_LOW); - channel.setDescription(description); - AudioAttributes att = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_UNKNOWN) - .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN) - .build(); - channel.setSound(null, null); - channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); - - sNotificationManager.createNotificationChannel(channel); - - } - } - - public void stopNotification() { - if (DEBUG) Log.d("NH", "stopNotification "); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mNotificationManager.deleteNotificationChannel(BRouterNotificationChannel1); - } - mNotificationManager.cancel(NOTIFICATION_ID); - } -} diff --git a/brouter-routing-app/src/main/res/layout/activity_binstaller.xml b/brouter-routing-app/src/main/res/layout/activity_binstaller.xml new file mode 100644 index 0000000..5f0e7c1 --- /dev/null +++ b/brouter-routing-app/src/main/res/layout/activity_binstaller.xml @@ -0,0 +1,45 @@ + + + + + +