diff --git a/.travis.yml b/.travis.yml index 5b43b03b..291ca59c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,26 @@ -sudo: false +sudo: true language: android jdk: oraclejdk8 android: components: + - build-tools-28.0.3 + - android-28 - tools - - platform-tools - - tools - - build-tools-27.0.3 - - android-16 - - android-19 - - sys-img-armeabi-v7a-android-19 + - android-21 + - sys-img-armeabi-v7a-android-21 + +licenses: + - android-sdk-license-.+ + - '.+' + +before_install: +- mkdir "$ANDROID_HOME/licenses" || true +- yes | sdkmanager "platforms;android-27" before_script: - - echo no | android create avd --force -n test -c 100M -t android-19 --abi armeabi-v7a + - echo no | android create avd --force -n test -c 100M -t android-21 --abi armeabi-v7a - emulator -avd test -no-audio -no-window & - android-wait-for-emulator - adb shell input keyevent 82 & diff --git a/app/build.gradle b/app/build.gradle index 8451e3f0..2b72e632 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,15 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 27 + + useLibrary 'org.apache.http.legacy' + compileSdkVersion 28 buildToolsVersion = '28.0.3' defaultConfig { applicationId "net.osmtracker" minSdkVersion 16 - targetSdkVersion 27 + targetSdkVersion 28 multiDexEnabled true testApplicationId "net.osmtracker.test" @@ -49,17 +51,36 @@ android { abortOnError false } } + + defaultConfig { + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + } dependencies { + implementation 'org.apache.james:apache-mime4j-core:0.7.2' - implementation 'org.apache.httpcomponents:httpclient:4.2.1' - implementation 'org.apache.httpcomponents:httpcore:4.2.1' - implementation 'org.apache.httpcomponents:httpmime:4.2.1' - implementation 'org.osmdroid:osmdroid-android:5.6.5' + + implementation ('org.apache.httpcomponents:httpmime:4.3.6') + //implementation 'org.apache.httpcomponents:httpclient-android:4.3.5' + + implementation 'org.osmdroid:osmdroid-android:6.1.0' + implementation 'org.apache.httpcomponents:httpcore:4.4.10' + implementation 'oauth.signpost:signpost-commonshttp4:1.2.1.2' implementation 'org.slf4j:slf4j-android:1.7.25' - implementation "com.android.support:support-compat:27.1.1" + implementation "com.android.support:support-compat:28.0.0" + + // Required for local unit tests (JUnit 4 framework) + testImplementation 'junit:junit:4.12' + + // Required for instrumented tests + androidTestImplementation "com.android.support.test:runner:1.0.2" + androidTestImplementation "com.android.support.test.espresso:espresso-core:3.0.2" + androidTestImplementation "com.android.support.test:rules:1.0.2" + + compileOnly 'org.jbundle.util.osgi.wrapped:org.jbundle.util.osgi.wrapped.org.apache.http.client:4.1.2' } task copyNorwegianValues(type: Copy) { diff --git a/app/src/androidTest/assets/gpx/gpx-test.gpx b/app/src/androidTest/assets/gpx/gpx-test.gpx index 48014f7d..5509a41e 100644 --- a/app/src/androidTest/assets/gpx/gpx-test.gpx +++ b/app/src/androidTest/assets/gpx/gpx-test.gpx @@ -1,5 +1,5 @@ - + 5812.2 diff --git a/app/src/androidTest/java/net/osmtracker/test/activity/OSMUploadTest.java b/app/src/androidTest/java/net/osmtracker/test/activity/OSMUploadTest.java deleted file mode 100644 index 1f1af44c..00000000 --- a/app/src/androidTest/java/net/osmtracker/test/activity/OSMUploadTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package net.osmtracker.test.activity; - -import net.osmtracker.activity.OpenStreetMapUpload; - -import android.test.ActivityInstrumentationTestCase2; - -public class OSMUploadTest extends ActivityInstrumentationTestCase2 { - - public OSMUploadTest() { - super("net.osmtracker", OpenStreetMapUpload.class); - } - - @Override - protected void setUp() throws Exception { - // MockData.mockBigTrack(getInstrumentation().getContext(), 2000, 2000); - } - - public void test() { - System.out.println("Test"); - } -} diff --git a/app/src/androidTest/java/net/osmtracker/test/activity/TrackDetailTest.java b/app/src/androidTest/java/net/osmtracker/test/activity/TrackDetailTest.java deleted file mode 100644 index 17810990..00000000 --- a/app/src/androidTest/java/net/osmtracker/test/activity/TrackDetailTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package net.osmtracker.test.activity; - -import junit.framework.Assert; - -import net.osmtracker.activity.TrackDetail; -import net.osmtracker.db.TrackContentProvider; -import net.osmtracker.db.TrackContentProvider.Schema; -import net.osmtracker.test.util.MockData; - -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Intent; -import android.database.Cursor; -import android.test.ActivityInstrumentationTestCase2; -import android.test.UiThreadTest; -import android.widget.Button; -import android.widget.EditText; -import android.widget.Spinner; - -public class TrackDetailTest extends ActivityInstrumentationTestCase2 { - - private long trackId; - - public TrackDetailTest() { - super("net.osmtracker", TrackDetail.class); - } - - @Override - protected void setUp() throws Exception { - trackId = MockData.mockTrack(getInstrumentation().getContext()); - - Intent i = new Intent(); - i.putExtra(Schema.COL_TRACK_ID, trackId); - setActivityIntent(i); - } - - @UiThreadTest - public void testSave() { - - ContentResolver cr = getInstrumentation().getContext().getContentResolver(); - Cursor cursor = cr.query( - ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId), - null, null, null, null); - - Assert.assertTrue(cursor.moveToFirst()); - Assert.assertEquals("gpx-test", cursor.getString(cursor.getColumnIndex(Schema.COL_NAME))); - Assert.assertNull(cursor.getString(cursor.getColumnIndex(Schema.COL_DESCRIPTION))); - Assert.assertNull(cursor.getString(cursor.getColumnIndex(Schema.COL_TAGS))); - Assert.assertEquals("Private", cursor.getString(cursor.getColumnIndex(Schema.COL_OSM_VISIBILITY))); - cursor.close(); - - ((EditText) getActivity().findViewById(net.osmtracker.R.id.trackdetail_item_name)).setText("test name"); - ((EditText) getActivity().findViewById(net.osmtracker.R.id.trackdetail_item_description)).setText("test description"); - ((EditText) getActivity().findViewById(net.osmtracker.R.id.trackdetail_item_tags)).setText("test tags"); - ((Spinner) getActivity().findViewById(net.osmtracker.R.id.trackdetail_item_osm_visibility)).setSelection(1); - ((Button) getActivity().findViewById(net.osmtracker.R.id.trackdetail_btn_ok)).performClick(); - - cursor = cr.query( - ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId), - null, null, null, null); - - Assert.assertTrue(cursor.moveToFirst()); - Assert.assertEquals("test name", cursor.getString(cursor.getColumnIndex(Schema.COL_NAME))); - Assert.assertEquals("test description", cursor.getString(cursor.getColumnIndex(Schema.COL_DESCRIPTION))); - Assert.assertEquals("test tags", cursor.getString(cursor.getColumnIndex(Schema.COL_TAGS))); - Assert.assertEquals("Public", cursor.getString(cursor.getColumnIndex(Schema.COL_OSM_VISIBILITY))); - cursor.close(); - - } - - -} diff --git a/app/src/androidTest/java/net/osmtracker/test/gpx/ExportTrackTaskTest.java b/app/src/androidTest/java/net/osmtracker/test/gpx/ExportTrackTaskTest.java deleted file mode 100644 index 75890c56..00000000 --- a/app/src/androidTest/java/net/osmtracker/test/gpx/ExportTrackTaskTest.java +++ /dev/null @@ -1,122 +0,0 @@ -package net.osmtracker.test.gpx; - -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.database.Cursor; -import android.os.Environment; -import android.preference.PreferenceManager; -import android.provider.MediaStore; -import android.test.ActivityInstrumentationTestCase2; - -import junit.framework.Assert; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - -import net.osmtracker.OSMTracker; -import net.osmtracker.activity.TrackManager; -import net.osmtracker.db.DataHelper; -import net.osmtracker.gpx.ExportToStorageTask; -import net.osmtracker.test.util.MockData; - -public class ExportTrackTaskTest extends ActivityInstrumentationTestCase2 { - - private long trackId; - private File trackFile; - - - public ExportTrackTaskTest() { - super("net.osmtracker", TrackManager.class); - } - - @Override - protected void setUp() throws Exception { - // Delete file entry in media library - getActivity().getContentResolver().delete( - MediaStore.Files.getContentUri("external"), - MediaStore.Files.FileColumns.DATA + " LIKE ?", - new String[] {"%/net.osmtracker/gpx-test"}); - - Cursor cursor = getActivity().managedQuery( - MediaStore.Files.getContentUri("external"), - null, - MediaStore.Files.FileColumns.DATA + " LIKE ?", - new String[] {"%/net.osmtracker/gpx-test"}, - null); - Assert.assertEquals(0, cursor.getCount()); - - trackFile = new File(Environment.getExternalStorageDirectory(), "net.osmtracker/gpx-test.gpx"); - if (trackFile.exists()) { - Assert.assertTrue(trackFile.delete()); - } - - trackId = MockData.mockTrack(getActivity()); - - new DataHelper(getActivity()).stopTracking(trackId); - - // Ensure easy filename - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); - Editor e = prefs.edit(); - e.clear(); - e.putString(OSMTracker.Preferences.KEY_OUTPUT_FILENAME, OSMTracker.Preferences.VAL_OUTPUT_FILENAME_NAME); - e.putBoolean(OSMTracker.Preferences.KEY_OUTPUT_DIR_PER_TRACK, false); - e.putBoolean(OSMTracker.Preferences.KEY_OUTPUT_GPX_HDOP_APPROXIMATION, true); - Assert.assertTrue(e.commit()); - } - - public void test() throws Exception { - - new ExportToStorageTask(getActivity(), trackId).execute().get(); - - // Ensure file contents are OK - Assert.assertTrue(trackFile.exists()); - Assert.assertEquals( - readFully( - getInstrumentation().getContext().getAssets().open("gpx/gpx-test.gpx")), - readFully(new FileInputStream(trackFile))); - - // Ensure the media library has been refreshed - // We have to wait for the refresh Intent to be dispatched - long maxWaitTime = 1000 * 5; - long waited = 0; - Cursor c = null; - while (waited < maxWaitTime) { - c = getActivity().managedQuery( - MediaStore.Files.getContentUri("external"), - null, - MediaStore.Files.FileColumns.DATA + " LIKE ?", - new String[]{"%/net.osmtracker/gpx-test.gpx"}, - null); - if (c.moveToFirst()) { - break; - } - - Thread.sleep(250); - waited += 250; - } - - Assert.assertEquals(1, c.getCount()); - Assert.assertEquals(0, c.getInt(c.getColumnIndex(MediaStore.Files.FileColumns.MEDIA_TYPE))); - Assert.assertEquals("gpx-test", c.getString(c.getColumnIndex(MediaStore.Files.FileColumns.TITLE))); - - } - - private static String readFully(InputStream is) throws IOException { - BufferedReader reader = new BufferedReader( - new InputStreamReader(is)); - - StringBuilder sb = new StringBuilder(); - String line; - while( (line=reader.readLine()) != null ) { - sb.append(line).append(System.getProperty("line.separator")); - } - reader.close(); - - return sb.toString(); - } - -} diff --git a/app/src/androidTest/java/net/osmtracker/test/util/MockData.java b/app/src/androidTest/java/net/osmtracker/test/util/MockData.java deleted file mode 100644 index 673066d4..00000000 --- a/app/src/androidTest/java/net/osmtracker/test/util/MockData.java +++ /dev/null @@ -1,127 +0,0 @@ -package net.osmtracker.test.util; - -import java.util.Calendar; -import java.util.Locale; -import java.util.Random; -import java.util.TimeZone; -import java.util.UUID; - -import net.osmtracker.db.DataHelper; -import net.osmtracker.db.TrackContentProvider; -import net.osmtracker.db.TrackContentProvider.Schema; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.location.Location; -import android.net.Uri; - -public class MockData { - - private static Object[][] mockTrackPoints = new Object[][] { - {12.34, 56.78, 0.42f, 4321.7d, 45.8f }, - {21.57, 12.6, 0.24f, 12.1d, 12.6f } - }; - - private static Object[][] mockWayPoints = new Object[][] { - {34.12, 18.45, 0.25f, 5812.2d, 284.5f, 2, "wp1", "http://link1.com", "uuid1"}, - {43.76, 31.89, 0.61f, 75.4d, 127.4f, 6, "wp2", "http://link2.com", "uuid2"} - }; - - public static long mockTrack(Context context) { - // Use same date for everything so that the generated - // GPX file will always be the same - Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ENGLISH); - c.clear(); - c.set(Calendar.YEAR, 2012); - c.set(Calendar.MONTH, Calendar.MARCH); - c.set(Calendar.DAY_OF_MONTH, 12); - c.set(Calendar.HOUR_OF_DAY, 16); - c.set(Calendar.MINUTE, 46); - c.set(Calendar.SECOND, 38); - - - ContentResolver cr = context.getContentResolver(); - - // Create new track - ContentValues values = new ContentValues(); - values.put(Schema.COL_NAME, ""); - values.put(Schema.COL_START_DATE, c.getTime().getTime()); - values.put(Schema.COL_ACTIVE, Schema.VAL_TRACK_ACTIVE); - values.put(Schema.COL_NAME, "gpx-test"); - Uri trackUri = cr.insert(TrackContentProvider.CONTENT_URI_TRACK, values); - long trackId = ContentUris.parseId(trackUri); - - DataHelper helper = new DataHelper(context); - for (Object[] mock: mockTrackPoints) { - Location l = new Location("test"); - l.setLatitude((Double) mock[0]); - l.setLongitude((Double) mock[1]); - l.setAccuracy((Float) mock[2]); - l.setAltitude((Double) mock[3]); - l.setSpeed((Float) mock[4]); - l.setTime(c.getTime().getTime()); - helper.track(trackId, l, DataHelper.AZIMUTH_INVALID, 0); - } - - for (Object[] mock: mockWayPoints) { - Location l = new Location("test"); - l.setLatitude((Double) mock[0]); - l.setLongitude((Double) mock[1]); - l.setAccuracy((Float) mock[2]); - l.setAltitude((Double) mock[3]); - l.setSpeed((Float) mock[4]); - l.setTime(c.getTime().getTime()); - - helper.wayPoint(trackId, l, (Integer) mock[5], (String) mock[6], (String) mock[7], (String) mock[8], DataHelper.AZIMUTH_INVALID, 0); - } - - return trackId; - } - - public static long mockBigTrack(Context context, int numWayPoints, int numTrackPoints) { - ContentResolver cr = context.getContentResolver(); - - // Create new track - ContentValues values = new ContentValues(); - values.put(Schema.COL_NAME, ""); - values.put(Schema.COL_START_DATE, System.currentTimeMillis()); - values.put(Schema.COL_ACTIVE, Schema.VAL_TRACK_ACTIVE); - values.put(Schema.COL_NAME, "gpx-big-test"); - Uri trackUri = cr.insert(TrackContentProvider.CONTENT_URI_TRACK, values); - long trackId = ContentUris.parseId(trackUri); - - Random r = new Random(); - - DataHelper helper = new DataHelper(context); - for (int i=0; i + android:versionName="0.7.2" android:versionCode="48" android:installLocation="auto"> + - + + + + diff --git a/app/src/main/java/net/osmtracker/activity/ButtonsPresets.java b/app/src/main/java/net/osmtracker/activity/ButtonsPresets.java index 1c7202f3..686cbc34 100644 --- a/app/src/main/java/net/osmtracker/activity/ButtonsPresets.java +++ b/app/src/main/java/net/osmtracker/activity/ButtonsPresets.java @@ -104,8 +104,7 @@ private void initializeAttributes(){ listener = new CheckBoxChangedListener(); prefs = PreferenceManager.getDefaultSharedPreferences(this); layoutsFileNames = new Hashtable(); - storageDir = File.separator + prefs.getString(OSMTracker.Preferences.KEY_STORAGE_DIR, - OSMTracker.Preferences.VAL_STORAGE_DIR); + storageDir = File.separator + OSMTracker.Preferences.VAL_STORAGE_DIR; } private void listLayouts(LinearLayout rootLayout){ diff --git a/app/src/main/java/net/osmtracker/activity/DisplayTrack.java b/app/src/main/java/net/osmtracker/activity/DisplayTrack.java index 9d1ab35d..7ca24aed 100644 --- a/app/src/main/java/net/osmtracker/activity/DisplayTrack.java +++ b/app/src/main/java/net/osmtracker/activity/DisplayTrack.java @@ -5,13 +5,18 @@ import net.osmtracker.view.DisplayTrackView; import net.osmtracker.db.TrackContentProvider; +import android.Manifest; import android.app.Activity; import android.app.AlertDialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.os.Bundle; import android.preference.PreferenceManager; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; import android.view.ViewGroup.LayoutParams; /** diff --git a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java index ca12c120..02157b57 100644 --- a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java +++ b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java @@ -10,6 +10,7 @@ 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; @@ -27,6 +28,7 @@ import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; +import android.util.DisplayMetrics; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -154,22 +156,23 @@ public class DisplayTrackMap extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - + // loading the preferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - + setContentView(R.layout.displaytrackmap); - + currentTrackId = getIntent().getExtras().getLong(TrackContentProvider.Schema.COL_TRACK_ID); setTitle(getTitle() + ": #" + currentTrackId); - + // Initialize OSM view + Configuration.getInstance().load(this, prefs); osmView = (MapView) findViewById(R.id.displaytrackmap_osmView); osmView.setMultiTouchControls(true); // pinch to zoom // 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) { osmViewController.setZoom(savedInstanceState.getInt(CURRENT_ZOOM, DEFAULT_ZOOM)); @@ -182,9 +185,11 @@ public void onCreate(Bundle savedInstanceState) { SharedPreferences settings = getPreferences(MODE_PRIVATE); osmViewController.setZoom(settings.getInt(LAST_ZOOM, DEFAULT_ZOOM)); } - + selectTileSource(); + setTileDpiScaling(); + createOverlays(); // Create content observer for trackpoints @@ -194,7 +199,7 @@ public void onChange(boolean selfChange) { pathChanged(); } }; - + // Register listeners for zoom buttons findViewById(R.id.displaytrackmap_imgZoomIn).setOnClickListener( new OnClickListener() { @Override @@ -215,25 +220,36 @@ public void onClick(View v) { */ public void selectTileSource() { String mapTile = prefs.getString(OSMTracker.Preferences.KEY_UI_MAP_TILE, OSMTracker.Preferences.VAL_UI_MAP_TILE_MAPNIK); - osmView.setTileSource(selectMapTile(mapTile)); + Log.e("TileMapName active", mapTile); + //osmView.setTileSource(selectMapTile(mapTile)); + osmView.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE); } + + /** + * 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. - * - * @param mapTile String that is the name of the tile provider - * @return ITileSource with the selected Tile-Source - */ - private ITileSource selectMapTile(String mapTile) { - try { - Field f = TileSourceFactory.class.getField(mapTile); - return (ITileSource) f.get(null); - } catch (Exception e) { - Log.e(TAG, "Invalid tile source '"+mapTile+"'", e); - return TileSourceFactory.MAPNIK; - } - } +// /** +// * Returns a ITileSource for the map according to the selected mapTile +// * String. The default is mapnik. +// * +// * @param mapTile String that is the name of the tile provider +// * @return ITileSource with the selected Tile-Source +// */ +// private ITileSource selectMapTile(String mapTile) { +// try { +// Field f = TileSourceFactory.class.getField(mapTile); +// return (ITileSource) f.get(null); +// } catch (Exception e) { +// Log.e(TAG, "Invalid tile source '"+mapTile+"'", e); +// Log.e(TAG, "Default tile source selected: '" + TileSourceFactory.DEFAULT_TILE_SOURCE.name() +"'"); +// return TileSourceFactory.DEFAULT_TILE_SOURCE; +// } +// } @Override @@ -249,31 +265,38 @@ protected void onSaveInstanceState(Bundle outState) { @Override protected void onResume() { - + + super.onResume(); + 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 getContentResolver().registerContentObserver( TrackContentProvider.trackPointsUri(currentTrackId), true, trackpointContentObserver); - + // Forget the last waypoint read from the DB - // This ensures that all waypoints for the track will be reloaded + // This ensures that all waypoints for the track will be reloaded // from the database to populate the path layout lastTrackPointIdProcessed = null; - + // Reload path pathChanged(); selectTileSource(); + + setTileDpiScaling(); // Refresh way points - // wayPointsOverlay.refresh(); - - super.onResume(); + wayPointsOverlay.refresh(); + } - + @Override protected void onPause() { // Unregister content observer @@ -346,7 +369,12 @@ public boolean onTouchEvent(MotionEvent event) { * Creates overlays over the OSM view */ private void createOverlays() { - pathOverlay = new PathOverlay(Color.BLUE, this); + DisplayMetrics metrics = new DisplayMetrics(); + 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); + osmView.getOverlays().add(pathOverlay); myLocationOverlay = new SimpleLocationOverlay(this); diff --git a/app/src/main/java/net/osmtracker/activity/TrackLogger.java b/app/src/main/java/net/osmtracker/activity/TrackLogger.java index b96daf05..ab8ad2dd 100644 --- a/app/src/main/java/net/osmtracker/activity/TrackLogger.java +++ b/app/src/main/java/net/osmtracker/activity/TrackLogger.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Date; +import java.util.HashSet; import net.osmtracker.OSMTracker; import net.osmtracker.R; @@ -92,6 +93,11 @@ public class TrackLogger extends Activity { * Bundle state key for tracking flag. */ public static final String STATE_IS_TRACKING = "isTracking"; + + /** + * The character to separate the tags of a track + */ + public static final String TAG_SEPARATOR = ","; /** * Bundle state key button state. @@ -164,7 +170,11 @@ public class TrackLogger extends Activity { private ComponentName mediaButtonReceiver; - private ArrayList layoutNameTags = new ArrayList(); + + /* + * Avoid taking care of duplicated elements + */ + private HashSet layoutNameTags = new HashSet(); @Override protected void onCreate(Bundle savedInstanceState) { @@ -214,44 +224,53 @@ protected void onCreate(Bundle savedInstanceState) { * Also, the default layout is excluded and the 'osmtracker' tag is added by default. */ private void saveTagsForTrack(){ - //obtain the current track id and initialize the values variable + // Obtain the current track id and initialize the values variable Uri trackUri = ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, currentTrackId); ContentValues values = new ContentValues(); - //Checking for previously saved tags - Cursor cursor = getContentResolver().query( trackUri, null, null, null, null); - StringBuilder previouslySavedTags = new StringBuilder(); - int index = cursor.getColumnIndex(TrackContentProvider.Schema.COL_TAGS); + // A set with all tags to save + HashSet tagsToSave = new HashSet<>(); + // Get and add previously saved tags to the set + Cursor cursor = getContentResolver().query( trackUri, null, null, null, null); + int tagsIndex = cursor.getColumnIndex(TrackContentProvider.Schema.COL_TAGS); + String previouslySavedTags = null; while (cursor.moveToNext()) { - if(cursor.getString(index) != null) - previouslySavedTags.append(cursor.getString(index) + ","); + if(cursor.getString(tagsIndex) != null) { + previouslySavedTags = cursor.getString(tagsIndex); + } + } + if(previouslySavedTags != null){ + for (String tag : previouslySavedTags.split(TAG_SEPARATOR)){ + tagsToSave.add(tag); + } } - - StringBuilder tags = new StringBuilder(); - - tags.append(previouslySavedTags); - ArrayList fixedTags = new ArrayList(); - //covert the file name to simple layout name + // Add the names of the layouts that were used in the track to the set for(String layoutFileName : layoutNameTags){ //OSMTracker.Preferences.VAL_UI_BUTTONS_LAYOUT -> 'default' if(! layoutFileName.equals(OSMTracker.Preferences.VAL_UI_BUTTONS_LAYOUT)){ - fixedTags.add(CustomLayoutsUtils.convertFileName(layoutFileName)); + // Covert the file name to simple layout name + tagsToSave.add(CustomLayoutsUtils.convertFileName(layoutFileName)); } } - fixedTags.add("osmtracker"); + // Check if the osmtracker tag has already been added + String trackerTag = "osmtracker"; + tagsToSave.add(trackerTag); - //create the string with all tags - for(String simpleName : fixedTags){ - tags.append(simpleName).append(","); - } + // Create the string with all tags + StringBuilder tagsString = new StringBuilder(); + for(String tag : tagsToSave){ + tagsString.append(tag).append(TAG_SEPARATOR); + } + int lastIndex = tagsString.length()-1; + tagsString.deleteCharAt(lastIndex); //set the values tag and update the table - values.put(TrackContentProvider.Schema.COL_TAGS, tags.toString()); + values.put(TrackContentProvider.Schema.COL_TAGS, tagsString.toString()); getContentResolver().update(trackUri, values, null, null); } @@ -279,8 +298,7 @@ protected void onResume() { // Try to inflate the buttons layout try { - String userLayout = prefs.getString( - OSMTracker.Preferences.KEY_UI_BUTTONS_LAYOUT, OSMTracker.Preferences.VAL_UI_BUTTONS_LAYOUT); + String userLayout = prefs.getString(OSMTracker.Preferences.KEY_UI_BUTTONS_LAYOUT, OSMTracker.Preferences.VAL_UI_BUTTONS_LAYOUT); if (OSMTracker.Preferences.VAL_UI_BUTTONS_LAYOUT.equals(userLayout)) { // Using default buttons layout mainLayout = new UserDefinedLayout(this, currentTrackId, null); @@ -288,9 +306,7 @@ protected void onResume() { // Using user buttons layout File layoutFile = new File( Environment.getExternalStorageDirectory(), - prefs.getString( - OSMTracker.Preferences.KEY_STORAGE_DIR, - OSMTracker.Preferences.VAL_STORAGE_DIR) + OSMTracker.Preferences.VAL_STORAGE_DIR + File.separator + Preferences.LAYOUTS_SUBDIR + File.separator + userLayout); mainLayout = new UserDefinedLayout(this, currentTrackId, layoutFile); @@ -334,9 +350,7 @@ protected void onResume() { //save the layout file name if it change, in tags array String layoutName = CustomLayoutsUtils.getCurrentLayoutName(getApplicationContext()); - if(! layoutNameTags.contains(layoutName)){ - layoutNameTags.add(layoutName); - } + layoutNameTags.add(layoutName); super.onResume(); } diff --git a/app/src/main/java/net/osmtracker/activity/TrackManager.java b/app/src/main/java/net/osmtracker/activity/TrackManager.java index 11a3e7e2..a3b4e733 100644 --- a/app/src/main/java/net/osmtracker/activity/TrackManager.java +++ b/app/src/main/java/net/osmtracker/activity/TrackManager.java @@ -52,8 +52,14 @@ public class TrackManager extends ListActivity { @SuppressWarnings("unused") private static final String TAG = TrackManager.class.getSimpleName(); + final private int RC_WRITE_PERMISSIONS_UPLOAD = 4; + final private int RC_WRITE_STORAGE_DISPLAY_TRACK = 3; final private int RC_WRITE_PERMISSIONS_EXPORT_ALL = 1; final private int RC_WRITE_PERMISSIONS_EXPORT_ONE = 2; + final private int RC_GPS_PERMISSION = 5; + + + MenuItem trackSelected; /** Bundle key for {@link #prevItemVisible} */ private static final String PREV_VISIBLE = "prev_visible"; @@ -70,6 +76,9 @@ public class TrackManager extends ListActivity { /** Track Identifier to export after request for write permission **/ private long trackId = -1; + /** This variable is used to communicate between code trying to start TrackLogger and the code that + * actually starts it when have GPS permissions */ + private Intent TrackLoggerStartIntent = null; private ImageButton btnNewTrack; @Override @@ -87,7 +96,7 @@ protected void onCreate(Bundle savedInstanceState) { btnNewTrack.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - startTrackLogger(); + startTrackLoggerForNewTrack(); //makes the button invisible when is an active track btnNewTrack.setVisibility(View.INVISIBLE); } @@ -204,13 +213,13 @@ public boolean onPrepareOptionsMenu(Menu menu) { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.trackmgr_menu_newtrack: - startTrackLogger(); + startTrackLoggerForNewTrack(); break; case R.id.trackmgr_menu_continuetrack: Intent i = new Intent(this, TrackLogger.class); i.putExtra(TrackLogger.STATE_IS_TRACKING, true); i.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, currentTrackId); - startActivity(i); + tryStartTrackLogger(i); break; case R.id.trackmgr_menu_stopcurrenttrack: stopActiveTrack(); @@ -235,27 +244,15 @@ public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }).create().show(); - break; case R.id.trackmgr_menu_exportall: // Confirm - new AlertDialog.Builder(this) - .setTitle(R.string.menu_exportall) - .setMessage(getResources().getString(R.string.trackmgr_exportall_confirm)) - .setCancelable(true) - .setIcon(android.R.drawable.ic_dialog_alert) - .setPositiveButton(R.string.menu_exportall, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - requestPermissionAndExport(TrackManager.this.RC_WRITE_PERMISSIONS_EXPORT_ALL); - } - }) - .setNegativeButton(android.R.string.cancel, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }).create().show(); + if (!writeExternalStoragePermissionGranted()){ + Log.e("DisplayTrackMapWrite", "Permission asked"); + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RC_WRITE_PERMISSIONS_EXPORT_ALL); + } + else exportAllTracks(); break; case R.id.trackmgr_menu_settings: // Start settings activity @@ -289,36 +286,49 @@ private void startTrackLogger(){ } - private void requestPermissionAndExport(int typeCode){ - if (ContextCompat.checkSelfPermission(this, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - + /** + * Starts TrackLogger Activity if GPS Permission is granted + * If there's no GPS Permission, then requests it and the OnPermissionResult will call this method again if granted + */ + private void tryStartTrackLogger(Intent intent){ + // If GPS Permission Granted + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + Log.i("#","Granted on try"); + startActivity(intent); + } else{ + // Permission is not granted + Log.i("#","Not Granted on try"); + this.TrackLoggerStartIntent = intent; // Should we show an explanation? - if (ActivityCompat.shouldShowRequestPermissionRationale(this, - Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - - // Show an explanation to the user *asynchronously* -- don't block - // this thread waiting for the user's response! After the user - // sees the explanation, try again to request the permission. - // TODO: explain why we need permission. - Log.w(TAG, "we should explain why we need write permission_REQUEST"); - Toast.makeText(this, "To export the GPX trace we need to write on the storage.", Toast.LENGTH_LONG).show(); + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) { + Log.i("#","Should explain"); + Toast.makeText(this, "Can't continue without GPS permission", Toast.LENGTH_LONG).show(); + } - } else { + // No explanation needed, just request the permission. + Log.i("#","Should not explain"); + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, RC_GPS_PERMISSION); - // No explanation needed, we can request the permission. - ActivityCompat.requestPermissions(this, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, typeCode); - } + } + } - } else { - switch (typeCode) { - case RC_WRITE_PERMISSIONS_EXPORT_ALL: - exportAllTracks(); - case RC_WRITE_PERMISSIONS_EXPORT_ONE: - exportOneTrack(); - } + /** + * This method prepare the new track and set an id, then start a new TrackLogger with the new track id + */ + private void startTrackLoggerForNewTrack(){ + // Start track logger activity + try { + Intent i = new Intent(this, TrackLogger.class); + // New track + currentTrackId = createNewTrack(); + i.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, currentTrackId); + tryStartTrackLogger(i); + } catch (CreateTrackException cte) { + Toast.makeText(this, + getResources().getString(R.string.trackmgr_newtrack_error).replace("{0}", cte.getMessage()), + Toast.LENGTH_LONG) + .show(); } } @@ -369,21 +379,23 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn @Override public boolean onContextItemSelected(MenuItem item) { final AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); - Intent i; - + trackSelected = item; + switch(item.getItemId()) { case R.id.trackmgr_contextmenu_stop: // stop the active track stopActiveTrack(); break; + case R.id.trackmgr_contextmenu_resume: // let's activate the track and start the TrackLogger activity setActiveTrack(info.id); i = new Intent(this, TrackLogger.class); i.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, info.id); - startActivity(i); + tryStartTrackLogger(i); break; + case R.id.trackmgr_contextmenu_delete: // Confirm and delete selected track @@ -405,38 +417,74 @@ public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }).create().show(); - break; + case R.id.trackmgr_contextmenu_export: trackId = info.id; - requestPermissionAndExport(this.RC_WRITE_PERMISSIONS_EXPORT_ONE); +// requestPermissionAndExport(this.RC_WRITE_PERMISSIONS_EXPORT_ONE); + if (!writeExternalStoragePermissionGranted()){ + Log.e("DisplayTrackMapWrite", "Permission asked"); + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RC_WRITE_PERMISSIONS_EXPORT_ONE); + } + else exportOneTrack(); break; + case R.id.trackmgr_contextmenu_osm_upload: - i = new Intent(this, OpenStreetMapUpload.class); - i.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, info.id); - startActivity(i); + if (!writeExternalStoragePermissionGranted()){ + Log.e("DisplayTrackMapWrite", "Permission asked"); + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RC_WRITE_PERMISSIONS_UPLOAD); + } + else uploadTrack(trackSelected); break; + case R.id.trackmgr_contextmenu_display: - // Start display track activity, with or without OSM background - boolean useOpenStreetMapBackground = PreferenceManager.getDefaultSharedPreferences(this).getBoolean( - OSMTracker.Preferences.KEY_UI_DISPLAYTRACK_OSM, OSMTracker.Preferences.VAL_UI_DISPLAYTRACK_OSM); - if (useOpenStreetMapBackground) { - i = new Intent(this, DisplayTrackMap.class); - } else { - i = new Intent(this, DisplayTrack.class); + if (!writeExternalStoragePermissionGranted()){ + Log.e("DisplayTrackMapWrite", "Permission asked"); + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RC_WRITE_STORAGE_DISPLAY_TRACK); } - i.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, info.id); - startActivity(i); + else displayTrack(trackSelected); break; + case R.id.trackmgr_contextmenu_details: i = new Intent(this, TrackDetail.class); i.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, info.id); startActivity(i); break; } + return super.onContextItemSelected(item); } + private void uploadTrack(MenuItem item){ + final AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + Intent i = new Intent(this, OpenStreetMapUpload.class); + i.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, info.id); + startActivity(i); + } + + private void displayTrack(MenuItem item){ + Log.e(TAG, "On Display Track"); + // Start display track activity, with or without OSM background + final AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + Intent i; + boolean useOpenStreetMapBackground = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(OSMTracker.Preferences.KEY_UI_DISPLAYTRACK_OSM, OSMTracker.Preferences.VAL_UI_DISPLAYTRACK_OSM); + if (useOpenStreetMapBackground) { + i = new Intent(this, DisplayTrackMap.class); + } else { + i = new Intent(this, DisplayTrack.class); + } + i.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, info.id); + startActivity(i); + } + + private boolean writeExternalStoragePermissionGranted(){ + Log.e("CHECKING", "Write"); + return ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } + /** * User has clicked the active track or a previous track. * @param lv listview; this @@ -452,12 +500,14 @@ protected void onListItemClick(ListView lv, View iv, final int position, final l i = new Intent(this, TrackLogger.class); i.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, currentTrackId); i.putExtra(TrackLogger.STATE_IS_TRACKING, true); + tryStartTrackLogger(i); + } else { // show track info i = new Intent(this, TrackDetail.class); i.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, id); + startActivity(i); } - startActivity(i); } /** @@ -575,6 +625,7 @@ public void onRequestPermissionsResult(int requestCode, Log.w(TAG, "we should explain why we need write permission_EXPORT_ALL"); Toast.makeText(this, "To export the GPX trace we need to write on the storage.", Toast.LENGTH_LONG).show(); } + break; } case RC_WRITE_PERMISSIONS_EXPORT_ONE: { // If request is cancelled, the result arrays are empty. @@ -592,6 +643,52 @@ public void onRequestPermissionsResult(int requestCode, Log.w(TAG, "we should explain why we need write permission_EXPORT_ONE"); Toast.makeText(this, "To export the GPX trace we need to write on the storage.", Toast.LENGTH_LONG).show(); } + break; + } + case RC_WRITE_STORAGE_DISPLAY_TRACK: { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Log.e("Result", "Permission granted"); + // permission was granted, yay! + displayTrack(trackSelected); + } else { + + // permission denied, boo! Disable the + // functionality that depends on this permission. + //TODO: add an informative message. + Log.w(TAG, "Permission not granted"); + Toast.makeText(this, "To display the track properly we need access to the storage.", Toast.LENGTH_LONG).show(); + } + break; + } + case RC_WRITE_PERMISSIONS_UPLOAD: { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Log.e("Result", "Permission granted"); + // permission was granted, yay! + uploadTrack(trackSelected); + } else { + + // permission denied, boo! Disable the + // functionality that depends on this permission. + //TODO: add an informative message. + Log.w(TAG, "Permission not granted"); + Toast.makeText(this, "To upload the track to OSM we need access to the storage.", Toast.LENGTH_LONG).show(); + } + break; + } + case RC_GPS_PERMISSION:{ + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED){ + Log.i("#","GPS Permission granted"); + tryStartTrackLogger(this.TrackLoggerStartIntent); + } + else{ + Log.i("#","GPS Permission denied"); + } + break; } } } diff --git a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java index 9d7a3cf7..927c54d0 100644 --- a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java +++ b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java @@ -222,6 +222,10 @@ private void exportTrackAsGpx(long trackId) throws ExportTrackException { File trackGPXExportDirectory = getExportDirectory(startDate); String filenameBase = buildGPXFilename(c); + + String tags = c.getString(c.getColumnIndex(TrackContentProvider.Schema.COL_TAGS)); + String track_description = c.getString(c.getColumnIndex(TrackContentProvider.Schema.COL_DESCRIPTION)); + c.close(); File trackFile = new File(trackGPXExportDirectory, filenameBase); @@ -236,7 +240,7 @@ private void exportTrackAsGpx(long trackId) throws ExportTrackException { publishProgress(new Long[]{trackId, (long) cTrackPoints.getCount(), (long) cWayPoints.getCount()}); try { - writeGpxFile(cTrackPoints, cWayPoints, trackFile); + writeGpxFile(tags, track_description, cTrackPoints, cWayPoints, trackFile); if (exportMediaFiles()) { copyWaypointFiles(trackId, trackGPXExportDirectory); } @@ -272,7 +276,7 @@ private void exportTrackAsGpx(long trackId) throws ExportTrackException { * @param target Target GPX file * @throws IOException */ - private void writeGpxFile(Cursor cTrackPoints, Cursor cWayPoints, File target) throws IOException { + private void writeGpxFile(String tags, String track_description, Cursor cTrackPoints, Cursor cWayPoints, File target) throws IOException { String accuracyOutput = PreferenceManager.getDefaultSharedPreferences(context).getString( OSMTracker.Preferences.KEY_OUTPUT_ACCURACY, @@ -292,7 +296,23 @@ private void writeGpxFile(Cursor cTrackPoints, Cursor cWayPoints, File target) t writer.write(XML_HEADER + "\n"); writer.write(TAG_GPX + "\n"); - + + if ((tags != null && !tags.equals("")) || (track_description != null && !track_description.equals(""))) { + writer.write("\t\n"); + if (tags != null && !tags.equals("")) { + for (String tag : tags.split(",")) { + writer.write("\t\t" + tag.trim() + "\n"); + } + } + + if (track_description != null && !track_description.equals("")) { + writer.write("\t\t" + track_description + "\n"); + } + + writer.write("\t\n"); + } + + writeWayPoints(writer, cWayPoints, accuracyOutput, fillHDOP, compassOutput); writeTrackPoints(context.getResources().getString(R.string.gpx_track_name), writer, cTrackPoints, fillHDOP, compassOutput); diff --git a/app/src/main/java/net/osmtracker/layout/DownloadCustomLayoutTask.java b/app/src/main/java/net/osmtracker/layout/DownloadCustomLayoutTask.java index 708a2b04..559d181e 100644 --- a/app/src/main/java/net/osmtracker/layout/DownloadCustomLayoutTask.java +++ b/app/src/main/java/net/osmtracker/layout/DownloadCustomLayoutTask.java @@ -44,7 +44,7 @@ protected Boolean doInBackground(String[] layoutData) { String layoutFolderName = layoutName.replace(" ", "_"); String iso = layoutData[1]; SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - String storageDir =prefs.getString(OSMTracker.Preferences.KEY_STORAGE_DIR, OSMTracker.Preferences.VAL_STORAGE_DIR); + String storageDir = File.separator + OSMTracker.Preferences.VAL_STORAGE_DIR; String layoutURL = URLCreator.createLayoutFileURL(context, layoutFolderName, iso); String layoutPath = Environment.getExternalStorageDirectory() + storageDir + File.separator + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 6f55061a..cfa5790f 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -1,63 +1,117 @@ - - + + - - - - - + + + + + - + - - + + - - - + + + - + - - - + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b5a7e36b..d7078d19 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -4,4 +4,4 @@ distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip -distributionSha256Sum=6ac2f8f9302f50241bf14cc5f4a3d88504ad20e61bb98c5fd048f7723b61397e +distributionSha256Sum=9af7345c199f1731c187c96d3fe3d31f5405192a42046bafa71d846c3d9adacb