diff --git a/build.gradle b/build.gradle index d2d1c0c6e7..c9ab30f24b 100644 --- a/build.gradle +++ b/build.gradle @@ -152,6 +152,7 @@ dependencies { implementation 'androidx.core:core-splashscreen:1.0.1' implementation 'androidx.mediarouter:mediarouter:1.8.1' implementation 'androidx.core:core-location-altitude:1.0.0-alpha03' + implementation 'androidx.work:work-runtime:2.10.2' androidTestImplementation 'androidx.test:core:1.7.0' androidTestImplementation 'androidx.test.ext:junit:1.3.0' diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index f0939f3d26..6c8d740ffd 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -395,21 +395,6 @@ limitations under the License. android:icon="@drawable/ic_logo_color_24dp" android:label="@string/recording_service" /> - - - - - - { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -31,14 +29,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { } @NonNull - protected View createRootView() { - viewBinding = AboutBinding.inflate(getLayoutInflater()); - return viewBinding.getRoot(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - viewBinding = null; + protected AboutBinding createRootView() { + return AboutBinding.inflate(getLayoutInflater()); } } diff --git a/src/main/java/de/dennisguse/opentracks/AbstractActivity.java b/src/main/java/de/dennisguse/opentracks/AbstractActivity.java index 478ef5bf24..f4f1d75745 100644 --- a/src/main/java/de/dennisguse/opentracks/AbstractActivity.java +++ b/src/main/java/de/dennisguse/opentracks/AbstractActivity.java @@ -26,14 +26,15 @@ import androidx.core.view.ViewCompat; import androidx.core.view.ViewGroupCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.viewbinding.ViewBinding; import de.dennisguse.opentracks.services.announcement.TTSManager; import de.dennisguse.opentracks.settings.PreferencesUtils; -/** - * @author Jimmy Shih - */ -public abstract class AbstractActivity extends AppCompatActivity { +// TODO Make ViewBinding a generic attribute and set to null in onDestroy() +public abstract class AbstractActivity extends AppCompatActivity { + + protected T viewBinding; @Override protected void onCreate(Bundle savedInstanceState) { @@ -46,13 +47,14 @@ protected void onCreate(Bundle savedInstanceState) { // Set volume control stream for text to speech setVolumeControlStream(TTSManager.AUDIO_STREAM); - View rootView = createRootView(); - setContentView(rootView); + viewBinding = createRootView(); + setContentView(viewBinding.getRoot()); - edge2edgeApplyInsets(rootView); + edge2edgeApplyInsets(); } - private void edge2edgeApplyInsets(View rootView) { + private void edge2edgeApplyInsets() { + View rootView = viewBinding.getRoot(); ViewCompat.setOnApplyWindowInsetsListener(rootView, (view, windowInsets) -> { Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout() | WindowInsetsCompat.Type.ime()); view.setPadding(insets.left, insets.top, insets.right, insets.bottom); @@ -69,5 +71,11 @@ public boolean onSupportNavigateUp() { } @NonNull - protected abstract View createRootView(); + protected abstract T createRootView(); + + @Override + protected void onDestroy() { + super.onDestroy(); + viewBinding = null; + } } diff --git a/src/main/java/de/dennisguse/opentracks/AbstractTrackDeleteActivity.java b/src/main/java/de/dennisguse/opentracks/AbstractTrackDeleteActivity.java index c7cf063d0f..140e2c4c7d 100644 --- a/src/main/java/de/dennisguse/opentracks/AbstractTrackDeleteActivity.java +++ b/src/main/java/de/dennisguse/opentracks/AbstractTrackDeleteActivity.java @@ -16,15 +16,21 @@ package de.dennisguse.opentracks; -import android.os.Handler; +import android.view.View; import android.widget.Toast; +import androidx.viewbinding.ViewBinding; +import androidx.work.Data; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkManager; +import androidx.work.WorkRequest; + import java.util.ArrayList; import java.util.Arrays; import java.util.stream.Collectors; import de.dennisguse.opentracks.data.models.Track; -import de.dennisguse.opentracks.services.TrackDeleteService; +import de.dennisguse.opentracks.services.TrackDeletionWorker; import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment.ConfirmDeleteCaller; @@ -37,7 +43,7 @@ * @author Jimmy Shih */ //TODO Check if this class is still such a good idea; inheritance might limit maintainability -public abstract class AbstractTrackDeleteActivity extends AbstractActivity implements ConfirmDeleteCaller, TrackDeleteService.TrackDeleteResultReceiver.Receiver { +public abstract class AbstractTrackDeleteActivity extends AbstractActivity implements ConfirmDeleteCaller { protected void deleteTracks(Track.Id... trackIds) { ConfirmDeleteDialogFragment.showDialog(getSupportFragmentManager(), trackIds); @@ -55,10 +61,29 @@ public void onConfirmDeleteDone(Track.Id... trackIds) { Toast.makeText(this, getString(R.string.track_delete_not_recording), Toast.LENGTH_LONG).show(); } - TrackDeleteService.enqueue(this, new TrackDeleteService.TrackDeleteResultReceiver(new Handler(), this), trackIdList); + WorkManager workManager = WorkManager.getInstance(this); + WorkRequest deleteRequest = new OneTimeWorkRequest.Builder(TrackDeletionWorker.class) + .setInputData(new Data.Builder() + .putLongArray(TrackDeletionWorker.TRACKIDS_KEY, Arrays.stream(trackIds).mapToLong(Track.Id::id).toArray()) + .build()) + .build(); + + workManager + .getWorkInfoByIdLiveData(deleteRequest.getId()) + .observe(this, workInfo -> { + if (workInfo != null) { + if (workInfo.getState().isFinished()) { + onDeleteFinished(); + } + } + }); + + workManager.enqueue(deleteRequest); } protected abstract void onDeleteConfirmed(); + protected abstract void onDeleteFinished(); + protected abstract Track.Id getRecordingTrackId(); } diff --git a/src/main/java/de/dennisguse/opentracks/HelpActivity.java b/src/main/java/de/dennisguse/opentracks/HelpActivity.java index 3c9963df56..cebdc53c46 100644 --- a/src/main/java/de/dennisguse/opentracks/HelpActivity.java +++ b/src/main/java/de/dennisguse/opentracks/HelpActivity.java @@ -9,21 +9,18 @@ import de.dennisguse.opentracks.databinding.HelpBinding; import de.dennisguse.opentracks.ui.util.ViewUtils; -public class HelpActivity extends AbstractActivity { - - private HelpBinding helpBinding; +public class HelpActivity extends AbstractActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setSupportActionBar(helpBinding.bottomAppBarLayout.bottomAppBar); + setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar); ViewUtils.makeClickableLinks(findViewById(android.R.id.content)); } @NonNull @Override - protected View createRootView() { - helpBinding = HelpBinding.inflate(getLayoutInflater()); - return helpBinding.getRoot(); + protected HelpBinding createRootView() { + return HelpBinding.inflate(getLayoutInflater()); } } diff --git a/src/main/java/de/dennisguse/opentracks/ShowErrorActivity.java b/src/main/java/de/dennisguse/opentracks/ShowErrorActivity.java index f2f06403f2..91aefe13e9 100644 --- a/src/main/java/de/dennisguse/opentracks/ShowErrorActivity.java +++ b/src/main/java/de/dennisguse/opentracks/ShowErrorActivity.java @@ -15,12 +15,10 @@ import de.dennisguse.opentracks.databinding.ActivityShowErrorBinding; -public class ShowErrorActivity extends AbstractActivity { +public class ShowErrorActivity extends AbstractActivity { public static final String EXTRA_ERROR_TEXT = "error"; - private ActivityShowErrorBinding viewBinding; - @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -32,9 +30,8 @@ public void onCreate(final Bundle savedInstanceState) { @NonNull @Override - protected View createRootView() { - viewBinding = ActivityShowErrorBinding.inflate(getLayoutInflater()); - return viewBinding.getRoot(); + protected ActivityShowErrorBinding createRootView() { + return ActivityShowErrorBinding.inflate(getLayoutInflater()); } private String createErrorTitle() { diff --git a/src/main/java/de/dennisguse/opentracks/TrackEditActivity.java b/src/main/java/de/dennisguse/opentracks/TrackEditActivity.java index b2728eaeee..47405dd682 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackEditActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackEditActivity.java @@ -37,7 +37,7 @@ * * @author Leif Hendrik Wilden */ -public class TrackEditActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller { +public class TrackEditActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller { public static final String EXTRA_TRACK_ID = "track_id"; @@ -49,8 +49,6 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit private Track track; private ActivityType activityType; - private TrackEditBinding viewBinding; - @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); @@ -125,9 +123,8 @@ public void onSaveInstanceState(@NonNull Bundle outState) { @NonNull @Override - protected View createRootView() { - viewBinding = TrackEditBinding.inflate(getLayoutInflater()); - return viewBinding.getRoot(); + protected TrackEditBinding createRootView() { + return TrackEditBinding.inflate(getLayoutInflater()); } private void setActivityTypeIcon(ActivityType activityType) { diff --git a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java index f6ef7a8dd7..e3fa1f8fbb 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java @@ -72,7 +72,7 @@ * * @author Leif Hendrik Wilden */ -public class TrackListActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller { +public class TrackListActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller { private static final String TAG = TrackListActivity.class.getSimpleName(); @@ -80,8 +80,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co private TrackRecordingServiceConnection recordingStatusConnection; private TrackListAdapter adapter; - private TrackListBinding viewBinding; - // Preferences private UnitSystem unitSystem = UnitSystem.defaultUnitSystem(); @@ -166,6 +164,13 @@ protected void onCreate(Bundle savedInstanceState) { } }); + viewBinding.trackListSearchView.getEditText().setOnEditorActionListener((v, actionId, event) -> { + searchQuery = viewBinding.trackListSearchView.getEditText().getText().toString(); + viewBinding.trackListSearchView.hide(); + loadData(); + return true; + }); + LinearLayoutManager layoutManager = new LinearLayoutManager(this); adapter = new TrackListAdapter(this, viewBinding.trackList, recordingStatus, unitSystem); viewBinding.trackList.setLayoutManager(layoutManager); @@ -248,24 +253,14 @@ protected void onStop() { @Override protected void onDestroy() { super.onDestroy(); - viewBinding = null; recordingStatusConnection = null; adapter = null; } @NonNull @Override - protected View createRootView() { - viewBinding = TrackListBinding.inflate(getLayoutInflater()); - - viewBinding.trackListSearchView.getEditText().setOnEditorActionListener((v, actionId, event) -> { - searchQuery = viewBinding.trackListSearchView.getEditText().getText().toString(); - viewBinding.trackListSearchView.hide(); - loadData(); - return true; - }); - - return viewBinding.getRoot(); + protected TrackListBinding createRootView() { + return TrackListBinding.inflate(getLayoutInflater()); } @Override diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java index 0b2561fb5a..ad281c1780 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java @@ -56,7 +56,7 @@ * @author Rodrigo Damazio */ //TODO Should not use TrackRecordingServiceConnection; only used to determine if there is NO current recording, to enable resume functionality. -public class TrackRecordedActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackDataHubInterface { +public class TrackRecordedActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackDataHubInterface { private static final String TAG = TrackRecordedActivity.class.getSimpleName(); @@ -68,8 +68,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement private TrackDataHub trackDataHub; - private TrackRecordedBinding viewBinding; - private Track.Id trackId; private RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT; @@ -138,9 +136,8 @@ protected void onSaveInstanceState(@NonNull Bundle outState) { @NonNull @Override - protected View createRootView() { - viewBinding = TrackRecordedBinding.inflate(getLayoutInflater()); - return viewBinding.getRoot(); + protected TrackRecordedBinding createRootView() { + return TrackRecordedBinding.inflate(getLayoutInflater()); } @Override diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java index a257098db5..6354f52a2e 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java @@ -56,7 +56,7 @@ * @author Leif Hendrik Wilden * @author Rodrigo Damazio */ -public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackDataHubInterface { +public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackDataHubInterface { public static final String EXTRA_TRACK_ID = "track_id"; @@ -66,13 +66,11 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc private Snackbar snackbar; - // The following are setFrequency in onCreate + // The following are set in onCreate private ContentProviderUtils contentProviderUtils; private TrackRecordingServiceConnection trackRecordingServiceConnection; private TrackDataHub trackDataHub; - private TrackRecordingBinding viewBinding; - private Track.Id trackId; private RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT; @@ -245,15 +243,13 @@ protected void onStop() { @Override protected void onDestroy() { super.onDestroy(); - viewBinding = null; trackRecordingServiceConnection = null; } @NonNull @Override - protected View createRootView() { - viewBinding = TrackRecordingBinding.inflate(getLayoutInflater()); - return viewBinding.getRoot(); + protected TrackRecordingBinding createRootView() { + return TrackRecordingBinding.inflate(getLayoutInflater()); } @Override diff --git a/src/main/java/de/dennisguse/opentracks/TrackStoppedActivity.java b/src/main/java/de/dennisguse/opentracks/TrackStoppedActivity.java index fa4cd50cf3..49eff7d691 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackStoppedActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackStoppedActivity.java @@ -16,21 +16,19 @@ import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.databinding.TrackStoppedBinding; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; +import de.dennisguse.opentracks.io.file.exporter.ExportUtils; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment; -import de.dennisguse.opentracks.util.ExportUtils; import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.StringUtils; -public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller { +public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller { private static final String TAG = TrackStoppedActivity.class.getSimpleName(); public static final String EXTRA_TRACK_ID = "track_id"; - private TrackStoppedBinding viewBinding; - private Track.Id trackId; @Override @@ -113,9 +111,8 @@ private void storeTrackMetaData(ContentProviderUtils contentProviderUtils, Track @NonNull @Override - protected View createRootView() { - viewBinding = TrackStoppedBinding.inflate(getLayoutInflater()); - return viewBinding.getRoot(); + protected TrackStoppedBinding createRootView() { + return TrackStoppedBinding.inflate(getLayoutInflater()); } private void setActivityTypeIcon(ActivityType activityType) { diff --git a/src/main/java/de/dennisguse/opentracks/introduction/IntroductionActivity.java b/src/main/java/de/dennisguse/opentracks/introduction/IntroductionActivity.java index 36ba0d4625..9a52f3af39 100644 --- a/src/main/java/de/dennisguse/opentracks/introduction/IntroductionActivity.java +++ b/src/main/java/de/dennisguse/opentracks/introduction/IntroductionActivity.java @@ -18,9 +18,7 @@ import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.util.IntentUtils; -public class IntroductionActivity extends AbstractActivity { - - private IntroductionBinding viewBinding; +public class IntroductionActivity extends AbstractActivity { @Override protected void onCreate(Bundle savedInstanceState) { @@ -49,15 +47,8 @@ protected void onCreate(Bundle savedInstanceState) { @NonNull @Override - protected View createRootView() { - viewBinding = IntroductionBinding.inflate(getLayoutInflater()); - return viewBinding.getRoot(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - viewBinding = null; + protected IntroductionBinding createRootView() { + return IntroductionBinding.inflate(getLayoutInflater()); } private static class CustomFragmentPagerAdapter extends FragmentStateAdapter { diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportActivity.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportActivity.java index f58efd95be..c4ef3f6559 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportActivity.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportActivity.java @@ -24,15 +24,20 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import androidx.documentfile.provider.DocumentFile; +import androidx.work.Data; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkInfo; +import androidx.work.WorkManager; +import androidx.work.WorkRequest; import java.util.ArrayList; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.stream.Collectors; +import de.dennisguse.opentracks.AbstractActivity; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.models.Track; @@ -41,7 +46,6 @@ import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFilenameGenerator; import de.dennisguse.opentracks.settings.PreferencesUtils; -import de.dennisguse.opentracks.util.ExportUtils; import de.dennisguse.opentracks.util.FileUtils; /** @@ -56,7 +60,7 @@ * So, for this check actually a different file name might be used than in the ExportService. * * Saved state as an object instead of individual values. */ -public class ExportActivity extends AppCompatActivity implements ExportService.ExportServiceResultReceiver.Receiver { +public class ExportActivity extends AbstractActivity { private static final String TAG = ExportActivity.class.getSimpleName(); @@ -84,8 +88,6 @@ private enum ConflictResolutionStrategy { private TrackFileFormat trackFileFormat; private Uri directoryUri; - private ExportService.ExportServiceResultReceiver resultReceiver; - private List directoryFiles; private int trackExportSuccessCount; @@ -94,8 +96,6 @@ private enum ConflictResolutionStrategy { private int trackExportSkippedCount; private int trackExportTotalCount; - private ExportActivityBinding viewBinding; - private ArrayList trackErrors = new ArrayList<>(); private ConflictResolutionStrategy autoConflict; @@ -147,8 +147,6 @@ public void run() { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - viewBinding = ExportActivityBinding.inflate(getLayoutInflater()); - setContentView(viewBinding.getRoot()); setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar); @@ -161,8 +159,6 @@ public void onCreate(Bundle savedInstanceState) { DocumentFile documentFile = DocumentFile.fromTreeUri(this, directoryUri); String directoryDisplayName = FileUtils.getPath(documentFile); - resultReceiver = new ExportService.ExportServiceResultReceiver(new Handler(), this); - if (savedInstanceState == null) { autoConflict = ConflictResolutionStrategy.CONFLICT_NONE; setProgress(); @@ -190,6 +186,12 @@ public void onCreate(Bundle savedInstanceState) { viewBinding.exportActivityToolbar.setTitle(getString(R.string.export_progress_message, directoryDisplayName)); } + @NonNull + @Override + protected ExportActivityBinding createRootView() { + return ExportActivityBinding.inflate(getLayoutInflater()); + } + @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); @@ -231,19 +233,45 @@ private void export(ExportTask exportTask, ConflictResolutionStrategy conflictRe if (fileExists && conflictResolution == ConflictResolutionStrategy.CONFLICT_NONE) { conflict(exportTask); - } else if (fileExists && conflictResolution == ConflictResolutionStrategy.CONFLICT_SKIP) { + return; + } + if (fileExists && conflictResolution == ConflictResolutionStrategy.CONFLICT_SKIP) { trackExportSkippedCount++; nextExport(exportTask); - } else { - ExportService.enqueue(this, resultReceiver, exportTask, directoryUri); + return; } + + WorkManager workManager = WorkManager.getInstance(this); + WorkRequest exportRequest = new OneTimeWorkRequest.Builder(ExportWorker.class) + .setInputData(new Data.Builder() + .putLongArray(ExportWorker.TRACKIDS_KEY, exportTask.getTrackIds().stream().mapToLong(Track.Id::id).toArray()) + .putString(ExportWorker.DIRECTORY_URI_KEY, directoryUri.toString()) + .putString(ExportWorker.TRACKFILEFORMAT_KEY, exportTask.getTrackFileFormat().toString()) + .putString(ExportWorker.FILENAME_KEY, exportTask.getFilename()) + .build()) + .build(); + + workManager + .getWorkInfoByIdLiveData(exportRequest.getId()) + .observe(this, workInfo -> { + if (workInfo != null) { + WorkInfo.State state = workInfo.getState(); + switch (state) { + case SUCCEEDED -> onExportSuccess(exportTask); + case FAILED -> + onExportError(exportTask, workInfo.getOutputData().getString(ExportWorker.RESULT_EXPORT_ERROR_MESSAGE_KEY)); + } + } + }); + + workManager.enqueue(exportRequest); } private void export(ExportTask exportTask) { export(exportTask, autoConflict); } - @Deprecated //TODO Check should be done in ExportService + @Deprecated //TODO Check should be done in ExportWorker private boolean exportFileExists(ExportTask exportTask) { String filename; if (exportTask.isMultiExport()) { @@ -314,7 +342,6 @@ private void onExportEnded() { } } - @Override public void onExportSuccess(ExportTask exportTask) { if (exportFileExists(exportTask)) { trackExportOverwrittenCount++; @@ -325,7 +352,6 @@ public void onExportSuccess(ExportTask exportTask) { nextExport(exportTask); } - @Override public void onExportError(ExportTask exportTask, String errorMessage) { trackExportErrorCount++; String name; diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportService.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportService.java deleted file mode 100644 index 871a3cb53a..0000000000 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportService.java +++ /dev/null @@ -1,103 +0,0 @@ -package de.dennisguse.opentracks.io.file.exporter; - -import android.app.job.JobService; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.ResultReceiver; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.core.app.JobIntentService; -import androidx.documentfile.provider.DocumentFile; - -import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.util.ExportUtils; - -public class ExportService extends JobIntentService { - - private static final String TAG = ExportService.class.getSimpleName(); - - private static final int JOB_ID = 1; - - private static final String EXTRA_RECEIVER = "extra_receiver"; - private static final String EXTRA_EXPORT_TASK = "export_task"; - private static final String EXTRA_DIRECTORY_URI = "extra_directory_uri"; - - public static void enqueue(Context context, ExportServiceResultReceiver receiver, ExportTask exportTask, Uri directoryUri) { - Intent intent = new Intent(context, JobService.class); - intent.putExtra(EXTRA_RECEIVER, receiver); - intent.putExtra(EXTRA_EXPORT_TASK, exportTask); - intent.putExtra(EXTRA_DIRECTORY_URI, directoryUri); - enqueueWork(context, ExportService.class, JOB_ID, intent); - } - - @Override - protected void onHandleWork(@NonNull Intent intent) { - // Get all data. - ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_RECEIVER); - ExportTask exportTask = intent.getParcelableExtra(EXTRA_EXPORT_TASK); - Uri directoryUri = intent.getParcelableExtra(EXTRA_DIRECTORY_URI); - - // Prepare resultCode and bundle to send to the receiver. - Bundle bundle = new Bundle(); - bundle.putParcelable(ExportServiceResultReceiver.RESULT_EXTRA_EXPORT_TASK, exportTask); - - // Build directory file. - DocumentFile directoryFile = DocumentFile.fromTreeUri(this, directoryUri); - if (directoryFile == null || !directoryFile.canWrite()) { - bundle.putString(ExportServiceResultReceiver.EXTRA_EXPORT_ERROR_MESSAGE, getString(R.string.export_cannot_write_to_dir) + ": " + directoryFile); - resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_ERROR, bundle); - return; - } - - // Export and send result - try { - ExportUtils.exportTrack(this, directoryFile, exportTask); - resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_SUCCESS, bundle); - } catch (Exception e) { - Log.e(TAG, "Export failed: " + e); - e.printStackTrace(); //TODO Remove - - bundle.putString(ExportServiceResultReceiver.EXTRA_EXPORT_ERROR_MESSAGE, e.getMessage()); - resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_ERROR, bundle); - } - } - - public static class ExportServiceResultReceiver extends ResultReceiver { - - public static final int RESULT_CODE_SUCCESS = 1; - public static final int RESULT_CODE_ERROR = 0; - - public static final String RESULT_EXTRA_EXPORT_TASK = "result_extra_export_task"; - - public static final String EXTRA_EXPORT_ERROR_MESSAGE = "extra_export_error_message"; - - private final Receiver receiver; - - public ExportServiceResultReceiver(Handler handler, @NonNull Receiver receiver) { - super(handler); - this.receiver = receiver; - } - - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - ExportTask exportTask = resultData.getParcelable(RESULT_EXTRA_EXPORT_TASK); - switch (resultCode) { - case RESULT_CODE_SUCCESS -> receiver.onExportSuccess(exportTask); - case RESULT_CODE_ERROR -> receiver.onExportError(exportTask, resultData.getString(EXTRA_EXPORT_ERROR_MESSAGE)); - default -> throw new RuntimeException("Unknown resultCode."); - } - } - - public interface Receiver { - default void onExportSuccess(ExportTask exportTask) { - } - - default void onExportError(ExportTask exportTask, String errorMessage) { - } - } - } -} diff --git a/src/main/java/de/dennisguse/opentracks/util/ExportUtils.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportUtils.java similarity index 60% rename from src/main/java/de/dennisguse/opentracks/util/ExportUtils.java rename to src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportUtils.java index 552ee12d68..28174b3513 100644 --- a/src/main/java/de/dennisguse/opentracks/util/ExportUtils.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportUtils.java @@ -1,15 +1,21 @@ -package de.dennisguse.opentracks.util; +package de.dennisguse.opentracks.io.file.exporter; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; -import android.os.Handler; import android.provider.DocumentsContract; import android.util.Log; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; import androidx.documentfile.provider.DocumentFile; +import androidx.work.Data; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkInfo; +import androidx.work.WorkManager; +import androidx.work.WorkRequest; import java.io.FileNotFoundException; import java.io.IOException; @@ -22,36 +28,79 @@ import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFilenameGenerator; -import de.dennisguse.opentracks.io.file.exporter.ExportService; -import de.dennisguse.opentracks.io.file.exporter.ExportService.ExportServiceResultReceiver; -import de.dennisguse.opentracks.io.file.exporter.ExportTask; -import de.dennisguse.opentracks.io.file.exporter.TrackExporter; import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.SettingsActivity; +import de.dennisguse.opentracks.util.IntentUtils; public class ExportUtils { private static final String TAG = ExportUtils.class.getSimpleName(); - public static void postWorkoutExport(Context context, Track.Id trackId) { + public static void postWorkoutExport(AppCompatActivity context, Track.Id trackId) { if (PreferencesUtils.shouldInstantExportAfterWorkout()) { TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(); DocumentFile directory = IntentUtils.toDocumentFile(context, PreferencesUtils.getDefaultExportDirectoryUri()); - ExportServiceResultReceiver resultReceiver = new ExportServiceResultReceiver(new Handler(), new ExportServiceResultReceiver.Receiver() { - @Override - public void onExportError(ExportTask unused, String errorMessage) { - Intent intent = new Intent(context, SettingsActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(SettingsActivity.EXTRAS_EXPORT_ERROR_MESSAGE, errorMessage); - context.startActivity(intent); - } - }); + WorkManager workManager = WorkManager.getInstance(context); + WorkRequest exportRequest = new OneTimeWorkRequest.Builder(ExportWorker.class) + .setInputData(new Data.Builder() + .putLongArray(ExportWorker.TRACKIDS_KEY, new long[]{trackId.id()}) + .putString(ExportWorker.DIRECTORY_URI_KEY, directory.toString()) + .putString(ExportWorker.TRACKFILEFORMAT_KEY, trackFileFormat.toString()) + .putString(ExportWorker.FILENAME_KEY, null) + .build()) + .build(); + + workManager + .getWorkInfoByIdLiveData(exportRequest.getId()) + .observe(context, workInfo -> { + if (workInfo != null) { + if (workInfo.getState() == WorkInfo.State.FAILED) { + Intent intent = new Intent(context, SettingsActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(SettingsActivity.EXTRAS_EXPORT_ERROR_MESSAGE, workInfo.getProgress().getString(ExportWorker.RESULT_EXPORT_ERROR_MESSAGE_KEY)); + context.startActivity(intent); + } + } + }); + + workManager.enqueue(exportRequest); + } + } + + public static void exportTrack(Context context, DocumentFile directory, @Nullable String filenameForMultiple, TrackFileFormat trackFileFormat, List trackIds) { + + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); + List tracks = trackIds.stream().map(contentProviderUtils::getTrack).toList(); + + Uri exportDocumentFileUri; + if (tracks.size() == 1) { + exportDocumentFileUri = getExportDocumentFileUri(context, tracks.get(0), trackFileFormat, directory); + } else { + String filename = TrackFilenameGenerator.format(filenameForMultiple, trackFileFormat); + exportDocumentFileUri = getExportDocumentFileUri(context, filename, trackFileFormat, directory); + } + + if (exportDocumentFileUri == null) { + throw new RuntimeException("Couldn't create document file for export"); + } - ExportService.enqueue(context, resultReceiver, new ExportTask(null, trackFileFormat, List.of(trackId)), directory.getUri()); + TrackExporter trackExporter = trackFileFormat.createTrackExporter(context, contentProviderUtils); + try (OutputStream outputStream = context.getContentResolver().openOutputStream(exportDocumentFileUri, "wt")) { + if (!trackExporter.writeTrack(tracks, outputStream)) { + if (!DocumentFile.fromSingleUri(context, exportDocumentFileUri).delete()) { + throw new RuntimeException("Unable to delete exportDocumentFile"); + } + throw new RuntimeException("Unable to export track"); + } + } catch (FileNotFoundException e) { + throw new RuntimeException("Unable to open exportDocumentFile " + exportDocumentFileUri, e); + } catch (IOException e) { + throw new RuntimeException("Unable to close exportDocumentFile output stream", e); } } + @Deprecated public static void exportTrack(Context context, DocumentFile directory, ExportTask exportTask) { ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); List tracks = exportTask.getTrackIds().stream().map(contentProviderUtils::getTrack).collect(Collectors.toList()); diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportWorker.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportWorker.java new file mode 100644 index 0000000000..ef08211836 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportWorker.java @@ -0,0 +1,70 @@ +package de.dennisguse.opentracks.io.file.exporter; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.documentfile.provider.DocumentFile; +import androidx.work.Data; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import java.util.Arrays; +import java.util.List; + +import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.data.models.Track; +import de.dennisguse.opentracks.io.file.TrackFileFormat; + +public class ExportWorker extends Worker { + + private static final String TAG = ExportWorker.class.getSimpleName(); + + static final String TRACKIDS_KEY = "TRACKIDS_KEY"; + static final String DIRECTORY_URI_KEY = "DIRECTORY_URI_KEY"; + static final String TRACKFILEFORMAT_KEY = "TRACKFILEFORMAT_KEY"; + static final String FILENAME_KEY = "FILENAME_KEY"; //optional; only used for multiple tracks into one file + + static final String RESULT_EXPORT_ERROR_MESSAGE_KEY = "EXPORT_ERROR_MESSAGE"; + + private final List trackIds; + + private final DocumentFile directoryFile; + + private final TrackFileFormat trackFileFormat; + + private final String filename; + + public ExportWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + + trackIds = Arrays.stream(getInputData().getLongArray(TRACKIDS_KEY)) + .mapToObj(Track.Id::new) + .toList(); + + directoryFile = DocumentFile.fromTreeUri(context, Uri.parse(getInputData().getString(DIRECTORY_URI_KEY))); + + trackFileFormat = TrackFileFormat.valueOf(getInputData().getString(TRACKFILEFORMAT_KEY)); + + filename = getInputData().getString(FILENAME_KEY); + } + + @NonNull + @Override + public Result doWork() { + if (directoryFile == null || !directoryFile.canWrite()) { + return Result.failure(new Data.Builder().putString(RESULT_EXPORT_ERROR_MESSAGE_KEY, getApplicationContext().getString(R.string.export_cannot_write_to_dir) + ": " + directoryFile).build()); + } + + try { + //TODO move method to ExportWorker? + ExportUtils.exportTrack(getApplicationContext(), directoryFile, filename, trackFileFormat, trackIds); + return Result.success(); + } catch (Exception e) { + Log.e(TAG, "Export failed: " + e); + + return Result.failure(new Data.Builder().putString(RESULT_EXPORT_ERROR_MESSAGE_KEY, e.getMessage()).build()); + } + } +} diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportActivity.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportActivity.java index b97d7a8dc3..a4560648d2 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportActivity.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportActivity.java @@ -25,17 +25,24 @@ import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.documentfile.provider.DocumentFile; -import androidx.fragment.app.FragmentActivity; -import androidx.lifecycle.ViewModelProvider; +import androidx.work.Data; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkInfo; +import androidx.work.WorkManager; +import androidx.work.WorkRequest; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import de.dennisguse.opentracks.AbstractActivity; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.TrackRecordedActivity; +import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.databinding.ImportActivityBinding; import de.dennisguse.opentracks.io.file.ErrorListDialog; +import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.IntentUtils; /** @@ -43,7 +50,7 @@ * * @author Rodrigo Damazio */ -public class ImportActivity extends FragmentActivity { +public class ImportActivity extends AbstractActivity { private static final String TAG = ImportActivity.class.getSimpleName(); @@ -52,23 +59,21 @@ public class ImportActivity extends FragmentActivity { private static final String BUNDLE_DOCUMENT_URIS = "document_uris"; private static final String BUNDLE_IS_DIRECTORY = "is_directory"; - private ImportActivityBinding viewBinding; - private ArrayList documentUris = new ArrayList<>(); + private ArrayList filesToImport = new ArrayList<>(); private boolean isDirectory; - private ImportViewModel viewModel; - private ImportViewModel.Summary summary; + private Summary summary; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - viewBinding = ImportActivityBinding.inflate(getLayoutInflater()); - setContentView(viewBinding.getRoot()); final Intent intent = getIntent(); final ClipData intentClipData = intent.getClipData(); + summary = new Summary(); + if (savedInstanceState == null) { if (intent.getData() != null) { documentUris.add(intent.getData()); @@ -99,18 +104,20 @@ public void onCreate(Bundle savedInstanceState) { documentFiles = documentUris.stream().map(it -> DocumentFile.fromSingleUri(this, it)).collect(Collectors.toList()); } - initViews(); - - viewModel = new ViewModelProvider(this).get(ImportViewModel.class); - viewModel.getImportData(documentFiles).observe(this, data -> { - summary = data; - setProgress(); - }); + //TODO init UI + loadData(documentFiles); + importNextFile(); //Works for a directory, but we might have received multiple files via SEND_MULTIPLE. viewBinding.importActivityToolbar.setTitle(getString(R.string.import_progress_message, documentFiles.get(0).getName())); } + @NonNull + @Override + protected ImportActivityBinding createRootView() { + return ImportActivityBinding.inflate(getLayoutInflater()); + } + @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); @@ -118,12 +125,60 @@ protected void onSaveInstanceState(@NonNull Bundle outState) { outState.putBoolean(BUNDLE_IS_DIRECTORY, isDirectory); } - private void initViews() { - viewBinding.importProgressDone.setText("0"); - viewBinding.importProgressTotal.setText("0"); - viewBinding.importProgressSummaryOk.setText("0"); - viewBinding.importProgressSummaryExists.setText("0"); - viewBinding.importProgressSummaryErrors.setText("0"); + private void loadData(List documentFiles) { + filesToImport.addAll(FileUtils.getFiles(documentFiles)); + summary.totalCount = filesToImport.size(); + } + + private void importNextFile() { + if (filesToImport.isEmpty()) { + onImportEnded(); + return; + } + + final DocumentFile documentFile = filesToImport.get(0); + + WorkManager workManager = WorkManager.getInstance(getApplication()); + WorkRequest importRequest = new OneTimeWorkRequest.Builder(ImportWorker.class) + .setInputData(new Data.Builder() + .putString(ImportWorker.URI_KEY, documentFile.getUri().toString()) + .build()) + .build(); + + workManager + .getWorkInfoByIdLiveData(importRequest.getId()) + .observe(this, workInfo -> { + if (workInfo != null) { + WorkInfo.State state = workInfo.getState(); + if (state.isFinished()) { + switch (state) { + case SUCCEEDED -> { + summary.importedTrackIds.addAll( + Arrays.stream(workInfo.getOutputData().getLongArray(ImportWorker.RESULT_SUCCESS_LIST_TRACKIDS_KEY)) + .mapToObj(Track.Id::new) + .toList()); + + summary.successCount++; + } + case FAILED -> { + if (workInfo.getOutputData().getBoolean(ImportWorker.RESULT_FAILURE_IS_DUPLICATE, false)) { + summary.existsCount++; + } else { + // Some error happened + String errorMessage = workInfo.getOutputData().getString(ImportWorker.RESULT_MESSAGE_KEY); + summary.fileErrors.add(getApplication().getString(R.string.import_error_info, documentFile.getName(), errorMessage)); + } + } + } + + setProgress(); + importNextFile(); + } + } + }); + + workManager.enqueue(importRequest); + filesToImport.remove(0); } private int getTotalDone() { @@ -193,4 +248,36 @@ private void toggleUIEndOk() { viewBinding.importProgressLeftButton.setVisibility(View.GONE); } } + + static class Summary { + private int totalCount; + private int successCount; + private int existsCount; + private final ArrayList importedTrackIds = new ArrayList<>(); + private final ArrayList fileErrors = new ArrayList<>(); + + public int getTotalCount() { + return totalCount; + } + + public int getSuccessCount() { + return successCount; + } + + public int getExistsCount() { + return existsCount; + } + + public int getErrorCount() { + return fileErrors.size(); + } + + public ArrayList getImportedTrackIds() { + return importedTrackIds; + } + + public ArrayList getFileErrors() { + return fileErrors; + } + } } \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportService.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportService.java deleted file mode 100644 index 5c9dab50c3..0000000000 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportService.java +++ /dev/null @@ -1,97 +0,0 @@ -package de.dennisguse.opentracks.io.file.importer; - -import android.app.job.JobService; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.ResultReceiver; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.core.app.JobIntentService; -import androidx.documentfile.provider.DocumentFile; - -import java.io.IOException; -import java.util.ArrayList; - -import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.data.ContentProviderUtils; -import de.dennisguse.opentracks.data.models.Distance; -import de.dennisguse.opentracks.data.models.Track; -import de.dennisguse.opentracks.io.file.TrackFileFormat; -import de.dennisguse.opentracks.settings.PreferencesUtils; -import de.dennisguse.opentracks.util.FileUtils; - -public class ImportService extends JobIntentService { - - private static final String TAG = ImportService.class.getSimpleName(); - - private static final int JOB_ID = 2; - - private static final String EXTRA_RECEIVER = "extra_receiver"; - private static final String EXTRA_URI = "extra_uri"; - - private ResultReceiver resultReceiver; - - public static void enqueue(Context context, ImportServiceResultReceiver receiver, Uri uri) { - Intent intent = new Intent(context, JobService.class); - intent.putExtra(EXTRA_RECEIVER, receiver); - intent.putExtra(EXTRA_URI, uri); - enqueueWork(context, ImportService.class, JOB_ID, intent); - } - - @Override - protected void onHandleWork(@NonNull Intent intent) { - resultReceiver = intent.getParcelableExtra(EXTRA_RECEIVER); - Uri uri = intent.getParcelableExtra(EXTRA_URI); - importFile(DocumentFile.fromSingleUri(this, uri)); - } - - private void importFile(DocumentFile file) { - ArrayList trackIds = new ArrayList<>(); - - String fileExtension = FileUtils.getExtension(file); - try { - Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(); - boolean preventReimport = PreferencesUtils.getPreventReimportTracks(); - - TrackImporter trackImporter = new TrackImporter(this, new ContentProviderUtils(this), maxRecordingDistance, preventReimport); - - if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) { - trackIds.addAll(new XMLImporter(new GPXTrackImporter(this, trackImporter)).importFile(this, file.getUri())); - } else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) { - trackIds.addAll(new XMLImporter(new KMLTrackImporter(this, trackImporter)).importFile(this, file.getUri())); - } else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) { - trackIds.addAll(new KMZTrackImporter(this, trackImporter).importFile(file.getUri())); - } else { - Log.d(TAG, "Unsupported file format."); - sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_unsupported_format)); - return; - } - - if (!trackIds.isEmpty()) { - sendResult(ImportServiceResultReceiver.RESULT_CODE_IMPORTED, trackIds, file, getString(R.string.import_file_imported, file.getName())); - } else { - sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, trackIds, file, getString(R.string.import_unable_to_import_file, file.getName())); - } - } catch (IOException e) { - Log.d(TAG, "Unable to import file", e); - sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_unable_to_import_file, e.getMessage())); - } catch (ImportParserException e) { - Log.d(TAG, "Parser error: " + e.getMessage(), e); - sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_parser_error, e.getMessage())); - } catch (ImportAlreadyExistsException e) { - Log.d(TAG, "Track already exists: " + e.getMessage(), e); - sendResult(ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS, null, file, e.getMessage()); - } - } - - private void sendResult(int resultCode, ArrayList trackId, DocumentFile file, String message) { - Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(ImportServiceResultReceiver.RESULT_EXTRA_LIST_TRACK_ID, trackId); - bundle.putString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME, file.getName()); - bundle.putString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE, message); - resultReceiver.send(resultCode, bundle); - } -} diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportServiceResultReceiver.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportServiceResultReceiver.java deleted file mode 100644 index e316314173..0000000000 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportServiceResultReceiver.java +++ /dev/null @@ -1,38 +0,0 @@ -package de.dennisguse.opentracks.io.file.importer; - -import android.os.Bundle; -import android.os.Handler; -import android.os.ResultReceiver; - -import androidx.annotation.NonNull; - -/** - * Create a new ResultReceive to receive results. - * Your {@link #onReceiveResult} method will be called from the thread running handler if given, or from an arbitrary thread if null. - */ -public class ImportServiceResultReceiver extends ResultReceiver { - - public static final int RESULT_CODE_ERROR = 0; - public static final int RESULT_CODE_IMPORTED = 1; - public static final int RESULT_CODE_ALREADY_EXISTS = 2; - - public static final String RESULT_EXTRA_LIST_TRACK_ID = "result_track_id"; - public static final String RESULT_EXTRA_FILENAME = "result_extra_filename"; - public static final String RESULT_EXTRA_MESSAGE = "result_extra_message"; - - private final Receiver receiver; - - public ImportServiceResultReceiver(Handler handler, @NonNull Receiver receiver) { - super(handler); - this.receiver = receiver; - } - - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - receiver.onReceiveResult(resultCode, resultData); - } - - public interface Receiver { - void onReceiveResult(int resultCode, Bundle resultData); - } -} diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportViewModel.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportViewModel.java deleted file mode 100644 index 8d1baa3928..0000000000 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportViewModel.java +++ /dev/null @@ -1,130 +0,0 @@ -package de.dennisguse.opentracks.io.file.importer; - -import android.app.Application; -import android.os.Bundle; -import android.os.Handler; - -import androidx.annotation.NonNull; -import androidx.documentfile.provider.DocumentFile; -import androidx.lifecycle.AndroidViewModel; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.data.models.Track; -import de.dennisguse.opentracks.util.FileUtils; - -public class ImportViewModel extends AndroidViewModel implements ImportServiceResultReceiver.Receiver { - - private static final String TAG = ImportViewModel.class.getSimpleName(); - - private MutableLiveData importData; - private final ImportServiceResultReceiver resultReceiver; - private final Summary summary; - private boolean cancel = false; - private final List filesToImport = new ArrayList<>(); - - public ImportViewModel(@NonNull Application application) { - super(application); - resultReceiver = new ImportServiceResultReceiver(new Handler(), this); - summary = new Summary(); - } - - LiveData getImportData(List documentFiles) { - if (importData == null) { - importData = new MutableLiveData<>(); - loadData(documentFiles); - } - return importData; - } - - void cancel() { - cancel = true; - } - - private void loadData(List documentFiles) { - List> nestedFileList = documentFiles.stream() - .map(FileUtils::getFiles) - .collect(Collectors.toList()); - - List fileList = new ArrayList<>(); - nestedFileList.forEach(fileList::addAll); - - summary.totalCount = fileList.size(); - filesToImport.addAll(fileList); - importNextFile(); - } - - private void importNextFile() { - if (cancel || filesToImport.isEmpty()) { - return; - } - ImportService.enqueue(getApplication(), resultReceiver, filesToImport.get(0).getUri()); - filesToImport.remove(0); - } - - @Override - public void onReceiveResult(int resultCode, Bundle resultData) { - if (resultData == null) { - throw new RuntimeException(TAG + ": onReceiveResult resultData NULL"); - } - - ArrayList trackIds = resultData.getParcelableArrayList(ImportServiceResultReceiver.RESULT_EXTRA_LIST_TRACK_ID); - String fileName = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME); - String message = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE); - - switch (resultCode) { - case ImportServiceResultReceiver.RESULT_CODE_ERROR -> { - summary.errorCount++; - summary.fileErrors.add(getApplication().getString(R.string.import_error_info, fileName, message)); - } - case ImportServiceResultReceiver.RESULT_CODE_IMPORTED -> { - summary.importedTrackIds.addAll(trackIds); - summary.successCount++; - } - case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS -> summary.existsCount++; - default -> - throw new RuntimeException(TAG + ": import service result code invalid: " + resultCode); - } - - importData.postValue(summary); - importNextFile(); - } - - static class Summary { - private int totalCount; - private int successCount; - private int existsCount; - private int errorCount; - private final ArrayList importedTrackIds = new ArrayList<>(); - private final ArrayList fileErrors = new ArrayList<>(); - - public int getTotalCount() { - return totalCount; - } - - public int getSuccessCount() { - return successCount; - } - - public int getExistsCount() { - return existsCount; - } - - public int getErrorCount() { - return errorCount; - } - - public ArrayList getImportedTrackIds() { - return importedTrackIds; - } - - public ArrayList getFileErrors() { - return fileErrors; - } - } -} diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportWorker.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportWorker.java new file mode 100644 index 0000000000..9ee0fcfbc5 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportWorker.java @@ -0,0 +1,100 @@ +package de.dennisguse.opentracks.io.file.importer; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.work.Data; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import java.io.IOException; +import java.util.ArrayList; + +import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.data.ContentProviderUtils; +import de.dennisguse.opentracks.data.models.Distance; +import de.dennisguse.opentracks.data.models.Track; +import de.dennisguse.opentracks.io.file.TrackFileFormat; +import de.dennisguse.opentracks.settings.PreferencesUtils; +import de.dennisguse.opentracks.util.FileUtils; + +public class ImportWorker extends Worker { + + private static final String TAG = ImportWorker.class.getSimpleName(); + + static final String URI_KEY = "DIRECTORY_URI_KEY"; + + static final String RESULT_SUCCESS_LIST_TRACKIDS_KEY = "RESULT_TRACK_IDS"; + static final String RESULT_URI_KEY = "RESULT_URI"; + static final String RESULT_MESSAGE_KEY = "RESULT_MESSAGE"; + static final String RESULT_FAILURE_IS_DUPLICATE = "RESULT_FAILURE_IS_DUPLICATE"; + + private final Uri uri; + + public ImportWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + + uri = Uri.parse(getInputData().getString(URI_KEY)); + } + + @NonNull + @Override + public Result doWork() { + ArrayList trackIds = new ArrayList<>(); + Context context = getApplicationContext(); + + Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(); + boolean preventReimport = PreferencesUtils.getPreventReimportTracks(); + TrackImporter trackImporter = new TrackImporter(context, new ContentProviderUtils(context), maxRecordingDistance, preventReimport); + + Data.Builder data = new Data.Builder() + .putString(RESULT_URI_KEY, uri.toString()); + + + String fileExtension = FileUtils.getExtension(uri); + try { + if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) { + trackIds.addAll(new XMLImporter(new GPXTrackImporter(getApplicationContext(), trackImporter)).importFile(context, uri)); + } else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) { + trackIds.addAll(new XMLImporter(new KMLTrackImporter(getApplicationContext(), trackImporter)).importFile(context, uri)); + } else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) { + trackIds.addAll(new KMZTrackImporter(context, trackImporter).importFile(uri)); + } else { + Log.d(TAG, "Unsupported file format."); + return Result.failure(data + .putString(RESULT_MESSAGE_KEY, context.getString(R.string.import_unsupported_format)) + .build()); + } + + if (!trackIds.isEmpty()) { + return Result.success(data + .putString(RESULT_MESSAGE_KEY, context.getString(R.string.import_file_imported, uri.toString())) //TODO unused? + .putLongArray(RESULT_SUCCESS_LIST_TRACKIDS_KEY, trackIds.stream().mapToLong(Track.Id::id).toArray()) + .build()); + } + + return Result.failure(data + .putString(RESULT_MESSAGE_KEY, context.getString(R.string.import_unable_to_import_file, uri.toString())) + .build()); + + } catch (IOException e) { + Log.d(TAG, "Unable to import file", e); + return Result.failure(data + .putString(RESULT_MESSAGE_KEY, context.getString(R.string.import_unable_to_import_file, e.getMessage())) + .build()); + } catch (ImportParserException e) { + Log.d(TAG, "Parser error: " + e.getMessage(), e); + return Result.failure(data + .putString(RESULT_MESSAGE_KEY, context.getString(R.string.import_parser_error, e.getMessage())) + .build()); + } catch (ImportAlreadyExistsException e) { + Log.d(TAG, "Track already exists: " + e.getMessage(), e); + return Result.failure(data + .putString(RESULT_MESSAGE_KEY, e.getMessage()) //TODO This is not used + .putBoolean(RESULT_FAILURE_IS_DUPLICATE, true) + .build()); + } + } +} diff --git a/src/main/java/de/dennisguse/opentracks/publicapi/StopRecording.java b/src/main/java/de/dennisguse/opentracks/publicapi/StopRecording.java index 7e0910fe42..3432576adb 100644 --- a/src/main/java/de/dennisguse/opentracks/publicapi/StopRecording.java +++ b/src/main/java/de/dennisguse/opentracks/publicapi/StopRecording.java @@ -1,9 +1,9 @@ package de.dennisguse.opentracks.publicapi; import de.dennisguse.opentracks.data.models.Track; +import de.dennisguse.opentracks.io.file.exporter.ExportUtils; import de.dennisguse.opentracks.services.RecordingData; import de.dennisguse.opentracks.services.TrackRecordingService; -import de.dennisguse.opentracks.util.ExportUtils; public class StopRecording extends AbstractAPIActivity { diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackDeleteService.java b/src/main/java/de/dennisguse/opentracks/services/TrackDeleteService.java deleted file mode 100644 index 3136ae731c..0000000000 --- a/src/main/java/de/dennisguse/opentracks/services/TrackDeleteService.java +++ /dev/null @@ -1,67 +0,0 @@ -package de.dennisguse.opentracks.services; - -import android.app.job.JobService; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.os.ResultReceiver; - -import androidx.annotation.NonNull; -import androidx.core.app.JobIntentService; - -import java.util.ArrayList; - -import de.dennisguse.opentracks.data.ContentProviderUtils; -import de.dennisguse.opentracks.data.models.Track; - -public class TrackDeleteService extends JobIntentService { - - private static final int JOB_ID = 3; - - private static final String EXTRA_RECEIVER = "extra_receiver"; - - private static final String EXTRA_TRACK_IDS = "extra_track_ids"; - - public static void enqueue(Context context, TrackDeleteResultReceiver receiver, ArrayList toBeDeleted) { - Intent intent = new Intent(context, JobService.class); - intent.putExtra(EXTRA_RECEIVER, receiver); - intent.putParcelableArrayListExtra(EXTRA_TRACK_IDS, toBeDeleted); - enqueueWork(context, TrackDeleteService.class, JOB_ID, intent); - } - - @Override - protected void onHandleWork(@NonNull Intent intent) { - ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_RECEIVER); - ArrayList trackIds = intent.getParcelableArrayListExtra(EXTRA_TRACK_IDS); - - ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this); - contentProviderUtils.deleteTracks(this, trackIds); - - resultReceiver.send(TrackDeleteResultReceiver.RESULT_CODE_SUCCESS, new Bundle()); - } - - public static class TrackDeleteResultReceiver extends ResultReceiver { - - public static final int RESULT_CODE_SUCCESS = 1; - - private final Receiver receiver; - - public TrackDeleteResultReceiver(Handler handler, @NonNull Receiver receiver) { - super(handler); - this.receiver = receiver; - } - - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - switch (resultCode) { - case RESULT_CODE_SUCCESS -> receiver.onDeleteFinished(); - default -> throw new RuntimeException("Unknown resultCode."); - } - } - - public interface Receiver { - void onDeleteFinished(); - } - } -} diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackDeletionWorker.java b/src/main/java/de/dennisguse/opentracks/services/TrackDeletionWorker.java new file mode 100644 index 0000000000..6f8cc8fd8d --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/services/TrackDeletionWorker.java @@ -0,0 +1,36 @@ +package de.dennisguse.opentracks.services; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import java.util.Arrays; +import java.util.List; + +import de.dennisguse.opentracks.data.ContentProviderUtils; +import de.dennisguse.opentracks.data.models.Track; + +public class TrackDeletionWorker extends Worker { + + public static final String TRACKIDS_KEY = "TRACKIDS_KEY"; + + private final List trackIds; + + public TrackDeletionWorker(@NonNull Context context, @NonNull WorkerParameters params) { + super(context, params); + + trackIds = Arrays.stream(getInputData().getLongArray(TRACKIDS_KEY)) + .mapToObj(Track.Id::new) + .toList(); + } + + @NonNull + @Override + public Result doWork() { + new ContentProviderUtils(getApplicationContext()).deleteTracks(getApplicationContext(), trackIds); + + return Result.success(); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java index d8792b6f50..e01c5f5386 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java +++ b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java @@ -14,7 +14,7 @@ import de.dennisguse.opentracks.databinding.SettingsBinding; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; -public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller { +public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller { public static final String EXTRAS_EXPORT_ERROR_MESSAGE = "Export error message"; @@ -24,8 +24,6 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity private PreferenceFragmentCompat fragment = null; - private SettingsBinding viewBinding; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -74,9 +72,8 @@ protected void onSaveInstanceState(@NonNull Bundle outState) { @NonNull @Override - protected View createRootView() { - viewBinding = SettingsBinding.inflate(getLayoutInflater()); - return viewBinding.getRoot(); + protected SettingsBinding createRootView() { + return SettingsBinding.inflate(getLayoutInflater()); } private PreferenceFragmentCompat getPreferenceScreen(String key) { diff --git a/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutEditActivity.java b/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutEditActivity.java index 9fe262defd..99c1ac398f 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutEditActivity.java +++ b/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutEditActivity.java @@ -21,10 +21,9 @@ import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutEditAdapter; import de.dennisguse.opentracks.ui.util.ArrayAdapterFilterDisabled; -public class SettingsCustomLayoutEditActivity extends AbstractActivity implements SettingsCustomLayoutEditAdapter.SettingsCustomLayoutItemClickListener { +public class SettingsCustomLayoutEditActivity extends AbstractActivity implements SettingsCustomLayoutEditAdapter.SettingsCustomLayoutItemClickListener { public static final String EXTRA_LAYOUT = "extraLayout"; - private ActivitySettingsCustomLayoutBinding viewBinding; private GridLayoutManager gridLayoutManager; private SettingsCustomLayoutEditAdapter adapterFieldsVisible; private SettingsCustomLayoutEditAdapter adapterFieldsHidden; @@ -118,9 +117,8 @@ protected void onDestroy() { @NonNull @Override - protected View createRootView() { - viewBinding = ActivitySettingsCustomLayoutBinding.inflate(getLayoutInflater()); - return viewBinding.getRoot(); + protected ActivitySettingsCustomLayoutBinding createRootView() { + return ActivitySettingsCustomLayoutBinding.inflate(getLayoutInflater()); } @Override diff --git a/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutListActivity.java b/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutListActivity.java index 8b016d3f22..e3937f49f9 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutListActivity.java @@ -24,9 +24,8 @@ import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutListAdapter; import de.dennisguse.opentracks.ui.util.RecyclerViewSwipeDeleteCallback; -public class SettingsCustomLayoutListActivity extends AbstractActivity implements SettingsCustomLayoutListAdapter.SettingsCustomLayoutProfileClickListener { +public class SettingsCustomLayoutListActivity extends AbstractActivity implements SettingsCustomLayoutListAdapter.SettingsCustomLayoutProfileClickListener { - private ActivitySettingsCustomLayoutListBinding viewBinding; private SettingsCustomLayoutListAdapter adapter; @Override @@ -110,10 +109,8 @@ protected void onResume() { @NonNull @Override - protected View createRootView() { - PreferencesUtils.getCustomLayout(); - viewBinding = ActivitySettingsCustomLayoutListBinding.inflate(getLayoutInflater()); - return viewBinding.getRoot(); + protected ActivitySettingsCustomLayoutListBinding createRootView() { + return ActivitySettingsCustomLayoutListBinding.inflate(getLayoutInflater()); } @Override diff --git a/src/main/java/de/dennisguse/opentracks/ui/aggregatedStatistics/AggregatedStatisticsActivity.java b/src/main/java/de/dennisguse/opentracks/ui/aggregatedStatistics/AggregatedStatisticsActivity.java index 645459061a..941ae2f784 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/aggregatedStatistics/AggregatedStatisticsActivity.java +++ b/src/main/java/de/dennisguse/opentracks/ui/aggregatedStatistics/AggregatedStatisticsActivity.java @@ -20,14 +20,12 @@ import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.databinding.AggregatedStatsBinding; -public class AggregatedStatisticsActivity extends AbstractActivity implements FilterDialogFragment.FilterDialogListener { +public class AggregatedStatisticsActivity extends AbstractActivity implements FilterDialogFragment.FilterDialogListener { public static final String EXTRA_TRACK_IDS = "track_ids"; static final String STATE_ARE_FILTERS_APPLIED = "areFiltersApplied"; - private AggregatedStatsBinding viewBinding; - private AggregatedStatisticsAdapter adapter; private AggregatedStatisticsModel viewModel; @@ -85,9 +83,8 @@ public void onSaveInstanceState(@NonNull Bundle outState) { @NonNull @Override - protected View createRootView() { - viewBinding = AggregatedStatsBinding.inflate(getLayoutInflater()); - return viewBinding.getRoot(); + protected AggregatedStatsBinding createRootView() { + return AggregatedStatsBinding.inflate(getLayoutInflater()); } @Override diff --git a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerDetailActivity.java b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerDetailActivity.java index bfbded0a60..82df689971 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerDetailActivity.java +++ b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerDetailActivity.java @@ -43,14 +43,12 @@ * * @author Leif Hendrik Wilden */ -public class MarkerDetailActivity extends AbstractActivity implements DeleteMarkerCaller { +public class MarkerDetailActivity extends AbstractActivity implements DeleteMarkerCaller { public static final String EXTRA_MARKER_ID = "marker_id"; private static final String TAG = MarkerDetailActivity.class.getSimpleName(); - private MarkerDetailActivityBinding viewBinding; - private Cursor cursor; @Override @@ -99,15 +97,13 @@ public void onPageSelected(int position) { @NonNull @Override - protected View createRootView() { - viewBinding = MarkerDetailActivityBinding.inflate(getLayoutInflater()); - return viewBinding.getRoot(); + protected MarkerDetailActivityBinding createRootView() { + return MarkerDetailActivityBinding.inflate(getLayoutInflater()); } @Override protected void onDestroy() { super.onDestroy(); - viewBinding = null; if (cursor != null) cursor.close(); cursor = null; diff --git a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerEditActivity.java b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerEditActivity.java index 10bfce2ae6..fd7871ce3c 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerEditActivity.java +++ b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerEditActivity.java @@ -57,7 +57,7 @@ * * @author Jimmy Shih */ -public class MarkerEditActivity extends AbstractActivity { +public class MarkerEditActivity extends AbstractActivity { public static final String EXTRA_TRACK_ID = "track_id"; public static final String EXTRA_MARKER_ID = "marker_id"; @@ -82,14 +82,10 @@ public class MarkerEditActivity extends AbstractActivity { private MarkerEditViewModel viewModel; - // UI elements - private MarkerEditBinding viewBinding; - @NonNull @Override - protected View createRootView() { - viewBinding = MarkerEditBinding.inflate(getLayoutInflater()); - return viewBinding.getRoot(); + protected MarkerEditBinding createRootView() { + return MarkerEditBinding.inflate(getLayoutInflater()); } @Override @@ -200,7 +196,6 @@ protected void onDestroy() { trackId = null; location = null; markerId = null; - viewBinding = null; viewModel = null; takePictureFromGallery = null; takePictureFromCamera = null; diff --git a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerListActivity.java b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerListActivity.java index 7f5cee2044..27b5da4f83 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerListActivity.java @@ -47,7 +47,7 @@ * * @author Leif Hendrik Wilden */ -public class MarkerListActivity extends AbstractActivity implements DeleteMarkerDialogFragment.DeleteMarkerCaller { +public class MarkerListActivity extends AbstractActivity implements DeleteMarkerDialogFragment.DeleteMarkerCaller { public static final String EXTRA_TRACK_ID = "track_id"; @@ -59,8 +59,6 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker private MarkerListAdapter adapter; - private MarkerListBinding viewBinding; - private TrackRecordingServiceConnection trackRecordingServiceConnection; private final TrackRecordingServiceConnection.Callback bindCallback = (service, unused) -> service.getRecordingStatusObservable() @@ -115,6 +113,13 @@ protected void onCreate(Bundle savedInstanceState) { setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar); setSupportActionBar(viewBinding.markerListToolbar); viewBinding.bottomAppBarLayout.bottomAppBar.setNavigationOnClickListener(item -> finish()); + + viewBinding.markerListSearchView.getEditText().setOnEditorActionListener((v, actionId, event) -> { + searchQuery = viewBinding.markerListSearchView.getEditText().getText().toString(); + viewBinding.markerListSearchView.hide(); + loadData(); + return true; + }); } @Override @@ -140,24 +145,14 @@ protected void onStop() { @Override protected void onDestroy() { super.onDestroy(); - viewBinding = null; adapter = null; contentProviderUtils = null; } @NonNull @Override - protected View createRootView() { - viewBinding = MarkerListBinding.inflate(getLayoutInflater()); - - viewBinding.markerListSearchView.getEditText().setOnEditorActionListener((v, actionId, event) -> { - searchQuery = viewBinding.markerListSearchView.getEditText().getText().toString(); - viewBinding.markerListSearchView.hide(); - loadData(); - return true; - }); - - return viewBinding.getRoot(); + protected MarkerListBinding createRootView() { + return MarkerListBinding.inflate(getLayoutInflater()); } @Override diff --git a/src/main/java/de/dennisguse/opentracks/util/FileUtils.java b/src/main/java/de/dennisguse/opentracks/util/FileUtils.java index b6e39e285c..23352cf107 100644 --- a/src/main/java/de/dennisguse/opentracks/util/FileUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/FileUtils.java @@ -249,4 +249,15 @@ public static ArrayList getFiles(DocumentFile file) { return files; } + + public static List getFiles(List documentFiles) { + List> nestedFileList = documentFiles.stream() + .map(FileUtils::getFiles) + .toList(); + + List fileList = new ArrayList<>(); + nestedFileList.forEach(fileList::addAll); + + return fileList; + } }