Loading .travis.yml +2 −2 Original line number Diff line number Diff line Loading @@ -3,11 +3,11 @@ jdk: oraclejdk8 android: components: - tools - build-tools-28.0.3 - platform-tools - build-tools-29.0.0 - android-29 - extra-android-m2repository - extra-google-android-support - android-28 script: - ./gradlew build after_success: Loading app/build.gradle +6 −5 Original line number Diff line number Diff line Loading @@ -10,12 +10,12 @@ apply plugin: 'com.android.application' android { compileSdkVersion 28 compileSdkVersion 29 defaultConfig { applicationId 'net.fabiszewski.ulogger' minSdkVersion 14 targetSdkVersion 28 minSdkVersion 19 targetSdkVersion 29 versionCode 207 versionName '2.7' } Loading @@ -26,14 +26,15 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { applicationIdSuffix ".debug" versionNameSuffix "-dev" applicationIdSuffix '.debug' versionNameSuffix '-dev' } } lintOptions { disable 'GoogleAppIndexingWarning' disable 'MissingTranslation' disable 'NotificationIconCompatibility' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 Loading app/src/main/AndroidManifest.xml +2 −3 Original line number Diff line number Diff line Loading @@ -19,8 +19,6 @@ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- For web sync retries --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- For exporting tracks --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- For foreground services API >= 28 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> Loading Loading @@ -51,7 +49,8 @@ <service android:name=".LoggerService" android:enabled="true" android:exported="false" /> android:exported="false" android:foregroundServiceType="location" /> <service android:name=".WebSyncService" android:exported="false" /> Loading app/src/main/java/net/fabiszewski/ulogger/ExternalCommandReceiver.java +12 −10 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ public class ExternalCommandReceiver extends BroadcastReceiver { } if (intent != null) { String command = intent.getStringExtra("command"); if (command != null) { switch (command) { case START_LOGGER: startLoggerService(context); Loading @@ -44,6 +45,7 @@ public class ExternalCommandReceiver extends BroadcastReceiver { break; } } } } Loading app/src/main/java/net/fabiszewski/ulogger/GpxExportService.java +70 −124 Original line number Diff line number Diff line Loading @@ -9,26 +9,20 @@ package net.fabiszewski.ulogger; import android.Manifest; import android.app.IntentService; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.os.Environment; import android.net.Uri; import android.util.Log; import android.util.Xml; import androidx.annotation.NonNull; import org.xmlpull.v1.XmlSerializer; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import java.io.OutputStream; /** * Export track to GPX format Loading @@ -37,7 +31,6 @@ public class GpxExportService extends IntentService { private static final String TAG = GpxExportService.class.getSimpleName(); public static final String BROADCAST_WRITE_PERMISSION_DENIED = "net.fabiszewski.ulogger.broadcast.write_permission_denied"; public static final String BROADCAST_EXPORT_FAILED = "net.fabiszewski.ulogger.broadcast.write_failed"; public static final String BROADCAST_EXPORT_DONE = "net.fabiszewski.ulogger.broadcast.write_ok"; Loading @@ -47,8 +40,8 @@ public class GpxExportService extends IntentService { private static final String schemaLocation = ns_gpx + " http://www.topografix.com/GPX/1/1/gpx.xsd " + ns_ulogger + " https://raw.githubusercontent.com/bfabiszewski/ulogger-server/master/scripts/gpx_extensions1.xsd"; private static final String ULOGGER_DIR = "ulogger_tracks"; private static final String GPX_EXTENSION = ".gpx"; public static final String GPX_EXTENSION = ".gpx"; public static final String GPX_MIME = "application/gpx+xml"; public GpxExportService() { super("GpxExportService"); Loading Loading @@ -84,42 +77,43 @@ public class GpxExportService extends IntentService { */ @Override protected void onHandleIntent(Intent intent) { if (!hasWritePermission()) { // no permission to write if (Logger.DEBUG) { Log.d(TAG, "[export gpx no permission]"); } sendBroadcast(BROADCAST_WRITE_PERMISSION_DENIED, null); return; if (intent != null && intent.getData() != null) { try { write(intent.getData()); sendBroadcast(BROADCAST_EXPORT_DONE, null); } catch (IOException e) { sendBroadcast(BROADCAST_EXPORT_FAILED, e.getMessage()); } if (!isExternalStorageWritable()) { // no access to external storage if (Logger.DEBUG) { Log.d(TAG, "[export gpx not writable]"); } sendBroadcast(BROADCAST_EXPORT_FAILED, getString(R.string.e_external_not_writable)); return; } String trackName = db.getTrackName(); if (trackName == null) { trackName = getString(R.string.unknown_track); } File dir = getDir(); if (dir == null) { if (Logger.DEBUG) { Log.d(TAG, "[export gpx failed to create output folder]"); } sendBroadcast(BROADCAST_EXPORT_FAILED, getString(R.string.e_output_dir)); return; /** * Write serialized track to URI * @param uri Target URI * @throws IOException Exception */ private void write(@NonNull Uri uri) throws IOException { OutputStream stream = getContentResolver().openOutputStream(uri); if (stream == null) { throw new IOException(getString(R.string.e_open_out_stream)); } try (BufferedOutputStream bufferedStream = new BufferedOutputStream(stream)) { serialize(bufferedStream); if (Logger.DEBUG) { Log.d(TAG, "[export gpx file written to " + uri); } } catch (IOException|IllegalArgumentException|IllegalStateException e) { if (Logger.DEBUG) { Log.d(TAG, "[export gpx write exception: " + e + "]"); } throw new IOException(e.getMessage()); } File file = getFile(dir, trackName); int i = 0; while (file.exists()) { file = getFile(dir, trackName + "_" + (++i)); } try (FileOutputStream stream = new FileOutputStream(file); OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("UTF-8")); BufferedWriter bufferedWriter = new BufferedWriter(writer)) { /** * Serialize and write * @param stream Output stream * @throws IOException Exception */ private void serialize(@NonNull OutputStream stream) throws IOException { XmlSerializer serializer = Xml.newSerializer(); serializer.setOutput(bufferedWriter); serializer.setOutput(stream, "UTF-8"); // header serializer.startDocument("UTF-8", true); Loading @@ -134,6 +128,10 @@ public class GpxExportService extends IntentService { serializer.attribute(null, "creator", creator); // metadata String trackName = db.getTrackName(); if (trackName == null) { trackName = getString(R.string.unknown_track); } long trackTimestamp = db.getFirstTimestamp(); String trackTime = DbAccess.getTimeISO8601(trackTimestamp); serializer.startTag(null, "metadata"); Loading @@ -150,14 +148,6 @@ public class GpxExportService extends IntentService { serializer.endTag("", "gpx"); serializer.endDocument(); serializer.flush(); if (Logger.DEBUG) { Log.d(TAG, "[export gpx file written to " + file.getPath()); } sendBroadcast(BROADCAST_EXPORT_DONE, null); } catch (IOException|IllegalArgumentException|IllegalStateException e) { if (Logger.DEBUG) { Log.d(TAG, "[export gpx exception: " + e + "]"); } sendBroadcast(BROADCAST_EXPORT_FAILED, e.getMessage()); } } /** Loading Loading @@ -238,50 +228,6 @@ public class GpxExportService extends IntentService { serializer.endTag(ns, name); } /** * Has user granted write permission? * * @return True if permission granted, false otherwise */ private boolean hasWritePermission() { return (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED); } /** * Is there external storage we can write to? * * @return True if writable, false otherwise */ private boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); return Environment.MEDIA_MOUNTED.equals(state); } /** * Set up directory in Downloads folder * * @return File instance or null in case of failure */ private File getDir() { File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), ULOGGER_DIR); if (!dir.exists() && !dir.mkdirs()) { dir = null; } return dir; } /** * Set up file instance with given name in given folder * * @param dir Folder * @param trackName File name * @return File instance */ private File getFile(@NonNull File dir, @NonNull String trackName) { String fileName = trackName.replaceAll("[?:\"'*|/\\\\<>]", "_") + GPX_EXTENSION; return new File(dir, fileName); } /** * Send broadcast message * @param broadcast Broadcast intent Loading Loading
.travis.yml +2 −2 Original line number Diff line number Diff line Loading @@ -3,11 +3,11 @@ jdk: oraclejdk8 android: components: - tools - build-tools-28.0.3 - platform-tools - build-tools-29.0.0 - android-29 - extra-android-m2repository - extra-google-android-support - android-28 script: - ./gradlew build after_success: Loading
app/build.gradle +6 −5 Original line number Diff line number Diff line Loading @@ -10,12 +10,12 @@ apply plugin: 'com.android.application' android { compileSdkVersion 28 compileSdkVersion 29 defaultConfig { applicationId 'net.fabiszewski.ulogger' minSdkVersion 14 targetSdkVersion 28 minSdkVersion 19 targetSdkVersion 29 versionCode 207 versionName '2.7' } Loading @@ -26,14 +26,15 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { applicationIdSuffix ".debug" versionNameSuffix "-dev" applicationIdSuffix '.debug' versionNameSuffix '-dev' } } lintOptions { disable 'GoogleAppIndexingWarning' disable 'MissingTranslation' disable 'NotificationIconCompatibility' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 Loading
app/src/main/AndroidManifest.xml +2 −3 Original line number Diff line number Diff line Loading @@ -19,8 +19,6 @@ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- For web sync retries --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- For exporting tracks --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- For foreground services API >= 28 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> Loading Loading @@ -51,7 +49,8 @@ <service android:name=".LoggerService" android:enabled="true" android:exported="false" /> android:exported="false" android:foregroundServiceType="location" /> <service android:name=".WebSyncService" android:exported="false" /> Loading
app/src/main/java/net/fabiszewski/ulogger/ExternalCommandReceiver.java +12 −10 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ public class ExternalCommandReceiver extends BroadcastReceiver { } if (intent != null) { String command = intent.getStringExtra("command"); if (command != null) { switch (command) { case START_LOGGER: startLoggerService(context); Loading @@ -44,6 +45,7 @@ public class ExternalCommandReceiver extends BroadcastReceiver { break; } } } } Loading
app/src/main/java/net/fabiszewski/ulogger/GpxExportService.java +70 −124 Original line number Diff line number Diff line Loading @@ -9,26 +9,20 @@ package net.fabiszewski.ulogger; import android.Manifest; import android.app.IntentService; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.os.Environment; import android.net.Uri; import android.util.Log; import android.util.Xml; import androidx.annotation.NonNull; import org.xmlpull.v1.XmlSerializer; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import java.io.OutputStream; /** * Export track to GPX format Loading @@ -37,7 +31,6 @@ public class GpxExportService extends IntentService { private static final String TAG = GpxExportService.class.getSimpleName(); public static final String BROADCAST_WRITE_PERMISSION_DENIED = "net.fabiszewski.ulogger.broadcast.write_permission_denied"; public static final String BROADCAST_EXPORT_FAILED = "net.fabiszewski.ulogger.broadcast.write_failed"; public static final String BROADCAST_EXPORT_DONE = "net.fabiszewski.ulogger.broadcast.write_ok"; Loading @@ -47,8 +40,8 @@ public class GpxExportService extends IntentService { private static final String schemaLocation = ns_gpx + " http://www.topografix.com/GPX/1/1/gpx.xsd " + ns_ulogger + " https://raw.githubusercontent.com/bfabiszewski/ulogger-server/master/scripts/gpx_extensions1.xsd"; private static final String ULOGGER_DIR = "ulogger_tracks"; private static final String GPX_EXTENSION = ".gpx"; public static final String GPX_EXTENSION = ".gpx"; public static final String GPX_MIME = "application/gpx+xml"; public GpxExportService() { super("GpxExportService"); Loading Loading @@ -84,42 +77,43 @@ public class GpxExportService extends IntentService { */ @Override protected void onHandleIntent(Intent intent) { if (!hasWritePermission()) { // no permission to write if (Logger.DEBUG) { Log.d(TAG, "[export gpx no permission]"); } sendBroadcast(BROADCAST_WRITE_PERMISSION_DENIED, null); return; if (intent != null && intent.getData() != null) { try { write(intent.getData()); sendBroadcast(BROADCAST_EXPORT_DONE, null); } catch (IOException e) { sendBroadcast(BROADCAST_EXPORT_FAILED, e.getMessage()); } if (!isExternalStorageWritable()) { // no access to external storage if (Logger.DEBUG) { Log.d(TAG, "[export gpx not writable]"); } sendBroadcast(BROADCAST_EXPORT_FAILED, getString(R.string.e_external_not_writable)); return; } String trackName = db.getTrackName(); if (trackName == null) { trackName = getString(R.string.unknown_track); } File dir = getDir(); if (dir == null) { if (Logger.DEBUG) { Log.d(TAG, "[export gpx failed to create output folder]"); } sendBroadcast(BROADCAST_EXPORT_FAILED, getString(R.string.e_output_dir)); return; /** * Write serialized track to URI * @param uri Target URI * @throws IOException Exception */ private void write(@NonNull Uri uri) throws IOException { OutputStream stream = getContentResolver().openOutputStream(uri); if (stream == null) { throw new IOException(getString(R.string.e_open_out_stream)); } try (BufferedOutputStream bufferedStream = new BufferedOutputStream(stream)) { serialize(bufferedStream); if (Logger.DEBUG) { Log.d(TAG, "[export gpx file written to " + uri); } } catch (IOException|IllegalArgumentException|IllegalStateException e) { if (Logger.DEBUG) { Log.d(TAG, "[export gpx write exception: " + e + "]"); } throw new IOException(e.getMessage()); } File file = getFile(dir, trackName); int i = 0; while (file.exists()) { file = getFile(dir, trackName + "_" + (++i)); } try (FileOutputStream stream = new FileOutputStream(file); OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("UTF-8")); BufferedWriter bufferedWriter = new BufferedWriter(writer)) { /** * Serialize and write * @param stream Output stream * @throws IOException Exception */ private void serialize(@NonNull OutputStream stream) throws IOException { XmlSerializer serializer = Xml.newSerializer(); serializer.setOutput(bufferedWriter); serializer.setOutput(stream, "UTF-8"); // header serializer.startDocument("UTF-8", true); Loading @@ -134,6 +128,10 @@ public class GpxExportService extends IntentService { serializer.attribute(null, "creator", creator); // metadata String trackName = db.getTrackName(); if (trackName == null) { trackName = getString(R.string.unknown_track); } long trackTimestamp = db.getFirstTimestamp(); String trackTime = DbAccess.getTimeISO8601(trackTimestamp); serializer.startTag(null, "metadata"); Loading @@ -150,14 +148,6 @@ public class GpxExportService extends IntentService { serializer.endTag("", "gpx"); serializer.endDocument(); serializer.flush(); if (Logger.DEBUG) { Log.d(TAG, "[export gpx file written to " + file.getPath()); } sendBroadcast(BROADCAST_EXPORT_DONE, null); } catch (IOException|IllegalArgumentException|IllegalStateException e) { if (Logger.DEBUG) { Log.d(TAG, "[export gpx exception: " + e + "]"); } sendBroadcast(BROADCAST_EXPORT_FAILED, e.getMessage()); } } /** Loading Loading @@ -238,50 +228,6 @@ public class GpxExportService extends IntentService { serializer.endTag(ns, name); } /** * Has user granted write permission? * * @return True if permission granted, false otherwise */ private boolean hasWritePermission() { return (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED); } /** * Is there external storage we can write to? * * @return True if writable, false otherwise */ private boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); return Environment.MEDIA_MOUNTED.equals(state); } /** * Set up directory in Downloads folder * * @return File instance or null in case of failure */ private File getDir() { File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), ULOGGER_DIR); if (!dir.exists() && !dir.mkdirs()) { dir = null; } return dir; } /** * Set up file instance with given name in given folder * * @param dir Folder * @param trackName File name * @return File instance */ private File getFile(@NonNull File dir, @NonNull String trackName) { String fileName = trackName.replaceAll("[?:\"'*|/\\\\<>]", "_") + GPX_EXTENSION; return new File(dir, fileName); } /** * Send broadcast message * @param broadcast Broadcast intent Loading