diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 5f350cb5..e5726f2d 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -28,7 +28,19 @@ jobs: run: chmod +x gradlew - name: Build with Gradle - run: ./gradlew build --stacktrace + run: ./gradlew assembleDebug --stacktrace + + - name: Rename output APK + run: | + DATE=$(date +'%Y%m%d-%H%M') + mv app/build/outputs/apk/debug/app-debug.apk app/build/outputs/apk/debug/OSMTracker-debug-$DATE.apk + echo "ARTIFACT_DATE=$DATE" >> $GITHUB_ENV + + - name: Upload a Build Artifact + uses: actions/upload-artifact@v4 + with: + name: debug-${{ env.ARTIFACT_DATE }} + path: app/build/outputs/apk/debug/OSMTracker-debug-*.apk - name: Run unit tests and jacoco coverage run: ./gradlew testDebugUnitTest jacocoTestReport --stacktrace diff --git a/app/build.gradle b/app/build.gradle index ae56b488..c66d293b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -91,7 +91,7 @@ dependencies { //implementation 'org.apache.httpcomponents:httpmime:4.5.6' - implementation 'org.osmdroid:osmdroid-android:6.1.5' + implementation 'org.osmdroid:osmdroid-android:6.1.20' //implementation 'org.apache.httpcomponents:httpcore:4.4.13' //implementation 'oauth.signpost:signpost-commonshttp4:1.2.1.2' diff --git a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java index 4ee496f6..678db42a 100644 --- a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java +++ b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java @@ -1,24 +1,5 @@ package net.osmtracker.activity; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - -import net.osmtracker.OSMTracker; -import net.osmtracker.R; -import net.osmtracker.db.TrackContentProvider; -import net.osmtracker.overlay.WayPointsOverlay; - -import org.osmdroid.api.IMapController; -import org.osmdroid.config.Configuration; -import org.osmdroid.tileprovider.tilesource.ITileSource; -import org.osmdroid.tileprovider.tilesource.TileSourceFactory; -import org.osmdroid.util.GeoPoint; -import org.osmdroid.views.MapView; -import org.osmdroid.views.overlay.PathOverlay; -import org.osmdroid.views.overlay.mylocation.SimpleLocationOverlay; -import org.osmdroid.views.overlay.ScaleBarOverlay; - import android.app.Activity; import android.content.ContentUris; import android.content.Intent; @@ -26,6 +7,7 @@ import android.database.ContentObserver; import android.database.Cursor; import android.graphics.Color; +import android.graphics.Paint; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; @@ -35,8 +17,23 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; + +import net.osmtracker.OSMTracker; +import net.osmtracker.R; +import net.osmtracker.db.TrackContentProvider; +import net.osmtracker.overlay.WayPointsOverlay; + +import org.osmdroid.api.IMapController; +import org.osmdroid.config.Configuration; +import org.osmdroid.tileprovider.tilesource.TileSourceFactory; +import org.osmdroid.util.GeoPoint; +import org.osmdroid.views.MapView; +import org.osmdroid.views.overlay.Polyline; +import org.osmdroid.views.overlay.ScaleBarOverlay; +import org.osmdroid.views.overlay.mylocation.SimpleLocationOverlay; + +import java.util.ArrayList; +import java.util.List; /** * Display current track over an OSM map. @@ -51,33 +48,30 @@ public class DisplayTrackMap extends Activity { private static final String TAG = DisplayTrackMap.class.getSimpleName(); - + /** * Key for keeping the zoom level in the saved instance bundle */ private static final String CURRENT_ZOOM = "currentZoom"; /** - * Key for keeping scrolled left position of OSM view activity re-creation - * + * Key for keeping scrolled left position of OSM view activity re-creation */ private static final String CURRENT_SCROLL_X = "currentScrollX"; - /** + /** * Key for keeping scrolled top position of OSM view across activity re-creation - * - */ + */ private static final String CURRENT_SCROLL_Y = "currentScrollY"; /** - * Key for keeping whether the map display should be centered to the gps location - * + * Key for keeping whether the map display should be centered to the gps location */ private static final String CURRENT_CENTER_TO_GPS_POS = "currentCenterToGpsPos"; /** - * Key for keeping whether the map display was zoomed and centered - * on an old track id loaded from the database (boolean {@link #zoomedToTrackAlready}) + * Key for keeping whether the map display was zoomed and centered + * on an old track id loaded from the database (boolean {@link #zoomedToTrackAlready}) */ private static final String CURRENT_ZOOMED_TO_TRACK = "currentZoomedToTrack"; @@ -89,30 +83,30 @@ public class DisplayTrackMap extends Activity { /** * Default zoom level */ - private static final int DEFAULT_ZOOM = 16; + private static final int DEFAULT_ZOOM = 16; /** * Main OSM view */ private MapView osmView; - + /** * Controller to interact with view */ private IMapController osmViewController; - + /** * OSM view overlay that displays current location */ private SimpleLocationOverlay myLocationOverlay; - + /** * OSM view overlay that displays current path */ - private PathOverlay pathOverlay; + private Polyline polyline; /** - * OSM view overlay that displays waypoints + * OSM view overlay that displays waypoints */ private WayPointsOverlay wayPointsOverlay; @@ -120,23 +114,23 @@ public class DisplayTrackMap extends Activity { * OSM view overlay for the map scale bar */ private ScaleBarOverlay scaleBarOverlay; - + /** * Current track id */ private long currentTrackId; - + /** - * whether the map display should be centered to the gps location + * whether the map display should be centered to the gps location */ private boolean centerToGpsPos = true; - + /** * whether the map display was already zoomed and centered * on an old track loaded from the database (should be done only once). */ private boolean zoomedToTrackAlready = false; - + /** * the last position we know */ @@ -145,12 +139,12 @@ public class DisplayTrackMap extends Activity { /** * The row id of the last location read from the database that has been added to the * list of layout points. Using this we to reduce DB load by only reading new points. - * Initially null, to indicate that no data has yet been read. + * Initially null, to indicate that no data has yet been read. */ private Integer lastTrackPointIdProcessed = null; - + /** - * Observes changes on trackpoints + * Observes changes on track points */ private ContentObserver trackpointContentObserver; @@ -173,14 +167,16 @@ public void onCreate(Bundle savedInstanceState) { // Initialize OSM view Configuration.getInstance().load(this, prefs); - osmView = (MapView) findViewById(R.id.displaytrackmap_osmView); - osmView.setMultiTouchControls(true); // pinch to zoom + + osmView = findViewById(R.id.displaytrackmap_osmView); + // pinch to zoom + osmView.setMultiTouchControls(true); // we'll use osmView to define if the screen is always on or not osmView.setKeepScreenOn(prefs.getBoolean(OSMTracker.Preferences.KEY_UI_DISPLAY_KEEP_ON, OSMTracker.Preferences.VAL_UI_DISPLAY_KEEP_ON)); osmViewController = osmView.getController(); // Check if there is a saved zoom level - if(savedInstanceState != null) { + if (savedInstanceState != null) { osmViewController.setZoom(savedInstanceState.getInt(CURRENT_ZOOM, DEFAULT_ZOOM)); osmView.scrollTo(savedInstanceState.getInt(CURRENT_SCROLL_X, 0), savedInstanceState.getInt(CURRENT_SCROLL_Y, 0)); @@ -194,11 +190,11 @@ public void onCreate(Bundle savedInstanceState) { selectTileSource(); - setTileDpiScaling(); + setTileDpiScaling(); createOverlays(); - // Create content observer for trackpoints + // Create content observer for track points trackpointContentObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { @@ -207,18 +203,8 @@ public void onChange(boolean selfChange) { }; // Register listeners for zoom buttons - findViewById(R.id.displaytrackmap_imgZoomIn).setOnClickListener( new OnClickListener() { - @Override - public void onClick(View v) { - osmViewController.zoomIn(); - } - }); - findViewById(R.id.displaytrackmap_imgZoomOut).setOnClickListener( new OnClickListener() { - @Override - public void onClick(View v) { - osmViewController.zoomOut(); - } - }); + findViewById(R.id.displaytrackmap_imgZoomIn).setOnClickListener(v -> osmViewController.zoomIn()); + findViewById(R.id.displaytrackmap_imgZoomOut).setOnClickListener(v -> osmViewController.zoomOut()); } /** @@ -231,14 +217,14 @@ public void selectTileSource() { osmView.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE); } - /** - * Make text on map better readable on high DPI displays - */ - public void setTileDpiScaling () { - osmView.setTilesScaledToDpi(true); - } + /** + * Make text on map better readable on high DPI displays + */ + public void setTileDpiScaling() { + osmView.setTilesScaledToDpi(true); + } + - // /** // * Returns a ITileSource for the map according to the selected mapTile // * String. The default is mapnik. @@ -268,20 +254,17 @@ protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } - @Override protected void onResume() { - super.onResume(); resumeActivity(); - } - private void resumeActivity(){ + private void resumeActivity() { // setKeepScreenOn depending on user's preferences osmView.setKeepScreenOn(prefs.getBoolean(OSMTracker.Preferences.KEY_UI_DISPLAY_KEEP_ON, OSMTracker.Preferences.VAL_UI_DISPLAY_KEEP_ON)); - // Register content observer for any trackpoint changes + // Register content observer for any track point changes getContentResolver().registerContentObserver( TrackContentProvider.trackPointsUri(currentTrackId), true, trackpointContentObserver); @@ -297,19 +280,18 @@ private void resumeActivity(){ selectTileSource(); setTileDpiScaling(); - + // Refresh way points wayPointsOverlay.refresh(); - } @Override protected void onPause() { // Unregister content observer getContentResolver().unregisterContentObserver(trackpointContentObserver); - + // Clear the points list. - pathOverlay.clearPath(); + polyline.setPoints(new ArrayList<>()); super.onPause(); } @@ -322,46 +304,43 @@ protected void onStop() { SharedPreferences settings = getPreferences(MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); editor.putInt(LAST_ZOOM, osmView.getZoomLevel()); - editor.commit(); + editor.apply(); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.displaytrackmap_menu, menu); + inflater.inflate(R.menu.displaytrackmap_menu, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onPrepareOptionsMenu(Menu menu) { - menu.findItem(R.id.displaytrackmap_menu_center_to_gps).setEnabled( (!centerToGpsPos && currentPosition != null ) ); + menu.findItem(R.id.displaytrackmap_menu_center_to_gps).setEnabled((!centerToGpsPos && currentPosition != null)); return super.onPrepareOptionsMenu(menu); } - - @Override public boolean onOptionsItemSelected(MenuItem item) { - switch(item.getItemId()){ - case R.id.displaytrackmap_menu_center_to_gps: - centerToGpsPos = true; - if(currentPosition != null){ - osmViewController.animateTo(currentPosition); - } - break; - case R.id.displaytrackmap_menu_settings: - // Start settings activity - startActivity(new Intent(this, Preferences.class)); - break; + switch (item.getItemId()) { + case R.id.displaytrackmap_menu_center_to_gps: + centerToGpsPos = true; + if (currentPosition != null) { + osmViewController.animateTo(currentPosition); + } + break; + case R.id.displaytrackmap_menu_settings: + // Start settings activity + startActivity(new Intent(this, Preferences.class)); + break; } return super.onOptionsItemSelected(item); } - @Override public boolean onTouchEvent(MotionEvent event) { - switch(event.getAction()){ + switch (event.getAction()) { case MotionEvent.ACTION_MOVE: if (currentPosition != null) centerToGpsPos = false; @@ -370,7 +349,6 @@ public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); } - /** * Creates overlays over the OSM view */ @@ -379,20 +357,22 @@ private void createOverlays() { this.getWindowManager().getDefaultDisplay().getMetrics(metrics); // set with to hopefully DPI independent 0.5mm - pathOverlay = new PathOverlay(Color.BLUE, (float)(metrics.densityDpi / 25.4 / 2),this); + polyline = new Polyline(); + Paint paint = polyline.getOutlinePaint(); + paint.setColor(Color.BLUE); + paint.setStrokeWidth((float) (metrics.densityDpi / 25.4 / 2)); + osmView.getOverlayManager().add(polyline); - osmView.getOverlays().add(pathOverlay); - myLocationOverlay = new SimpleLocationOverlay(this); osmView.getOverlays().add(myLocationOverlay); - + wayPointsOverlay = new WayPointsOverlay(this, currentTrackId); osmView.getOverlays().add(wayPointsOverlay); scaleBarOverlay = new ScaleBarOverlay(osmView); osmView.getOverlays().add(scaleBarOverlay); } - + /** * On track path changed, update the two overlays and repaint view. * If {@link #lastTrackPointIdProcessed} is null, this is the initial call @@ -404,7 +384,7 @@ private void pathChanged() { if (isFinishing()) { return; } - + // See if the track is active. // If not, we'll calculate initial track bounds // while retrieving from the database. @@ -412,18 +392,21 @@ private void pathChanged() { boolean doInitialBoundsCalc = false; double minLat = 91.0, minLon = 181.0; double maxLat = -91.0, maxLon = -181.0; - if ((! zoomedToTrackAlready) && (lastTrackPointIdProcessed == null)) { + if ((!zoomedToTrackAlready) && (lastTrackPointIdProcessed == null)) { final String[] proj_active = {TrackContentProvider.Schema.COL_ACTIVE}; Cursor cursor = getContentResolver().query( - ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, currentTrackId), - proj_active, null, null, null); - if (cursor.moveToFirst()) { - doInitialBoundsCalc = - (cursor.getInt(cursor.getColumnIndex(TrackContentProvider.Schema.COL_ACTIVE)) == TrackContentProvider.Schema.VAL_TRACK_INACTIVE); + ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, currentTrackId), + proj_active, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + int colIndex = cursor.getColumnIndex(TrackContentProvider.Schema.COL_ACTIVE); + if (colIndex != -1) { + doInitialBoundsCalc = + (cursor.getInt(colIndex) == TrackContentProvider.Schema.VAL_TRACK_INACTIVE); + } + cursor.close(); } - cursor.close(); } - + // Projection: The columns to retrieve. Here, we want the latitude, // longitude and primary key only String[] projection = {TrackContentProvider.Schema.COL_LATITUDE, TrackContentProvider.Schema.COL_LONGITUDE, TrackContentProvider.Schema.COL_ID}; @@ -431,71 +414,69 @@ private void pathChanged() { String selection = null; // SelectionArgs: The parameter replacements to use for the '?' in the selection String[] selectionArgs = null; - - // Only request the track points that we have not seen yet + + // Only request the track points that we have not seen yet // If we have processed any track points in this session then // lastTrackPointIdProcessed will not be null. We only want // to see data from rows with a primary key greater than lastTrackPointIdProcessed if (lastTrackPointIdProcessed != null) { selection = TrackContentProvider.Schema.COL_ID + " > ?"; - List<String> selectionArgsList = new ArrayList<String>(); + List<String> selectionArgsList = new ArrayList<>(); selectionArgsList.add(lastTrackPointIdProcessed.toString()); - - selectionArgs = selectionArgsList.toArray(new String[1]); + selectionArgs = selectionArgsList.toArray(new String[1]); } // Retrieve any points we have not yet seen Cursor c = getContentResolver().query( - TrackContentProvider.trackPointsUri(currentTrackId), - projection, selection, selectionArgs, TrackContentProvider.Schema.COL_ID + " asc"); - - int numberOfPointsRetrieved = c.getCount(); - if (numberOfPointsRetrieved > 0 ) { - c.moveToFirst(); - double lastLat = 0; - double lastLon = 0; - int primaryKeyColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_ID); - int latitudeColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_LATITUDE); - int longitudeColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_LONGITUDE); - - // Add each new point to the track - while(!c.isAfterLast()) { - lastLat = c.getDouble(latitudeColumnIndex); - lastLon = c.getDouble(longitudeColumnIndex); - lastTrackPointIdProcessed = c.getInt(primaryKeyColumnIndex); - pathOverlay.addPoint((int)(lastLat * 1e6), (int)(lastLon * 1e6)); - if (doInitialBoundsCalc) { - if (lastLat < minLat) minLat = lastLat; - if (lastLon < minLon) minLon = lastLon; - if (lastLat > maxLat) maxLat = lastLat; - if (lastLon > maxLon) maxLon = lastLon; + TrackContentProvider.trackPointsUri(currentTrackId), + projection, selection, selectionArgs, TrackContentProvider.Schema.COL_ID + " asc"); + + if (c != null) { + int numberOfPointsRetrieved = c.getCount(); + if (numberOfPointsRetrieved > 0) { + c.moveToFirst(); + double lastLat = 0; + double lastLon = 0; + int primaryKeyColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_ID); + int latitudeColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_LATITUDE); + int longitudeColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_LONGITUDE); + + // Add each new point to the track + while (!c.isAfterLast()) { + lastLat = c.getDouble(latitudeColumnIndex); + lastLon = c.getDouble(longitudeColumnIndex); + lastTrackPointIdProcessed = c.getInt(primaryKeyColumnIndex); + polyline.addPoint(new GeoPoint(lastLat, lastLon)); + if (doInitialBoundsCalc) { + if (lastLat < minLat) minLat = lastLat; + if (lastLon < minLon) minLon = lastLon; + if (lastLat > maxLat) maxLat = lastLat; + if (lastLon > maxLon) maxLon = lastLon; + } + c.moveToNext(); } - c.moveToNext(); - } - - // Last point is current position. - currentPosition = new GeoPoint(lastLat, lastLon); - myLocationOverlay.setLocation(currentPosition); - if(centerToGpsPos) { - osmViewController.setCenter(currentPosition); - } - - // Repaint - osmView.invalidate(); - if (doInitialBoundsCalc && (numberOfPointsRetrieved > 1)) { - // osmdroid-3.0.8 hangs if we directly call zoomToSpan during initial onResume, - // so post a Runnable instead for after it's done initializing. - final double north = maxLat, east = maxLon, south = minLat, west = minLon; - osmView.post(new Runnable() { - @Override - public void run() { - osmViewController.zoomToSpan((int) (north-south), (int) (east-west)); + + // Last point is current position. + currentPosition = new GeoPoint(lastLat, lastLon); + myLocationOverlay.setLocation(currentPosition); + if (centerToGpsPos) { + osmViewController.setCenter(currentPosition); + } + + // Repaint + osmView.invalidate(); + if (doInitialBoundsCalc && (numberOfPointsRetrieved > 1)) { + // osmdroid-3.0.8 hangs if we directly call zoomToSpan during initial onResume, + // so post a Runnable instead for after it's done initializing. + final double north = maxLat, east = maxLon, south = minLat, west = minLon; + osmView.post(() -> { + osmViewController.zoomToSpan((int) (north - south), (int) (east - west)); osmViewController.setCenter(new GeoPoint((north + south) / 2, (east + west) / 2)); zoomedToTrackAlready = true; - } - }); + }); + } } + c.close(); } - c.close(); } }