forked from upstream-mirrors/OpenTracks
@@ -395,11 +395,6 @@ limitations under the License.
|
|||||||
android:icon="@drawable/ic_logo_color_24dp"
|
android:icon="@drawable/ic_logo_color_24dp"
|
||||||
android:label="@string/recording_service" />
|
android:label="@string/recording_service" />
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".io.file.importer.ImportService"
|
|
||||||
android:exported="false"
|
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
|
||||||
|
|
||||||
<!--Necessary for SDK < 33 to (re-)store per app locale information-->
|
<!--Necessary for SDK < 33 to (re-)store per app locale information-->
|
||||||
<service
|
<service
|
||||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||||
|
|||||||
@@ -25,13 +25,12 @@ import android.view.View;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.AbstractActivity;
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||||
import de.dennisguse.opentracks.databinding.ImportActivityBinding;
|
import de.dennisguse.opentracks.databinding.ImportActivityBinding;
|
||||||
@@ -43,7 +42,7 @@ import de.dennisguse.opentracks.util.IntentUtils;
|
|||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class ImportActivity extends FragmentActivity {
|
public class ImportActivity extends AbstractActivity {
|
||||||
|
|
||||||
private static final String TAG = ImportActivity.class.getSimpleName();
|
private static final String TAG = ImportActivity.class.getSimpleName();
|
||||||
|
|
||||||
@@ -57,14 +56,11 @@ public class ImportActivity extends FragmentActivity {
|
|||||||
private ArrayList<Uri> documentUris = new ArrayList<>();
|
private ArrayList<Uri> documentUris = new ArrayList<>();
|
||||||
private boolean isDirectory;
|
private boolean isDirectory;
|
||||||
|
|
||||||
private ImportViewModel viewModel;
|
private Importer importer;
|
||||||
private ImportViewModel.Summary summary;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewBinding = ImportActivityBinding.inflate(getLayoutInflater());
|
|
||||||
setContentView(viewBinding.getRoot());
|
|
||||||
|
|
||||||
final Intent intent = getIntent();
|
final Intent intent = getIntent();
|
||||||
final ClipData intentClipData = intent.getClipData();
|
final ClipData intentClipData = intent.getClipData();
|
||||||
@@ -99,16 +95,36 @@ public class ImportActivity extends FragmentActivity {
|
|||||||
documentFiles = documentUris.stream().map(it -> DocumentFile.fromSingleUri(this, it)).collect(Collectors.toList());
|
documentFiles = documentUris.stream().map(it -> DocumentFile.fromSingleUri(this, it)).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel = new ViewModelProvider(this).get(ImportViewModel.class);
|
importer = new Importer(this, documentFiles);
|
||||||
viewModel.getImportData(documentFiles).observe(this, data -> {
|
importer.getLiveData()
|
||||||
summary = data;
|
.observe(this, summary -> {
|
||||||
setProgress();
|
setProgress(summary);
|
||||||
});
|
});
|
||||||
|
importer.startImport();
|
||||||
|
|
||||||
//Works for a directory, but we might have received multiple files via SEND_MULTIPLE.
|
//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()));
|
viewBinding.importActivityToolbar.setTitle(getString(R.string.import_progress_message, documentFiles.get(0).getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected View createRootView() {
|
||||||
|
viewBinding = ImportActivityBinding.inflate(getLayoutInflater());
|
||||||
|
return viewBinding.getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
viewBinding = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSupportNavigateUp() {
|
||||||
|
importer.cancel();
|
||||||
|
return super.onSupportNavigateUp();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
@@ -116,7 +132,7 @@ public class ImportActivity extends FragmentActivity {
|
|||||||
outState.putBoolean(BUNDLE_IS_DIRECTORY, isDirectory);
|
outState.putBoolean(BUNDLE_IS_DIRECTORY, isDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setProgress() {
|
private void setProgress(Importer.Summary summary) {
|
||||||
int done = summary.getCount();
|
int done = summary.getCount();
|
||||||
viewBinding.importProgressDone.setText("" + done);
|
viewBinding.importProgressDone.setText("" + done);
|
||||||
viewBinding.importProgressTotal.setText("" + summary.getTotalCount());
|
viewBinding.importProgressTotal.setText("" + summary.getTotalCount());
|
||||||
@@ -130,11 +146,11 @@ public class ImportActivity extends FragmentActivity {
|
|||||||
viewBinding.importProgressSummaryErrorsGroup.setVisibility(summary.getErrorCount() > 0 ? View.VISIBLE : View.GONE);
|
viewBinding.importProgressSummaryErrorsGroup.setVisibility(summary.getErrorCount() > 0 ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (summary.isDone()) {
|
if (summary.isDone()) {
|
||||||
onImportEnded();
|
onImportEnded(summary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onImportEnded() {
|
private void onImportEnded(Importer.Summary summary) {
|
||||||
viewBinding.importProgressAlertMsg.setVisibility(View.VISIBLE);
|
viewBinding.importProgressAlertMsg.setVisibility(View.VISIBLE);
|
||||||
viewBinding.importProgressAlertIcon.setVisibility(View.VISIBLE);
|
viewBinding.importProgressAlertIcon.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
@@ -146,13 +162,13 @@ public class ImportActivity extends FragmentActivity {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (summary.getErrorCount() > 0) {
|
if (summary.getErrorCount() > 0) {
|
||||||
toggleUIEndWithErrors();
|
toggleUIEndWithErrors(summary);
|
||||||
} else {
|
} else {
|
||||||
toggleUIEndOk();
|
toggleUIEndOk(summary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleUIEndWithErrors() {
|
private void toggleUIEndWithErrors(Importer.Summary summary) {
|
||||||
viewBinding.importProgressLeftButton.setVisibility(View.VISIBLE);
|
viewBinding.importProgressLeftButton.setVisibility(View.VISIBLE);
|
||||||
viewBinding.importProgressLeftButton.setText(getString(R.string.generic_show_errors));
|
viewBinding.importProgressLeftButton.setText(getString(R.string.generic_show_errors));
|
||||||
viewBinding.importProgressLeftButton.setOnClickListener((view) -> ErrorListDialog.showDialog(getSupportFragmentManager(), getString(R.string.import_error_list_dialog_title), summary.getFileErrors()));
|
viewBinding.importProgressLeftButton.setOnClickListener((view) -> ErrorListDialog.showDialog(getSupportFragmentManager(), getString(R.string.import_error_list_dialog_title), summary.getFileErrors()));
|
||||||
@@ -160,7 +176,7 @@ public class ImportActivity extends FragmentActivity {
|
|||||||
viewBinding.importProgressAlertMsg.setText(getResources().getQuantityString(R.plurals.generic_completed_with_errors, summary.getErrorCount(), summary.getErrorCount()));
|
viewBinding.importProgressAlertMsg.setText(getResources().getQuantityString(R.plurals.generic_completed_with_errors, summary.getErrorCount(), summary.getErrorCount()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleUIEndOk() {
|
private void toggleUIEndOk(Importer.Summary summary) {
|
||||||
viewBinding.importProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_dialog_success_24dp));
|
viewBinding.importProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_dialog_success_24dp));
|
||||||
viewBinding.importProgressAlertMsg.setText(getString(R.string.generic_completed));
|
viewBinding.importProgressAlertMsg.setText(getString(R.string.generic_completed));
|
||||||
|
|
||||||
|
|||||||
@@ -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<Track.Id> 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<Track.Id> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-38
@@ -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 <var>handler</var> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +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<Summary> importData;
|
|
||||||
private final ImportServiceResultReceiver resultReceiver;
|
|
||||||
private final Summary summary;
|
|
||||||
private boolean cancel = false;
|
|
||||||
private final List<DocumentFile> filesToImport = new ArrayList<>();
|
|
||||||
|
|
||||||
public ImportViewModel(@NonNull Application application) {
|
|
||||||
super(application);
|
|
||||||
resultReceiver = new ImportServiceResultReceiver(new Handler(), this);
|
|
||||||
summary = new Summary();
|
|
||||||
}
|
|
||||||
|
|
||||||
LiveData<Summary> getImportData(List<DocumentFile> documentFiles) {
|
|
||||||
if (importData == null) {
|
|
||||||
importData = new MutableLiveData<>();
|
|
||||||
loadData(documentFiles);
|
|
||||||
}
|
|
||||||
return importData;
|
|
||||||
}
|
|
||||||
|
|
||||||
void cancel() {
|
|
||||||
cancel = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadData(List<DocumentFile> documentFiles) {
|
|
||||||
List<ArrayList<DocumentFile>> nestedFileList = documentFiles.stream()
|
|
||||||
.map(FileUtils::getFiles)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
List<DocumentFile> 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<Track.Id> 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<Track.Id> importedTrackIds = new ArrayList<>();
|
|
||||||
private final ArrayList<String> 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<Track.Id> getImportedTrackIds() {
|
|
||||||
return importedTrackIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<String> getFileErrors() {
|
|
||||||
return fileErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCount() {
|
|
||||||
return getSuccessCount() + getExistsCount() + getErrorCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDone() {
|
|
||||||
return getTotalCount() == getCount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<Track.Id> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package de.dennisguse.opentracks.io.file.importer;
|
||||||
|
|
||||||
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
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.R;
|
||||||
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
|
|
||||||
|
//TODO Cancel does not abort current import
|
||||||
|
//TODO Provide some intermediate progress (not only when a file got imported)
|
||||||
|
public class Importer {
|
||||||
|
|
||||||
|
private final MutableLiveData<Summary> liveData = new MutableLiveData<>();
|
||||||
|
private final Summary summary = new Summary();
|
||||||
|
private final List<DocumentFile> filesToImport = new ArrayList<>();
|
||||||
|
|
||||||
|
private boolean cancel = false;
|
||||||
|
|
||||||
|
private final ImportActivity context;
|
||||||
|
private final WorkManager workManager;
|
||||||
|
|
||||||
|
//TODO Add summary callback
|
||||||
|
Importer(ImportActivity context, List<DocumentFile> documentFiles) {
|
||||||
|
this.context = context;
|
||||||
|
this.workManager = WorkManager.getInstance(context);
|
||||||
|
|
||||||
|
List<ArrayList<DocumentFile>> nestedFileList = documentFiles.stream()
|
||||||
|
.map(FileUtils::getFiles)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<DocumentFile> fileList = new ArrayList<>();
|
||||||
|
nestedFileList.forEach(fileList::addAll);
|
||||||
|
|
||||||
|
summary.totalCount = fileList.size();
|
||||||
|
filesToImport.addAll(fileList);
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<Summary> getLiveData() {
|
||||||
|
return liveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startImport() {
|
||||||
|
liveData.postValue(summary);
|
||||||
|
importNextFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel() {
|
||||||
|
cancel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void importNextFile() {
|
||||||
|
if (cancel || filesToImport.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final DocumentFile documentFile = filesToImport.get(0);
|
||||||
|
|
||||||
|
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(context, 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(context.getString(R.string.import_error_info, documentFile.getName(), errorMessage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
liveData.postValue(summary);
|
||||||
|
importNextFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
workManager.enqueue(importRequest);
|
||||||
|
filesToImport.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Summary {
|
||||||
|
private int totalCount;
|
||||||
|
private int successCount;
|
||||||
|
private int existsCount;
|
||||||
|
private final ArrayList<Track.Id> importedTrackIds = new ArrayList<>();
|
||||||
|
private final ArrayList<String> 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<Track.Id> getImportedTrackIds() {
|
||||||
|
return importedTrackIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<String> getFileErrors() {
|
||||||
|
return fileErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCount() {
|
||||||
|
return getSuccessCount() + getExistsCount() + getErrorCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDone() {
|
||||||
|
return getTotalCount() == getCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user