diff --git a/fastlane/metadata/android/en-US/changelogs/136.txt b/fastlane/metadata/android/en-US/changelogs/136.txt new file mode 100644 index 000000000..4c5ffd396 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/136.txt @@ -0,0 +1,3 @@ +* HTTP File Upload Target (separate from normal Custom URL logger). +* Significant motion heartbeat interval setting for long durations. +* Fixed bug where GPX track segments were not being created correctly. \ No newline at end of file diff --git a/gpslogger/build.gradle b/gpslogger/build.gradle index 01cb6b3a2..e885cd9b7 100644 --- a/gpslogger/build.gradle +++ b/gpslogger/build.gradle @@ -45,8 +45,8 @@ android { targetSdkVersion 35 compileSdk 35 - versionCode 135 - versionName "135" + versionCode 136 + versionName "136-rc1" // Used by AppAuth-Android manifestPlaceholders = [ diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/GpsLoggingService.java b/gpslogger/src/main/java/com/mendhak/gpslogger/GpsLoggingService.java index 7eb06a679..6ce4ed690 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/GpsLoggingService.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/GpsLoggingService.java @@ -289,7 +289,7 @@ private void handleIntent(Intent intent) { preferenceHelper.setPassiveFilterInterval(passiveFilterInterval); needToStartGpsManager = true; } - + if(bundle.get(IntentConstants.LOG_ONCE) != null){ boolean logOnceIntent = bundle.getBoolean(IntentConstants.LOG_ONCE); LOG.debug("Intent received - Log Once: " + String.valueOf(logOnceIntent)); @@ -435,7 +435,9 @@ protected void startLogging() { showNotification(); setupAutoSendTimers(); setupSignificantMotionSensor(); - resetCurrentFileName(true); + + resetCurrentFileName(Strings.isNullOrEmpty(session.getCurrentFormattedFileName())); + notifyClientsStarted(true); startPassiveManager(); startGpsManager(); @@ -806,8 +808,19 @@ private boolean userHasBeenStillForTooLong() { if(!preferenceHelper.shouldLogOnlyIfSignificantMotion()){ return false; } - return !session.hasDescription() && !session.isSinglePointMode() && + + boolean hasBeenStill = !session.hasDescription() && !session.isSinglePointMode() && (session.getUserStillSinceTimeStamp() > 0 && (System.currentTimeMillis() - session.getUserStillSinceTimeStamp()) > (preferenceHelper.getMinimumLoggingInterval() * 1000)); + + if (hasBeenStill && preferenceHelper.getSignificantMotionBypassInterval() >= 1) { + long bypassIntervalInMillis = preferenceHelper.getSignificantMotionBypassInterval() * 60 * 1000L; + if (System.currentTimeMillis() - session.getLatestTimeStamp() >= bypassIntervalInMillis) { + LOG.debug("Significant motion bypass interval passed, so let's allow a point to be logged."); + return false; + } + } + + return hasBeenStill; } private void startAbsoluteTimer() { @@ -1243,7 +1256,13 @@ private void setAlarmForNextPoint() { * @param loc Location object */ private void writeToFile(Location loc) { - session.setAddNewTrackSegment(false); + //session.setAddNewTrackSegment(false); + + if(FileLoggerFactory.getFileLoggers(getApplicationContext()).isEmpty()){ + Systems.showErrorNotification(getApplicationContext(), + String.format("%s %s", getString(R.string.summary_loggingto), getString(R.string.summary_loggingto_screen))); + return; + } try { LOG.debug("Calling file writers"); @@ -1259,6 +1278,7 @@ private void writeToFile(Location loc) { Systems.showErrorNotification(this, getString(R.string.could_not_write_to_file)); } + session.setAddNewTrackSegment(false); session.clearDescription(); EventBus.getDefault().post(new ServiceEvents.AnnotationStatus(true)); } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/GpsMainActivity.java b/gpslogger/src/main/java/com/mendhak/gpslogger/GpsMainActivity.java index 3ea60fa41..5b80d054c 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/GpsMainActivity.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/GpsMainActivity.java @@ -846,6 +846,7 @@ public boolean onProfileLongClick(View view, final IProfile iProfile, boolean b) materialDrawer.addItem(GpsLoggerDrawerItem.newPrimary(R.string.pref_autosend_title, R.string.pref_autosend_summary, R.drawable.autosend, 1003)); materialDrawer.addItem(GpsLoggerDrawerItem.newPrimary(R.string.log_customurl_setup_title, null, R.drawable.customurlsender, 1020)); + materialDrawer.addItem(GpsLoggerDrawerItem.newPrimary(R.string.http_file_upload_setup_title, null, R.drawable.customurlsender, 1021)); materialDrawer.addItem(GpsLoggerDrawerItem.newPrimary(R.string.dropbox_setup_title, null, R.drawable.dropbox, 1005)); materialDrawer.addItem(GpsLoggerDrawerItem.newPrimary(R.string.google_drive_setup_title, null, R.drawable.googledrive, 1011)); materialDrawer.addItem(GpsLoggerDrawerItem.newPrimary(R.string.sftp_setup_title, null, R.drawable.sftp, 1015)); @@ -905,6 +906,9 @@ public boolean onItemClick(View view, int i, IDrawerItem iDrawerItem) { case 1020: launchPreferenceScreen(MainPreferenceActivity.PREFERENCE_FRAGMENTS.CUSTOMURL); break; + case 1021: + launchPreferenceScreen(MainPreferenceActivity.PREFERENCE_FRAGMENTS.HTTPFILEUPLOAD); + break; case 9000: startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://gpslogger.app"))); break; @@ -1231,6 +1235,7 @@ public boolean onMenuItemClick(MenuItem item) { return true; case R.id.mnuAutoSendNow: forceAutoSendNow(); + return true; case R.id.mnuOwnCloud: uploadToOwnCloud(); return true; @@ -1240,6 +1245,9 @@ public boolean onMenuItemClick(MenuItem item) { case R.id.mnuCustomUrl: uploadToCustomURL(); return true; + case R.id.mnuHttpFileUpload: + uploadToHttpFileUpload(); + return true; default: return true; } @@ -1250,8 +1258,12 @@ private void forceAutoSendNow() { LOG.debug("User forced an auto send"); if (preferenceHelper.isAutoSendEnabled()) { - Dialogs.progress(this, getString(R.string.autosend_sending)); - EventBus.getDefault().post(new CommandEvents.AutoSend(null)); + if (FileSenderFactory.getAvailableFileAutoSenders().isEmpty()) { + launchPreferenceScreen(MainPreferenceActivity.PREFERENCE_FRAGMENTS.UPLOAD); + } else { + Dialogs.progress(this, getString(R.string.autosend_sending)); + EventBus.getDefault().post(new CommandEvents.AutoSend(null)); + } } else { launchPreferenceScreen(MainPreferenceActivity.PREFERENCE_FRAGMENTS.UPLOAD); @@ -1321,6 +1333,15 @@ private void uploadToCustomURL(){ showFileListDialog(FileSenderFactory.getCustomUrlSender()); } + private void uploadToHttpFileUpload(){ + if(!FileSenderFactory.getHttpFileUploadSender().isAvailable()){ + launchPreferenceScreen(MainPreferenceActivity.PREFERENCE_FRAGMENTS.HTTPFILEUPLOAD); + return; + } + + showFileListDialog(FileSenderFactory.getHttpFileUploadSender()); + } + private void uploadToSFTP(){ if(!FileSenderFactory.getSFTPSender().isAvailable()){ launchPreferenceScreen(MainPreferenceActivity.PREFERENCE_FRAGMENTS.SFTP); @@ -1610,6 +1631,23 @@ public void onEventMainThread(UploadEvents.CustomUrl upload){ } } + @EventBusHook + public void onEventMainThread(UploadEvents.HttpFileUpload upload){ + LOG.debug("HTTP File Upload Event completed, success: " + upload.success); + Dialogs.hideProgress(); + + if(!upload.success){ + LOG.error(getString(R.string.http_file_upload_setup_title) + + "-" + + getString(R.string.upload_failure)); + + if(userInvokedUpload){ + Dialogs.showError(getString(R.string.sorry), getString(R.string.upload_failure), upload.message, upload.throwable, this); + userInvokedUpload = false; + } + } + } + @EventBusHook public void onEventMainThread(UploadEvents.AutoEmail upload){ LOG.debug("Auto Email Event completed, success: " + upload.success); diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/MainPreferenceActivity.java b/gpslogger/src/main/java/com/mendhak/gpslogger/MainPreferenceActivity.java index 4670caec1..8b364bfd9 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/MainPreferenceActivity.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/MainPreferenceActivity.java @@ -141,6 +141,10 @@ protected void onCreate(Bundle savedInstanceState) { setTitle(R.string.sftp_setup_title); preferenceFragmentCompat = new SFTPSettingsFragment(); break; + case PREFERENCE_FRAGMENTS.HTTPFILEUPLOAD: + setTitle(R.string.http_file_upload_setup_title); + preferenceFragmentCompat = new HttpFileUploadSettingsFragment(); + break; } getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, preferenceFragmentCompat).commit(); @@ -190,6 +194,7 @@ public static class PREFERENCE_FRAGMENTS { public static final String OWNCLOUD = "OwnCloudAuthorizationFragment"; public static final String OSM = "OSMAuthorizationFragment"; public static final String SFTP = "SFTPSettingsFragment"; + public static final String HTTPFILEUPLOAD = "HttpFileUploadSettingsFragment"; } } 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 69c4bca4e..733cc6ac4 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/common/PreferenceHelper.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/common/PreferenceHelper.java @@ -447,7 +447,7 @@ public int getAbsoluteTimeoutForAcquiringPosition() { public void setAbsoluteTimeoutForAcquiringPosition(int absoluteTimeout) { prefs.edit().putString(PreferenceNames.ABSOLUTE_TIMEOUT, String.valueOf(absoluteTimeout)).apply(); } - + /** * Reduce redundant passive location updates by adjusting the minimum collection interval (in seconds). */ @@ -720,6 +720,64 @@ public boolean shouldLogToOpenGTS() { } + @ProfilePreference(name=PreferenceNames.AUTOSEND_HTTPFILEUPLOAD_ENABLED) + public boolean isHttpFileUploadAutoSendEnabled() { + return prefs.getBoolean(PreferenceNames.AUTOSEND_HTTPFILEUPLOAD_ENABLED, false); + } + + @ProfilePreference(name=PreferenceNames.HTTPFILEUPLOAD_URL) + public String getHttpFileUploadUrl() { + return prefs.getString(PreferenceNames.HTTPFILEUPLOAD_URL, ""); + } + + public void setHttpFileUploadUrl(String url) { + prefs.edit().putString(PreferenceNames.HTTPFILEUPLOAD_URL, url).apply(); + } + + @ProfilePreference(name=PreferenceNames.HTTPFILEUPLOAD_METHOD) + public String getHttpFileUploadMethod() { + return prefs.getString(PreferenceNames.HTTPFILEUPLOAD_METHOD, "POST"); + } + + public void setHttpFileUploadMethod(String method) { + prefs.edit().putString(PreferenceNames.HTTPFILEUPLOAD_METHOD, method).apply(); + } + + @ProfilePreference(name=PreferenceNames.HTTPFILEUPLOAD_HEADERS) + public String getHttpFileUploadHeaders() { + return prefs.getString(PreferenceNames.HTTPFILEUPLOAD_HEADERS, ""); + } + + public void setHttpFileUploadHeaders(String headers) { + prefs.edit().putString(PreferenceNames.HTTPFILEUPLOAD_HEADERS, headers).apply(); + } + + @ProfilePreference(name=PreferenceNames.HTTPFILEUPLOAD_BASICAUTH_USERNAME) + public String getHttpFileUploadUsername() { + return prefs.getString(PreferenceNames.HTTPFILEUPLOAD_BASICAUTH_USERNAME, ""); + } + + public void setHttpFileUploadUsername(String username) { + prefs.edit().putString(PreferenceNames.HTTPFILEUPLOAD_BASICAUTH_USERNAME, username).apply(); + } + + @ProfilePreference(name=PreferenceNames.HTTPFILEUPLOAD_BASICAUTH_PASSWORD) + public String getHttpFileUploadPassword() { + return prefs.getString(PreferenceNames.HTTPFILEUPLOAD_BASICAUTH_PASSWORD, ""); + } + + public void setHttpFileUploadPassword(String password) { + prefs.edit().putString(PreferenceNames.HTTPFILEUPLOAD_BASICAUTH_PASSWORD, password).apply(); + } + + @ProfilePreference(name=PreferenceNames.HTTPFILEUPLOAD_BODY_TYPE) + public String getHttpFileUploadBodyType() { + return prefs.getString(PreferenceNames.HTTPFILEUPLOAD_BODY_TYPE, "form-data"); + } + + public void setHttpFileUploadBodyType(String bodyType) { + prefs.edit().putString(PreferenceNames.HTTPFILEUPLOAD_BODY_TYPE, bodyType).apply(); + } @ProfilePreference(name=PreferenceNames.LOG_PASSIVE_LOCATIONS) @@ -1246,6 +1304,20 @@ public void setShouldLogOnlyIfSignificantMotion(boolean value){ prefs.edit().putBoolean(PreferenceNames.ONLY_LOG_IF_SIGNIFICANT_MOTION, value).apply(); } + @ProfilePreference(name= PreferenceNames.SIGNIFICANT_MOTION_BYPASS_INTERVAL) + public int getSignificantMotionBypassInterval() { + try{ + return Integer.parseInt(prefs.getString(PreferenceNames.SIGNIFICANT_MOTION_BYPASS_INTERVAL, "0")); + } + catch(Exception e){ + return 0; + } + } + + public void setSignificantMotionBypassInterval(int value){ + prefs.edit().putString(PreferenceNames.SIGNIFICANT_MOTION_BYPASS_INTERVAL, String.valueOf(value)).apply(); + } + @SuppressWarnings("unchecked") public void savePropertiesFromPreferences(File f) throws IOException { 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 f955ea069..cf4c0d0b8 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/common/PreferenceNames.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/common/PreferenceNames.java @@ -50,6 +50,15 @@ public class PreferenceNames { public static final String LOG_TO_URL_BASICAUTH_PASSWORD = "log_customurl_basicauth_password"; public static final String LOG_TO_URL_DISCARD_OFFLINE_LOCATIONS_ENABLED = "log_customurl_discard_offline_locations_enabled"; public static final String AUTOSEND_CUSTOMURL_ENABLED = "autocustomurl_enabled"; + + public static final String AUTOSEND_HTTPFILEUPLOAD_ENABLED = "autohttpfileupload_enabled"; + public static final String HTTPFILEUPLOAD_URL = "httpfileupload_url"; + public static final String HTTPFILEUPLOAD_METHOD = "httpfileupload_method"; + public static final String HTTPFILEUPLOAD_HEADERS = "httpfileupload_headers"; + public static final String HTTPFILEUPLOAD_BASICAUTH_USERNAME = "httpfileupload_basicauth_username"; + public static final String HTTPFILEUPLOAD_BASICAUTH_PASSWORD = "httpfileupload_basicauth_password"; + public static final String HTTPFILEUPLOAD_BODY_TYPE = "httpfileupload_body_type"; + public static final String LOG_TO_OPENGTS = "log_opengts"; public static final String LOG_PASSIVE_LOCATIONS="log_passive_locations"; public static final String LOG_SATELLITE_LOCATIONS = "log_satellite_locations"; @@ -148,5 +157,6 @@ public static enum DegreesDisplayFormat { public static final String ANNOTATIONS_BUTTON_SETTINGS = "annotations_buttons"; public static final String ONLY_LOG_IF_SIGNIFICANT_MOTION = "only_log_if_significant_motion"; + public static final String SIGNIFICANT_MOTION_BYPASS_INTERVAL = "significant_motion_bypass_interval"; } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/common/events/UploadEvents.java b/gpslogger/src/main/java/com/mendhak/gpslogger/common/events/UploadEvents.java index f5d2e9842..138214baf 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/common/events/UploadEvents.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/common/events/UploadEvents.java @@ -105,4 +105,6 @@ public static class SFTP extends BaseUploadEvent { public String fingerprint; public String hostKey; } + + public static class HttpFileUpload extends BaseUploadEvent {} } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/FileSender.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/FileSender.java index e96739616..49e4490ce 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/FileSender.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/FileSender.java @@ -35,6 +35,7 @@ public static class SenderNames { public static final String SFTP = "SFTP_SENDER"; public static final String OPENSTREETMAP = "OSM_SENDER"; public static final String CUSTOMURL = "CUSTOM_URL_SENDER"; + public static final String HTTPFILEUPLOAD = "HTTP_FILE_UPLOAD_SENDER"; } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/FileSenderFactory.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/FileSenderFactory.java index 2defe6ba3..da4e59923 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/FileSenderFactory.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/FileSenderFactory.java @@ -27,6 +27,7 @@ import com.mendhak.gpslogger.senders.email.AutoEmailManager; import com.mendhak.gpslogger.senders.ftp.FtpManager; import com.mendhak.gpslogger.senders.googledrive.GoogleDriveManager; +import com.mendhak.gpslogger.senders.http.HttpFileUploadManager; import com.mendhak.gpslogger.senders.opengts.OpenGTSManager; import com.mendhak.gpslogger.senders.osm.OpenStreetMapManager; import com.mendhak.gpslogger.senders.owncloud.OwnCloudManager; @@ -80,6 +81,10 @@ public static FileSender getCustomUrlSender(){ return new CustomUrlManager(PreferenceHelper.getInstance()); } + public static FileSender getHttpFileUploadSender() { + return new HttpFileUploadManager(PreferenceHelper.getInstance()); + } + public static void autoSendFiles(final String fileToSend) { PreferenceHelper preferenceHelper = PreferenceHelper.getInstance(); @@ -164,6 +169,8 @@ public static FileSender getSenderByName(String senderName) { return getSFTPSender(); case FileSender.SenderNames.CUSTOMURL: return getCustomUrlSender(); + case FileSender.SenderNames.HTTPFILEUPLOAD: + return getHttpFileUploadSender(); default: return null; @@ -181,10 +188,11 @@ private static List getAllFileSenders(){ senders.add(getOwnCloudSender()); senders.add(getSFTPSender()); senders.add(getCustomUrlSender()); + senders.add(getHttpFileUploadSender()); return senders; } - private static List getAvailableFileAutoSenders() { + public static List getAvailableFileAutoSenders() { List senders = new ArrayList<>(); @@ -224,6 +232,10 @@ private static List getAvailableFileAutoSenders() { senders.add(getCustomUrlSender()); } + if(getHttpFileUploadSender().isAutoSendAvailable()){ + senders.add(getHttpFileUploadSender()); + } + return senders; } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/http/HttpFileUploadManager.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/http/HttpFileUploadManager.java new file mode 100644 index 000000000..8fa57d105 --- /dev/null +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/http/HttpFileUploadManager.java @@ -0,0 +1,77 @@ +package com.mendhak.gpslogger.senders.http; + +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; +import com.mendhak.gpslogger.senders.FileSender; + +import de.greenrobot.event.EventBus; +import org.slf4j.Logger; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; + +public class HttpFileUploadManager extends FileSender { + + private static final Logger LOG = Logs.of(HttpFileUploadManager.class); + private final PreferenceHelper preferenceHelper; + + public HttpFileUploadManager(PreferenceHelper preferenceHelper) { + this.preferenceHelper = preferenceHelper; + } + + public void testHttpFileUpload() { + try { + final File testFile = Files.createTestFile(); + + String tag = String.valueOf(Objects.hashCode(testFile.getAbsolutePath())); + HashMap dataMap = new HashMap() {{ + put("filePath", testFile.getAbsolutePath()); + }}; + Systems.startWorkManagerRequest(HttpFileUploadWorker.class, dataMap, tag); + + } catch (Exception ex) { + EventBus.getDefault().post(new UploadEvents.HttpFileUpload().failed()); + LOG.error("Error while testing HTTP File Upload: " + ex.getMessage()); + } + + LOG.debug("Added background HTTP File Upload job"); + } + + @Override + public void uploadFile(List files) { + for (File f : files) { + String tag = String.valueOf(Objects.hashCode(f.getAbsolutePath())); + HashMap dataMap = new HashMap<>(); + dataMap.put("filePath", f.getAbsolutePath()); + + Systems.startWorkManagerRequest(HttpFileUploadWorker.class, dataMap, tag); + } + } + + @Override + public boolean isAvailable() { + return !Strings.isNullOrEmpty(preferenceHelper.getHttpFileUploadUrl()) && + !Strings.isNullOrEmpty(preferenceHelper.getHttpFileUploadMethod()); + } + + @Override + public boolean hasUserAllowedAutoSending() { + return preferenceHelper.isHttpFileUploadAutoSendEnabled(); + } + + @Override + public String getName() { + return SenderNames.HTTPFILEUPLOAD; + } + + @Override + public boolean accept(File dir, String name) { + return true; + } +} diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/senders/http/HttpFileUploadWorker.java b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/http/HttpFileUploadWorker.java new file mode 100644 index 000000000..dbbaf6cfd --- /dev/null +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/senders/http/HttpFileUploadWorker.java @@ -0,0 +1,133 @@ +package com.mendhak.gpslogger.senders.http; + +import android.content.Context; + +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.Networks; +import com.mendhak.gpslogger.common.slf4j.Logs; + +import org.slf4j.Logger; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import javax.net.ssl.X509TrustManager; + +import de.greenrobot.event.EventBus; +import okhttp3.Credentials; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class HttpFileUploadWorker extends Worker { + + private static final Logger LOG = Logs.of(HttpFileUploadWorker.class); + + public HttpFileUploadWorker(@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 HttpFileUploadWorker"); + return Result.failure(); + } + + File fileToUpload = new File(filePath); + PreferenceHelper preferenceHelper = PreferenceHelper.getInstance(); + + String url = preferenceHelper.getHttpFileUploadUrl(); + String method = preferenceHelper.getHttpFileUploadMethod(); + String headersString = preferenceHelper.getHttpFileUploadHeaders(); + String username = preferenceHelper.getHttpFileUploadUsername(); + String password = preferenceHelper.getHttpFileUploadPassword(); + String bodyType = preferenceHelper.getHttpFileUploadBodyType(); + + try { + LOG.info("HTTP File Uploading " + fileToUpload.getName() + " to " + url + " using " + method + " (" + bodyType + ")"); + + OkHttpClient.Builder okBuilder = new OkHttpClient.Builder(); + okBuilder.sslSocketFactory(Networks.getSocketFactory(AppSettings.getInstance()), + (X509TrustManager) Networks.getTrustManager(AppSettings.getInstance())); + + Request.Builder requestBuilder = new Request.Builder().url(url); + + // Add custom headers + if (!Strings.isNullOrEmpty(headersString)) { + String[] lines = headersString.split("\n"); + for (String line : lines) { + if (line.contains(":")) { + String[] parts = line.split(":", 2); + requestBuilder.addHeader(parts[0].trim(), parts[1].trim()); + } + } + } + + // Basic Auth + if (!Strings.isNullOrEmpty(username) && !Strings.isNullOrEmpty(password)) { + requestBuilder.addHeader("Authorization", Credentials.basic(username, password)); + } + + RequestBody requestBody; + if ("form-data".equalsIgnoreCase(bodyType)) { + requestBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", fileToUpload.getName(), + RequestBody.create(MediaType.parse("application/octet-stream"), fileToUpload)) + .build(); + } else { + // Binary stream + requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), fileToUpload); + } + + requestBuilder.method(method, requestBody); + + Request request = requestBuilder.build(); + Response response = okBuilder.build().newCall(request).execute(); + + if (response.isSuccessful()) { + LOG.debug("HTTP File Upload complete with successful response code " + response.code()); + response.close(); + + EventBus.getDefault().post(new UploadEvents.HttpFileUpload().succeeded()); + Systems.sendFileUploadedBroadcast(getApplicationContext(), new String[]{fileToUpload.getAbsolutePath()}, "httpfileupload"); + + return Result.success(); + } else { + String errorBody = response.body() != null ? response.body().string() : "Empty response body"; + LOG.error("HTTP File Upload failed with code " + response.code() + ": " + errorBody); + response.close(); + + if (getRunAttemptCount() < 3) { + return Result.retry(); + } + + EventBus.getDefault().post(new UploadEvents.HttpFileUpload().failed("Response code " + response.code(), new Throwable(errorBody))); + return Result.failure(); + } + + } catch (Exception e) { + LOG.error("Exception during HTTP File Upload", e); + if (getRunAttemptCount() < 3) { + return Result.retry(); + } + EventBus.getDefault().post(new UploadEvents.HttpFileUpload().failed(e.getMessage(), e)); + return Result.failure(); + } + } +} 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 531351eb3..ddb08997b 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 @@ -69,8 +69,10 @@ public static boolean validSettings( String username, String password, String directory) { - return !Strings.isNullOrEmpty(servername); - + return !Strings.isNullOrEmpty(servername) + && !Strings.isNullOrEmpty(username) + && !Strings.isNullOrEmpty(password) + && !Strings.isNullOrEmpty(directory); } @Override diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/HttpFileUploadSettingsFragment.java b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/HttpFileUploadSettingsFragment.java new file mode 100644 index 000000000..4b5485c4f --- /dev/null +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/HttpFileUploadSettingsFragment.java @@ -0,0 +1,282 @@ +package com.mendhak.gpslogger.ui.fragments.settings; + +import android.os.Bundle; +import android.text.InputType; + +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentActivity; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import com.mendhak.gpslogger.R; +import com.mendhak.gpslogger.common.EventBusHook; +import com.mendhak.gpslogger.common.PreferenceHelper; +import com.mendhak.gpslogger.common.PreferenceNames; +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.PreferenceValidator; +import com.mendhak.gpslogger.senders.http.HttpFileUploadManager; +import com.mendhak.gpslogger.ui.Dialogs; + +import de.greenrobot.event.EventBus; +import org.slf4j.Logger; + +import eltos.simpledialogfragment.SimpleDialog; +import eltos.simpledialogfragment.form.Input; +import eltos.simpledialogfragment.form.SimpleFormDialog; +import eltos.simpledialogfragment.list.SimpleListDialog; + +public class HttpFileUploadSettingsFragment extends PreferenceFragmentCompat implements + SimpleDialog.OnDialogResultListener, + PreferenceValidator, + Preference.OnPreferenceClickListener { + + private static final Logger LOG = Logs.of(HttpFileUploadSettingsFragment.class); + private final PreferenceHelper preferenceHelper = PreferenceHelper.getInstance(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Preference urlPreference = findPreference(PreferenceNames.HTTPFILEUPLOAD_URL); + urlPreference.setSummary(preferenceHelper.getHttpFileUploadUrl()); + urlPreference.setOnPreferenceClickListener(this); + + Preference bodyTypePreference = findPreference(PreferenceNames.HTTPFILEUPLOAD_BODY_TYPE); + bodyTypePreference.setSummary(getBodyTypeDisplay(preferenceHelper.getHttpFileUploadBodyType())); + bodyTypePreference.setOnPreferenceClickListener(this); + + Preference methodPreference = findPreference(PreferenceNames.HTTPFILEUPLOAD_METHOD); + methodPreference.setSummary(preferenceHelper.getHttpFileUploadMethod()); + methodPreference.setOnPreferenceClickListener(this); + + Preference headersPreference = findPreference(PreferenceNames.HTTPFILEUPLOAD_HEADERS); + headersPreference.setSummary(preferenceHelper.getHttpFileUploadHeaders()); + headersPreference.setOnPreferenceClickListener(this); + + Preference authPreference = findPreference("httpfileupload_basicauth"); + updateAuthSummary(authPreference); + authPreference.setOnPreferenceClickListener(this); + + findPreference("httpfileupload_test").setOnPreferenceClickListener(this); + + updateMethodPreferenceState(); + registerEventBus(); + } + + private void updateAuthSummary(Preference authPreference) { + String username = preferenceHelper.getHttpFileUploadUsername(); + if (!Strings.isNullOrEmpty(username)) { + authPreference.setSummary(username + ":***"); + } else { + authPreference.setSummary(""); + } + } + + private String getBodyTypeDisplay(String bodyType) { + String[] entries = getResources().getStringArray(R.array.http_body_types); + String[] values = getResources().getStringArray(R.array.http_body_type_values); + for (int i = 0; i < values.length; i++) { + if (values[i].equals(bodyType)) { + return entries[i]; + } + } + return entries[0]; + } + + private void updateMethodPreferenceState() { + Preference methodPreference = findPreference(PreferenceNames.HTTPFILEUPLOAD_METHOD); + if ("form-data".equals(preferenceHelper.getHttpFileUploadBodyType())) { + preferenceHelper.setHttpFileUploadMethod("POST"); + methodPreference.setSummary("POST"); + methodPreference.setEnabled(false); + } else { + methodPreference.setEnabled(true); + methodPreference.setSummary(preferenceHelper.getHttpFileUploadMethod()); + } + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.httpfileuploadsettings, rootKey); + } + + @Override + public void onDestroy() { + unregisterEventBus(); + super.onDestroy(); + } + + private void registerEventBus() { + EventBus.getDefault().register(this); + } + + private void unregisterEventBus() { + try { + EventBus.getDefault().unregister(this); + } catch (Throwable t) { + // Safe to ignore + } + } + + @Override + public boolean isValid() { + HttpFileUploadManager manager = new HttpFileUploadManager(preferenceHelper); + return !manager.hasUserAllowedAutoSending() || manager.isAvailable(); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + if (preference.getKey().equals(PreferenceNames.HTTPFILEUPLOAD_URL)) { + SimpleFormDialog.build() + .title("URL") + .neg(R.string.cancel) + .pos(R.string.ok) + .fields( + Input.plain(PreferenceNames.HTTPFILEUPLOAD_URL) + .text(preferenceHelper.getHttpFileUploadUrl()) + .required() + ) + .show(this, PreferenceNames.HTTPFILEUPLOAD_URL); + return true; + } + + if (preference.getKey().equals(PreferenceNames.HTTPFILEUPLOAD_METHOD)) { + SimpleListDialog.build() + .title(R.string.customurl_http_method) + .items(getActivity(), R.array.http_methods) + .choiceMode(SimpleListDialog.SINGLE_CHOICE_DIRECT) + .show(this, PreferenceNames.HTTPFILEUPLOAD_METHOD); + return true; + } + + if (preference.getKey().equals(PreferenceNames.HTTPFILEUPLOAD_BODY_TYPE)) { + SimpleListDialog.build() + .title(R.string.http_file_upload_body_type) + .items(getActivity(), R.array.http_body_types) + .choiceMode(SimpleListDialog.SINGLE_CHOICE_DIRECT) + .show(this, PreferenceNames.HTTPFILEUPLOAD_BODY_TYPE); + return true; + } + + if (preference.getKey().equals(PreferenceNames.HTTPFILEUPLOAD_HEADERS)) { + SimpleFormDialog.build() + .title(R.string.http_file_upload_http_headers) + .neg(R.string.cancel) + .pos(R.string.ok) + .msgHtml("Content-Type: application/json
Authorization: Basic abcdefg
ApiToken: 12345") + .fields( + Input.plain(PreferenceNames.HTTPFILEUPLOAD_HEADERS) + .text(preferenceHelper.getHttpFileUploadHeaders()) + .inputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE) + ) + .show(this, PreferenceNames.HTTPFILEUPLOAD_HEADERS); + return true; + } + + if (preference.getKey().equals("httpfileupload_basicauth")) { + SimpleFormDialog.build() + .title("Basic Authentication") + .neg(R.string.cancel) + .pos(R.string.ok) + .fields( + Input.plain(PreferenceNames.HTTPFILEUPLOAD_BASICAUTH_USERNAME) + .text(preferenceHelper.getHttpFileUploadUsername()) + .hint(R.string.autoftp_username), + Input.plain(PreferenceNames.HTTPFILEUPLOAD_BASICAUTH_PASSWORD) + .text(preferenceHelper.getHttpFileUploadPassword()) + .hint(R.string.autoftp_password) + .showPasswordToggle() + .inputType(InputType.TYPE_TEXT_VARIATION_PASSWORD) + ) + .show(this, "httpfileupload_basicauth"); + return true; + } + + if (preference.getKey().equals("httpfileupload_test")) { + HttpFileUploadManager manager = new HttpFileUploadManager(preferenceHelper); + if (!manager.isAvailable()) { + Dialogs.alert(getString(R.string.autoftp_invalid_settings), + getString(R.string.autoftp_invalid_summary), + getActivity()); + return false; + } + + Dialogs.progress((FragmentActivity) getActivity(), "Testing HTTP File Upload"); + manager.testHttpFileUpload(); + return true; + } + + return false; + } + + @EventBusHook + public void onEventMainThread(UploadEvents.HttpFileUpload o) { + LOG.debug("HTTP File Upload Event completed, success: " + o.success); + Dialogs.hideProgress(); + if (!o.success) { + Dialogs.showError(getString(R.string.sorry), "HTTP File Upload Test Failed", o.message, o.throwable, (FragmentActivity) getActivity()); + } else { + Dialogs.alert(getString(R.string.success), "HTTP File Upload Test Succeeded", getActivity()); + } + } + + @Override + public boolean onResult(@NonNull String dialogTag, int which, @NonNull Bundle extras) { + if (which != BUTTON_POSITIVE) { + return true; + } + + if (dialogTag.equals(PreferenceNames.HTTPFILEUPLOAD_URL)) { + String url = extras.getString(PreferenceNames.HTTPFILEUPLOAD_URL); + preferenceHelper.setHttpFileUploadUrl(url); + findPreference(PreferenceNames.HTTPFILEUPLOAD_URL).setSummary(url); + return true; + } + + if (dialogTag.equals(PreferenceNames.HTTPFILEUPLOAD_METHOD)) { + String method = extras.getString(SimpleListDialog.SELECTED_SINGLE_LABEL); + preferenceHelper.setHttpFileUploadMethod(method); + findPreference(PreferenceNames.HTTPFILEUPLOAD_METHOD).setSummary(method); + return true; + } + + if (dialogTag.equals(PreferenceNames.HTTPFILEUPLOAD_BODY_TYPE)) { + String label = extras.getString(SimpleListDialog.SELECTED_SINGLE_LABEL); + String[] entries = getResources().getStringArray(R.array.http_body_types); + String[] values = getResources().getStringArray(R.array.http_body_type_values); + + String value = values[0]; + for (int i = 0; i < entries.length; i++) { + if (entries[i].equals(label)) { + value = values[i]; + break; + } + } + + preferenceHelper.setHttpFileUploadBodyType(value); + findPreference(PreferenceNames.HTTPFILEUPLOAD_BODY_TYPE).setSummary(getBodyTypeDisplay(value)); + updateMethodPreferenceState(); + return true; + } + + if (dialogTag.equals(PreferenceNames.HTTPFILEUPLOAD_HEADERS)) { + String headers = extras.getString(PreferenceNames.HTTPFILEUPLOAD_HEADERS); + preferenceHelper.setHttpFileUploadHeaders(headers); + findPreference(PreferenceNames.HTTPFILEUPLOAD_HEADERS).setSummary(headers); + return true; + } + + if (dialogTag.equals("httpfileupload_basicauth")) { + String username = extras.getString(PreferenceNames.HTTPFILEUPLOAD_BASICAUTH_USERNAME); + String password = extras.getString(PreferenceNames.HTTPFILEUPLOAD_BASICAUTH_PASSWORD); + preferenceHelper.setHttpFileUploadUsername(username); + preferenceHelper.setHttpFileUploadPassword(password); + updateAuthSummary(findPreference("httpfileupload_basicauth")); + return true; + } + + return false; + } +} diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/PerformanceSettingsFragment.java b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/PerformanceSettingsFragment.java index edc85deab..6c1a74dd9 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/PerformanceSettingsFragment.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/PerformanceSettingsFragment.java @@ -61,22 +61,24 @@ public void onCreate(Bundle savedInstanceState) { findPreference(PreferenceNames.ABSOLUTE_TIMEOUT).setOnPreferenceClickListener(this); findPreference(PreferenceNames.ABSOLUTE_TIMEOUT).setSummary(String.valueOf(preferenceHelper.getAbsoluteTimeoutForAcquiringPosition()) + getString(R.string.seconds)); - + findPreference(PreferenceNames.PASSIVE_FILTER_INTERVAL).setOnPreferenceClickListener(this); findPreference(PreferenceNames.PASSIVE_FILTER_INTERVAL).setSummary(String.valueOf(preferenceHelper.getPassiveFilterInterval()) + getString(R.string.seconds)); findPreference(PreferenceNames.ALTITUDE_SUBTRACT_OFFSET).setOnPreferenceClickListener(this); findPreference(PreferenceNames.ALTITUDE_SUBTRACT_OFFSET).setSummary(String.valueOf(preferenceHelper.getSubtractAltitudeOffset()) + getString(R.string.meters)); + findPreference(PreferenceNames.SIGNIFICANT_MOTION_BYPASS_INTERVAL).setOnPreferenceClickListener(this); + findPreference(PreferenceNames.SIGNIFICANT_MOTION_BYPASS_INTERVAL).setSummary(String.valueOf(preferenceHelper.getSignificantMotionBypassInterval()) + " " + getString(R.string.minutes)); + SensorManager sensorManager = (SensorManager)AppSettings.getInstance().getSystemService(android.content.Context.SENSOR_SERVICE); Sensor significantMotionSensor = sensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION); SwitchPreferenceCompat significantMotionSwitch = findPreference(PreferenceNames.ONLY_LOG_IF_SIGNIFICANT_MOTION); if(significantMotionSensor == null && significantMotionSwitch != null){ significantMotionSwitch.setChecked(false); significantMotionSwitch.setEnabled(false); + findPreference(PreferenceNames.SIGNIFICANT_MOTION_BYPASS_INTERVAL).setEnabled(false); } - - } @Override @@ -198,6 +200,21 @@ public boolean onPreferenceClick(Preference preference) { return true; } + if(preference.getKey().equalsIgnoreCase(PreferenceNames.SIGNIFICANT_MOTION_BYPASS_INTERVAL)){ + SimpleFormDialog.build() + .title(R.string.time_in_minutes_dialog_title) + .msg(R.string.significant_motion_bypass_interval_summary) + .fields( + Input.plain(PreferenceNames.SIGNIFICANT_MOTION_BYPASS_INTERVAL) + .inputType(InputType.TYPE_CLASS_NUMBER) + .required() + .text(String.valueOf(preferenceHelper.getSignificantMotionBypassInterval())) + .max(4) + ) + .show(this, PreferenceNames.SIGNIFICANT_MOTION_BYPASS_INTERVAL); + return true; + } + return false; } @@ -255,6 +272,13 @@ public boolean onResult(@NonNull String dialogTag, int which, @NonNull Bundle ex return true; } + if(dialogTag.equalsIgnoreCase(PreferenceNames.SIGNIFICANT_MOTION_BYPASS_INTERVAL)){ + String time = extras.getString(PreferenceNames.SIGNIFICANT_MOTION_BYPASS_INTERVAL); + preferenceHelper.setSignificantMotionBypassInterval(Integer.valueOf(time)); + findPreference(PreferenceNames.SIGNIFICANT_MOTION_BYPASS_INTERVAL).setSummary(String.valueOf(preferenceHelper.getSignificantMotionBypassInterval()) + " " + getString(R.string.minutes)); + return true; + } + return false; } } diff --git a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/UploadSettingsFragment.java b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/UploadSettingsFragment.java index c98333ad7..f4810d112 100644 --- a/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/UploadSettingsFragment.java +++ b/gpslogger/src/main/java/com/mendhak/gpslogger/ui/fragments/settings/UploadSettingsFragment.java @@ -175,6 +175,23 @@ public void onClick(View view) { } }); + ((SwitchPlusClickPreference)findPreference(PreferenceNames.AUTOSEND_HTTPFILEUPLOAD_ENABLED)) + .setSwitchClickListener(new SwitchPlusClickPreference.SwitchPlusClickListener() { + + @Override + public void onCheckedChanged(SwitchCompat buttonView, boolean isChecked) { + // No need to do anything, the value gets propagated. + } + + @Override + public void onClick(View view) { + + Intent intent = new Intent(getActivity(), MainPreferenceActivity.class); + intent.putExtra("preference_fragment", MainPreferenceActivity.PREFERENCE_FRAGMENTS.HTTPFILEUPLOAD); + startActivity(intent); + } + }); + ((SwitchPlusClickPreference)findPreference(PreferenceNames.AUTOSEND_DROPBOX_ENABLED)) .setSwitchClickListener(new SwitchPlusClickPreference.SwitchPlusClickListener() { diff --git a/gpslogger/src/main/res/menu/gps_main.xml b/gpslogger/src/main/res/menu/gps_main.xml index 8f1ecd2e9..b7bb63b4c 100644 --- a/gpslogger/src/main/res/menu/gps_main.xml +++ b/gpslogger/src/main/res/menu/gps_main.xml @@ -11,6 +11,7 @@ + diff --git a/gpslogger/src/main/res/values/arrays.xml b/gpslogger/src/main/res/values/arrays.xml index 144e4a345..1fb8867c0 100644 --- a/gpslogger/src/main/res/values/arrays.xml +++ b/gpslogger/src/main/res/values/arrays.xml @@ -75,5 +75,19 @@ dark + + POST + PUT + + + + Multipart form-data + Binary stream + + + + form-data + binary + - \ No newline at end of file + diff --git a/gpslogger/src/main/res/values/strings.xml b/gpslogger/src/main/res/values/strings.xml index 115489757..599f1e840 100644 --- a/gpslogger/src/main/res/values/strings.xml +++ b/gpslogger/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - + Upload settings @@ -6,7 +6,6 @@ Close navigation drawer - GPSLogger GPSLogger Settings OK @@ -189,7 +188,6 @@ When I press stop - Email Settings Username with your email provider Password with your email provider @@ -209,7 +207,6 @@ Stopped - OpenStreetMap Authorize this app @@ -262,7 +259,7 @@ ownCloud Base URL Folder to use on ownCloud server. Ensure that it exists or upload will fail. Upload a test file to the ownCloud server - + Testing ownCloud upload Upload @@ -432,7 +429,7 @@ Use MSL instead of WGS84 Subtract altitude offset A value in meters to subtract from GPS altitudes. Use a negative number to add a value to the altitude. Only applies to GPS satellite points, not network or NMEA. - Could not upload the file + Could not upload the file Inaccurate point discarded Only %s m traveled. Point discarded. Send on Wi-Fi only @@ -502,5 +499,12 @@ Prompt for details when logging starts Only log if there is significant motion Only log if significant activity is detected such as walking, biking, driving. Significant motion is determined by your OS. - + Extended no motion heartbeat interval + During extended periods of no significant motion, ocassionally bypass the check and take a heartbeat log anyway as determined by this interval. A value less than 1 minute disables the interval. + Time in minutes + + HTTP File Upload + Uploads files to an arbitrary URL using HTTP POST or PUT. + Encoding + HTTP Headers diff --git a/gpslogger/src/main/res/xml/httpfileuploadsettings.xml b/gpslogger/src/main/res/xml/httpfileuploadsettings.xml new file mode 100644 index 000000000..9617ebf2f --- /dev/null +++ b/gpslogger/src/main/res/xml/httpfileuploadsettings.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + diff --git a/gpslogger/src/main/res/xml/pref_performance.xml b/gpslogger/src/main/res/xml/pref_performance.xml index c8ce30a15..60c8cd6b6 100644 --- a/gpslogger/src/main/res/xml/pref_performance.xml +++ b/gpslogger/src/main/res/xml/pref_performance.xml @@ -1,4 +1,4 @@ - + @@ -102,7 +102,7 @@ android:title="@string/absolute_timeout_title" app:iconSpaceReserved="false" /> - + + + diff --git a/gpslogger/src/main/res/xml/pref_upload.xml b/gpslogger/src/main/res/xml/pref_upload.xml index 868a8ddad..6000e8740 100644 --- a/gpslogger/src/main/res/xml/pref_upload.xml +++ b/gpslogger/src/main/res/xml/pref_upload.xml @@ -1,4 +1,4 @@ - + + app:icon="@drawable/customurlsender" /> + + + android:title="@string/google_drive_setup_title" /> " + + "20260331" + + "" + + "88.7111657481467" + + "0.047.2gps71.51.32.0\n" + + "" + + ""; + try(FileWriter writer = new FileWriter(tempFile)){ + writer.write(existingContent); + } + + Gpx10WriteHandler writeHandler = new Gpx10WriteHandler(null, tempFile, null, true); + writeHandler.run(); + + String fileContent = new String(Files.readAllBytes(tempFile.toPath())); + + assertThat("File should contain two track segments", fileContent.split("").length, is(2)); + assertThat("File should contain two track segments", fileContent.split("").length, is(2)); + + + } + @Test public void GetTrackPointXml_WhenHDOPPresent_ThenFormattedInXML(){ diff --git a/gpslogger/src/test/java/com/mendhak/gpslogger/senders/http/HttpFileUploadManagerTest.java b/gpslogger/src/test/java/com/mendhak/gpslogger/senders/http/HttpFileUploadManagerTest.java new file mode 100644 index 000000000..2e1fd02e5 --- /dev/null +++ b/gpslogger/src/test/java/com/mendhak/gpslogger/senders/http/HttpFileUploadManagerTest.java @@ -0,0 +1,70 @@ +package com.mendhak.gpslogger.senders.http; + +import androidx.test.filters.SmallTest; + +import com.mendhak.gpslogger.common.PreferenceHelper; +import com.mendhak.gpslogger.senders.FileSender; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@SmallTest +@RunWith(MockitoJUnitRunner.class) +public class HttpFileUploadManagerTest { + + @Test + public void isAvailable_WhenUrlAndMethodPresent_ReturnsTrue() { + PreferenceHelper pm = mock(PreferenceHelper.class); + when(pm.getHttpFileUploadUrl()).thenReturn("https://example.com/upload"); + when(pm.getHttpFileUploadMethod()).thenReturn("POST"); + + HttpFileUploadManager manager = new HttpFileUploadManager(pm); + assertThat("URL and method are required", manager.isAvailable(), is(true)); + } + + @Test + public void isAvailable_WhenUrlMissing_ReturnsFalse() { + PreferenceHelper pm = mock(PreferenceHelper.class); + when(pm.getHttpFileUploadUrl()).thenReturn(""); + + HttpFileUploadManager manager = new HttpFileUploadManager(pm); + assertThat("URL is required", manager.isAvailable(), is(false)); + } + + @Test + public void isAvailable_WhenMethodMissing_ReturnsFalse() { + PreferenceHelper pm = mock(PreferenceHelper.class); + when(pm.getHttpFileUploadUrl()).thenReturn("https://example.com/upload"); + when(pm.getHttpFileUploadMethod()).thenReturn(""); + + HttpFileUploadManager manager = new HttpFileUploadManager(pm); + assertThat("HTTP method is required", manager.isAvailable(), is(false)); + } + + @Test + public void hasUserAllowedAutoSending_UsesPreferenceFlag() { + PreferenceHelper pm = mock(PreferenceHelper.class); + when(pm.isHttpFileUploadAutoSendEnabled()).thenReturn(true); + + HttpFileUploadManager manager = new HttpFileUploadManager(pm); + assertThat("Autosend preference should drive behavior", manager.hasUserAllowedAutoSending(), is(true)); + } + + @Test + public void getName_ReturnsHttpFileUploadSenderName() { + HttpFileUploadManager manager = new HttpFileUploadManager(mock(PreferenceHelper.class)); + assertThat("Sender name should be HTTP File Upload", manager.getName(), is(FileSender.SenderNames.HTTPFILEUPLOAD)); + } + + @Test + public void accept_AnyFileName_ReturnsTrue() { + HttpFileUploadManager manager = new HttpFileUploadManager(mock(PreferenceHelper.class)); + assertThat("HTTP File Upload accepts any log file type", manager.accept(null, "anything.txt"), is(true)); + } +} diff --git a/gpslogger/src/test/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudManagerTest.java b/gpslogger/src/test/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudManagerTest.java index 11b7e992d..6e5598cf5 100644 --- a/gpslogger/src/test/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudManagerTest.java +++ b/gpslogger/src/test/java/com/mendhak/gpslogger/senders/owncloud/OwnCloudManagerTest.java @@ -26,7 +26,10 @@ public void IsAvailable_WithValidValues_IsAvailable(){ assertThat("Server name should not be empty", ocm.isAvailable(), is(false)); when(pm.getOwnCloudBaseUrl()).thenReturn("sadfasdf"); - assertThat("Server name should not be empty", ocm.isAvailable(), is(true)); + when(pm.getOwnCloudDirectory()).thenReturn("/"); + when(pm.getOwnCloudUsername()).thenReturn("example"); + when(pm.getOwnCloudPassword()).thenReturn("example"); + assertThat("Server name, directory, username and password should not be empty", ocm.isAvailable(), is(true)); } @Test @@ -36,6 +39,9 @@ public void IsAutoSendAvailable_WhenUserCheckedPreference_ThenAvailable(){ OwnCloudManager ocm = new OwnCloudManager(pm); when(pm.getOwnCloudBaseUrl()).thenReturn("sadfasdf"); + when(pm.getOwnCloudDirectory()).thenReturn("/"); + when(pm.getOwnCloudUsername()).thenReturn("example"); + when(pm.getOwnCloudPassword()).thenReturn("example"); assertThat("Valid but unchecked - not available", ocm.isAutoSendAvailable(), is(false)); when(pm.isOwnCloudAutoSendEnabled()).thenReturn(true);