diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index c32963628..773b9958c 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -18,7 +18,7 @@ jobs: java-version: '17' - name: Build with Gradle - run: ./gradlew assembleDebugUnitTest -Dpre-dex=false + run: ./gradlew assembleDebugUnitTest -Dpre-dex=false --no-configuration-cache - name: Run Unit Tests - run: ./gradlew testDebugUnitTest -Dpre-dex=false -q + run: ./gradlew testDebugUnitTest -Dpre-dex=false -q --no-configuration-cache diff --git a/assets/text/faq/faq13-difference-other-apps.md b/assets/text/faq/faq13-difference-other-apps.md index 051c73615..001c33b69 100644 --- a/assets/text/faq/faq13-difference-other-apps.md +++ b/assets/text/faq/faq13-difference-other-apps.md @@ -1,6 +1,8 @@ ## How is this different from other logging apps? -It's meant to be more battery efficient. A lot of other apps, such as [OpenTracks](https://github.com/OpenTracksApp/OpenTracks), usually go with the assumption that you have a data connection available and your routes won't be very long. They use CPU wakelocks and log points extremely frequently with high accuracy. The aim of GPSLogger is to log points and stay quiet. +It's meant to be more battery efficient. A lot of other apps, such as [OpenTracks](https://github.com/OpenTracksApp/OpenTracks), usually go with the assumption that you have a data connection available and your routes won't be very long. +They use CPU wakelocks and log points extremely frequently with high accuracy. The aim of GPSLogger is to log points and stay quiet. To put it another way, OpenTracks or similar are better suited for runs; GPSLogger is suited for days out, hiking, photography. +[BasicAirData GPSLogger](https://play.google.com/store/apps/details?id=eu.basicairdata.graziano.gpslogger&hl=en&gl=US), [Ultra GPS Logger](https://play.google.com/store/apps/details?id=com.flashlight.lite.gps.logger&hl=en&gl=US) have similar names but aren't associated with this project. \ No newline at end of file diff --git a/assets/text/faq/faq14-tasker-automation.md b/assets/text/faq/faq14-tasker-automation.md index 4513dc856..a42f4468d 100644 --- a/assets/text/faq/faq14-tasker-automation.md +++ b/assets/text/faq/faq14-tasker-automation.md @@ -54,20 +54,32 @@ The app comes with a Start and a Stop **shortcut** (long press home screen, add ### Listening to GPSLogger +GPSLogger sends a broadcast start/stop of logging, or file uploaded, which you can receive as an event. + +In Tasker, this would be the `Intent Received` event. +Set the action to `com.mendhak.gpslogger.EVENT`. +You can then access the extras as `%variablename` or `%arrayname1`. + +In Automate, you can use the Broadcast Receive block. +Set the Action to `com.mendhak.gpslogger.EVENT`. +Set the dictionary with broadcast extras to a variable, then access the extras as `myvar["variablename"]` or `myvar["arrayname"][0]`. + +From there in your task, you can look at the following variables. + +*Start/Stop logging* + +* `gpsloggerevent` - `started` or `stopped` +* `filename` - the base filename that was chosen (no extension) +* `startedtimestamp` - timestamp when logging was started (epoch) +* `duration` - seconds since the current session started +* `distance` - meters travelled since the session started + + +*File uploaded* + +* `gpsloggerevent` - `fileuploaded` +* `filepaths` - an array of file paths that were uploaded, even if it's just a single file +* `sendertype` - which sender was used to upload the file, e.g. `customurl`, `ftp`, etc. -(Experimental feature) GPSLogger sends a broadcast start/stop of logging, which you can receive as an event. - -In Tasker, this would look like: - -> Event: Intent Received - Action: com.mendhak.gpslogger.EVENT - -From there in your task, you can look at the following variables - - * `%gpsloggerevent` - `started` or `stopped` - * `%filename` - the base filename that was chosen (no extension) - * `%startedtimestamp` - timestamp when logging was started (epoch) - * `%duration` - seconds since the current session started - * `%distance` - meters travelled since the session started In a custom application, receive the `com.mendhak.gpslogger.EVENT` broadcast and have a look inside the extras. \ No newline at end of file diff --git a/assets/text/faq/faq17-other-uses.md b/assets/text/faq/faq17-other-uses.md index 3e2738153..d2facc2c3 100644 --- a/assets/text/faq/faq17-other-uses.md +++ b/assets/text/faq/faq17-other-uses.md @@ -3,13 +3,13 @@ The GPS files produced by this app are generally used for processing *other* things. -A common use case is to geotag photos. Many cameras, especially SLRs, don't have built-in GPS. After a day (or days) out of photography, you may have hundreds of photos that need to be geotagged so that their locations can appear properly when used elsewhere. +A common use case is to geotag photos. Many cameras, especially SLRs, don't have built-in GPS. After a day (or days) out of photography, you may have hundreds of photos that need to be geotagged so that their locations can appear properly when used elsewhere. I have had success with: * [GeoSetter](http://www.geosetter.de/en/) - GUI, comprehensive options with map display -* [ExifTool](http://askubuntu.com/questions/599395/how-can-i-batch-tag-several-hundred-photos-with-separately-recorded-gps-data) - command line, lots of options -* Lightroom's map module - very basic and limited +* [digiKam](https://www.digikam.org/) - Open Source photo management. Geotagging and geocoding are tools within this software +* [ExifTool](http://askubuntu.com/questions/599395/how-can-i-batch-tag-several-hundred-photos-with-separately-recorded-gps-data) - command line, lots of options There are of course other uses of the produced files, these are a few I've seen over the years; it's usually a combination of a log file produced from GPSLogger with a secondary software to process the files. diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..495c5038e --- /dev/null +++ b/build.gradle @@ -0,0 +1 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. diff --git a/fastlane/metadata/android/en-US/changelogs/131.txt b/fastlane/metadata/android/en-US/changelogs/131.txt new file mode 100644 index 000000000..df7c0f2b0 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/131.txt @@ -0,0 +1,6 @@ +* OpenStreetMap option to be prompted before logging starts +* Switching to Android WorkManager for sending tasks, should be more reliable. +* Broadcast is sent when file upload completed, useful for automation to clean up files. +* Error notifications and less crashing if permissions have been revoked or file writes fail. +* New Custom URL parameter, %SPD_KPH +* Clarified autosend description: dynamic file name change also results in the file being sent. \ No newline at end of file diff --git a/gpslogger/build.gradle b/gpslogger/build.gradle index 18e4f04eb..6a92746f5 100644 --- a/gpslogger/build.gradle +++ b/gpslogger/build.gradle @@ -7,9 +7,9 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' + classpath 'com.android.tools.build:gradle:8.1.4' - classpath 'org.jacoco:org.jacoco.core:0.7.4.201502262128' + classpath 'org.jacoco:org.jacoco.core:0.8.7' classpath "com.moowork.gradle:gradle-node-plugin:0.13" } } @@ -37,16 +37,18 @@ repositories { } android { - compileSdkVersion 30 + compileSdkVersion 34 defaultConfig { applicationId "com.mendhak.gpslogger" minSdkVersion 16 + //noinspection ExpiredTargetSdkVersion targetSdkVersion 30 + compileSdk 34 + versionCode 131 + versionName "131-rc2" - versionCode 130 - versionName "130" - + // Used by AppAuth-Android manifestPlaceholders = [ appAuthRedirectScheme: 'com.mendhak.gpslogger' ] @@ -86,8 +88,6 @@ android { keyAlias RELEASE_KEY_ALIAS keyPassword RELEASE_KEY_PASSWORD } - - } buildTypes { @@ -121,10 +121,10 @@ android { dependencies { // implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation "androidx.activity:activity:1.3.1" - implementation "androidx.fragment:fragment:1.3.6" - implementation "androidx.preference:preference:1.1.1" - implementation "androidx.constraintlayout:constraintlayout:2.1.0" + implementation "androidx.activity:activity:1.8.2" + implementation "androidx.fragment:fragment:1.6.2" + implementation "androidx.preference:preference:1.2.1" + implementation "androidx.constraintlayout:constraintlayout:2.1.4" //Google Drive Oauth @@ -136,7 +136,7 @@ dependencies { //Debug Logging - implementation('org.slf4j:slf4j-api:1.7.6') + implementation('org.slf4j:slf4j-api:1.7.30') implementation('com.github.tony19:logback-android-classic:1.1.1-2'){ exclude group: 'com.google.android', module: 'android' } @@ -145,7 +145,7 @@ dependencies { //Android lollipop/material features including the Toolbar - implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.appcompat:appcompat:1.6.1' //Cardviews @@ -163,10 +163,10 @@ dependencies { //Progress button implementation 'com.github.dmytrodanylyk.android-process-button:library:1.0.4' - //Android Priority Jobqueue - implementation ('com.birbit:android-priority-jobqueue:2.0.1'){ - exclude group: 'com.google.android', module: 'android' - } + //Android's WorkManager + implementation 'androidx.work:work-runtime:2.9.0' + // We need to use Gson to help with WorkManager limitations + implementation 'com.google.code.gson:gson:2.10.1' //Event bus implementation 'de.greenrobot:eventbus:2.4.0' @@ -215,8 +215,8 @@ dependencies { testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:3.10.0' testImplementation 'org.json:json:20180813' - testImplementation 'androidx.test:runner:1.4.0' - testImplementation 'androidx.test:rules:1.4.0' + testImplementation 'androidx.test:runner:1.5.2' + testImplementation 'androidx.test:rules:1.5.0' } @@ -233,7 +233,7 @@ tasks.withType(Test) { } } -tasks.whenTaskAdded { task -> +tasks.configureEach { task -> //Don't run lint. Takes too long. if (task.name.contains("lint")) { task.enabled = false @@ -264,7 +264,7 @@ task buildTranslationArray { } preBuild.dependsOn buildTranslationArray -tasks.whenTaskAdded { task -> +tasks.configureEach { task -> if (task.name == 'preDebugBuild' || task.name == 'preReleaseBuild') { task.dependsOn buildTranslationArray } diff --git a/gpslogger/src/main/AndroidManifest.xml b/gpslogger/src/main/AndroidManifest.xml index dc9719aa2..775d9bfe5 100644 --- a/gpslogger/src/main/AndroidManifest.xml +++ b/gpslogger/src/main/AndroidManifest.xml @@ -1,9 +1,7 @@ + android:installLocation="auto"> @@ -31,6 +29,8 @@ + + diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/GpsLoggingService.java b/gpslogger/src/main/java/com/mendhak/gpslogger/GpsLoggingService.java index d215ec3e1..5e4550464 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/GpsLoggingService.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/GpsLoggingService.java @@ -55,7 +55,6 @@ @SuppressLint("MissingPermission") public class GpsLoggingService extends Service { private static NotificationManager notificationManager; - private static int NOTIFICATION_ID = 8675309; private final IBinder binder = new GpsLoggingBinder(); AlarmManager nextPointAlarmManager; private NotificationCompat.Builder nfc; @@ -89,10 +88,10 @@ public IBinder onBind(Intent arg0) { public void onCreate() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - startForeground(NOTIFICATION_ID, getNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION); + startForeground(NotificationChannelNames.GPSLOGGER_DEFAULT_NOTIFICATION_ID, getNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION); } else { - startForeground(NOTIFICATION_ID, getNotification()); + startForeground(NotificationChannelNames.GPSLOGGER_DEFAULT_NOTIFICATION_ID, getNotification()); } } catch (Exception ex) { LOG.error("Could not start GPSLoggingService in foreground. ", ex); @@ -126,18 +125,21 @@ public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - startForeground(NOTIFICATION_ID, getNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION); + startForeground(NotificationChannelNames.GPSLOGGER_DEFAULT_NOTIFICATION_ID, getNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION); } else { - startForeground(NOTIFICATION_ID, getNotification()); + startForeground(NotificationChannelNames.GPSLOGGER_DEFAULT_NOTIFICATION_ID, getNotification()); } } catch (Exception ex) { LOG.error("Could not start GPSLoggingService in foreground. ", ex); } if(session.isStarted() && gpsLocationListener == null && towerLocationListener == null && passiveLocationListener == null) { - LOG.warn("App might be recovering from an unexpected stop. Starting logging again."); - startLogging(); + if(Systems.hasUserGrantedAllNecessaryPermissions(this)){ + LOG.warn("App might be recovering from an unexpected stop. Starting logging again."); + startLogging(); + } + } handleIntent(intent); @@ -179,8 +181,7 @@ private void handleIntent(Intent intent) { if(!Systems.locationPermissionsGranted(this)){ LOG.error("User has not granted permission to access location services. Will not continue!"); - stopLogging(); - stopSelf(); + Systems.showErrorNotification(this, getString(R.string.gpslogger_permissions_permanently_denied)); return; } @@ -398,10 +399,10 @@ protected void startLogging() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - startForeground(NOTIFICATION_ID, getNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION); + startForeground(NotificationChannelNames.GPSLOGGER_DEFAULT_NOTIFICATION_ID, getNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION); } else { - startForeground(NOTIFICATION_ID, getNotification()); + startForeground(NotificationChannelNames.GPSLOGGER_DEFAULT_NOTIFICATION_ID, getNotification()); } } catch (Exception ex) { LOG.error("Could not start GPSLoggingService in foreground. ", ex); @@ -420,7 +421,7 @@ protected void startLogging() { } private void notifyByBroadcast(boolean loggingStarted) { - LOG.debug("Sending a custom broadcast"); + LOG.debug("Sending a started/stopped broadcast"); String event = (loggingStarted) ? "started" : "stopped"; Intent sendIntent = new Intent(); sendIntent.setAction("com.mendhak.gpslogger.EVENT"); @@ -529,21 +530,7 @@ private Notification getNotification() { if (nfc == null) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - - NotificationChannel channel = new NotificationChannel("gpslogger", getString(R.string.app_name), NotificationManager.IMPORTANCE_DEFAULT); - channel.enableLights(false); - channel.enableVibration(false); - channel.setSound(null,null); - channel.setLockscreenVisibility(preferenceHelper.shouldHideNotificationFromLockScreen() ? Notification.VISIBILITY_PRIVATE : Notification.VISIBILITY_PUBLIC); - - channel.setShowBadge(true); - manager.createNotificationChannel(channel); - - } - - nfc = new NotificationCompat.Builder(getApplicationContext(),"gpslogger") + nfc = new NotificationCompat.Builder(getApplicationContext(), NotificationChannelNames.GPSLOGGER_DEFAULT) .setSmallIcon(R.drawable.notification) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.gpsloggericon3)) .setPriority( preferenceHelper.shouldHideNotificationFromStatusBar() ? NotificationCompat.PRIORITY_MIN : NotificationCompat.PRIORITY_LOW) @@ -566,22 +553,20 @@ private Notification getNotification() { } } - - nfc.setContentTitle(contentTitle); nfc.setContentText(contentText); nfc.setStyle(new NotificationCompat.BigTextStyle().bigText(contentText).setBigContentTitle(contentTitle)); nfc.setWhen(notificationTime); //notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - //notificationManager.notify(NOTIFICATION_ID, nfc.build()); + //notificationManager.notify(NotificationChannelNames.GPSLOGGER_DEFAULT_ID, nfc.build()); return nfc.build(); } private void showNotification(){ Notification notif = getNotification(); notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - notificationManager.notify(NOTIFICATION_ID, notif); + notificationManager.notify(NotificationChannelNames.GPSLOGGER_DEFAULT_NOTIFICATION_ID, notif); } @SuppressWarnings("ResourceType") @@ -1092,6 +1077,7 @@ private void writeToFile(Location loc) { } catch(Exception e){ LOG.error(getString(R.string.could_not_write_to_file), e); + Systems.showErrorNotification(this, getString(R.string.could_not_write_to_file)); } session.clearDescription(); @@ -1192,6 +1178,14 @@ public void onEvent(CommandEvents.LogOnce logOnce){ } + @EventBusHook + public void onEvent(CommandEvents.FileWriteFailure writeFailure){ + Systems.showErrorNotification(this, getString(R.string.could_not_write_to_file)); + if(writeFailure.stopLoggingDueToNMEA){ + LOG.error("Could not write to NMEA file, stopping logging due to high frequency of write failures"); + stopLogging(); + } + } @EventBusHook public void onEvent(ProfileEvents.SwitchToProfile switchToProfileEvent){ diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/GpsMainActivity.java b/gpslogger/src/main/java/com/mendhak/gpslogger/GpsMainActivity.java index c19d4f1a5..ad8a1a574 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/GpsMainActivity.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/GpsMainActivity.java @@ -65,6 +65,7 @@ import androidx.appcompat.view.menu.ActionMenuItemView; import androidx.appcompat.widget.ActionMenuView; import androidx.appcompat.widget.Toolbar; +import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; import androidx.drawerlayout.widget.DrawerLayout; @@ -158,9 +159,7 @@ protected void onCreate(Bundle savedInstanceState) { setUpNavigationDrawer(savedInstanceState); loadDefaultFragmentView(); - startAndBindService(); - registerEventBus(); - registerConscryptProvider(); + if(!Systems.hasUserGrantedAllNecessaryPermissions(this)){ LOG.debug("Permission check - missing permissions"); @@ -170,6 +169,10 @@ protected void onCreate(Bundle savedInstanceState) { else { LOG.debug("Permission check - OK"); + startAndBindService(); + registerEventBus(); + registerConscryptProvider(); + if(preferenceHelper.shouldStartLoggingOnAppLaunch()){ LOG.debug("Start logging on app launch"); EventBus.getDefault().postSticky(new CommandEvents.RequestStartStop(true)); @@ -269,6 +272,9 @@ public boolean onResult(@NonNull String dialogTag, int which, @NonNull Bundle ex permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE); permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + permissions.add(Manifest.permission.POST_NOTIFICATIONS); + } if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { // Only on Android 10 (Q), the permission dialog can include an 'Allow all the time' @@ -1501,6 +1507,16 @@ private void startAndBindService() { * Stops the service if it isn't logging. Also unbinds. */ private void stopAndUnbindServiceIfRequired() { + if(!NotificationManagerCompat.from(this).areNotificationsEnabled()) { + // Alright. Why is this needed. + // If the notification permission has been revoked or not granted for whatever reason. + // When the application opens, the service starts, then stops right away. + // Android requires a notification to be shown for a foreground service within 5 seconds. + // So the application crashes and comes back repeatedly. Very weird. + // The answer - if notifications are disabled, don't unbind the service. It will stop on its own. + // Might be related: https://stackoverflow.com/questions/73067939/start-foreground-service-after-notification-permission-was-disabled-causes-crash + return; + } if (session.isBoundToService()) { try { @@ -1514,6 +1530,10 @@ private void stopAndUnbindServiceIfRequired() { if (!session.isStarted()) { LOG.debug("Stopping the service"); try { + // Stop service crashes if the intent is null. lol + if(serviceIntent == null){ + serviceIntent = new Intent(this, GpsLoggingService.class); + } stopService(serviceIntent); } catch (Exception e) { LOG.error("Could not stop the service", e); diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/common/AppSettings.java b/gpslogger/src/main/java/com/mendhak/gpslogger/common/AppSettings.java index 2b07d3074..1575a3d9e 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/common/AppSettings.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/common/AppSettings.java @@ -20,24 +20,28 @@ package com.mendhak.gpslogger.common; import android.app.Application; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; + -import com.birbit.android.jobqueue.JobManager; -import com.birbit.android.jobqueue.config.Configuration; -import com.birbit.android.jobqueue.log.CustomLogger; import com.mendhak.gpslogger.BuildConfig; +import com.mendhak.gpslogger.R; import com.mendhak.gpslogger.common.slf4j.Logs; import de.greenrobot.event.EventBus; import org.slf4j.Logger; public class AppSettings extends Application { - private static JobManager jobManager; + private static AppSettings instance; private static Logger LOG; @Override public void onCreate() { + Systems.setAppTheme(PreferenceHelper.getInstance().getAppThemeSetting()); super.onCreate(); @@ -50,25 +54,33 @@ public void onCreate() { EventBus.builder().logNoSubscriberMessages(false).sendNoSubscriberEvent(false).installDefaultEventBus(); LOG.debug("EventBus configured"); - //Configure the Job Queue - Configuration config = new Configuration.Builder(getInstance()) - .networkUtil(new WifiNetworkUtil(getInstance())) - .consumerKeepAlive(60) - .minConsumerCount(0) - .maxConsumerCount(1) -// .customLogger(jobQueueLogger) - .build(); - jobManager = new JobManager(config); - LOG.debug("Job Queue configured"); + createNotificationChannels(); } - /** - * Returns a configured Job Queue Manager - */ - public static JobManager getJobManager() { - return jobManager; + private void createNotificationChannels() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + + NotificationChannel channel = new NotificationChannel(NotificationChannelNames.GPSLOGGER_DEFAULT, getString(R.string.app_name), NotificationManager.IMPORTANCE_DEFAULT); + channel.enableLights(false); + channel.enableVibration(false); + channel.setSound(null,null); + channel.setLockscreenVisibility(PreferenceHelper.getInstance().shouldHideNotificationFromLockScreen() ? Notification.VISIBILITY_PRIVATE : Notification.VISIBILITY_PUBLIC); + + channel.setShowBadge(true); + manager.createNotificationChannel(channel); + + NotificationChannel channelErrors = new NotificationChannel(NotificationChannelNames.GPSLOGGER_ERRORS, getString(R.string.error), NotificationManager.IMPORTANCE_HIGH); + channelErrors.enableLights(true); + channelErrors.enableVibration(true); + channelErrors.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); + channelErrors.setShowBadge(true); + manager.createNotificationChannel(channelErrors); + + } } + public AppSettings() { instance = this; } @@ -81,35 +93,4 @@ public static AppSettings getInstance() { } - private final CustomLogger jobQueueLogger = new CustomLogger() { - @Override - public boolean isDebugEnabled() { - return BuildConfig.DEBUG; - } - - @Override - public void d(String text, Object... args) { - - LOG.debug(String.format(text, args)); - } - - @Override - public void e(Throwable t, String text, Object... args) { - LOG.error(String.format(text, args), t); - } - - @Override - public void e(String text, Object... args) { - - LOG.error(String.format(text, args)); - } - - @Override - public void v(String text, Object... args) { - LOG.debug(String.format(text,args)); - } - }; - - - } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/common/NotificationChannelNames.java b/gpslogger/src/main/java/com/mendhak/gpslogger/common/NotificationChannelNames.java new file mode 100644 index 000000000..8ed069764 --- /dev/null +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/common/NotificationChannelNames.java @@ -0,0 +1,8 @@ +package com.mendhak.gpslogger.common; + +public class NotificationChannelNames { + public static final String GPSLOGGER_DEFAULT = "gpslogger"; + public static final int GPSLOGGER_DEFAULT_NOTIFICATION_ID = 8675309; + public static final String GPSLOGGER_ERRORS = "gpslogger_errors"; + public static final int GPSLOGGER_ERRORS_NOTIFICATION_ID = 32202; +} diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/common/PreferenceHelper.java b/gpslogger/src/main/java/com/mendhak/gpslogger/common/PreferenceHelper.java index b9899f2ce..02d385e25 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/common/PreferenceHelper.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/common/PreferenceHelper.java @@ -992,6 +992,13 @@ public boolean isOsmAutoSendEnabled() { } + /** + * Whether to prompt user for OSM details before starting logging + */ + @ProfilePreference(name= PreferenceNames.OPENSTREETMAP_PROMPT_WHEN_LOGGING_STARTS) + public boolean shouldPromptForOSMDetailsWhenLoggingStarts() { + return prefs.getBoolean(PreferenceNames.OPENSTREETMAP_PROMPT_WHEN_LOGGING_STARTS, false); + } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/common/PreferenceNames.java b/gpslogger/src/main/java/com/mendhak/gpslogger/common/PreferenceNames.java index b4cbbe59f..6cd6d300e 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/common/PreferenceNames.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/common/PreferenceNames.java @@ -89,6 +89,7 @@ public class PreferenceNames { public static final String OPENSTREETMAP_DESCRIPTION = "osm_description"; public static final String OPENSTREETMAP_TAGS = "osm_tags"; public static final String OPENSTREETMAP_VISIBILITY = "osm_visibility"; + public static final String OPENSTREETMAP_PROMPT_WHEN_LOGGING_STARTS = "osm_promptfordetails_when_logging_starts"; public static final String AUTOSEND_DROPBOX_ENABLED = "dropbox_enabled"; public static final String DROPBOX_LONG_LIVED_ACCESS_TOKEN = "DROPBOX_ACCESS_KEY"; public static final String DROPBOX_REFRESH_TOKEN = "DROPBOX_REFRESH_TOKEN"; diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/common/Strings.java b/gpslogger/src/main/java/com/mendhak/gpslogger/common/Strings.java index d2152e5bc..83d058515 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/common/Strings.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/common/Strings.java @@ -22,6 +22,8 @@ import android.content.Context; import android.os.Build; + +import com.google.gson.Gson; import com.mendhak.gpslogger.BuildConfig; import com.mendhak.gpslogger.R; @@ -672,5 +674,12 @@ public String toString() { } + public static String serializeTojson(Object obj){ + return new Gson().toJson(obj); + } + + public static T deserializeFromJson(String json, Class clazz){ + return new Gson().fromJson(json, clazz); + } } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/common/Systems.java b/gpslogger/src/main/java/com/mendhak/gpslogger/common/Systems.java index 7a5ff4b81..9bbb42f0b 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/common/Systems.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/common/Systems.java @@ -22,6 +22,8 @@ import android.Manifest; import android.annotation.TargetApi; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -30,16 +32,30 @@ import android.content.pm.Signature; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.BitmapFactory; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.BatteryManager; import android.os.Build; import android.os.PowerManager; import android.provider.Settings; +import android.text.Html; + import androidx.appcompat.app.AppCompatDelegate; +import androidx.core.app.NotificationCompat; import androidx.core.content.ContextCompat; +import androidx.core.text.HtmlCompat; import androidx.fragment.app.FragmentActivity; - +import androidx.work.BackoffPolicy; +import androidx.work.Constraints; +import androidx.work.Data; +import androidx.work.ExistingWorkPolicy; +import androidx.work.NetworkType; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkManager; + +import com.mendhak.gpslogger.GpsMainActivity; +import com.mendhak.gpslogger.R; import com.mendhak.gpslogger.common.slf4j.Logs; import org.slf4j.Logger; @@ -50,6 +66,7 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -234,4 +251,75 @@ else if(appThemeSetting.equalsIgnoreCase("light")){ } + /** + * Starts a OneTimeWorkRequest with the given worker class and data map and tag. The constraints are set to + * UNMETERED network type if the user has set the app to only send on wifi. Otherwise it is set to + * CONNECTED. The initial delay is set to 1 second to avoid the work being enqueued immediately. + * The backoff criteria is set to exponential with a 30 second initial delay. The tag is used to + * uniquely identify the work request, and it replaces any existing work with the same tag. + * @param workerClass + * @param dataMap + * @return + */ + public static void startWorkManagerRequest(Class workerClass, HashMap dataMap, String tag) { + + androidx.work.Data data = new Data.Builder().putAll(dataMap).build(); + + Constraints constraints = new Constraints.Builder() + .setRequiredNetworkType(PreferenceHelper.getInstance().shouldAutoSendOnWifiOnly() ? NetworkType.UNMETERED: NetworkType.CONNECTED) + .build(); + + OneTimeWorkRequest workRequest = new OneTimeWorkRequest + .Builder(workerClass) + .setConstraints(constraints) + .setInitialDelay(1, java.util.concurrent.TimeUnit.SECONDS) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, java.util.concurrent.TimeUnit.SECONDS) + .setInputData(data) + .build(); + + WorkManager.getInstance(AppSettings.getInstance()) + .enqueueUniqueWork(tag, ExistingWorkPolicy.REPLACE, workRequest); + } + + public static void sendFileUploadedBroadcast(Context context, String[] filePaths, String senderType){ + LOG.debug("Sending a file uploaded broadcast"); + Intent sendIntent = new Intent(); + sendIntent.setAction("com.mendhak.gpslogger.EVENT"); + sendIntent.putExtra("gpsloggerevent", "fileuploaded"); + sendIntent.putExtra("filepaths", filePaths); + sendIntent.putExtra("sendertype", senderType); + context.sendBroadcast(sendIntent); + } + + /** + * Show an error notification with a warning emoji ⚠️, this is only used for important errors worth notifying the user for. + * Such as location permissions missing, unable to write to storage. + * @param context The application context, so that the notification service can be accessed. + * @param message A single line message to show in the notification. + */ + public static void showErrorNotification(Context context, String message){ + LOG.debug("Showing fatal notification"); + + Intent contentIntent = new Intent(context, GpsMainActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + NotificationCompat.Builder nfc = new NotificationCompat.Builder(context.getApplicationContext(), NotificationChannelNames.GPSLOGGER_ERRORS) + .setSmallIcon(android.R.drawable.stat_sys_warning) + //.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), android.R.drawable.stat_sys_warning)) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setCategory(NotificationCompat.CATEGORY_ERROR) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setContentTitle(context.getString(R.string.error)) + .setContentText(HtmlCompat.fromHtml(message, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()) + .setStyle(new NotificationCompat.BigTextStyle().bigText(Html.fromHtml(message).toString()).setBigContentTitle(context.getString(R.string.error))) + .setOngoing(false) + .setOnlyAlertOnce(true) + .setAutoCancel(true) + .setContentIntent(pendingIntent); + + + NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE); + notificationManager.notify(NotificationChannelNames.GPSLOGGER_ERRORS_NOTIFICATION_ID, nfc.build()); + + } } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/common/WifiNetworkUtil.java b/gpslogger/src/main/java/com/mendhak/gpslogger/common/WifiNetworkUtil.java deleted file mode 100644 index cc2c2b1d9..000000000 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/common/WifiNetworkUtil.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2016 mendhak - * - * This file is part of GPSLogger for Android. - * - * GPSLogger for Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * GPSLogger for Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GPSLogger for Android. If not, see . - */ - -package com.mendhak.gpslogger.common; - - -import android.annotation.TargetApi; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkInfo; -import android.net.NetworkRequest; -import android.os.Build; -import android.os.PowerManager; - -import com.birbit.android.jobqueue.network.NetworkEventProvider; -import com.birbit.android.jobqueue.network.NetworkUtil; - -/** - * default implementation for network Utility to observe network events - */ -public class WifiNetworkUtil implements NetworkUtil, NetworkEventProvider { - private Listener listener; - public WifiNetworkUtil(Context context) { - context = context.getApplicationContext(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - listenForIdle(context); - } - listenNetworkViaConnectivityManager(context); - } else { - context.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - dispatchNetworkChange(context); - } - }, getNetworkIntentFilter()); - } - } - - @TargetApi(23) - private void listenNetworkViaConnectivityManager(final Context context) { - ConnectivityManager cm = (ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkRequest request = new NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) - .build(); - cm.registerNetworkCallback(request, new ConnectivityManager.NetworkCallback() { - @Override - public void onAvailable(Network network) { - dispatchNetworkChange(context); - } - }); - } - - @TargetApi(23) - private void listenForIdle(Context context) { - context.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - dispatchNetworkChange(context); - } - }, new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)); - } - - void dispatchNetworkChange(Context context) { - if(listener == null) {//shall not be but just be safe - return; - } - //http://developer.android.com/reference/android/net/ConnectivityManager.html#EXTRA_NETWORK_INFO - //Since NetworkInfo can vary based on UID, applications should always obtain network information - // through getActiveNetworkInfo() or getAllNetworkInfo(). - listener.onNetworkChange(getNetworkStatus(context)); - } - - @Override - public int getNetworkStatus(Context context) { - if (Systems.isDozing(context)) { - return NetworkUtil.DISCONNECTED; - } - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo netInfo = cm.getActiveNetworkInfo(); - if (netInfo == null) { - return NetworkUtil.DISCONNECTED; - } - - boolean isWifiRequired = PreferenceHelper.getInstance().shouldAutoSendOnWifiOnly(); - boolean isDeviceOnWifi = true; - - if(isWifiRequired){ - isDeviceOnWifi = (netInfo.getType() == ConnectivityManager.TYPE_WIFI); - } - - if ( netInfo.isConnected() && isDeviceOnWifi){ - return NetworkUtil.UNMETERED; - } - - return NetworkUtil.METERED; - } - - @TargetApi(23) - private static IntentFilter getNetworkIntentFilter() { - IntentFilter networkIntentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - networkIntentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); - } - return networkIntentFilter; - } - - @Override - public void setListener(Listener listener) { - this.listener = listener; - } -} \ No newline at end of file diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/common/events/CommandEvents.java b/gpslogger/src/main/java/com/mendhak/gpslogger/common/events/CommandEvents.java index c38d2729c..4e0ea73ee 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/common/events/CommandEvents.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/common/events/CommandEvents.java @@ -72,4 +72,20 @@ public Annotate(String annotation) { */ public static class LogOnce { } + + /** + * Used to indicate that the file write failed. + * The intention is to then notify the user of the failure since it represents data loss. + * Pass stopLogging, if true, ask the logging to stop. Use this for NMEA which is very high frequency. + */ + public static class FileWriteFailure { + public boolean stopLoggingDueToNMEA; + + public FileWriteFailure(){ + this.stopLoggingDueToNMEA = false; + } + public FileWriteFailure(boolean stopLoggingDueToNMEA) { + this.stopLoggingDueToNMEA = stopLoggingDueToNMEA; + } + } } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/customurl/CustomUrlJob.java b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/customurl/CustomUrlJob.java deleted file mode 100644 index 5420ffe88..000000000 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/customurl/CustomUrlJob.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2016 mendhak - * - * This file is part of GPSLogger for Android. - * - * GPSLogger for Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * GPSLogger for Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GPSLogger for Android. If not, see . - */ - -package com.mendhak.gpslogger.loggers.customurl; - - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.birbit.android.jobqueue.Job; -import com.birbit.android.jobqueue.Params; -import com.birbit.android.jobqueue.RetryConstraint; -import com.mendhak.gpslogger.common.AppSettings; -import com.mendhak.gpslogger.common.events.UploadEvents; -import com.mendhak.gpslogger.common.network.Networks; -import com.mendhak.gpslogger.common.slf4j.Logs; - -import org.slf4j.Logger; - -import java.io.File; -import java.util.ArrayList; -import java.util.Map; - -import javax.net.ssl.X509TrustManager; - -import de.greenrobot.event.EventBus; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - - -public class CustomUrlJob extends Job { - - private static final Logger LOG = Logs.of(CustomUrlJob.class); - - private UploadEvents.BaseUploadEvent callbackEvent; - - private File csvFile; - private ArrayList urlRequests; - - public CustomUrlJob(ArrayList urlRequests, File csvFile, UploadEvents.BaseUploadEvent callbackEvent) { - super(new Params(1).requireNetwork().persist()); - - this.callbackEvent = callbackEvent; - this.urlRequests = urlRequests; - this.csvFile = csvFile; - } - - @Override - public void onAdded() { - } - - @Override - public void onRun() throws Throwable { - - boolean success = true; - String responseError = null; - String responseThrowableMessage = null; - - if(urlRequests != null && urlRequests.size() > 0){ - - for (CustomUrlRequest urlRequest : urlRequests) { - LOG.info("HTTP Request - " + urlRequest.getLogURL()); - - OkHttpClient.Builder okBuilder = new OkHttpClient.Builder(); - okBuilder.sslSocketFactory(Networks.getSocketFactory(AppSettings.getInstance()), - (X509TrustManager) Networks.getTrustManager(AppSettings.getInstance())); - Request.Builder requestBuilder = new Request.Builder().url(urlRequest.getLogURL()); - - for(Map.Entry header : urlRequest.getHttpHeaders().entrySet()){ - requestBuilder.addHeader(header.getKey(), header.getValue()); - } - - if ( ! urlRequest.getHttpMethod().equalsIgnoreCase("GET")) { - RequestBody body = RequestBody.create(null, urlRequest.getHttpBody()); - requestBuilder = requestBuilder.method(urlRequest.getHttpMethod(),body); - } - - Request request = requestBuilder.build(); - Response response = okBuilder.build().newCall(request).execute(); - - if (response.isSuccessful()) { - LOG.debug("HTTP request complete with successful response code " + response); - } - else { - LOG.error("HTTP request complete with unexpected response code " + response ); - responseError = "Unexpected code " + response; - responseThrowableMessage = response.body().string(); - success = false; - } - - response.body().close(); - - if(!success){ - break; - } - } - } - - if(success){ - EventBus.getDefault().post(callbackEvent.succeeded()); - } - else { - EventBus.getDefault().post(callbackEvent.failed("Unexpected code " + responseError, new Throwable(responseThrowableMessage))); - } - } - - @Override - protected void onCancel(int cancelReason, @Nullable Throwable throwable) { - EventBus.getDefault().post(callbackEvent.failed("Could not send to custom URL", throwable)); - LOG.error("Custom URL: maximum attempts failed, giving up", throwable); - } - - @Override - protected RetryConstraint shouldReRunOnThrowable(@NonNull Throwable throwable, int runCount, int maxRunCount) { - LOG.warn(String.format("Custom URL: attempt %d failed, maximum %d attempts", runCount, maxRunCount)); - return RetryConstraint.createExponentialBackoff(runCount, 5000); - } - - - @Override - protected int getRetryLimit() { - return 3; - } -} diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/customurl/CustomUrlWorker.java b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/customurl/CustomUrlWorker.java new file mode 100644 index 000000000..0fa1a8c52 --- /dev/null +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/customurl/CustomUrlWorker.java @@ -0,0 +1,168 @@ +package com.mendhak.gpslogger.loggers.customurl; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.work.Data; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import com.mendhak.gpslogger.common.AppSettings; +import com.mendhak.gpslogger.common.PreferenceHelper; +import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; +import com.mendhak.gpslogger.common.events.UploadEvents; +import com.mendhak.gpslogger.common.network.Networks; +import com.mendhak.gpslogger.common.slf4j.Logs; +import com.mendhak.gpslogger.senders.customurl.CustomUrlManager; +import com.mendhak.gpslogger.senders.opengts.OpenGTSManager; + +import org.slf4j.Logger; + +import java.io.File; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.X509TrustManager; + +import de.greenrobot.event.EventBus; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class CustomUrlWorker extends Worker { + + private static final Logger LOG = Logs.of(CustomUrlWorker.class); + public CustomUrlWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + + @NonNull + @Override + public Result doWork() { + + UploadEvents.BaseUploadEvent callbackEvent = getCallbackEvent(); + CustomUrlRequest[] urlRequests = getCustomUrlRequests(getInputData()); + + if(urlRequests == null || urlRequests.length == 0){ + EventBus.getDefault().post(callbackEvent.failed("Nothing to process", new Throwable("Nothing to process"))); + return Result.failure(); + } + + boolean success = true; + String responseError = null; + String responseThrowableMessage = null; + + for (CustomUrlRequest urlRequest : urlRequests) { + try{ + LOG.info("HTTP Request - " + urlRequest.getLogURL()); + + OkHttpClient.Builder okBuilder = new OkHttpClient.Builder(); + okBuilder.sslSocketFactory(Networks.getSocketFactory(AppSettings.getInstance()), + (X509TrustManager) Networks.getTrustManager(AppSettings.getInstance())); + Request.Builder requestBuilder = new Request.Builder().url(urlRequest.getLogURL()); + + for (Map.Entry header : urlRequest.getHttpHeaders().entrySet()) { + requestBuilder.addHeader(header.getKey(), header.getValue()); + } + + if (!urlRequest.getHttpMethod().equalsIgnoreCase("GET")) { + RequestBody body = RequestBody.create(null, urlRequest.getHttpBody()); + requestBuilder = requestBuilder.method(urlRequest.getHttpMethod(), body); + } + + Request request = requestBuilder.build(); + Response response = okBuilder.build().newCall(request).execute(); + + if (response.isSuccessful()) { + LOG.debug("HTTP request complete with successful response code " + response); + } else { + LOG.error("HTTP request complete with unexpected response code " + response); + responseError = "Unexpected code " + response; + responseThrowableMessage = response.body().string(); + success = false; + } + + response.body().close(); + + if (!success) { + break; + } + } + catch (Exception e) { + LOG.error("Exception during Custom URL processing " + e); + responseError = "Exception " + e; + responseThrowableMessage = e.getMessage(); + success = false; + break; + } + } + + if(success) { + + // Notify internal listeners + EventBus.getDefault().post(callbackEvent.succeeded()); + + String gpxFilePath = getInputData().getString("gpxFilePath"); + String csvFilePath = getInputData().getString("csvFilePath"); + // Notify external listeners + if(!Strings.isNullOrEmpty(gpxFilePath) || !Strings.isNullOrEmpty(csvFilePath)){ + String[] filePaths = new String[]{ Strings.isNullOrEmpty(gpxFilePath) ? csvFilePath : gpxFilePath }; + Systems.sendFileUploadedBroadcast(getApplicationContext(), filePaths, getInputData().getString("callbackType")); + } + + return Result.success(); + } + else { + if(getRunAttemptCount() < getRetryLimit()){ + LOG.warn(String.format("Custom URL - attempt %d of %d failed, will retry", getRunAttemptCount(), getRetryLimit())); + return Result.retry(); + } + + EventBus.getDefault() + .post(callbackEvent.failed("Unexpected code " + responseError, new Throwable(responseThrowableMessage))); + return Result.failure(); + } + } + + private CustomUrlRequest[] getCustomUrlRequests(Data inputData) { + CustomUrlRequest[] urlRequests = null; + String[] serializedRequests = inputData.getStringArray("urlRequests"); + String gpxFilePath = inputData.getString("gpxFilePath"); + String csvFilePath = inputData.getString("csvFilePath"); + + if(!Strings.isNullOrEmpty(gpxFilePath)){ + OpenGTSManager openGTSManager = new OpenGTSManager(PreferenceHelper.getInstance(), Systems.getBatteryInfo(AppSettings.getInstance()).BatteryLevel); + List gpxCustomUrlRequests = openGTSManager.getCustomUrlRequestsFromGPX(new File(gpxFilePath)); + urlRequests = gpxCustomUrlRequests.toArray(new CustomUrlRequest[0]); + } + else if(!Strings.isNullOrEmpty(csvFilePath)){ + CustomUrlManager customUrlManager = new CustomUrlManager(PreferenceHelper.getInstance()); + List csvCustomUrlRequests = customUrlManager.getCustomUrlRequestsFromCSV(new File(csvFilePath)); + urlRequests = csvCustomUrlRequests.toArray(new CustomUrlRequest[0]); + } + else if(serializedRequests != null && serializedRequests.length > 0) { + urlRequests = new CustomUrlRequest[serializedRequests.length]; + for (int i = 0; i < serializedRequests.length; i++) { + urlRequests[i] = Strings.deserializeFromJson(serializedRequests[i], CustomUrlRequest.class); + } + } + return urlRequests; + } + + @NonNull + private UploadEvents.BaseUploadEvent getCallbackEvent() { + String callbackType = getInputData().getString("callbackType"); + UploadEvents.BaseUploadEvent callbackEvent = new UploadEvents.CustomUrl(); + + if(!Strings.isNullOrEmpty(callbackType) && callbackType.equals("opengts")){ + callbackEvent = new UploadEvents.OpenGTS(); + } + return callbackEvent; + } + + protected int getRetryLimit() { + return 3; + } +} diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/geojson/GeoJSONWriterPoints.java b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/geojson/GeoJSONWriterPoints.java index cd28eb0f3..1dfac8ac7 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/geojson/GeoJSONWriterPoints.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/geojson/GeoJSONWriterPoints.java @@ -4,6 +4,7 @@ import androidx.annotation.NonNull; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.events.CommandEvents; import com.mendhak.gpslogger.common.slf4j.Logs; import com.mendhak.gpslogger.loggers.Files; @@ -13,6 +14,8 @@ import java.io.IOException; import java.io.RandomAccessFile; +import de.greenrobot.event.EventBus; + /** * Created by clemens on 10.05.17. */ @@ -66,8 +69,8 @@ public void run() { raf.close(); } } catch (IOException e) { - e.printStackTrace(); - LOG.error("GeoJSONWriterPoints", e); + EventBus.getDefault().post(new CommandEvents.FileWriteFailure()); + LOG.error("Failed to write to GeoJSON file", e); } } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/gpx/Gpx10FileLogger.java b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/gpx/Gpx10FileLogger.java index 2cd6a1452..36af142d3 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/gpx/Gpx10FileLogger.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/gpx/Gpx10FileLogger.java @@ -26,6 +26,7 @@ import com.mendhak.gpslogger.common.PreferenceHelper; import com.mendhak.gpslogger.common.RejectionHandler; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.events.CommandEvents; import com.mendhak.gpslogger.common.slf4j.Logs; import com.mendhak.gpslogger.loggers.FileLogger; import com.mendhak.gpslogger.loggers.Files; @@ -37,6 +38,8 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import de.greenrobot.event.EventBus; + public class Gpx10FileLogger implements FileLogger { protected final static Object lock = new Object(); @@ -160,7 +163,8 @@ public void run() { LOG.debug("Finished annotation to GPX10 File"); } catch (Exception e) { - LOG.error("Gpx10FileLogger.annotate", e); + EventBus.getDefault().post(new CommandEvents.FileWriteFailure()); + LOG.error("Error annotating GPX file", e); } } @@ -241,7 +245,8 @@ public void run() { LOG.debug("Finished writing to GPX10 file"); } catch (Exception e) { - LOG.error("Gpx10FileLogger.write", e); + EventBus.getDefault().post(new CommandEvents.FileWriteFailure()); + LOG.error("Error writing to GPX file", e); } } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/kml/Kml22FileLogger.java b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/kml/Kml22FileLogger.java index 87e01242e..b9952a83c 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/kml/Kml22FileLogger.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/kml/Kml22FileLogger.java @@ -24,6 +24,7 @@ import com.mendhak.gpslogger.common.PreferenceHelper; import com.mendhak.gpslogger.common.RejectionHandler; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.events.CommandEvents; import com.mendhak.gpslogger.common.slf4j.Logs; import com.mendhak.gpslogger.loggers.FileLogger; import com.mendhak.gpslogger.loggers.Files; @@ -36,6 +37,8 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import de.greenrobot.event.EventBus; + public class Kml22FileLogger implements FileLogger { protected final static Object lock = new Object(); private final static ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, @@ -117,7 +120,8 @@ public void run() { } } catch (Exception e) { - LOG.error("Kml22FileLogger.annotate", e); + EventBus.getDefault().post(new CommandEvents.FileWriteFailure()); + LOG.error("Error writing KML annotation", e); } } @@ -222,7 +226,8 @@ public void run() { } } catch (Exception e) { - LOG.error("Kml22FileLogger.write", e); + EventBus.getDefault().post(new CommandEvents.FileWriteFailure()); + LOG.error("Error writing KML file", e); } } } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/nmea/NmeaFileLogger.java b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/nmea/NmeaFileLogger.java index bee4d5ad5..5653fcfcd 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/nmea/NmeaFileLogger.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/nmea/NmeaFileLogger.java @@ -23,8 +23,12 @@ import com.mendhak.gpslogger.common.RejectionHandler; import com.mendhak.gpslogger.common.Session; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.events.CommandEvents; +import com.mendhak.gpslogger.common.slf4j.Logs; import com.mendhak.gpslogger.loggers.Files; +import org.slf4j.Logger; + import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; @@ -33,9 +37,12 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import de.greenrobot.event.EventBus; + public class NmeaFileLogger { protected final static Object lock = new Object(); + private static final Logger LOG = Logs.of(NmeaFileLogger.class); String fileName; private final static ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(10), new RejectionHandler()); @@ -60,7 +67,8 @@ public void write(long timestamp, String nmeaSentence) { try { nmeaFile.createNewFile(); } catch (IOException e) { - + LOG.error("Error creating NMEA file", e); + EventBus.getDefault().post(new CommandEvents.FileWriteFailure(true)); } } @@ -71,6 +79,7 @@ public void write(long timestamp, String nmeaSentence) { class NmeaWriteHandler implements Runnable { + private static final Logger LOG = Logs.of(NmeaWriteHandler.class); File gpxFile; String nmeaSentence; @@ -92,7 +101,8 @@ public void run() { Files.addToMediaDatabase(gpxFile, "text/plain"); } catch (IOException e) { - + LOG.error("Error writing NMEA sentence", e); + EventBus.getDefault().post(new CommandEvents.FileWriteFailure(true)); } } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/opengts/OpenGtsUdpJob.java b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/opengts/OpenGtsUdpJob.java deleted file mode 100644 index 60a08378b..000000000 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/opengts/OpenGtsUdpJob.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2016 mendhak - * - * This file is part of GPSLogger for Android. - * - * GPSLogger for Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * GPSLogger for Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GPSLogger for Android. If not, see . - */ - -package com.mendhak.gpslogger.loggers.opengts; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.birbit.android.jobqueue.Job; -import com.birbit.android.jobqueue.Params; -import com.birbit.android.jobqueue.RetryConstraint; -import com.mendhak.gpslogger.common.SerializableLocation; -import com.mendhak.gpslogger.common.Strings; -import com.mendhak.gpslogger.common.events.UploadEvents; -import com.mendhak.gpslogger.common.slf4j.Logs; -import com.mendhak.gpslogger.senders.opengts.OpenGTSManager; -import de.greenrobot.event.EventBus; -import org.slf4j.Logger; - -import java.net.*; - - -public class OpenGtsUdpJob extends Job { - - String server; - int port ; - String accountName ; - String path ; - String deviceId ; - String communication; - SerializableLocation[] locations; - private static final Logger LOG = Logs.of(OpenGtsUdpJob.class); - - public OpenGtsUdpJob(String server, int port, String accountName, String path, String deviceId, String communication, SerializableLocation[] locations){ - super(new Params(1).requireNetwork().persist()); - - this.server = server; - this.port = port; - this.accountName = accountName; - this.path = path; - this.deviceId = deviceId; - this.communication = communication; - this.locations = locations; - } - - @Override - public void onAdded() { - - } - - @Override - public void onRun() throws Throwable { - - LOG.debug("Running OpenGTS Job"); - sendRAW(deviceId, accountName, locations); - EventBus.getDefault().post(new UploadEvents.OpenGTS().succeeded()); - } - - @Override - protected void onCancel(int cancelReason, @Nullable Throwable throwable) { - - } - - @Override - protected RetryConstraint shouldReRunOnThrowable(@NonNull Throwable throwable, int runCount, int maxRunCount) { - LOG.error("Could not send to OpenGTS", throwable); - EventBus.getDefault().post(new UploadEvents.OpenGTS().failed("Could not send to OpenGTS", throwable)); - return RetryConstraint.CANCEL; - } - - - public void sendRAW(String id, String accountName, SerializableLocation[] locations) throws Exception { - for (SerializableLocation loc : locations) { - if(Strings.isNullOrEmpty(accountName)){ - accountName = id; - } - String message = accountName + "/" + id + "/" + OpenGTSManager.gprmcEncode(loc); - DatagramSocket socket = new DatagramSocket(); - byte[] buffer = message.getBytes(); - DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName(server), port); - LOG.debug("Sending UDP " + message); - socket.send(packet); - socket.close(); - } - } - - - - - -} diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/opengts/OpenGtsUdpWorker.java b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/opengts/OpenGtsUdpWorker.java new file mode 100644 index 000000000..9b082ab6e --- /dev/null +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/loggers/opengts/OpenGtsUdpWorker.java @@ -0,0 +1,88 @@ +package com.mendhak.gpslogger.loggers.opengts; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import com.mendhak.gpslogger.common.PreferenceHelper; +import com.mendhak.gpslogger.common.SerializableLocation; +import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; +import com.mendhak.gpslogger.common.events.UploadEvents; +import com.mendhak.gpslogger.common.slf4j.Logs; +import com.mendhak.gpslogger.senders.GpxReader; +import com.mendhak.gpslogger.senders.opengts.OpenGTSManager; + +import org.slf4j.Logger; + +import java.io.File; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.util.List; + +import de.greenrobot.event.EventBus; + +public class OpenGtsUdpWorker extends Worker{ + + public static final Logger LOG = Logs.of(OpenGtsUdpWorker.class); + public OpenGtsUdpWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + @NonNull + @Override + public Result doWork() { + + PreferenceHelper preferenceHelper = PreferenceHelper.getInstance(); + String server = preferenceHelper.getOpenGTSServer(); + int port = Integer.valueOf(preferenceHelper.getOpenGTSServerPort()); + String accountName = preferenceHelper.getOpenGTSAccountName(); + String deviceId = preferenceHelper.getOpenGTSDeviceId(); + + String gpxFilePath = getInputData().getString("gpxFilePath"); + String[] serializedLocations = getInputData().getStringArray("locations"); + + try { + if(!Strings.isNullOrEmpty(gpxFilePath)){ + List locations = GpxReader.getPoints(new File(gpxFilePath)); + sendRAW(deviceId, accountName, server, port, locations.toArray(new SerializableLocation[0])); + } + else if (serializedLocations != null && serializedLocations.length > 0){ + SerializableLocation[] locations = new SerializableLocation[serializedLocations.length]; + for (int i = 0; i < serializedLocations.length; i++) { + locations[i] = Strings.deserializeFromJson(serializedLocations[i], SerializableLocation.class); + } + sendRAW(deviceId, accountName, server, port, locations); + } + + // Notify internal listeners + EventBus.getDefault().post(new UploadEvents.OpenGTS().succeeded()); + // Notify external listeners + Systems.sendFileUploadedBroadcast(getApplicationContext(), new String[]{gpxFilePath}, "opengts"); + + } + catch(Exception ex){ + LOG.error("Could not send to OpenGTS", ex); + EventBus.getDefault().post(new UploadEvents.OpenGTS().failed("Could not send to OpenGTS", ex)); + return Result.failure(); + } + return Result.success(); + } + + private void sendRAW(String id, String accountName, String server, int port, SerializableLocation[] locations) throws Exception { + for (SerializableLocation loc : locations) { + if(Strings.isNullOrEmpty(accountName)){ + accountName = id; + } + String message = accountName + "/" + id + "/" + OpenGTSManager.gprmcEncode(loc); + DatagramSocket socket = new DatagramSocket(); + byte[] buffer = message.getBytes(); + DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName(server), port); + LOG.debug("Sending UDP " + message); + socket.send(packet); + socket.close(); + } + } +} diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/customurl/CustomUrlManager.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/customurl/CustomUrlManager.java index c8e672f7e..bfd9ccee6 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/customurl/CustomUrlManager.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/customurl/CustomUrlManager.java @@ -3,18 +3,15 @@ import android.location.Location; import android.os.Bundle; -import com.birbit.android.jobqueue.JobManager; -import com.mendhak.gpslogger.common.AppSettings; import com.mendhak.gpslogger.common.BundleConstants; import com.mendhak.gpslogger.common.PreferenceHelper; import com.mendhak.gpslogger.common.SerializableLocation; import com.mendhak.gpslogger.common.Strings; import com.mendhak.gpslogger.common.Systems; -import com.mendhak.gpslogger.common.events.UploadEvents; import com.mendhak.gpslogger.common.slf4j.Logs; import com.mendhak.gpslogger.loggers.csv.CSVFileLogger; -import com.mendhak.gpslogger.loggers.customurl.CustomUrlJob; import com.mendhak.gpslogger.loggers.customurl.CustomUrlRequest; +import com.mendhak.gpslogger.loggers.customurl.CustomUrlWorker; import com.mendhak.gpslogger.senders.FileSender; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVRecord; @@ -23,11 +20,12 @@ import java.io.FileReader; import java.io.Reader; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; public class CustomUrlManager extends FileSender { @@ -44,9 +42,16 @@ public void uploadFile(List files) { for (File f : files) { if (f.getName().endsWith(".csv")) { foundFileToSend = true; - List locations = getLocationsFromCSV(f); - LOG.debug(locations.size() + " points were read from " + f.getName()); - sendLocations(locations.toArray(new SerializableLocation[locations.size()]), f); + + String tag = String.valueOf(Objects.hashCode(f.getName())); + + HashMap dataMap = new HashMap() {{ + put("csvFilePath", f.getAbsolutePath()); + put("callbackType", "customurl"); + }}; + + Systems.startWorkManagerRequest(CustomUrlWorker.class, dataMap, tag); + } } @@ -145,45 +150,21 @@ private String unApplyDecimalComma(String recordValue) { return recordValue.replace(",","."); } - private void sendLocations(SerializableLocation[] locations, File csvFile){ - if(locations.length > 0){ - - ArrayList requests = new ArrayList<>(); - String customLoggingUrl = preferenceHelper.getCustomLoggingUrl(); - String httpBody = preferenceHelper.getCustomLoggingHTTPBody(); - String httpHeaders = preferenceHelper.getCustomLoggingHTTPHeaders(); - String httpMethod = preferenceHelper.getCustomLoggingHTTPMethod(); - - for(SerializableLocation loc: locations){ - try { - String finalUrl = getFormattedTextblock(customLoggingUrl, loc); - String finalBody = getFormattedTextblock(httpBody, loc); - String finalHeaders = getFormattedTextblock(httpHeaders, loc); - - requests.add(new CustomUrlRequest(finalUrl, httpMethod, - finalBody, finalHeaders, preferenceHelper.getCustomLoggingBasicAuthUsername(), - preferenceHelper.getCustomLoggingBasicAuthPassword())); - } catch (Exception e) { - LOG.error("Could not build the Custom URL to send", e); - } - } - - JobManager jobManager = AppSettings.getJobManager(); - jobManager.addJobInBackground( - new CustomUrlJob( - requests, - csvFile, - new UploadEvents.CustomUrl())); - } - } public void sendByHttp(String url, String method, String body, String headers, String username, String password){ - JobManager jobManager = AppSettings.getJobManager(); - CustomUrlRequest request = new CustomUrlRequest(url, method, - body, headers, username, password); - ArrayList requests = new ArrayList<>(Arrays.asList(request)); - jobManager.addJobInBackground(new CustomUrlJob(requests, null, new UploadEvents.CustomUrl())); + + CustomUrlRequest request = new CustomUrlRequest(url, method, body, headers, username, password); + String serializedRequest = Strings.serializeTojson(request); + String tag = String.valueOf(Objects.hashCode(serializedRequest)); + + HashMap dataMap = new HashMap() {{ + put("urlRequests", new String[]{serializedRequest}); + put("callbackType", "customurl"); + }}; + + Systems.startWorkManagerRequest(CustomUrlWorker.class, dataMap, tag); + } private String getFormattedTextblock(String textToFormat, SerializableLocation loc) throws Exception { @@ -216,6 +197,7 @@ public String getFormattedTextblock(String customLoggingUrl, replacements.put("acc", String.valueOf(sLoc.getAccuracy())); replacements.put("dir", String.valueOf(sLoc.getBearing())); replacements.put("prov", String.valueOf(sLoc.getProvider())); + replacements.put("spd_kph", String.valueOf(sLoc.getSpeed()*3.6)); replacements.put("spd", String.valueOf(sLoc.getSpeed())); replacements.put("timestamp", String.valueOf(sLoc.getTime()/1000)); @@ -276,4 +258,34 @@ public String getName() { public boolean accept(File dir, String name) { return name.toLowerCase().contains(".csv"); } + + public List getCustomUrlRequestsFromCSV(File f) { + List locations = getLocationsFromCSV(f); + LOG.debug(locations.size() + " points were read from " + f.getName()); + + List requests = new ArrayList<>(); + + String customLoggingUrl = preferenceHelper.getCustomLoggingUrl(); + String httpBody = preferenceHelper.getCustomLoggingHTTPBody(); + String httpHeaders = preferenceHelper.getCustomLoggingHTTPHeaders(); + String httpMethod = preferenceHelper.getCustomLoggingHTTPMethod(); + + for(SerializableLocation loc: locations){ + try { + String finalUrl = getFormattedTextblock(customLoggingUrl, loc); + String finalBody = getFormattedTextblock(httpBody, loc); + String finalHeaders = getFormattedTextblock(httpHeaders, loc); + + requests.add(new CustomUrlRequest(finalUrl, httpMethod, + finalBody, finalHeaders, preferenceHelper.getCustomLoggingBasicAuthUsername(), + preferenceHelper.getCustomLoggingBasicAuthPassword())); + } catch (Exception e) { + LOG.error("Could not build the Custom URL to send", e); + } + } + + return requests; + + } + } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/dropbox/DropBoxManager.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/dropbox/DropBoxManager.java index 8e1c4910d..6bb0395dd 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/dropbox/DropBoxManager.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/dropbox/DropBoxManager.java @@ -21,22 +21,21 @@ import android.content.Context; - -import com.birbit.android.jobqueue.CancelResult; -import com.birbit.android.jobqueue.JobManager; -import com.birbit.android.jobqueue.TagConstraint; import com.dropbox.core.*; import com.dropbox.core.android.Auth; import com.dropbox.core.oauth.DbxCredential; -import com.mendhak.gpslogger.common.AppSettings; + import com.mendhak.gpslogger.common.PreferenceHelper; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; import com.mendhak.gpslogger.common.slf4j.Logs; import com.mendhak.gpslogger.senders.FileSender; import org.slf4j.Logger; import java.io.File; +import java.util.HashMap; import java.util.List; +import java.util.Objects; public class DropBoxManager extends FileSender { @@ -85,7 +84,7 @@ public void unLink() { public void uploadFile(List files) { for (File f : files) { LOG.debug(f.getName()); - uploadFile(f.getName()); + uploadFile(f); } } @@ -104,15 +103,14 @@ public String getName() { return SenderNames.DROPBOX; } - public void uploadFile(final String fileName) { + public void uploadFile(File fileToUpload) { + + HashMap dataMap = new HashMap(){{ + put("filePath", fileToUpload.getAbsolutePath()); + }}; + String tag = String.valueOf(Objects.hashCode(fileToUpload.getName())); + Systems.startWorkManagerRequest(DropboxWorker.class, dataMap, tag); - final JobManager jobManager = AppSettings.getJobManager(); - jobManager.cancelJobsInBackground(new CancelResult.AsyncCancelCallback() { - @Override - public void onCancelled(CancelResult cancelResult) { - jobManager.addJobInBackground(new DropboxJob(fileName)); - } - }, TagConstraint.ANY, DropboxJob.getJobTag(fileName)); } @Override diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/dropbox/DropboxJob.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/dropbox/DropboxJob.java deleted file mode 100644 index ae003a904..000000000 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/dropbox/DropboxJob.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2016 mendhak - * - * This file is part of GPSLogger for Android. - * - * GPSLogger for Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * GPSLogger for Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GPSLogger for Android. If not, see . - */ - -package com.mendhak.gpslogger.senders.dropbox; - - - - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.birbit.android.jobqueue.Job; -import com.birbit.android.jobqueue.Params; -import com.birbit.android.jobqueue.RetryConstraint; -import com.dropbox.core.DbxRequestConfig; -import com.dropbox.core.oauth.DbxCredential; -import com.dropbox.core.v2.DbxClientV2; -import com.dropbox.core.v2.files.WriteMode; -import com.mendhak.gpslogger.common.PreferenceHelper; -import com.mendhak.gpslogger.common.Strings; -import com.mendhak.gpslogger.common.events.UploadEvents; -import com.mendhak.gpslogger.common.slf4j.Logs; -import de.greenrobot.event.EventBus; -import org.slf4j.Logger; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; - - -public class DropboxJob extends Job { - - - private static final Logger LOG = Logs.of(DropboxJob.class); - private static PreferenceHelper preferenceHelper = PreferenceHelper.getInstance(); - String fileName; - - - protected DropboxJob(String fileName) { - super(new Params(1).requireNetwork().persist().addTags(getJobTag(fileName))); - - this.fileName = fileName; - } - - @Override - public void onAdded() { - LOG.debug("Dropbox job added"); - } - - @Override - public void onRun() throws Throwable { - File gpsDir = new File(preferenceHelper.getGpsLoggerFolder()); - File gpxFile = new File(gpsDir, fileName); - - try { - LOG.debug("Beginning upload to dropbox..."); - InputStream inputStream = new FileInputStream(gpxFile); - DbxRequestConfig requestConfig = DbxRequestConfig.newBuilder("GPSLogger").build(); - DbxClientV2 mDbxClient; - - if(!Strings.isNullOrEmpty(PreferenceHelper.getInstance().getDropboxRefreshToken())){ - DbxCredential dropboxCred = DbxCredential.Reader.readFully(PreferenceHelper.getInstance().getDropboxRefreshToken()); - mDbxClient = new DbxClientV2(requestConfig, dropboxCred); - } - else { - //For existing users that already have long lived access tokens stored. - mDbxClient = new DbxClientV2(requestConfig, PreferenceHelper.getInstance().getDropboxLongLivedAccessKey()); - } - - mDbxClient.files().uploadBuilder("/" + fileName).withMode(WriteMode.OVERWRITE).uploadAndFinish(inputStream); - - EventBus.getDefault().post(new UploadEvents.Dropbox().succeeded()); - LOG.info("Dropbox - file uploaded"); - } catch (Exception e) { - LOG.error("Could not upload to Dropbox" , e); - EventBus.getDefault().post(new UploadEvents.Dropbox().failed(e.getMessage(), e)); - } - - } - - @Override - protected void onCancel(int cancelReason, @Nullable Throwable throwable) { - - } - - @Override - protected RetryConstraint shouldReRunOnThrowable(@NonNull Throwable throwable, int runCount, int maxRunCount) { - EventBus.getDefault().post(new UploadEvents.Dropbox().failed("Could not upload to Dropbox", throwable)); - LOG.error("Could not upload to Dropbox", throwable); - return RetryConstraint.CANCEL; - } - - - public static String getJobTag(String fileName) { - return "DROPBOX" + fileName; - } -} diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/dropbox/DropboxWorker.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/dropbox/DropboxWorker.java new file mode 100644 index 000000000..3c700b46d --- /dev/null +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/dropbox/DropboxWorker.java @@ -0,0 +1,79 @@ +package com.mendhak.gpslogger.senders.dropbox; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import com.dropbox.core.DbxRequestConfig; +import com.dropbox.core.oauth.DbxCredential; +import com.dropbox.core.v2.DbxClientV2; +import com.dropbox.core.v2.files.WriteMode; +import com.mendhak.gpslogger.common.PreferenceHelper; +import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; +import com.mendhak.gpslogger.common.events.UploadEvents; +import com.mendhak.gpslogger.common.slf4j.Logs; + +import org.slf4j.Logger; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; + +import de.greenrobot.event.EventBus; + +public class DropboxWorker extends Worker { + + private static final Logger LOG = Logs.of(DropboxWorker.class); + public DropboxWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + + @NonNull + @Override + public Result doWork() { + + String filePath = getInputData().getString("filePath"); + if(Strings.isNullOrEmpty(filePath)) { + EventBus.getDefault().post(new UploadEvents.Dropbox().failed("Dropbox upload failed", new Throwable("Nothing to upload."))); + return Result.failure(); + } + + File fileToUpload = new File(filePath); + + try { + LOG.debug("Beginning upload to dropbox..."); + InputStream inputStream = new FileInputStream(fileToUpload); + DbxRequestConfig requestConfig = DbxRequestConfig.newBuilder("GPSLogger").build(); + DbxClientV2 mDbxClient; + + if(!Strings.isNullOrEmpty(PreferenceHelper.getInstance().getDropboxRefreshToken())){ + DbxCredential dropboxCred = DbxCredential.Reader.readFully(PreferenceHelper.getInstance().getDropboxRefreshToken()); + mDbxClient = new DbxClientV2(requestConfig, dropboxCred); + } + else { + //For existing users that already have long lived access tokens stored. + mDbxClient = new DbxClientV2(requestConfig, PreferenceHelper.getInstance().getDropboxLongLivedAccessKey()); + } + + mDbxClient.files().uploadBuilder("/" + fileToUpload.getName()).withMode(WriteMode.OVERWRITE).uploadAndFinish(inputStream); + + // Notify internal listeners + EventBus.getDefault().post(new UploadEvents.Dropbox().succeeded()); + // Notify external listeners + Systems.sendFileUploadedBroadcast(getApplicationContext(), new String[]{fileToUpload.getAbsolutePath()}, "dropbox"); + + LOG.info("Dropbox - file uploaded"); + } catch (Exception e) { + LOG.error("Could not upload to Dropbox" , e); + EventBus.getDefault().post(new UploadEvents.Dropbox().failed(e.getMessage(), e)); + return Result.failure(); + } + + + + return Result.success(); + } +} diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/email/AutoEmailManager.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/email/AutoEmailManager.java index d921418cb..b7770bda6 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/email/AutoEmailManager.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/email/AutoEmailManager.java @@ -18,18 +18,16 @@ */ package com.mendhak.gpslogger.senders.email; -import com.birbit.android.jobqueue.CancelResult; -import com.birbit.android.jobqueue.JobManager; -import com.birbit.android.jobqueue.TagConstraint; -import com.mendhak.gpslogger.common.AppSettings; import com.mendhak.gpslogger.common.PreferenceHelper; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; import com.mendhak.gpslogger.senders.FileSender; import java.io.File; -import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Objects; public class AutoEmailManager extends FileSender { @@ -42,29 +40,22 @@ public AutoEmailManager(PreferenceHelper helper) { @Override public void uploadFile(List files) { - final ArrayList filesToSend = new ArrayList<>(); - - //If a zip file exists, remove others - for (File f : files) { - filesToSend.add(f); + String[] fileNames = new String[files.size()]; + for (int i = 0; i < files.size(); i++) { + fileNames[i] = files.get(i).getAbsolutePath(); } final String subject = "GPS Log file generated at "+ Strings.getReadableDateTime(new Date()); - final String body = "GPS Log file generated at "+ Strings.getReadableDateTime(new Date()); - final JobManager jobManager = AppSettings.getJobManager(); - jobManager.cancelJobsInBackground(new CancelResult.AsyncCancelCallback() { - @Override - public void onCancelled(CancelResult cancelResult) { - jobManager.addJobInBackground(new AutoEmailJob(preferenceHelper.getSmtpServer(), - preferenceHelper.getSmtpPort(), preferenceHelper.getSmtpUsername(), preferenceHelper.getSmtpPassword(), - preferenceHelper.isSmtpSsl(), preferenceHelper.getAutoEmailTargets(), preferenceHelper.getSmtpSenderAddress(), - subject, body, filesToSend.toArray(new File[filesToSend.size()]))); - } - }, TagConstraint.ANY, AutoEmailJob.getJobTag(filesToSend.toArray(new File[filesToSend.size()]))); - + HashMap dataMap = new HashMap() {{ + put("subject", subject); + put("body", body); + put("fileNames", fileNames); + }}; + String tag = String.valueOf(Objects.hashCode(fileNames)) ; + Systems.startWorkManagerRequest(AutoEmailWorker.class, dataMap, tag); } @Override @@ -83,18 +74,19 @@ public String getName() { } - public void sendTestEmail(String smtpServer, String smtpPort, - String smtpUsername, String smtpPassword, boolean smtpUseSsl, - String emailTarget, String fromAddress) { + public void sendTestEmail() { String subject = "Test Email from GPSLogger at " + Strings.getReadableDateTime(new Date()); String body ="Test Email from GPSLogger at " + Strings.getReadableDateTime(new Date()); - JobManager jobManager = AppSettings.getJobManager(); - jobManager.addJobInBackground(new AutoEmailJob(smtpServer, - smtpPort, smtpUsername, smtpPassword, smtpUseSsl, - emailTarget, fromAddress, subject, body, new File[]{})); + HashMap dataMap = new HashMap() {{ + put("subject", subject); + put("body", body); + put("fileNames", new String[]{}); + }}; + String tag = String.valueOf(Objects.hashCode(new String[]{})) ; + Systems.startWorkManagerRequest(AutoEmailWorker.class, dataMap, tag); } @Override diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/email/AutoEmailJob.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/email/AutoEmailWorker.java similarity index 71% rename from gpslogger/src/main/java/com/mendhak/gpslogger/senders/email/AutoEmailJob.java rename to gpslogger/src/main/java/com/mendhak/gpslogger/senders/email/AutoEmailWorker.java index 1ce23dcf6..2fef8973b 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/email/AutoEmailJob.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/email/AutoEmailWorker.java @@ -1,96 +1,70 @@ -/* - * Copyright (C) 2016 mendhak - * - * This file is part of GPSLogger for Android. - * - * GPSLogger for Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * GPSLogger for Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GPSLogger for Android. If not, see . - */ - package com.mendhak.gpslogger.senders.email; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import android.content.Context; import android.util.Base64; -import com.birbit.android.jobqueue.Job; -import com.birbit.android.jobqueue.Params; -import com.birbit.android.jobqueue.RetryConstraint; +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + import com.mendhak.gpslogger.common.AppSettings; -import com.mendhak.gpslogger.common.network.Networks; +import com.mendhak.gpslogger.common.PreferenceHelper; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; import com.mendhak.gpslogger.common.events.UploadEvents; +import com.mendhak.gpslogger.common.network.LocalX509TrustManager; +import com.mendhak.gpslogger.common.network.Networks; import com.mendhak.gpslogger.common.slf4j.Logs; import com.mendhak.gpslogger.loggers.Files; import com.mendhak.gpslogger.loggers.Streams; -import com.mendhak.gpslogger.common.network.LocalX509TrustManager; -import de.greenrobot.event.EventBus; + import org.apache.commons.net.ProtocolCommandEvent; import org.apache.commons.net.ProtocolCommandListener; -import org.apache.commons.net.smtp.*; +import org.apache.commons.net.smtp.AuthenticatingSMTPClient; +import org.apache.commons.net.smtp.SMTPClient; +import org.apache.commons.net.smtp.SMTPReply; +import org.apache.commons.net.smtp.SimpleSMTPHeader; import org.slf4j.Logger; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.UUID; +import de.greenrobot.event.EventBus; -public class AutoEmailJob extends Job { - - private static final Logger LOG = Logs.of(AutoEmailJob.class); - String smtpServer; - String smtpPort; - String smtpUsername; - String smtpPassword; - boolean smtpUseSsl; - String csvEmailTargets; - String fromAddress; - String subject; - String body; - File[] files; - static ArrayList smtpServerResponses; - static UploadEvents.AutoEmail smtpFailureEvent; - - - - protected AutoEmailJob(String smtpServer, - String smtpPort, String smtpUsername, String smtpPassword, - boolean smtpUseSsl, String csvEmailTargets, String fromAddress, - String subject, String body, File[] files) { - super(new Params(1).requireNetwork().persist().addTags(getJobTag(files))); - this.smtpServer = smtpServer; - this.smtpPort = smtpPort; - this.smtpPassword = smtpPassword; - this.smtpUsername = smtpUsername; - this.smtpUseSsl = smtpUseSsl; - this.csvEmailTargets = csvEmailTargets; - this.fromAddress = fromAddress; - this.subject = subject; - this.body = body; - this.files = files; - - smtpServerResponses = new ArrayList<>(); - smtpFailureEvent = null; - } - - @Override - public void onAdded() { +public class AutoEmailWorker extends Worker { + private static final Logger LOG = Logs.of(AutoEmailWorker.class); + public AutoEmailWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); } + @NonNull @Override - public void onRun() throws Throwable { + public Result doWork() { + + PreferenceHelper preferenceHelper = PreferenceHelper.getInstance(); + String smtpServer = preferenceHelper.getSmtpServer(); + String smtpPort = preferenceHelper.getSmtpPort(); + String smtpPassword = preferenceHelper.getSmtpPassword(); + String smtpUsername = preferenceHelper.getSmtpUsername(); + boolean smtpUseSsl = preferenceHelper.isSmtpSsl(); + String csvEmailTargets = preferenceHelper.getAutoEmailTargets(); + String fromAddress = preferenceHelper.getSmtpSenderAddress(); + + String subject = getInputData().getString("subject"); + String body = getInputData().getString("body"); + String[] fileNames = getInputData().getStringArray("fileNames"); + + File[] files = new File[fileNames.length]; + for (int i = 0; i < fileNames.length; i++) { + files[i] = new File(fileNames[i]); + } int port = Strings.toInt(smtpPort,25); @@ -100,6 +74,9 @@ public void onRun() throws Throwable { AuthenticatingSMTPClient client = new AuthenticatingSMTPClient(); + ArrayList smtpServerResponses = new ArrayList<>(); + UploadEvents.AutoEmail smtpFailureEvent = null; + try { client.addProtocolCommandListener(new ProtocolCommandListener() { @@ -191,7 +168,10 @@ public void protocolReplyReceived(ProtocolCommandEvent event) { } else { LOG.info("Email - file sent"); + // Notify internal listeners EventBus.getDefault().post(new UploadEvents.AutoEmail().succeeded()); + // Notify external listeners + Systems.sendFileUploadedBroadcast(getApplicationContext(), fileNames, "email"); } } else { @@ -217,20 +197,13 @@ public void protocolReplyReceived(ProtocolCommandEvent event) { smtpFailureEvent.smtpMessages = new ArrayList<>(Arrays.asList(client.getReplyStrings())); } EventBus.getDefault().post(smtpFailureEvent); + return Result.failure(); } } - } - @Override - protected void onCancel(int cancelReason, @Nullable Throwable throwable) { - LOG.debug("Email job cancelled"); - } - @Override - protected RetryConstraint shouldReRunOnThrowable(@NonNull Throwable throwable, int runCount, int maxRunCount) { - LOG.error("Could not send email", throwable); - return RetryConstraint.CANCEL; + return Result.success(); } @@ -263,14 +236,4 @@ private static void checkReply(SMTPClient sc) throws Exception { throw new Exception("Permanent SMTP error " + sc.getReplyString()); } } - - - public static String getJobTag(File[] files) { - StringBuilder sb = new StringBuilder(); - for(File f : files){ - sb.append(f.getName()).append("."); - } - return "EMAIL" + sb.toString(); - - } } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/ftp/FtpManager.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/ftp/FtpManager.java index fd4508367..09b23fbe8 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/ftp/FtpManager.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/ftp/FtpManager.java @@ -21,23 +21,20 @@ package com.mendhak.gpslogger.senders.ftp; - -import com.birbit.android.jobqueue.CancelResult; -import com.birbit.android.jobqueue.JobManager; -import com.birbit.android.jobqueue.TagConstraint; -import com.mendhak.gpslogger.common.AppSettings; import com.mendhak.gpslogger.common.PreferenceHelper; +import com.mendhak.gpslogger.common.Systems; import com.mendhak.gpslogger.common.events.UploadEvents; import com.mendhak.gpslogger.common.slf4j.Logs; import com.mendhak.gpslogger.loggers.Files; import com.mendhak.gpslogger.senders.FileSender; + import de.greenrobot.event.EventBus; import org.slf4j.Logger; -import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileOutputStream; +import java.util.HashMap; import java.util.List; +import java.util.Objects; public class FtpManager extends FileSender { private static final Logger LOG = Logs.of(FtpManager.class); @@ -48,28 +45,22 @@ public FtpManager(PreferenceHelper preferenceHelper) { this.preferenceHelper = preferenceHelper; } - public void testFtp(final String servername, final String username, final String password, final String directory, final int port, final boolean useFtps, final String protocol, final boolean implicit) { + public void testFtp() { try { final File testFile = Files.createTestFile(); - final JobManager jobManager = AppSettings.getJobManager(); + HashMap dataMap = new HashMap() {{ + put("filePath", testFile.getAbsolutePath()); + }}; - jobManager.cancelJobsInBackground(new CancelResult.AsyncCancelCallback() { - @Override - public void onCancelled(CancelResult cancelResult) { - jobManager.addJobInBackground(new FtpJob(servername, port, username, password, directory, - useFtps, protocol, implicit, testFile, testFile.getName())); - } - }, TagConstraint.ANY, FtpJob.getJobTag(testFile)); + String tag = String.valueOf(Objects.hashCode(testFile)) ; + Systems.startWorkManagerRequest(FtpWorker.class, dataMap, tag); } catch (Exception ex) { EventBus.getDefault().post(new UploadEvents.Ftp().failed(ex.getMessage(), ex)); } - - - } @Override @@ -77,6 +68,7 @@ public void uploadFile(List files) { if (!validSettings(preferenceHelper.getFtpServerName(), preferenceHelper.getFtpUsername(), preferenceHelper.getFtpPassword(), preferenceHelper.getFtpPort(), preferenceHelper.shouldFtpUseFtps(), preferenceHelper.getFtpProtocol(), preferenceHelper.isFtpImplicit())) { EventBus.getDefault().post(new UploadEvents.Ftp().failed()); + return; } for (File f : files) { @@ -103,17 +95,12 @@ public String getName() { public void uploadFile(final File f) { - final JobManager jobManager = AppSettings.getJobManager(); - jobManager.cancelJobsInBackground(new CancelResult.AsyncCancelCallback() { - @Override - public void onCancelled(CancelResult cancelResult) { - jobManager.addJobInBackground(new FtpJob(preferenceHelper.getFtpServerName(), preferenceHelper.getFtpPort(), - preferenceHelper.getFtpUsername(), preferenceHelper.getFtpPassword(), preferenceHelper.getFtpDirectory(), - preferenceHelper.shouldFtpUseFtps(), preferenceHelper.getFtpProtocol(), preferenceHelper.isFtpImplicit(), - f, f.getName())); - } - }, TagConstraint.ANY, FtpJob.getJobTag(f)); + HashMap dataMap = new HashMap() {{ + put("filePath", f.getAbsolutePath()); + }}; + String tag = String.valueOf(Objects.hashCode(f)) ; + Systems.startWorkManagerRequest(FtpWorker.class, dataMap, tag); } @Override diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/ftp/FtpJob.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/ftp/FtpWorker.java similarity index 73% rename from gpslogger/src/main/java/com/mendhak/gpslogger/senders/ftp/FtpJob.java rename to gpslogger/src/main/java/com/mendhak/gpslogger/senders/ftp/FtpWorker.java index 9e4e34374..f29947b85 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/ftp/FtpJob.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/ftp/FtpWorker.java @@ -1,38 +1,20 @@ -/* - * Copyright (C) 2016 mendhak - * - * This file is part of GPSLogger for Android. - * - * GPSLogger for Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * GPSLogger for Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GPSLogger for Android. If not, see . - */ - package com.mendhak.gpslogger.senders.ftp; +import android.content.Context; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import androidx.work.Worker; +import androidx.work.WorkerParameters; -import com.birbit.android.jobqueue.Job; -import com.birbit.android.jobqueue.Params; -import com.birbit.android.jobqueue.RetryConstraint; import com.mendhak.gpslogger.common.AppSettings; -import com.mendhak.gpslogger.common.network.Networks; +import com.mendhak.gpslogger.common.PreferenceHelper; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; import com.mendhak.gpslogger.common.events.UploadEvents; +import com.mendhak.gpslogger.common.network.Networks; import com.mendhak.gpslogger.common.slf4j.LoggingOutputStream; import com.mendhak.gpslogger.common.slf4j.Logs; -import de.greenrobot.event.EventBus; + import org.apache.commons.net.PrintCommandListener; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; @@ -40,54 +22,62 @@ import org.apache.commons.net.ftp.FTPSClient; import org.slf4j.Logger; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; -public class FtpJob extends Job { - - private static final Logger LOG = Logs.of(FtpJob.class); +import de.greenrobot.event.EventBus; - String server; - int port; - String username; - String password; - boolean useFtps; - String protocol; - boolean implicit; - File gpxFile; - String fileName; - String directory; +public class FtpWorker extends Worker { + private static final Logger LOG = Logs.of(FtpWorker.class); static UploadEvents.Ftp jobResult; static ArrayList ftpServerResponses; - protected FtpJob(String server, int port, String username, - String password, String directory, boolean useFtps, String protocol, boolean implicit, - File gpxFile, String fileName) { - super(new Params(1).requireNetwork().persist().addTags(getJobTag(gpxFile))); - - this.server = server; - this.port = port; - this.username = username; - this.password = password; - this.useFtps = useFtps; - this.protocol = protocol; - this.implicit = implicit; - this.gpxFile = gpxFile; - this.fileName = fileName; - this.directory = directory; + public FtpWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + @NonNull + @Override + public Result doWork() { ftpServerResponses = new ArrayList<>(); - jobResult = null; + PreferenceHelper preferenceHelper = PreferenceHelper.getInstance(); + String server = preferenceHelper.getFtpServerName(); + int port = preferenceHelper.getFtpPort(); + String username = preferenceHelper.getFtpUsername(); + String password = preferenceHelper.getFtpPassword(); + boolean useFtps = preferenceHelper.shouldFtpUseFtps(); + String protocol = preferenceHelper.getFtpProtocol(); + boolean implicit = preferenceHelper.isFtpImplicit(); + + String filePath = getInputData().getString("filePath"); + File file = new File(filePath); + + String directory = preferenceHelper.getFtpDirectory(); + + if (upload(server, username, password, directory, port, useFtps, protocol, implicit, file, file.getName())) { + LOG.info("FTP - file uploaded"); + // Notify internal listeners + EventBus.getDefault().post(new UploadEvents.Ftp().succeeded()); + // Notify external listeners + Systems.sendFileUploadedBroadcast(getApplicationContext(), new String[]{file.getAbsolutePath()}, "ftp"); + return Result.success(); + } else { + jobResult.ftpMessages = ftpServerResponses; + EventBus.getDefault().post(jobResult); + } + + return Result.success(); } + public synchronized static boolean upload(String server, String username, String password, String directory, int port, boolean useFtps, String protocol, boolean implicit, File gpxFile, String fileName) { @@ -197,7 +187,6 @@ public synchronized static boolean upload(String server, String username, String return true; } - private static void ftpCreateDirectoryTree(FTPClient client, String dirTree) throws IOException { boolean dirExists = true; @@ -236,36 +225,4 @@ private static void logServerReply(FTPClient client) { } } } - - @Override - public void onAdded() { - - } - - @Override - public void onRun() throws Throwable { - if (upload(server, username, password, directory, port, useFtps, protocol, implicit, gpxFile, fileName)) { - LOG.info("FTP - file uploaded"); - EventBus.getDefault().post(new UploadEvents.Ftp().succeeded()); - } else { - jobResult.ftpMessages = ftpServerResponses; - EventBus.getDefault().post(jobResult); - } - } - - @Override - protected void onCancel(int cancelReason, @Nullable Throwable throwable) { - - } - - @Override - protected RetryConstraint shouldReRunOnThrowable(@NonNull Throwable throwable, int runCount, int maxRunCount) { - EventBus.getDefault().post(new UploadEvents.Ftp().failed("Could not FTP file", throwable)); - LOG.error("Could not FTP file", throwable); - return RetryConstraint.CANCEL; - } - - public static String getJobTag(File testFile) { - return "FTP"+testFile.getName(); - } } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/googledrive/GoogleDriveManager.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/googledrive/GoogleDriveManager.java index 8b29d99cd..fbe1652c5 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/googledrive/GoogleDriveManager.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/googledrive/GoogleDriveManager.java @@ -3,12 +3,9 @@ import android.content.Context; import android.net.Uri; -import com.birbit.android.jobqueue.CancelResult; -import com.birbit.android.jobqueue.JobManager; -import com.birbit.android.jobqueue.TagConstraint; -import com.mendhak.gpslogger.common.AppSettings; import com.mendhak.gpslogger.common.PreferenceHelper; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; import com.mendhak.gpslogger.common.slf4j.Logs; import com.mendhak.gpslogger.senders.FileSender; @@ -21,7 +18,9 @@ import org.slf4j.Logger; import java.io.File; +import java.util.HashMap; import java.util.List; +import java.util.Objects; public class GoogleDriveManager extends FileSender { @@ -81,18 +80,19 @@ public static AuthState getAuthState() { public void uploadFile(List files) { for (File f : files) { LOG.debug(f.getName()); - uploadFile(f.getName()); + uploadFile(f); } } - public void uploadFile(String fileName) { - final JobManager jobManager = AppSettings.getJobManager(); - jobManager.cancelJobsInBackground(new CancelResult.AsyncCancelCallback() { - @Override - public void onCancelled(CancelResult cancelResult) { - jobManager.addJobInBackground(new GoogleDriveJob(fileName)); - } - }, TagConstraint.ANY, GoogleDriveJob.getJobTag(fileName)); + public void uploadFile(File fileToUpload) { + + String tag = String.valueOf(Objects.hashCode(fileToUpload)); + HashMap dataMap = new HashMap() {{ + put("filePath", fileToUpload.getAbsolutePath()); + }}; + + Systems.startWorkManagerRequest(GoogleDriveWorker.class, dataMap, tag); + } @Override diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/googledrive/GoogleDriveJob.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/googledrive/GoogleDriveWorker.java similarity index 60% rename from gpslogger/src/main/java/com/mendhak/gpslogger/senders/googledrive/GoogleDriveJob.java rename to gpslogger/src/main/java/com/mendhak/gpslogger/senders/googledrive/GoogleDriveWorker.java index d274fbed1..c8fb14bb3 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/googledrive/GoogleDriveJob.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/googledrive/GoogleDriveWorker.java @@ -1,16 +1,17 @@ package com.mendhak.gpslogger.senders.googledrive; +import android.content.Context; import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.work.Worker; +import androidx.work.WorkerParameters; -import com.birbit.android.jobqueue.Job; -import com.birbit.android.jobqueue.Params; -import com.birbit.android.jobqueue.RetryConstraint; import com.mendhak.gpslogger.common.AppSettings; import com.mendhak.gpslogger.common.PreferenceHelper; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; import com.mendhak.gpslogger.common.events.UploadEvents; import com.mendhak.gpslogger.common.slf4j.Logs; import com.mendhak.gpslogger.loggers.Files; @@ -35,69 +36,66 @@ import okhttp3.RequestBody; import okhttp3.Response; -public class GoogleDriveJob extends Job { +public class GoogleDriveWorker extends Worker { + private static final Logger LOG = Logs.of(GoogleDriveWorker.class); - private static final Logger LOG = Logs.of(GoogleDriveJob.class); - private static final PreferenceHelper preferenceHelper = PreferenceHelper.getInstance(); - private final AtomicBoolean taskDone = new AtomicBoolean(false); - private final String fileName; private String googleDriveAccessToken; - protected GoogleDriveJob(String fileName) { - super(new Params(1).requireNetwork().persist().addTags(getJobTag(fileName)).groupBy("GoogleDrive")); - this.fileName = fileName; - } - public static String getJobTag(String fileName) { - return "GOOGLEDRIVE" + fileName; + public GoogleDriveWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); } + @NonNull @Override - public void onAdded() { - LOG.debug("Google Drive job added"); - } + public Result doWork() { + + String filePath = getInputData().getString("filePath"); + File fileToUpload = new File(filePath); + boolean success = true; + String failureMessage = ""; + Throwable failureThrowable = null; + - @Override - public void onRun() throws Throwable { - File gpsDir = new File(preferenceHelper.getGpsLoggerFolder()); AuthState authState = GoogleDriveManager.getAuthState(); if (!authState.isAuthorized()) { EventBus.getDefault().post(new UploadEvents.GoogleDrive().failed("Could not upload to Google Drive. Not Authorized.")); } - AuthorizationService authorizationService = GoogleDriveManager.getAuthorizationService(AppSettings.getInstance()); - - // The performActionWithFreshTokens seems to happen on a UI thread! (Why??) - // So I can't do network calls on this thread. - // Instead, updating a class level variable, and waiting for it afterwards. - // https://github.com/openid/AppAuth-Android/issues/123 - authState.performActionWithFreshTokens(authorizationService, new AuthState.AuthStateAction() { - @Override - public void execute(@Nullable String accessToken, @Nullable String idToken, @Nullable AuthorizationException ex) { - if (ex != null) { - EventBus.getDefault().post(new UploadEvents.GoogleDrive().failed(ex.toJsonString(), ex)); + final AtomicBoolean taskDone = new AtomicBoolean(false); + PreferenceHelper preferenceHelper = PreferenceHelper.getInstance(); + + try { + AuthorizationService authorizationService = GoogleDriveManager.getAuthorizationService(AppSettings.getInstance()); + + // The performActionWithFreshTokens seems to happen on a UI thread! (Why??) + // So I can't do network calls on this thread. + // Instead, updating a class level variable, and waiting for it afterwards. + // https://github.com/openid/AppAuth-Android/issues/123 + authState.performActionWithFreshTokens(authorizationService, new AuthState.AuthStateAction() { + @Override + public void execute(@Nullable String accessToken, @Nullable String idToken, @Nullable AuthorizationException ex) { + if (ex != null) { + EventBus.getDefault().post(new UploadEvents.GoogleDrive().failed(ex.toJsonString(), ex)); + taskDone.set(true); + LOG.error(ex.toJsonString(), ex); + return; + } + googleDriveAccessToken = accessToken; taskDone.set(true); - LOG.error(ex.toJsonString(), ex); - return; } - googleDriveAccessToken = accessToken; - taskDone.set(true); - } - }); - - // Wait for the performActionWithFreshTokens.execute callback - // (which happens on the UI thread for some reason) to complete. - while (!taskDone.get()) { - Thread.sleep(500); - } - - if (Strings.isNullOrEmpty(googleDriveAccessToken)) { - LOG.error("Failed to fetch Access Token for Google Drive. Stopping this job."); - return; - } + }); + // Wait for the performActionWithFreshTokens.execute callback + // (which happens on the UI thread for some reason) to complete. + while (!taskDone.get()) { + Thread.sleep(500); + } - try { + if (Strings.isNullOrEmpty(googleDriveAccessToken)) { + LOG.error("Failed to fetch Access Token for Google Drive. Stopping this job."); + return Result.failure(); + } // Figure out the Folder ID to upload to, from the path; recursively create if it doesn't exist. String folderPath = preferenceHelper.getGoogleDriveFolderPath(); @@ -119,36 +117,59 @@ public void execute(@Nullable String accessToken, @Nullable String idToken, @Nul String gpsLoggerFolderId = latestFolderId; if (Strings.isNullOrEmpty(gpsLoggerFolderId)) { - EventBus.getDefault().post(new UploadEvents.GoogleDrive().failed("Could not create folder")); - return; + failureMessage = "Could not create folder"; + success = false; } + else { + // Now search for the file + String gpxFileId = getFileIdFromFileName(googleDriveAccessToken, fileToUpload.getName(), gpsLoggerFolderId); - // Now search for the file - String gpxFileId = getFileIdFromFileName(googleDriveAccessToken, fileName, gpsLoggerFolderId); + if (Strings.isNullOrEmpty(gpxFileId)) { + LOG.debug("Creating an empty file first."); + gpxFileId = createEmptyFile(googleDriveAccessToken, fileToUpload.getName(), Files.getMimeTypeFromFileName(fileToUpload.getName()), gpsLoggerFolderId); - if (Strings.isNullOrEmpty(gpxFileId)) { - LOG.debug("Creating an empty file first."); - gpxFileId = createEmptyFile(googleDriveAccessToken, fileName, Files.getMimeTypeFromFileName(fileName), gpsLoggerFolderId); + if (Strings.isNullOrEmpty(gpxFileId)) { + failureMessage = "Could not create file"; + success = false; + } + } - if (Strings.isNullOrEmpty(gpxFileId)) { - EventBus.getDefault().post(new UploadEvents.GoogleDrive().failed("Could not create file")); - return; + // The above empty file creation needs to happen first - this shouldn't be an 'else' to the above if. + if (!Strings.isNullOrEmpty(gpxFileId)) { + LOG.debug("Uploading file contents"); + updateFileContents(googleDriveAccessToken, gpxFileId, fileToUpload); } - } - // Upload contents to file - if (!Strings.isNullOrEmpty(gpxFileId)) { - LOG.debug("Uploading file contents"); - updateFileContents(googleDriveAccessToken, gpxFileId, fileName); } - EventBus.getDefault().post(new UploadEvents.GoogleDrive().succeeded()); - } catch (Exception e) { LOG.error(e.getMessage(), e); - EventBus.getDefault().post(new UploadEvents.GoogleDrive().failed(e.getMessage(), e)); + success = false; + failureMessage = e.getMessage(); + failureThrowable = e; + } + + if(success){ + // Notify internal listeners + EventBus.getDefault().post(new UploadEvents.GoogleDrive().succeeded()); + // Notify external listeners + Systems.sendFileUploadedBroadcast(getApplicationContext(), new String[]{fileToUpload.getAbsolutePath()}, "googledrive"); + return Result.success(); } + if(getRunAttemptCount() < getRetryLimit()){ + LOG.warn(String.format("Google Drive - attempt %d of %d failed, will retry", getRunAttemptCount(), getRetryLimit())); + return Result.retry(); + } + + if(failureThrowable == null) { + failureThrowable = new Exception(failureMessage); + } + + EventBus.getDefault() + .post(new UploadEvents.GoogleDrive().failed(failureMessage, failureThrowable)); + return Result.failure(); + } private String getFileIdFromFileName(String accessToken, String fileName, String inFolderId) throws Exception { @@ -211,8 +232,8 @@ private String createEmptyFile(String accessToken, String fileName, String mimeT return fileId; } - private String updateFileContents(String accessToken, String gpxFileId, String fileName) throws Exception { - FileInputStream fis = new FileInputStream(new File(preferenceHelper.getGpsLoggerFolder(), fileName)); + private String updateFileContents(String accessToken, String gpxFileId, File fileToUpload) throws Exception { + FileInputStream fis = new FileInputStream(fileToUpload); String fileId = null; String fileUpdateUrl = "https://www.googleapis.com/upload/drive/v3/files/" + gpxFileId + "?uploadType=media"; @@ -221,7 +242,7 @@ private String updateFileContents(String accessToken, String gpxFileId, String f Request.Builder requestBuilder = new Request.Builder().url(fileUpdateUrl); requestBuilder.addHeader("Authorization", "Bearer " + accessToken); - RequestBody body = RequestBody.create(MediaType.parse(Files.getMimeTypeFromFileName(fileName)), Streams.getByteArrayFromInputStream(fis)); + RequestBody body = RequestBody.create(MediaType.parse(Files.getMimeTypeFromFileName(fileToUpload.getName())), Streams.getByteArrayFromInputStream(fis)); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { requestBuilder.addHeader("X-HTTP-Method-Override", "PATCH"); } @@ -239,23 +260,7 @@ private String updateFileContents(String accessToken, String gpxFileId, String f return fileId; } - @Override - protected void onCancel(int cancelReason, @Nullable Throwable throwable) { - EventBus.getDefault().post(new UploadEvents.GoogleDrive().failed("Could not send to Google Drive", throwable)); - LOG.error("Google Drive: maximum attempts failed, giving up", throwable); - } - - @Override - protected RetryConstraint shouldReRunOnThrowable(@NonNull Throwable throwable, int runCount, int maxRunCount) { - EventBus.getDefault().post(new UploadEvents.GoogleDrive().failed("Could not upload to Google Drive", throwable)); - LOG.error("Could not upload to Google Drive", throwable); - return RetryConstraint.CANCEL; - } - - @Override protected int getRetryLimit() { return 3; } - - } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/opengts/OpenGTSManager.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/opengts/OpenGTSManager.java index 39bb95afd..b140477b9 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/opengts/OpenGTSManager.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/opengts/OpenGTSManager.java @@ -19,13 +19,11 @@ package com.mendhak.gpslogger.senders.opengts; -import com.birbit.android.jobqueue.JobManager; import com.mendhak.gpslogger.common.*; -import com.mendhak.gpslogger.common.events.UploadEvents; import com.mendhak.gpslogger.common.slf4j.Logs; -import com.mendhak.gpslogger.loggers.customurl.CustomUrlJob; import com.mendhak.gpslogger.loggers.customurl.CustomUrlRequest; -import com.mendhak.gpslogger.loggers.opengts.OpenGtsUdpJob; +import com.mendhak.gpslogger.loggers.customurl.CustomUrlWorker; +import com.mendhak.gpslogger.loggers.opengts.OpenGtsUdpWorker; import com.mendhak.gpslogger.senders.FileSender; import com.mendhak.gpslogger.senders.GpxReader; import org.slf4j.Logger; @@ -56,10 +54,24 @@ public void uploadFile(List files) { // Use only gpx for (File f : files) { if (f.getName().endsWith(".gpx")) { - List locations = getLocationsFromGPX(f); - LOG.debug(locations.size() + " points were read from " + f.getName()); - sendLocations(locations.toArray(new SerializableLocation[locations.size()])); + String communication = preferenceHelper.getOpenGTSServerCommunicationMethod(); + + if(communication.equalsIgnoreCase("udp")){ + String tag = String.valueOf(Objects.hashCode(f.getName())); + HashMap dataMap = new HashMap(){{ + put("gpxFilePath", f.getAbsolutePath()); + }}; + Systems.startWorkManagerRequest(OpenGtsUdpWorker.class, dataMap, tag); + } + else { + String tag = String.valueOf(Objects.hashCode(f.getName())); + HashMap dataMap = new HashMap(){{ + put("gpxFilePath", f.getAbsolutePath()); + put("callbackType", "opengts"); + }}; + Systems.startWorkManagerRequest(CustomUrlWorker.class, dataMap, tag); + } } } @@ -76,8 +88,16 @@ public void sendLocations(SerializableLocation[] locations){ String communication = preferenceHelper.getOpenGTSServerCommunicationMethod(); if(communication.equalsIgnoreCase("udp")){ - JobManager jobManager = AppSettings.getJobManager(); - jobManager.addJobInBackground(new OpenGtsUdpJob(server, port, accountName, path, deviceId, communication, locations)); + String tag = String.valueOf(Objects.hashCode(locations)); + String[] serializedLocations = new String[locations.length]; + for (int i = 0; i < locations.length; i++) { + serializedLocations[i] = Strings.serializeTojson(locations[i]); + } + HashMap dataMap = new HashMap(){{ + put("locations", serializedLocations); + }}; + + Systems.startWorkManagerRequest(OpenGtsUdpWorker.class, dataMap, tag); } else { sendByHttp(deviceId, accountName, locations, communication, path, server, port); @@ -87,19 +107,23 @@ public void sendLocations(SerializableLocation[] locations){ } void sendByHttp(String deviceId, String accountName, SerializableLocation[] locations, String communication, String path, String server, int port) { - ArrayList requests = new ArrayList<>(); - JobManager jobManager = AppSettings.getJobManager(); - for(SerializableLocation loc:locations){ - String finalUrl = getUrl(deviceId, accountName, loc, communication, path, server, port, batteryLevel ); + String[] serializedRequests = new String[locations.length]; + for (int i = 0; i < locations.length; i++) { + String finalUrl = getUrl(deviceId, accountName, locations[i], communication, path, server, port, batteryLevel ); CustomUrlRequest request = new CustomUrlRequest(finalUrl); - requests.add(request); + serializedRequests[i] = Strings.serializeTojson(request); } - jobManager.addJobInBackground(new CustomUrlJob(requests, null, new UploadEvents.OpenGTS())); - } + String tag = String.valueOf(Objects.hashCode(serializedRequests)); + HashMap dataMap = new HashMap(){{ + put("urlRequests", serializedRequests); + put("callbackType", "opengts"); + }}; + Systems.startWorkManagerRequest(CustomUrlWorker.class, dataMap, tag); + } /** @@ -238,14 +262,27 @@ public String getName() { return SenderNames.OPENGTS; } - private List getLocationsFromGPX(File f) { - List locations = Collections.emptyList(); + public List getCustomUrlRequestsFromGPX(File f) { + List requests = new ArrayList<>(); try { - locations = GpxReader.getPoints(f); + List locations = GpxReader.getPoints(f); + LOG.debug(locations.size() + " points were read from " + f.getName()); + for (SerializableLocation location : locations) { + String finalUrl = getUrl(preferenceHelper.getOpenGTSDeviceId(), + preferenceHelper.getOpenGTSAccountName(), + location, + preferenceHelper.getOpenGTSServerCommunicationMethod(), + preferenceHelper.getOpenGTSServerPath(), + preferenceHelper.getOpenGTSServer(), + Integer.valueOf(preferenceHelper.getOpenGTSServerPort()), + batteryLevel); + CustomUrlRequest request = new CustomUrlRequest(finalUrl); + requests.add(request); + } } catch (Exception e) { - LOG.error("OpenGTSManager.getLocationsFromGPX", e); + LOG.error("OpenGTSManager.getCustomUrlRequestsFromGPX", e); } - return locations; + return requests; } @Override diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/osm/OSMJob.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/osm/OSMJob.java deleted file mode 100644 index bb9c5116f..000000000 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/osm/OSMJob.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2016 mendhak - * - * This file is part of GPSLogger for Android. - * - * GPSLogger for Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * GPSLogger for Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GPSLogger for Android. If not, see . - */ - -package com.mendhak.gpslogger.senders.osm; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.birbit.android.jobqueue.Job; -import com.birbit.android.jobqueue.Params; -import com.birbit.android.jobqueue.RetryConstraint; -import com.mendhak.gpslogger.common.AppSettings; -import com.mendhak.gpslogger.common.Strings; -import com.mendhak.gpslogger.common.events.UploadEvents; -import com.mendhak.gpslogger.common.network.Networks; -import com.mendhak.gpslogger.common.slf4j.Logs; - -import net.openid.appauth.AuthState; -import net.openid.appauth.AuthorizationException; -import net.openid.appauth.AuthorizationService; - -import org.slf4j.Logger; - -import java.io.File; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.net.ssl.X509TrustManager; - -import de.greenrobot.event.EventBus; -import okhttp3.MediaType; -import okhttp3.MultipartBody; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; - -public class OSMJob extends Job { - - - private static final Logger LOG = Logs.of(OSMJob.class); - - private final AtomicBoolean taskDone = new AtomicBoolean(false); - private String openStreetMapAccessToken; - String gpsTraceUrl; - File chosenFile; - String description; - String tags; - String visibility; - - protected OSMJob(String gpsTraceUrl, File chosenFile, String description, String tags, String visibility) { - super(new Params(1).requireNetwork().persist().addTags(getJobTag(chosenFile))); - - this.gpsTraceUrl = gpsTraceUrl; - this.chosenFile = chosenFile; - this.description = description; - this.tags = tags; - this.visibility = visibility; - } - - @Override - public void onAdded() { - - LOG.debug("OSM Job added"); - } - - @Override - public void onRun() throws Throwable { - - AuthState authState = OpenStreetMapManager.getAuthState(); - - if(!authState.isAuthorized()){ - EventBus.getDefault().post(new UploadEvents.OpenStreetMap().failed("Could not upload to OpenStreetMap. Not authorized.")); - } - - AuthorizationService authorizationService = OpenStreetMapManager.getAuthorizationService(AppSettings.getInstance()); - - // The performActionWithFreshTokens seems to happen on a UI thread! (Why??) - // So I can't do network calls on this thread. - // Instead, updating a class level variable, and waiting for it afterwards. - // https://github.com/openid/AppAuth-Android/issues/123 - authState.performActionWithFreshTokens(authorizationService, new AuthState.AuthStateAction() { - @Override - public void execute(@Nullable String accessToken, @Nullable String idToken, @Nullable AuthorizationException ex) { - if (ex != null){ - EventBus.getDefault().post(new UploadEvents.OpenStreetMap().failed(ex.toJsonString(), ex)); - taskDone.set(true); - LOG.error(ex.toJsonString(), ex); - return; - } - - openStreetMapAccessToken = accessToken; - taskDone.set(true); - } - }); - - // Wait for the performActionWithFreshTokens.execute callback - // (which happens on the UI thread for some reason) to complete. - while (!taskDone.get()) { - Thread.sleep(500); - } - - if (Strings.isNullOrEmpty(openStreetMapAccessToken)) { - LOG.error("Failed to fetch Access Token for OpenStreetMap. Stopping this job."); - return; - } - - OkHttpClient client = new OkHttpClient.Builder() - .sslSocketFactory(Networks.getSocketFactory(AppSettings.getInstance()), - (X509TrustManager) Networks.getTrustManager(AppSettings.getInstance())) - .build(); - - RequestBody requestBody = new MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart("file", chosenFile.getName(), RequestBody.create(MediaType.parse("application/xml+gpx"), chosenFile)) - .addFormDataPart("description", Strings.isNullOrEmpty(description) ? "GPSLogger for Android" : description) - .addFormDataPart("tags", tags) - .addFormDataPart("visibility",visibility) - .build(); - - Request request = new Request.Builder() - .url(gpsTraceUrl) - .addHeader("Authorization", "Bearer " + openStreetMapAccessToken) - .post(requestBody) - .build(); - - Response response = client.newCall(request).execute(); - ResponseBody body = response.body(); - - if(response.isSuccessful()){ - String message = body.string(); - LOG.debug("Response from OpenStreetMap: " + message); - LOG.info("OpenStreetMap - file uploaded"); - EventBus.getDefault().post(new UploadEvents.OpenStreetMap().succeeded()); - } - else { - body.close(); - EventBus.getDefault().post(new UploadEvents.OpenStreetMap().failed()); - } - } - - @Override - protected void onCancel(int cancelReason, @Nullable Throwable throwable) { - LOG.error("Could not send to OpenStreetMap", throwable); - EventBus.getDefault().post(new UploadEvents.OpenStreetMap().failed("Could not send to OpenStreetMap", throwable)); - } - - @Override - protected RetryConstraint shouldReRunOnThrowable(@NonNull Throwable throwable, int runCount, int maxRunCount) { - return RetryConstraint.createExponentialBackoff(runCount, 3000); - } - - public static String getJobTag(File gpxFile) { - return "OSM" + gpxFile.getName(); - - } - - @Override - protected int getRetryLimit() { - return 3; - } -} diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/osm/OpenStreetMapManager.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/osm/OpenStreetMapManager.java index 612b6a713..e1f0a9e77 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/osm/OpenStreetMapManager.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/osm/OpenStreetMapManager.java @@ -22,12 +22,9 @@ import android.content.Context; import android.net.Uri; -import com.birbit.android.jobqueue.CancelResult; -import com.birbit.android.jobqueue.JobManager; -import com.birbit.android.jobqueue.TagConstraint; -import com.mendhak.gpslogger.common.AppSettings; import com.mendhak.gpslogger.common.PreferenceHelper; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; import com.mendhak.gpslogger.common.slf4j.Logs; import com.mendhak.gpslogger.senders.FileSender; @@ -39,7 +36,9 @@ import org.json.JSONException; import org.slf4j.Logger; import java.io.File; +import java.util.HashMap; import java.util.List; +import java.util.Objects; public class OpenStreetMapManager extends FileSender { @@ -47,7 +46,6 @@ public class OpenStreetMapManager extends FileSender { private static final Logger LOG = Logs.of(OpenStreetMapManager.class); - static final String OSM_GPSTRACE_URL = "https://www.openstreetmap.org/api/0.6/gpx/create"; private PreferenceHelper preferenceHelper; public OpenStreetMapManager(PreferenceHelper preferenceHelper) { @@ -128,21 +126,13 @@ public String getName() { public void uploadFile(String fileName) { File gpxFolder = new File(preferenceHelper.getGpsLoggerFolder()); final File chosenFile = new File(gpxFolder, fileName); + String tag = String.valueOf(Objects.hashCode(chosenFile)); - final String gpsTraceUrl = OSM_GPSTRACE_URL; + HashMap dataMap = new HashMap() {{ + put("filePath", chosenFile.getAbsolutePath()); + }}; - - final String description = preferenceHelper.getOSMDescription(); - final String tags = preferenceHelper.getOSMTags(); - final String visibility = preferenceHelper.getOSMVisibility(); - - final JobManager jobManager = AppSettings.getJobManager(); - jobManager.cancelJobsInBackground(new CancelResult.AsyncCancelCallback() { - @Override - public void onCancelled(CancelResult cancelResult) { - jobManager.addJobInBackground(new OSMJob(gpsTraceUrl, chosenFile, description, tags, visibility)); - } - }, TagConstraint.ANY, OSMJob.getJobTag(chosenFile)); + Systems.startWorkManagerRequest(OpenStreetMapWorker.class, dataMap, tag); } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/osm/OpenStreetMapWorker.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/osm/OpenStreetMapWorker.java new file mode 100644 index 000000000..f8c2f4076 --- /dev/null +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/osm/OpenStreetMapWorker.java @@ -0,0 +1,180 @@ +package com.mendhak.gpslogger.senders.osm; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import com.mendhak.gpslogger.common.AppSettings; +import com.mendhak.gpslogger.common.PreferenceHelper; +import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; +import com.mendhak.gpslogger.common.events.UploadEvents; +import com.mendhak.gpslogger.common.network.Networks; +import com.mendhak.gpslogger.common.slf4j.Logs; + +import net.openid.appauth.AuthState; +import net.openid.appauth.AuthorizationException; +import net.openid.appauth.AuthorizationService; + +import org.slf4j.Logger; + +import java.io.File; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.net.ssl.X509TrustManager; + +import de.greenrobot.event.EventBus; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class OpenStreetMapWorker extends Worker { + + private static final Logger LOG = Logs.of(OpenStreetMapWorker.class); + + private String openStreetMapAccessToken; + + public OpenStreetMapWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + + @NonNull + @Override + public Result doWork() { + + String filePath = getInputData().getString("filePath"); + File fileToUpload = new File(filePath); + + final AtomicBoolean taskDone = new AtomicBoolean(false); + AuthState authState = OpenStreetMapManager.getAuthState(); + boolean success; + String failureMessage = ""; + Throwable failureThrowable = null; + + if(!authState.isAuthorized()){ + EventBus.getDefault().post(new UploadEvents.OpenStreetMap().failed("Could not upload to OpenStreetMap. Not authorized.")); + return Result.failure(); + } + + try { + AuthorizationService authorizationService = OpenStreetMapManager.getAuthorizationService(AppSettings.getInstance()); + + // The performActionWithFreshTokens seems to happen on a UI thread! (Why??) + // So I can't do network calls on this thread. + // Instead, updating a class level variable, and waiting for it afterwards. + // https://github.com/openid/AppAuth-Android/issues/123 + authState.performActionWithFreshTokens(authorizationService, new AuthState.AuthStateAction() { + @Override + public void execute(@Nullable String accessToken, @Nullable String idToken, @Nullable AuthorizationException ex) { + if (ex != null){ + taskDone.set(true); + LOG.error(ex.toJsonString(), ex); + return; + } + + openStreetMapAccessToken = accessToken; + taskDone.set(true); + } + }); + + // Wait for the performActionWithFreshTokens.execute callback + // (which happens on the UI thread for some reason) to complete. + while (!taskDone.get()) { + Thread.sleep(500); + } + + if (Strings.isNullOrEmpty(openStreetMapAccessToken)) { + LOG.error("Failed to fetch Access Token for OpenStreetMap. Stopping this job."); + success = false; + failureMessage = "Failed to fetch Access Token for OpenStreetMap."; + } + else { + + PreferenceHelper preferenceHelper = PreferenceHelper.getInstance(); + String description = preferenceHelper.getOSMDescription(); + String tags = preferenceHelper.getOSMTags(); + String visibility = preferenceHelper.getOSMVisibility(); + + + OkHttpClient client = new OkHttpClient.Builder() + .sslSocketFactory(Networks.getSocketFactory(AppSettings.getInstance()), + (X509TrustManager) Networks.getTrustManager(AppSettings.getInstance())) + .build(); + + RequestBody requestBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", fileToUpload.getName(), RequestBody.create(MediaType.parse("application/xml+gpx"), fileToUpload)) + .addFormDataPart("description", Strings.isNullOrEmpty(description) ? "GPSLogger for Android" : description) + .addFormDataPart("tags", tags) + .addFormDataPart("visibility",visibility) + .build(); + + Request request = new Request.Builder() + .url("https://www.openstreetmap.org/api/0.6/gpx/create") + .addHeader("Authorization", "Bearer " + openStreetMapAccessToken) + .post(requestBody) + .build(); + + Response response = client.newCall(request).execute(); + ResponseBody body = response.body(); + + if(response.isSuccessful()){ + String message = body.string(); + LOG.debug("Response from OpenStreetMap: " + message); + LOG.info("OpenStreetMap - file uploaded"); + success = true; + } + else { + failureMessage = "Failed to upload to OpenStreetMap"; + if(body != null){ + failureMessage = body.string(); + } + + EventBus.getDefault().post(new UploadEvents.OpenStreetMap().failed()); + success = false; + + } + } + } + catch(Exception ex){ + success = false; + failureMessage = ex.getMessage(); + failureThrowable = ex; + } + + + if(success){ + + // Notify internal listeners + EventBus.getDefault().post(new UploadEvents.OpenStreetMap().succeeded()); + // Notify external listeners + Systems.sendFileUploadedBroadcast(getApplicationContext(), new String[]{fileToUpload.getAbsolutePath()}, "openstreetmap"); + return Result.success(); + } + else { + if(getRunAttemptCount() < getRetryLimit()){ + LOG.warn(String.format("OpenStreetMap Upload - attempt %d of %d failed, will retry", getRunAttemptCount(), getRetryLimit())); + return Result.retry(); + } + + if(failureThrowable == null){ + failureThrowable = new Exception(failureMessage); + } + + EventBus.getDefault().post(new UploadEvents.OpenStreetMap().failed(failureMessage, failureThrowable)); + return Result.failure(); + + } + } + + protected int getRetryLimit() { + return 3; + } +} diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudJob.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudJob.java deleted file mode 100644 index 29a12b1b1..000000000 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudJob.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2016 mendhak - * - * This file is part of GPSLogger for Android. - * - * GPSLogger for Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * GPSLogger for Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GPSLogger for Android. If not, see . - */ - -package com.mendhak.gpslogger.senders.owncloud; - -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.birbit.android.jobqueue.Job; -import com.birbit.android.jobqueue.Params; -import com.birbit.android.jobqueue.RetryConstraint; -import com.mendhak.gpslogger.common.AppSettings; -import com.mendhak.gpslogger.common.events.UploadEvents; -import com.mendhak.gpslogger.common.network.LocalX509TrustManager; -import com.mendhak.gpslogger.common.network.Networks; -import com.mendhak.gpslogger.common.slf4j.Logs; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.OwnCloudClientFactory; -import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; -import com.owncloud.android.lib.common.network.AdvancedSslSocketFactory; -import com.owncloud.android.lib.common.network.AdvancedX509TrustManager; -import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; -import com.owncloud.android.lib.common.operations.RemoteOperation; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.resources.files.CreateRemoteFolderOperation; -import com.owncloud.android.lib.resources.files.FileUtils; -import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation; - -import org.apache.commons.httpclient.protocol.Protocol; -import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; -import org.slf4j.Logger; - -import java.io.File; -import java.security.GeneralSecurityException; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; - -import de.greenrobot.event.EventBus; - -public class OwnCloudJob extends Job implements OnRemoteOperationListener { - - private static final Logger LOG = Logs.of(OwnCloudJob.class); - - - String servername; - String username; - String password; - String directory; - File localFile; - String remoteFileName; - - public OwnCloudJob(String servername, String username, String password, String directory, - File localFile, String remoteFileName) - { - super(new Params(1).requireNetwork().persist().addTags(getJobTag(localFile))); - this.servername = servername; - this.username = username; - this.password = password; - this.directory = directory; - this.localFile = localFile; - this.remoteFileName = remoteFileName; - - } - - @Override - public void onAdded() { - LOG.debug("ownCloud Job: onAdded"); - } - - @Override - public void onRun() throws Throwable { - - LOG.debug("ownCloud Job: Uploading '" + localFile.getName() + "'"); - - - Protocol pr = Protocol.getProtocol("https"); - - try { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init( - null, - new TrustManager[] { new LocalX509TrustManager(Networks.getKnownServersStore(AppSettings.getInstance())) }, - null - ); - - ProtocolSocketFactory psf = new AdvancedSslSocketFactory(sslContext, new AdvancedX509TrustManager(Networks.getKnownServersStore(AppSettings.getInstance())), null); - - - Protocol.registerProtocol( "https", new Protocol("https", psf, 443)); - - } catch (GeneralSecurityException e) { - LOG.error("Self-signed confident SSL context could not be loaded", e); - } - - - OwnCloudClient client = OwnCloudClientFactory.createOwnCloudClient(Uri.parse(servername), AppSettings.getInstance(), true); - client.setDefaultTimeouts('\uea60', '\uea60'); - client.setFollowRedirects(true); - client.setCredentials( - OwnCloudCredentialsFactory.newBasicCredentials(username, password) - ); - - //Create the folder, in case it doesn't already exist on OwnCloud. - CreateRemoteFolderOperation createOperation = new CreateRemoteFolderOperation(directory, false); - createOperation.execute( client); - - String remotePath = directory + FileUtils.PATH_SEPARATOR + localFile.getName(); - String mimeType = "application/octet-stream"; //unused - UploadRemoteFileOperation uploadOperation = new UploadRemoteFileOperation(localFile.getAbsolutePath(), remotePath, mimeType); - uploadOperation.execute(client,this,null); - } - - @Override - protected void onCancel(int cancelReason, @Nullable Throwable throwable) { - LOG.debug("ownCloud Job: onCancel"); - } - - @Override - protected RetryConstraint shouldReRunOnThrowable(@NonNull Throwable throwable, int runCount, int maxRunCount) { - LOG.error("Could not upload to OwnCloud", throwable); - EventBus.getDefault().post(new UploadEvents.OwnCloud().failed("Could not upload to OwnCloud", throwable)); - return RetryConstraint.CANCEL; - } - - @Override - public void onRemoteOperationFinish(RemoteOperation remoteOperation, RemoteOperationResult result) { - - if (!result.isSuccess()) { - LOG.error(result.getLogMessage(), result.getException()); - EventBus.getDefault().post(new UploadEvents.OwnCloud().failed(result.getLogMessage(), result.getException())); - } else { - LOG.info("OwnCloud - file uploaded"); - EventBus.getDefault().post(new UploadEvents.OwnCloud().succeeded()); - } - - LOG.debug("ownCloud Job: onRun finished"); - } - - public static String getJobTag(File gpxFile) { - return "OWNCLOUD" + gpxFile.getName(); - } -} \ No newline at end of file diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudManager.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudManager.java index 0059ca7cd..531351eb3 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudManager.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudManager.java @@ -19,12 +19,10 @@ package com.mendhak.gpslogger.senders.owncloud; -import com.birbit.android.jobqueue.CancelResult; -import com.birbit.android.jobqueue.JobManager; -import com.birbit.android.jobqueue.TagConstraint; -import com.mendhak.gpslogger.common.AppSettings; + import com.mendhak.gpslogger.common.PreferenceHelper; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; import com.mendhak.gpslogger.common.events.UploadEvents; import com.mendhak.gpslogger.common.slf4j.Logs; import com.mendhak.gpslogger.loggers.Files; @@ -34,7 +32,9 @@ import org.slf4j.Logger; import java.io.File; +import java.util.HashMap; import java.util.List; +import java.util.Objects; public class OwnCloudManager extends FileSender { @@ -45,28 +45,22 @@ public OwnCloudManager(PreferenceHelper preferenceHelper) { this.preferenceHelper = preferenceHelper; } - public void testOwnCloud(final String servername, final String username, final String password, final String directory) { - - + public void testOwnCloud() { try { final File testFile = Files.createTestFile(); - final JobManager jobManager = AppSettings.getJobManager(); - jobManager.cancelJobsInBackground(new CancelResult.AsyncCancelCallback() { - @Override - public void onCancelled(CancelResult cancelResult) { - jobManager.addJobInBackground(new OwnCloudJob(servername, username, password, directory, - testFile, testFile.getName())); - } - }, TagConstraint.ANY, OwnCloudJob.getJobTag(testFile)); + + String tag = String.valueOf(Objects.hashCode(testFile)); + HashMap dataMap = new HashMap(){{ + put("filePath", testFile.getAbsolutePath()); + }}; + Systems.startWorkManagerRequest(OwnCloudWorker.class, dataMap, tag); } catch (Exception ex) { EventBus.getDefault().post(new UploadEvents.Ftp().failed()); LOG.error("Error while testing ownCloud upload: " + ex.getMessage()); } - - LOG.debug("Added background ownCloud upload job"); } @@ -107,18 +101,12 @@ public String getName() { public void uploadFile(final File f) { - final JobManager jobManager = AppSettings.getJobManager(); - jobManager.cancelJobsInBackground(new CancelResult.AsyncCancelCallback() { - @Override - public void onCancelled(CancelResult cancelResult) { - jobManager.addJobInBackground(new OwnCloudJob( - preferenceHelper.getOwnCloudBaseUrl(), - preferenceHelper.getOwnCloudUsername(), - preferenceHelper.getOwnCloudPassword(), - preferenceHelper.getOwnCloudDirectory(), - f, f.getName())); - } - }, TagConstraint.ANY, OwnCloudJob.getJobTag(f)); + + String tag = String.valueOf(Objects.hashCode(f)); + HashMap dataMap = new HashMap(){{ + put("filePath", f.getAbsolutePath()); + }}; + Systems.startWorkManagerRequest(OwnCloudWorker.class, dataMap, tag); } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudWorker.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudWorker.java new file mode 100644 index 000000000..7490f6a4a --- /dev/null +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudWorker.java @@ -0,0 +1,160 @@ +package com.mendhak.gpslogger.senders.owncloud; + +import android.content.Context; +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import com.mendhak.gpslogger.common.AppSettings; +import com.mendhak.gpslogger.common.PreferenceHelper; +import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; +import com.mendhak.gpslogger.common.events.UploadEvents; +import com.mendhak.gpslogger.common.network.LocalX509TrustManager; +import com.mendhak.gpslogger.common.network.Networks; +import com.mendhak.gpslogger.common.slf4j.Logs; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.OwnCloudClientFactory; +import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; +import com.owncloud.android.lib.common.network.AdvancedSslSocketFactory; +import com.owncloud.android.lib.common.network.AdvancedX509TrustManager; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.resources.files.CreateRemoteFolderOperation; +import com.owncloud.android.lib.resources.files.FileUtils; +import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation; + +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.slf4j.Logger; + +import java.io.File; +import java.security.GeneralSecurityException; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +import de.greenrobot.event.EventBus; + +public class OwnCloudWorker extends Worker implements OnRemoteOperationListener { + + private static final Logger LOG = Logs.of(OwnCloudWorker.class); + + final AtomicInteger taskStatus = new AtomicInteger(-1); + Throwable failureThrowable = null; + String failureMessage = ""; + + public OwnCloudWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + + @NonNull + @Override + public Result doWork() { + + String filePath = getInputData().getString("filePath"); + if(Strings.isNullOrEmpty(filePath)) { + LOG.error("No file path provided to upload to OwnCloud"); + return Result.failure(); + } + File fileToUpload = new File(filePath); + + PreferenceHelper preferenceHelper = PreferenceHelper.getInstance(); + String servername = preferenceHelper.getOwnCloudBaseUrl(); + String username = preferenceHelper.getOwnCloudUsername(); + String password = preferenceHelper.getOwnCloudPassword(); + String directory = preferenceHelper.getOwnCloudDirectory(); + + + try { + LOG.debug("ownCloud Job: Uploading '" + fileToUpload.getName() + "'"); + + + Protocol pr = Protocol.getProtocol("https"); + + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init( + null, + new TrustManager[] { new LocalX509TrustManager(Networks.getKnownServersStore(AppSettings.getInstance())) }, + null + ); + + ProtocolSocketFactory psf = new AdvancedSslSocketFactory(sslContext, new AdvancedX509TrustManager(Networks.getKnownServersStore(AppSettings.getInstance())), null); + + + Protocol.registerProtocol( "https", new Protocol("https", psf, 443)); + + } catch (GeneralSecurityException e) { + LOG.error("Self-signed confident SSL context could not be loaded", e); + } + + OwnCloudClient client = OwnCloudClientFactory.createOwnCloudClient(Uri.parse(servername), AppSettings.getInstance(), true); + client.setDefaultTimeouts('\uea60', '\uea60'); + client.setFollowRedirects(true); + client.setCredentials( + OwnCloudCredentialsFactory.newBasicCredentials(username, password) + ); + + //Create the folder, in case it doesn't already exist on OwnCloud. + CreateRemoteFolderOperation createOperation = new CreateRemoteFolderOperation(directory, false); + createOperation.execute( client); + + String remotePath = directory + FileUtils.PATH_SEPARATOR + fileToUpload.getName(); + String mimeType = "application/octet-stream"; //unused + UploadRemoteFileOperation uploadOperation = new UploadRemoteFileOperation(fileToUpload.getAbsolutePath(), remotePath, mimeType); + uploadOperation.execute(client,this,null); + + /* + Doing this simply because ListenerWorker is too complicated to implement, + And the documentation is not clear on how to use it. + */ + for(int i = 0; i < 24; i++) { + Thread.sleep(5000); + if(taskStatus.get() != -1) { + break; + } + } + + // If it failed, OR never completed in time. + if(taskStatus.get() <= 0) { + LOG.error("Failed to upload to OwnCloud"); + EventBus.getDefault().post(new UploadEvents.OwnCloud().failed(failureMessage, failureThrowable)); + return Result.failure(); + } + else { + LOG.info("OwnCloud - file uploaded"); + + // Notify internal listeners + EventBus.getDefault().post(new UploadEvents.OwnCloud().succeeded()); + // Notify external listeners + Systems.sendFileUploadedBroadcast(getApplicationContext(), new String[]{fileToUpload.getAbsolutePath()}, "owncloud"); + return Result.success(); + } + } + catch (Exception ex) { + LOG.error("Error in OwnCloudWorker.doWork(): " + ex.getMessage()); + EventBus.getDefault().post(new UploadEvents.OwnCloud().failed(ex.getMessage(), ex)); + return Result.failure(); + } + } + + @Override + public void onRemoteOperationFinish(RemoteOperation remoteOperation, RemoteOperationResult result) { + + if (!result.isSuccess()) { + LOG.error(result.getLogMessage(), result.getException()); + failureThrowable = result.getException(); + failureMessage = result.getLogMessage(); + taskStatus.set(0); + } else { + taskStatus.set(1); + } + + LOG.debug("ownCloud Job: onRun finished"); + } +} diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/sftp/SFTPManager.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/sftp/SFTPManager.java index a072500ea..fe40b426c 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/sftp/SFTPManager.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/sftp/SFTPManager.java @@ -1,16 +1,16 @@ package com.mendhak.gpslogger.senders.sftp; -import com.birbit.android.jobqueue.CancelResult; -import com.birbit.android.jobqueue.JobManager; -import com.birbit.android.jobqueue.TagConstraint; -import com.mendhak.gpslogger.common.AppSettings; + import com.mendhak.gpslogger.common.PreferenceHelper; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; import com.mendhak.gpslogger.senders.FileSender; import java.io.File; +import java.util.HashMap; import java.util.List; +import java.util.Objects; public class SFTPManager extends FileSender { @@ -28,14 +28,12 @@ public void uploadFile(List files) { } public void uploadFile(final File file){ - final JobManager jobManager = AppSettings.getJobManager(); - jobManager.cancelJobsInBackground(new CancelResult.AsyncCancelCallback() { - @Override - public void onCancelled(CancelResult cancelResult) { - jobManager.addJobInBackground(new SFTPJob(file, preferenceHelper.getSFTPRemoteServerPath(), preferenceHelper.getSFTPHost(),preferenceHelper.getSFTPPort(),preferenceHelper.getSFTPPrivateKeyFilePath(), - preferenceHelper.getSFTPPrivateKeyPassphrase(),preferenceHelper.getSFTPUser(),preferenceHelper.getSFTPPassword(),preferenceHelper.getSFTPKnownHostKey())); - } - }, TagConstraint.ANY, SFTPJob.getJobTag(file)); + String tag = String.valueOf(Objects.hashCode(file)); + HashMap dataMap = new HashMap(){{ + put("filePath", file.getAbsolutePath()); + }}; + Systems.startWorkManagerRequest(SFTPWorker.class, dataMap, tag); + } @Override diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/sftp/SFTPJob.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/sftp/SFTPWorker.java similarity index 57% rename from gpslogger/src/main/java/com/mendhak/gpslogger/senders/sftp/SFTPJob.java rename to gpslogger/src/main/java/com/mendhak/gpslogger/senders/sftp/SFTPWorker.java index 03855efcc..9cedc5896 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/sftp/SFTPJob.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/sftp/SFTPWorker.java @@ -1,38 +1,42 @@ package com.mendhak.gpslogger.senders.sftp; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import android.content.Context; import android.util.Base64; -import com.birbit.android.jobqueue.Job; -import com.birbit.android.jobqueue.Params; -import com.birbit.android.jobqueue.RetryConstraint; -import com.jcraft.jsch.*; +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import com.jcraft.jsch.Channel; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.HostKey; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.SftpException; +import com.mendhak.gpslogger.common.PreferenceHelper; import com.mendhak.gpslogger.common.Strings; +import com.mendhak.gpslogger.common.Systems; import com.mendhak.gpslogger.common.events.UploadEvents; import com.mendhak.gpslogger.common.slf4j.Logs; -import de.greenrobot.event.EventBus; + + import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.slf4j.Logger; -import java.io.*; + +import java.io.File; +import java.io.FileInputStream; import java.security.Security; import java.util.Properties; -public class SFTPJob extends Job { - private static final Logger LOG = Logs.of(SFTPJob.class); - private final File localFile; - private final String host; - private final int port; - private final String pathToPrivateKey; - private final String privateKeyPassphrase; - private final String username; - private final String password; - private final String hostKey; - private final String remoteDir; - - public SFTPJob(File localFile, String remoteDir, String host, int port, String pathToPrivateKey, String privateKeyPassphrase, String username, String password, String hostKey) { - super(new Params(1).requireNetwork().persist().addTags(getJobTag(localFile))); +import de.greenrobot.event.EventBus; + +public class SFTPWorker extends Worker { + + private static final Logger LOG = Logs.of(SFTPWorker.class); + + public SFTPWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + + super(context, workerParams); try { Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); @@ -41,26 +45,30 @@ public SFTPJob(File localFile, String remoteDir, String host, int port, String p catch(Exception ex){ LOG.error("Could not add BouncyCastle provider.", ex); } - - this.localFile = localFile; - this.remoteDir = remoteDir; - this.host = host; - this.port = port; - this.pathToPrivateKey = pathToPrivateKey; - this.privateKeyPassphrase = privateKeyPassphrase; - this.username = username; - this.password = password; - this.hostKey = hostKey; } - + @NonNull @Override - public void onAdded() { - LOG.debug("SFTP Job added"); - } + public Result doWork() { + + String filePath = getInputData().getString("filePath"); + if(Strings.isNullOrEmpty(filePath)) { + LOG.error("No file path provided to upload to SFTP"); + EventBus.getDefault().post(new UploadEvents.SFTP().failed("No file path provided to upload to SFTP", null)); + return Result.failure(); + } + File fileToUpload = new File(filePath); + + PreferenceHelper preferenceHelper = PreferenceHelper.getInstance(); + String hostKey = preferenceHelper.getSFTPKnownHostKey(); + String host = preferenceHelper.getSFTPHost(); + String username = preferenceHelper.getSFTPUser(); + String password = preferenceHelper.getSFTPPassword(); + int port = preferenceHelper.getSFTPPort(); + String remoteDir = preferenceHelper.getSFTPRemoteServerPath(); + String pathToPrivateKey = preferenceHelper.getSFTPPrivateKeyFilePath(); + String privateKeyPassphrase = preferenceHelper.getSFTPPrivateKeyPassphrase(); - @Override - public void onRun() throws Throwable { LOG.debug("SFTP Job onRun"); com.jcraft.jsch.Session session = null; JSch.setLogger(new SftpLogger()); @@ -68,19 +76,19 @@ public void onRun() throws Throwable { FileInputStream fis = null; try { - String keystring = this.hostKey; + String keystring = hostKey; if (!Strings.isNullOrEmpty(keystring)) { byte[] key = Base64.decode(keystring, Base64.DEFAULT); jsch.getHostKeyRepository().add(new HostKey(host, key), null); } - if(!Strings.isNullOrEmpty(this.pathToPrivateKey)){ - jsch.addIdentity(this.pathToPrivateKey, this.privateKeyPassphrase); + if(!Strings.isNullOrEmpty(pathToPrivateKey)){ + jsch.addIdentity(pathToPrivateKey, privateKeyPassphrase); } - session = jsch.getSession(this.username, this.host, this.port); - session.setPassword(this.password); + session = jsch.getSession(username, host, port); + session.setPassword(password); Properties prop = new Properties(); prop.put("StrictHostKeyChecking", "yes"); @@ -95,10 +103,10 @@ public void onRun() throws Throwable { Channel channel = session.openChannel("sftp"); channel.connect(); ChannelSftp channelSftp = (ChannelSftp) channel; - LOG.debug("Changing directory to " + this.remoteDir); - channelSftp.cd(this.remoteDir); - LOG.debug("Uploading " + this.localFile.getName() + " to remote server"); - channelSftp.put(new FileInputStream(this.localFile), this.localFile.getName(), ChannelSftp.OVERWRITE); + LOG.debug("Changing directory to " + remoteDir); + channelSftp.cd(remoteDir); + LOG.debug("Uploading " + fileToUpload.getName() + " to remote server"); + channelSftp.put(new FileInputStream(fileToUpload), fileToUpload.getName(), ChannelSftp.OVERWRITE); LOG.debug("Disconnecting"); channelSftp.disconnect(); @@ -106,16 +114,23 @@ public void onRun() throws Throwable { session.disconnect(); LOG.info("SFTP - file uploaded"); + // Notify internal listeners EventBus.getDefault().post(new UploadEvents.SFTP().succeeded()); + // Notify external listeners + Systems.sendFileUploadedBroadcast(getApplicationContext(), new String[]{fileToUpload.getAbsolutePath()}, "sftp"); + return Result.success(); } else { EventBus.getDefault().post(new UploadEvents.SFTP().failed("Could not connect, unknown reasons", null)); + return Result.failure(); } } catch (SftpException sftpex) { LOG.error(sftpex.getMessage(), sftpex); EventBus.getDefault().post(new UploadEvents.SFTP().failed(sftpex.getMessage(), sftpex)); + return Result.failure(); } catch (final JSchException jex) { LOG.error(jex.getMessage(), jex); + if (jex.getMessage().contains("reject HostKey") || jex.getMessage().contains("HostKey has been changed")) { LOG.debug(session.getHostKey().getKey()); UploadEvents.SFTP sftpException = new UploadEvents.SFTP(); @@ -123,11 +138,14 @@ public void onRun() throws Throwable { sftpException.fingerprint = session.getHostKey().getFingerPrint(jsch); EventBus.getDefault().post(sftpException.failed(jex.getMessage(), jex)); } else { - throw jex; + EventBus.getDefault().post(new UploadEvents.SFTP().failed(jex.getMessage(), jex)); } + + return Result.failure(); } catch (Exception ex) { LOG.error(ex.getMessage(), ex); EventBus.getDefault().post(new UploadEvents.SFTP().failed(ex.getMessage(), ex)); + return Result.failure(); } finally { try { fis.close(); @@ -136,24 +154,6 @@ public void onRun() throws Throwable { } } - @Override - protected void onCancel(int cancelReason, @Nullable Throwable throwable) { - LOG.debug("SFTP Job Cancelled"); - } - - @Override - protected RetryConstraint shouldReRunOnThrowable(@NonNull Throwable throwable, int runCount, int maxRunCount) { - LOG.error("Could not upload to SFTP server", throwable); - EventBus.getDefault().post(new UploadEvents.SFTP().failed(throwable.getMessage(), throwable)); - return RetryConstraint.CANCEL; - } - - - public static String getJobTag(File gpxFile) { - return "SFTP" + gpxFile.getName(); - } - - public static class SftpLogger implements com.jcraft.jsch.Logger { public boolean isEnabled(int level){ @@ -173,7 +173,4 @@ public void log(int level, String message){ } } } - } - - diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/display/GenericViewFragment.java b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/display/GenericViewFragment.java index 259477a55..9b7cac6d8 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/display/GenericViewFragment.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/display/GenericViewFragment.java @@ -37,7 +37,6 @@ import com.mendhak.gpslogger.senders.FileSenderFactory; import com.mendhak.gpslogger.ui.Dialogs; import de.greenrobot.event.EventBus; -import eltos.simpledialogfragment.SimpleDialog; import eltos.simpledialogfragment.form.FormElement; import eltos.simpledialogfragment.form.Hint; import eltos.simpledialogfragment.form.Input; @@ -130,7 +129,9 @@ public void requestToggleLogging() { //If the user needs to be prompted about OpenStreetMap settings, build some form elements for it. if(preferenceHelper.isAutoSendEnabled() && preferenceHelper.isOsmAutoSendEnabled() - && FileSenderFactory.getOsmSender().isAutoSendAvailable()){ + && FileSenderFactory.getOsmSender().isAutoSendAvailable() + && preferenceHelper.shouldPromptForOSMDetailsWhenLoggingStarts() + ){ formElements.add(Hint.plain(R.string.osm_setup_title)); formElements.addAll(Dialogs.getOpenStreetMapFormElementsForDialog(preferenceHelper)); } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/AutoEmailFragment.java b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/AutoEmailFragment.java index fb393c17c..dfdb6708d 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/AutoEmailFragment.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/AutoEmailFragment.java @@ -216,9 +216,7 @@ public boolean onPreferenceClick(Preference preference) { - aem.sendTestEmail(smtpServer, smtpPort, - smtpUsername, smtpPassword, - useSsl, smtpTarget, senderAddress); + aem.sendTestEmail(); return true; } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/CustomUrlFragment.java b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/CustomUrlFragment.java index f91460d0b..c79af7318 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/CustomUrlFragment.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/CustomUrlFragment.java @@ -117,30 +117,32 @@ public boolean onPreferenceClick(Preference preference) { "{4} %SAT
" + "{5} %ALT
" + "{6} %SPD
" + - "{7} %ACC
" + - "{8} %DIR
" + - "{9} %PROV
" + - "{10} %TIMESTAMP
" + - "{11} %TIME
" + - "{12} %TIMEOFFSET
" + - "{13} %DATE
" + - "{14} %STARTTIMESTAMP
" + - "{15} %BATT
" + - "{16} %ISCHARGING
" + - "{17} %AID
" + - "{18} %SER
" + - "{19} %FILENAME
" + - "{20} %PROFILE
" + - "{21} %HDOP
" + - "{22} %VDOP
" + - "{23} %PDOP
" + - "{24} %DIST
" + - "{25} %ALL"; + "{7} %SPD_KPH
" + + "{8} %ACC
" + + "{9} %DIR
" + + "{10} %PROV
" + + "{11} %TIMESTAMP
" + + "{12} %TIME
" + + "{13} %TIMEOFFSET
" + + "{14} %DATE
" + + "{15} %STARTTIMESTAMP
" + + "{16} %BATT
" + + "{17} %ISCHARGING
" + + "{18} %AID
" + + "{19} %SER
" + + "{20} %FILENAME
" + + "{21} %PROFILE
" + + "{22} %HDOP
" + + "{23} %VDOP
" + + "{24} %PDOP
" + + "{25} %DIST
" + + "{26} %ALL"; String legend1 = MessageFormat.format(legendFormat, codeGreen, getString(R.string.txt_latitude), getString(R.string.txt_longitude), getString(R.string.txt_annotation), getString(R.string.txt_satellites), getString(R.string.txt_altitude), getString(R.string.txt_speed), - getString(R.string.txt_accuracy), getString(R.string.txt_direction), getString(R.string.txt_provider), + getString(R.string.txt_speed_kph), getString(R.string.txt_accuracy), getString(R.string.txt_direction), + getString(R.string.txt_provider), getString(R.string.txt_timestamp_epoch), getString(R.string.txt_time_isoformat), getString(R.string.txt_time_with_offset_isoformat), diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/DropboxAuthorizationFragment.java b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/DropboxAuthorizationFragment.java index 657c36687..84e078373 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/DropboxAuthorizationFragment.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/DropboxAuthorizationFragment.java @@ -128,7 +128,7 @@ private void uploadTestFile() { try { File testFile = Files.createTestFile(); - manager.uploadFile(testFile.getName()); + manager.uploadFile(testFile); } catch (Exception ex) { LOG.error("Could not create local test file", ex); diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/FtpFragment.java b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/FtpFragment.java index a398f0848..5a71cfdb8 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/FtpFragment.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/FtpFragment.java @@ -181,7 +181,7 @@ public boolean onPreferenceClick(Preference preference) { boolean useFtpsPreference = preferenceHelper.shouldFtpUseFtps(); String sslTlsPreference = preferenceHelper.getFtpProtocol(); boolean implicitPreference = preferenceHelper.isFtpImplicit(); - String directoryPreference = preferenceHelper.getFtpDirectory(); + if (!helper.validSettings(servernamePreference, usernamePreference, passwordPreference, portPreference, @@ -194,11 +194,7 @@ public boolean onPreferenceClick(Preference preference) { } Dialogs.progress((FragmentActivity) getActivity(), getString(R.string.autoftp_testing)); - - - helper.testFtp(servernamePreference, usernamePreference, passwordPreference, - directoryPreference, portPreference, useFtpsPreference, - sslTlsPreference, implicitPreference); + helper.testFtp(); } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/GoogleDriveSettingsFragment.java b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/GoogleDriveSettingsFragment.java index cfb039ac8..d15707d3c 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/GoogleDriveSettingsFragment.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/GoogleDriveSettingsFragment.java @@ -243,7 +243,7 @@ private void uploadTestFile() { try { File testFile = Files.createTestFile(); - manager.uploadFile(testFile.getName()); + manager.uploadFile(testFile); } catch (Exception ex) { LOG.error("Could not create local test file", ex); diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/OSMAuthorizationFragment.java b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/OSMAuthorizationFragment.java index 2444b7a27..7b2b28960 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/OSMAuthorizationFragment.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/OSMAuthorizationFragment.java @@ -105,6 +105,8 @@ private void setPreferencesState() { tagsPref.setOnPreferenceClickListener(this); tagsPref.setSummary(preferenceHelper.getOSMTags()); + Preference promptPref = findPreference(PreferenceNames.OPENSTREETMAP_PROMPT_WHEN_LOGGING_STARTS); + Preference resetPref = findPreference("osm_resetauth"); if (!manager.isOsmAuthorized()) { @@ -113,13 +115,14 @@ private void setPreferencesState() { visibilityPref.setEnabled(false); descriptionPref.setEnabled(false); tagsPref.setEnabled(false); + promptPref.setEnabled(false); } else { resetPref.setTitle(R.string.osm_resetauth); resetPref.setSummary(""); visibilityPref.setEnabled(true); descriptionPref.setEnabled(true); tagsPref.setEnabled(true); - + promptPref.setEnabled(true); } resetPref.setOnPreferenceClickListener(this); diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/OwnCloudSettingsFragment.java b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/OwnCloudSettingsFragment.java index d1defa51f..08f500626 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/OwnCloudSettingsFragment.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/OwnCloudSettingsFragment.java @@ -195,7 +195,7 @@ else if(preference.getKey().equals("owncloud_test")){ Dialogs.progress((FragmentActivity) getActivity(), getString(R.string.owncloud_testing)); OwnCloudManager helper = new OwnCloudManager(PreferenceHelper.getInstance()); - helper.testOwnCloud(server, user, pass, directory); + helper.testOwnCloud(); } diff --git a/gpslogger/src/main/res/values/strings.xml b/gpslogger/src/main/res/values/strings.xml index 166c520a7..ae8eca117 100644 --- a/gpslogger/src/main/res/values/strings.xml +++ b/gpslogger/src/main/res/values/strings.xml @@ -26,6 +26,7 @@ Latitude: Longitude: Altitude: + Speed (in km/h): Speed: Direction: Satellites: @@ -283,7 +284,7 @@ Allow auto sending - Allows sending the files to various targets + Allows sending the files to various targets, at a defined frequency, or if the name changes due to custom naming How frequently log files should be sent, in minutes. Set to 0 to disable this. How often? @@ -492,5 +493,6 @@ Edit annotation button Button color + Prompt for details when logging starts diff --git a/gpslogger/src/main/res/xml/osmsettings.xml b/gpslogger/src/main/res/xml/osmsettings.xml index 71cca00c1..a65018bb0 100644 --- a/gpslogger/src/main/res/xml/osmsettings.xml +++ b/gpslogger/src/main/res/xml/osmsettings.xml @@ -36,5 +36,10 @@ android:title="@string/osm_tags" app:iconSpaceReserved="false" /> + diff --git a/gpslogger/src/test/java/com/mendhak/gpslogger/senders/customurl/CustomUrlManagerTest.java b/gpslogger/src/test/java/com/mendhak/gpslogger/senders/customurl/CustomUrlManagerTest.java index b9bc5b727..26d197866 100644 --- a/gpslogger/src/test/java/com/mendhak/gpslogger/senders/customurl/CustomUrlManagerTest.java +++ b/gpslogger/src/test/java/com/mendhak/gpslogger/senders/customurl/CustomUrlManagerTest.java @@ -27,15 +27,15 @@ public void getFormattedUrl_WhenPlaceholders_ValuesSubstituted() throws Exceptio .withAltitude(45) .withAccuracy(8) .withBearing(359) - .withSpeed(9001) + .withSpeed(9005) .withTime(1457205869949l) .build(); CustomUrlManager manager = new CustomUrlManager(null); - String expected = "http://192.168.1.65:8000/test?lat=12.193&lon=19.111&sat=9&desc=blah&alt=45.0&acc=8.0&dir=359.0&prov=MOCK&spd=9001.0&time=2016-03-05T19:24:29.949Z&battery=91.0&androidId=22&serial=SRS11&activity="; - String urlTemplate = "http://192.168.1.65:8000/test?lat=%LAT&lon=%LON&sat=%SAT&desc=%DESC&alt=%ALT&acc=%ACC&dir=%DIR&prov=%PROV&spd=%SPD&time=%TIME&battery=%BATT&androidId=%AID&serial=%SER&activity=%act"; + String expected = "http://192.168.1.65:8000/test?lat=12.193&lon=19.111&sat=9&desc=blah&alt=45.0&acc=8.0&dir=359.0&prov=MOCK&spd_kph=32418.0&spd=9005.0&time=2016-03-05T19:24:29.949Z&battery=91.0&androidId=22&serial=SRS11&activity="; + String urlTemplate = "http://192.168.1.65:8000/test?lat=%LAT&lon=%LON&sat=%SAT&desc=%DESC&alt=%ALT&acc=%ACC&dir=%DIR&prov=%PROV&spd_kph=%SPD_KPH&spd=%SPD&time=%TIME&battery=%BATT&androidId=%AID&serial=%SER&activity=%act"; assertThat("Placeholders are substituted", manager.getFormattedTextblock(urlTemplate, new SerializableLocation(loc), "blah", "22", 91, false, "SRS11", 0, "", "", @@ -289,7 +289,7 @@ public void getFormattedUrl_WhenALLParameters_AllKeyValuesAddedDirectly() throws .putExtra(BundleConstants.VDOP, "19").withTime(1457205869949l).build(); CustomUrlManager manager = new CustomUrlManager(null); String expected = "http://192.168.1.65:8000/test?lat=12.193&lon=19.456&sat=0&desc=&alt=0.0" + - "&acc=0.0&dir=0.0&prov=MOCK&spd=0.0×tamp=1457205869" + + "&acc=0.0&dir=0.0&prov=MOCK&spd_kph=0.0&spd=0.0×tamp=1457205869" + "&timeoffset=2016-03-05T21:24:29.949%2B02:00&time=2016-03-05T19:24:29.949Z" + "&starttimestamp=1495884681&date=2016-03-05&batt=0.0&ischarging=false&aid=&ser=" + "&act=&filename=20170527abc&profile=Default+Profile&hdop=&vdop=19&pdop=&dist=0&"; @@ -307,7 +307,7 @@ public void getFormattedUrl_WhenALLParametersInBody_AllKeyValuesAddedDirectly() .putExtra(BundleConstants.VDOP, "19").withTime(1457205869949l).build(); CustomUrlManager manager = new CustomUrlManager(null); String expected = "lat=12.193&lon=19.456&sat=0&desc=&alt=0.0&acc=0.0&dir=0.0&prov=MOCK" + - "&spd=0.0×tamp=1457205869&timeoffset=2016-03-05T21:24:29.949%2B02:00" + + "&spd_kph=0.0&spd=0.0×tamp=1457205869&timeoffset=2016-03-05T21:24:29.949%2B02:00" + "&time=2016-03-05T19:24:29.949Z&starttimestamp=1495884681&date=2016-03-05" + "&batt=0.0&ischarging=false&aid=&ser=&act=&filename=20170527abc" + "&profile=Default+Profile&hdop=&vdop=19&pdop=&dist=0&"; diff --git a/gradle.properties b/gradle.properties index d9244dbe8..28ae68cd0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,3 +5,8 @@ org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m android.useAndroidX=true android.enableJetifier=true android.jetifier.ignorelist=jsch-0.1.67.jar +android.enableR8.fullMode=false +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false +org.gradle.unsafe.configuration-cache=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b..da1db5f04 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists