From acc2627bde8ccb899ed6e177e997cbfea292445a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20Mart=C3=ADnez?= Date: Fri, 23 Oct 2020 18:57:37 +0200 Subject: [PATCH] Activity with progress and summary for import all option. Fixes #471. ErrorListDialog shared by ExportActivity and ImportActivity. --- .../io/file/importer/ExportImportTest.java | 10 + src/main/AndroidManifest.xml | 9 +- .../ImportFinishedDialogFragment.java | 111 --------- .../ImportProgressDialogFragment.java | 230 ------------------ .../opentracks/io/file/ErrorListDialog.java | 67 +++++ .../io/file/exporter/ExportActivity.java | 42 +--- .../exporter/ExportServiceResultReceiver.java | 7 +- .../importer/AbstractFileTrackImporter.java | 16 +- .../io/file/importer/ImportActivity.java | 152 ++++++++++-- .../ImportAlreadyExistsException.java | 7 + .../file/importer/ImportParserException.java | 11 + .../io/file/importer/ImportService.java | 90 +++++++ .../importer/ImportServiceResultReceiver.java | 38 +++ .../io/file/importer/ImportViewModel.java | 119 +++++++++ .../io/file/importer/KmzTrackImporter.java | 24 +- .../opentracks/util/ExportUtils.java | 42 ++-- .../res/layout/import_progress_activity.xml | 135 ++++++++++ src/main/res/values/strings.xml | 26 +- src/main/res/values/themes_custom.xml | 10 - 19 files changed, 701 insertions(+), 445 deletions(-) delete mode 100644 src/main/java/de/dennisguse/opentracks/fragments/ImportFinishedDialogFragment.java delete mode 100644 src/main/java/de/dennisguse/opentracks/fragments/ImportProgressDialogFragment.java create mode 100644 src/main/java/de/dennisguse/opentracks/io/file/ErrorListDialog.java create mode 100644 src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAlreadyExistsException.java create mode 100644 src/main/java/de/dennisguse/opentracks/io/file/importer/ImportParserException.java create mode 100644 src/main/java/de/dennisguse/opentracks/io/file/importer/ImportService.java create mode 100644 src/main/java/de/dennisguse/opentracks/io/file/importer/ImportServiceResultReceiver.java create mode 100644 src/main/java/de/dennisguse/opentracks/io/file/importer/ImportViewModel.java create mode 100644 src/main/res/layout/import_progress_activity.xml diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java index c89822828..4d9428d0c 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java @@ -1,6 +1,7 @@ package de.dennisguse.opentracks.io.file.importer; import android.content.Context; +import android.database.sqlite.SQLiteConstraintException; import android.util.Log; import android.util.Pair; @@ -9,7 +10,9 @@ import androidx.test.filters.LargeTest; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -59,6 +62,9 @@ public class ExportImportTest { private Track.Id importTrackId; private final Track.Id trackId = new Track.Id(System.currentTimeMillis()); + @Rule + public ExpectedException exception = ExpectedException.none(); + @Before public void setUp() { Pair track = TestDataUtil.createTrack(trackId, 10); @@ -181,6 +187,8 @@ public class ExportImportTest { TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context); + exception.expect(ImportAlreadyExistsException.class); + // when // 1. export ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); @@ -270,6 +278,8 @@ public class ExportImportTest { TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context); + exception.expect(ImportAlreadyExistsException.class); + // when // 1. export ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index e9709c572..ec9914415 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -68,9 +68,7 @@ limitations under the License. - + @@ -333,5 +331,10 @@ limitations under the License. android:name=".io.file.exporter.ExportService" android:exported="false" android:permission="android.permission.BIND_JOB_SERVICE" /> + + diff --git a/src/main/java/de/dennisguse/opentracks/fragments/ImportFinishedDialogFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/ImportFinishedDialogFragment.java deleted file mode 100644 index d90c36091..000000000 --- a/src/main/java/de/dennisguse/opentracks/fragments/ImportFinishedDialogFragment.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2012 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package de.dennisguse.opentracks.fragments; - -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.FragmentManager; - -import de.dennisguse.opentracks.R; - -public class ImportFinishedDialogFragment extends DialogFragment { - - private static final String TAG = ImportFinishedDialogFragment.class.getSimpleName(); - - private static final String EXTRA_DIRECTORY_NAME_KEY = "directory_name"; - private static final String EXTRA_IMPORTED_TRACK_COUNT = "imported_track_count"; - private static final String EXTRA_FILE_COUNT = "file_count"; - - private static final String FINISHED_DIALOG_TAG = "finished_dialog_tag"; - - private DismissCallback caller; - - public static void showDialog(FragmentManager fragmentManager, String directoryDisplayName, int successCount, int fileCount) { - Bundle bundle = new Bundle(); - bundle.putString(EXTRA_DIRECTORY_NAME_KEY, directoryDisplayName); - bundle.putInt(EXTRA_IMPORTED_TRACK_COUNT, successCount); - bundle.putInt(EXTRA_FILE_COUNT, fileCount); - - ImportFinishedDialogFragment dialogFragment = new ImportFinishedDialogFragment(); - dialogFragment.setArguments(bundle); - dialogFragment.show(fragmentManager, FINISHED_DIALOG_TAG); - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - try { - caller = (DismissCallback) context; - } catch (ClassCastException e) { - throw new ClassCastException(context + " must implement " + DismissCallback.class.getSimpleName()); - } - } - - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - String directoryDisplayName = getArguments().getString(EXTRA_DIRECTORY_NAME_KEY); - int importedTrackCount = getArguments().getInt(EXTRA_IMPORTED_TRACK_COUNT); - int totalTrackCount = getArguments().getInt(EXTRA_FILE_COUNT); - - int iconId; - int titleId; - String message; - String totalFiles = getResources().getQuantityString(R.plurals.files, totalTrackCount, totalTrackCount); - if (importedTrackCount == totalTrackCount) { - if (totalTrackCount == 0) { - iconId = R.drawable.ic_dialog_info_24dp; - titleId = R.string.import_no_file_title; - message = getString(R.string.import_no_file, directoryDisplayName); - } else { - iconId = R.drawable.ic_dialog_success_24dp; - titleId = R.string.generic_success_title; - message = getString(R.string.import_success, totalFiles, directoryDisplayName); - } - } else { - iconId = R.drawable.ic_dialog_error_24dp; - titleId = R.string.generic_error_title; - message = getString(R.string.import_error, importedTrackCount, totalFiles, directoryDisplayName); - } - Dialog dialog = new AlertDialog.Builder(getContext()) - .setCancelable(true).setIcon(iconId) - .setMessage(message) - .setPositiveButton(R.string.generic_ok, (dialogInterface, which) -> { - dialogInterface.dismiss(); - caller.onDismissed(); - }) - .setTitle(titleId) - .create(); - dialog.setCanceledOnTouchOutside(false); - return dialog; - } - - @Override - public void onDetach() { - super.onDetach(); - caller = null; - } - - public interface DismissCallback { - void onDismissed(); - } -} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/fragments/ImportProgressDialogFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/ImportProgressDialogFragment.java deleted file mode 100644 index f6ffdada0..000000000 --- a/src/main/java/de/dennisguse/opentracks/fragments/ImportProgressDialogFragment.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright 2012 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package de.dennisguse.opentracks.fragments; - -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.documentfile.provider.DocumentFile; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.FragmentManager; - -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.io.file.TrackFileFormat; -import de.dennisguse.opentracks.io.file.importer.GpxFileTrackImporter; -import de.dennisguse.opentracks.io.file.importer.KmlFileTrackImporter; -import de.dennisguse.opentracks.io.file.importer.KmzTrackImporter; -import de.dennisguse.opentracks.io.file.importer.TrackImporter; -import de.dennisguse.opentracks.util.DialogUtils; -import de.dennisguse.opentracks.util.FileUtils; - -public class ImportProgressDialogFragment extends DialogFragment { - - private static final String EXTRA_IS_TREE_KEY = "tree_uri"; - - private static final String EXTRA_DATA_URI_KEY = "data_uri"; - - private static final String TAG = ImportProgressDialogFragment.class.getSimpleName(); - - public static final String IMPORT_DIALOG_TAG = "import_dialog_tag"; - - private DismissCallback caller; - - private ImportThread importThread; - - private String directoryDisplayName; - - private int trackImportSuccessCount; - private int fileCount; - - public static void showDialog(FragmentManager fragmentManager, Uri documentFileUri, boolean isDirectory) { - Bundle bundle = new Bundle(); - bundle.putParcelable(EXTRA_DATA_URI_KEY, documentFileUri); - bundle.putBoolean(EXTRA_IS_TREE_KEY, isDirectory); - - ImportProgressDialogFragment dialogFragment = new ImportProgressDialogFragment(); - dialogFragment.setArguments(bundle); - dialogFragment.setRetainInstance(true); - dialogFragment.show(fragmentManager, IMPORT_DIALOG_TAG); - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // This fragment uses a background thread, so it should not be recreated during activity re-creation. - setRetainInstance(true); - - Uri directoryUri = getArguments().getParcelable(EXTRA_DATA_URI_KEY); - DocumentFile documentFile; - if (getArguments().getBoolean(EXTRA_IS_TREE_KEY)) { - documentFile = DocumentFile.fromTreeUri(getContext(), directoryUri); - } else { - documentFile = DocumentFile.fromSingleUri(getContext(), directoryUri); - } - directoryDisplayName = FileUtils.getPath(documentFile); - - importThread = new ImportThread(documentFile); - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - try { - caller = (DismissCallback) context; - } catch (ClassCastException e) { - throw new ClassCastException(context + " must implement " + DismissCallback.class.getSimpleName()); - } - } - - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - ProgressDialog progressDialog = DialogUtils.createHorizontalProgressDialog( - getContext(), R.string.import_progress_message, - dialogInterface -> { - importThread.interrupt(); - dialogInterface.dismiss(); - caller.onImportCanceled(directoryDisplayName, trackImportSuccessCount, fileCount); - }, directoryDisplayName); - - progressDialog.setIndeterminate(false); - progressDialog.setMax(fileCount); - progressDialog.setProgress(Math.min(trackImportSuccessCount, fileCount)); - - return progressDialog; - } - - @Override - public void onStart() { - super.onStart(); - setProgress(); - if (!importThread.isAlive()) { - importThread.start(); - } - } - - @Override - public void onPause() { - super.onPause(); - if (!importThread.isAlive()) { - importThread.interrupt(); - } - } - - @Override - public void onDetach() { - super.onDetach(); - caller = null; - } - - private void onProgress(int trackImportSuccessCount, int fileCount) { - this.trackImportSuccessCount = trackImportSuccessCount; - this.fileCount = fileCount; - setProgress(); - } - - private void setProgress() { - ProgressDialog dialog = (ProgressDialog) getDialog(); - if (dialog != null) { - dialog.setIndeterminate(false); - dialog.setMax(fileCount); - dialog.setProgress(Math.min(trackImportSuccessCount, fileCount)); - } - } - - private void onImportCompleted(int successCount, int fileCount) { - if (caller != null && !isDetached()) { - caller.onImportFinished(directoryDisplayName, successCount, fileCount); - } - dismissAllowingStateLoss(); - } - - private class ImportThread extends Thread { - - private final DocumentFile file; - - ImportThread(DocumentFile file) { - this.file = file; - } - - @Override - public void run() { - Context context = ImportProgressDialogFragment.this.getContext(); - - List files = FileUtils.getFiles(file); - - int importedTrackCount = 0; - for (int i = 0; i < files.size(); i++) { - onProgress(i, files.size()); - - if (Thread.interrupted()) { - return; - } - if (importFile(context, files.get(i))) { - importedTrackCount++; - } - } - - onImportCompleted(importedTrackCount, files.size()); - } - - /** - * Imports a file. - * - * @param file the file - */ - private boolean importFile(final Context context, final DocumentFile file) { - TrackImporter trackImporter; - String fileExtension = FileUtils.getExtension(file); - - if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) { - trackImporter = new GpxFileTrackImporter(context); - } else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) { - trackImporter = new KmlFileTrackImporter(ImportProgressDialogFragment.this.getContext()); - } else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) { - trackImporter = new KmzTrackImporter(context, file.getUri()); - } else { - Log.i(TAG, "Unsupported file format."); - return false; - } - - try (InputStream inputStream = context.getContentResolver().openInputStream(file.getUri())) { - return trackImporter.importFile(inputStream) != null; - } catch (IOException e) { - Log.e(TAG, "Unable to import file", e); - return false; - } - } - } - - public interface DismissCallback { - void onImportCanceled(String directoryDisplayName, int successCount, int fileCount); - - void onImportFinished(String directoryDisplayName, int successCount, int fileCount); - } -} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/io/file/ErrorListDialog.java b/src/main/java/de/dennisguse/opentracks/io/file/ErrorListDialog.java new file mode 100644 index 000000000..9453c511f --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/io/file/ErrorListDialog.java @@ -0,0 +1,67 @@ +package de.dennisguse.opentracks.io.file; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentManager; + +import java.util.ArrayList; + +import de.dennisguse.opentracks.R; + +public class ErrorListDialog extends DialogFragment { + + public static final String TAG = ErrorListDialog.class.getSimpleName(); + + private static final String EXTRA_TITLE = "extra_title"; + private static final String EXTRA_ERROR_LIST = "extra_error_list"; + + private ArrayList errorList; + private String title; + + public static void showDialog(FragmentManager fragmentManager, String title, ArrayList errorList) { + Bundle bundle = new Bundle(); + bundle.putString(EXTRA_TITLE, title); + bundle.putStringArrayList(EXTRA_ERROR_LIST, errorList); + + ErrorListDialog errorListDialog = new ErrorListDialog(); + errorListDialog.setArguments(bundle); + errorListDialog.setRetainInstance(true); + errorListDialog.show(fragmentManager, TAG); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + title = getArguments().getString(EXTRA_TITLE); + errorList = getArguments().getStringArrayList(EXTRA_ERROR_LIST); + } else { + title = savedInstanceState.getString(EXTRA_TITLE); + errorList = savedInstanceState.getStringArrayList(EXTRA_ERROR_LIST); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(EXTRA_TITLE, title); + outState.putStringArrayList(EXTRA_ERROR_LIST, errorList); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final String[] tracks = errorList.stream().toArray(String[]::new); + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity()) + .setTitle(title) + .setItems(tracks, null) + .setPositiveButton(R.string.generic_ok, (dialog, which) -> dismiss()); + AlertDialog dialog = alertDialogBuilder.create(); + return dialog; + } +} 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 5c346f884..866f7b272 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 @@ -16,8 +16,6 @@ package de.dennisguse.opentracks.io.file.exporter; -import android.app.AlertDialog; -import android.app.Dialog; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; @@ -34,7 +32,6 @@ import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; import androidx.documentfile.provider.DocumentFile; -import androidx.fragment.app.DialogFragment; import androidx.fragment.app.FragmentActivity; import java.util.ArrayList; @@ -45,6 +42,7 @@ import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TracksColumns; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; +import de.dennisguse.opentracks.io.file.ErrorListDialog; import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.util.ExportUtils; import de.dennisguse.opentracks.util.FileUtils; @@ -68,6 +66,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes private static final String BUNDLE_SKIPPED_COUNT = "track_export_skipped_count"; private static final String BUNDLE_TOTAL_COUNT = "track_export_total_count"; private static final String BUNDLE_DIRECTORY_FILES = "track_directory_files"; + private static final String BUNDLE_TRACK_ERRORS = "track_errors"; private static final int CONFLICT_NONE = 0; private static final int CONFLICT_OVERWRITE = 1; @@ -99,6 +98,8 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes private Button viewLeftButton; private Button viewRightButton; + private ArrayList trackErrors = new ArrayList<>(); + private int autoConflict; private ContentProviderUtils contentProviderUtils; @@ -159,8 +160,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes Toolbar toolbar = findViewById(R.id.toolbar); toolbar.setTitle(getString(R.string.export_progress_message, directoryDisplayName)); - resultReceiver = new ExportServiceResultReceiver(new Handler()); - resultReceiver.setReceiver(this); + resultReceiver = new ExportServiceResultReceiver(new Handler(), this); viewTotal = findViewById(R.id.export_progress_total); viewDone = findViewById(R.id.export_progress_done); @@ -187,6 +187,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes trackExportSkippedCount = savedInstanceState.getInt(BUNDLE_SKIPPED_COUNT); trackExportTotalCount = savedInstanceState.getInt(BUNDLE_TOTAL_COUNT); directoryFiles = savedInstanceState.getStringArrayList(BUNDLE_DIRECTORY_FILES); + trackErrors = savedInstanceState.getStringArrayList(BUNDLE_TRACK_ERRORS); setProgress(); initExport(getTotalDone()); @@ -203,6 +204,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes outState.putInt(BUNDLE_SKIPPED_COUNT, trackExportSkippedCount); outState.putInt(BUNDLE_TOTAL_COUNT, trackExportTotalCount); outState.putStringArrayList(BUNDLE_DIRECTORY_FILES, (ArrayList) directoryFiles); + outState.putStringArrayList(BUNDLE_TRACK_ERRORS, trackErrors); } @Override @@ -220,7 +222,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes } this.doubleBackToCancel = true; - Toast.makeText(this, getString(R.string.export_click_twice_cancel), Toast.LENGTH_SHORT).show(); + Toast.makeText(this, getString(R.string.generic_click_twice_cancel), Toast.LENGTH_SHORT).show(); new Handler().postDelayed(() -> doubleBackToCancel=false, 2000); } @@ -311,15 +313,15 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes if (trackExportErrorCount > 0) { viewLeftButton.setVisibility(View.VISIBLE); - viewLeftButton.setText(getString(R.string.export_show_errors)); - viewLeftButton.setOnClickListener((view) -> new ErrorListDialog().show(getSupportFragmentManager(), ErrorListDialog.TAG)); + viewLeftButton.setText(getString(R.string.generic_show_errors)); + viewLeftButton.setOnClickListener((view) -> ErrorListDialog.showDialog(getSupportFragmentManager(), getString(R.string.export_track_errors), trackErrors)); viewAlertIcon.setImageDrawable(getDrawable(R.drawable.ic_report_problem_24)); - String msg = getResources().getQuantityString(R.plurals.export_all_end_with_errors, trackExportErrorCount, trackExportErrorCount); + String msg = getResources().getQuantityString(R.plurals.generic_completed_with_errors, trackExportErrorCount, trackExportErrorCount); viewAlertMsg.setText(msg); } else { viewLeftButton.setVisibility(View.GONE); viewAlertIcon.setImageDrawable(getDrawable(R.drawable.ic_dialog_success_24dp)); - viewAlertMsg.setText(getString(R.string.export_all_end)); + viewAlertMsg.setText(getString(R.string.generic_completed)); } viewRightButton.setOnClickListener((view) -> finish()); @@ -337,7 +339,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes case ExportServiceResultReceiver.RESULT_CODE_ERROR: trackExportErrorCount++; Track track = contentProviderUtils.getTrack(trackId); - ErrorListDialog.trackErrors.add(track.getName()); + trackErrors.add(track.getName()); break; case ExportServiceResultReceiver.RESULT_CODE_SUCCESS: if (ExportUtils.isExportFileExists(trackId, trackFileFormat.getExtension(), directoryFiles)) { @@ -413,22 +415,4 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes } } } - - public static class ErrorListDialog extends DialogFragment { - - public static final String TAG = ErrorListDialog.class.getSimpleName(); - - public static List trackErrors = new ArrayList<>(); - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final String[] tracks = trackErrors.stream().toArray(String[]::new); - AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity()) - .setTitle(getString(R.string.export_track_errors)) - .setItems(tracks, null) - .setPositiveButton(R.string.generic_ok, (dialog, which) -> dismiss()); - AlertDialog dialog = alertDialogBuilder.create(); - return dialog; - } - } } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportServiceResultReceiver.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportServiceResultReceiver.java index cea13a887..aa6949c25 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportServiceResultReceiver.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportServiceResultReceiver.java @@ -17,8 +17,9 @@ public class ExportServiceResultReceiver extends ResultReceiver { private Receiver receiver; - public ExportServiceResultReceiver(Handler handler) { + public ExportServiceResultReceiver(Handler handler, Receiver receiver) { super(handler); + this.receiver = receiver; } @Override @@ -28,10 +29,6 @@ public class ExportServiceResultReceiver extends ResultReceiver { } } - public void setReceiver(Receiver receiver) { - this.receiver = receiver; - } - public interface Receiver { void onReceiveResult(int resultCode, Bundle resultData); } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java index 5f2a63451..28cdccd6c 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java @@ -134,13 +134,18 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track saxParser.parse(inputStream, this); Log.d(TAG, "Total import time: " + (System.currentTimeMillis() - start) + "ms"); if (trackIds.size() != 1) { - throw new MultiTracksImportException(); + // TODO Multi track is not supported yet. + throw new ImportParserException("Multi track not supported"); } return trackIds.get(0); - } catch (MultiTracksImportException | IOException | SAXException | SQLiteConstraintException | ParserConfigurationException e) { + } catch (IOException | SAXException | ParserConfigurationException e) { Log.e(TAG, "Unable to import file", e); cleanImport(); - return null; + throw new ImportParserException(e); + } catch (SQLiteConstraintException e) { + Log.e(TAG, "Unable to import file", e); + cleanImport(); + throw new ImportAlreadyExistsException(e); } } @@ -550,9 +555,4 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track // The number of buffered locations int numBufferedTrackPoints = 0; } - - @Deprecated - //TODO According to the reader AND writer, we support multiple tracks per file; we should implement it here also. - private static class MultiTracksImportException extends RuntimeException { - } } 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 d8ad381e4..dccd63086 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 @@ -16,62 +16,178 @@ package de.dennisguse.opentracks.io.file.importer; -import android.content.ClipData; +import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; +import androidx.documentfile.provider.DocumentFile; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModelProvider; -import de.dennisguse.opentracks.fragments.ImportFinishedDialogFragment; -import de.dennisguse.opentracks.fragments.ImportProgressDialogFragment; +import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.io.file.ErrorListDialog; /** * An activity to import files from the external storage. * * @author Rodrigo Damazio */ -public class ImportActivity extends FragmentActivity implements ImportProgressDialogFragment.DismissCallback, ImportFinishedDialogFragment.DismissCallback { +public class ImportActivity extends FragmentActivity { private static final String TAG = ImportActivity.class.getSimpleName(); public static final String EXTRA_DIRECTORY_URI_KEY = "directory_uri"; - private static final String BUNDLE_ACTIVITY_RECREATED = "activity_recreated"; + private static final String BUNDLE_TOOLBAR_TITLE = "toolbar_title"; + private static final String BUNDLE_DOCUMENT_URI = "document_uri"; + private static final String BUNDLE_IS_DIRECTORY = "is_directory"; + + private TextView viewTotal; + private TextView viewDone; + private TextView viewSummary; + private ProgressBar viewProgressBar; + private ImageView viewAlertIcon; + private TextView viewAlertMsg; + private Button viewLeftButton; + private Button viewRightButton; + + boolean doubleBackToCancel = false; + + private Uri documentUri; + private boolean isDirectory; + private String toolbarTitle; + + private ImportViewModel viewModel; + private ImportViewModel.Summary summary; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setContentView(R.layout.import_progress_activity); - if (savedInstanceState == null || !savedInstanceState.getBoolean(BUNDLE_ACTIVITY_RECREATED, false)) { + Toolbar toolbar = findViewById(R.id.toolbar); + + viewTotal = findViewById(R.id.import_progress_total); + viewDone = findViewById(R.id.import_progress_done); + viewSummary = findViewById(R.id.import_progress_summary); + viewProgressBar = findViewById(R.id.import_progress_bar); + viewAlertIcon = findViewById(R.id.import_progress_alert_icon); + viewAlertMsg = findViewById(R.id.import_progress_alert_msg); + viewLeftButton = findViewById(R.id.import_progress_left_button); + viewRightButton = findViewById(R.id.import_progress_right_button); + + final DocumentFile documentFile; + + if (savedInstanceState == null) { if (getIntent().getData() != null) { - ImportProgressDialogFragment.showDialog(getSupportFragmentManager(), getIntent().getData(), false); + documentUri = getIntent().getData(); + isDirectory = false; } else if (getIntent().getClipData() != null && getIntent().getClipData().getItemCount() > 0) { - ImportProgressDialogFragment.showDialog(getSupportFragmentManager(), getIntent().getClipData().getItemAt(0).getUri(), false); + documentUri = getIntent().getClipData().getItemAt(0).getUri(); + isDirectory = false; } else { // Started from DirectoryChooserActivity - ImportProgressDialogFragment.showDialog(getSupportFragmentManager(), getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY), true); + documentUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY); + isDirectory = true; } + + documentFile = isDirectory ? DocumentFile.fromTreeUri(this, documentUri) : DocumentFile.fromSingleUri(this, documentUri); + toolbarTitle = getString(R.string.import_progress_message, documentFile.getName()); + } else { + documentUri = savedInstanceState.getParcelable(BUNDLE_DOCUMENT_URI); + toolbarTitle = savedInstanceState.getString(BUNDLE_TOOLBAR_TITLE); + isDirectory = savedInstanceState.getBoolean(BUNDLE_IS_DIRECTORY); + + documentFile = isDirectory ? DocumentFile.fromTreeUri(this, documentUri) : DocumentFile.fromSingleUri(this, documentUri); } + + toolbar.setTitle(toolbarTitle); + initViews(); + + viewModel = new ViewModelProvider(this).get(ImportViewModel.class); + viewModel.getImportData(documentFile).observe(this, data -> { + summary = data; + setProgress(); + }); } @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); - outState.putBoolean(BUNDLE_ACTIVITY_RECREATED, true); + outState.putParcelable(BUNDLE_DOCUMENT_URI, documentUri); + outState.putBoolean(BUNDLE_IS_DIRECTORY, isDirectory); + outState.putString(BUNDLE_TOOLBAR_TITLE, toolbarTitle); } @Override - public void onImportCanceled(String directoryDisplayName, int successCount, int fileCount) { - onImportFinished(directoryDisplayName, successCount, fileCount); + public void onBackPressed() { + if (doubleBackToCancel || getTotalDone() == summary.getTotalCount()) { + super.onBackPressed(); + viewModel.cancel(); + getViewModelStore().clear(); + return; + } + + this.doubleBackToCancel = true; + Toast.makeText(this, getString(R.string.generic_click_twice_cancel), Toast.LENGTH_SHORT).show(); + + new Handler().postDelayed(() -> doubleBackToCancel=false, 2000); } - @Override - public void onImportFinished(final String directoryDisplayName, final int successCount, final int fileCount) { - runOnUiThread(() -> ImportFinishedDialogFragment.showDialog(getSupportFragmentManager(), directoryDisplayName, successCount, fileCount)); + private void initViews() { + viewDone.setText("0"); + viewTotal.setText("0"); + viewSummary.setText(getString(R.string.import_progress_review, 0, 0, 0, 0)); } - @Override - public void onDismissed() { - finish(); + private int getTotalDone() { + return summary.getSuccessCount() + summary.getExistsCount() + summary.getErrorCount(); + } + + private void setProgress() { + int done = getTotalDone(); + + viewDone.setText("" + done); + viewTotal.setText("" + summary.getTotalCount()); + + viewProgressBar.setProgress((int) ((float) done / (float) summary.getTotalCount() * 100f)); + viewSummary.setText(getString(R.string.import_progress_review, getTotalDone(), summary.getSuccessCount(), summary.getExistsCount(), summary.getErrorCount())); + + if (done == summary.getTotalCount()) { + onImportEnded(); + } + } + + private void onImportEnded() { + viewAlertIcon.setVisibility(View.VISIBLE); + viewAlertMsg.setVisibility(View.VISIBLE); + viewRightButton.setVisibility(View.VISIBLE); + viewRightButton.setText(getString(R.string.generic_ok)); + + if (summary.getErrorCount() > 0) { + viewLeftButton.setVisibility(View.VISIBLE); + viewLeftButton.setText(getString(R.string.generic_show_errors)); + viewLeftButton.setOnClickListener((view) -> ErrorListDialog.showDialog(getSupportFragmentManager(), getString(R.string.import_error_list_dialog_title), summary.getFileErrors())); + viewAlertIcon.setImageDrawable(getDrawable(R.drawable.ic_report_problem_24)); + String msg = getResources().getQuantityString(R.plurals.generic_completed_with_errors, summary.getErrorCount(), summary.getErrorCount()); + viewAlertMsg.setText(msg); + } else { + viewLeftButton.setVisibility(View.GONE); + viewAlertIcon.setImageDrawable(getDrawable(R.drawable.ic_dialog_success_24dp)); + viewAlertMsg.setText(getString(R.string.generic_completed)); + } + + viewRightButton.setOnClickListener((view) -> { + getViewModelStore().clear(); + finish(); + }); } } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAlreadyExistsException.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAlreadyExistsException.java new file mode 100644 index 000000000..c8f493314 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAlreadyExistsException.java @@ -0,0 +1,7 @@ +package de.dennisguse.opentracks.io.file.importer; + +class ImportAlreadyExistsException extends RuntimeException { + public ImportAlreadyExistsException(Exception e) { + super(e); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportParserException.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportParserException.java new file mode 100644 index 000000000..292d4786b --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportParserException.java @@ -0,0 +1,11 @@ +package de.dennisguse.opentracks.io.file.importer; + +class ImportParserException extends RuntimeException { + public ImportParserException(String msg) { + super(msg); + } + + public ImportParserException(Exception e) { + super(e); + } +} 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 new file mode 100644 index 000000000..3d3de8c66 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportService.java @@ -0,0 +1,90 @@ +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.io.InputStream; + +import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.content.data.Track; +import de.dennisguse.opentracks.io.file.TrackFileFormat; +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) { + TrackImporter trackImporter; + String fileExtension = FileUtils.getExtension(file); + + if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) { + trackImporter = new GpxFileTrackImporter(this); + } else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) { + trackImporter = new KmlFileTrackImporter(this); + } else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) { + trackImporter = new KmzTrackImporter(this, file.getUri()); + } else { + Log.d(TAG, "Unsupported file format."); + sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file.getName(), getString(R.string.import_unsupported_format)); + return; + } + + try (InputStream inputStream = getContentResolver().openInputStream(file.getUri())) { + Track.Id trackId = trackImporter.importFile(inputStream); + if (trackId != null) { + sendResult(ImportServiceResultReceiver.RESULT_CODE_IMPORTED, trackId, file.getName(), getString(R.string.import_file_imported, file.getName())); + } else { + sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, trackId, file.getName(), 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.getName(), 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.getName(), 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.getName(), e.getMessage()); + } + } + + private void sendResult(int resultCode, Track.Id trackId, String fileName, String message) { + Bundle bundle = new Bundle(); + bundle.putParcelable(ImportServiceResultReceiver.RESULT_EXTRA_TRACK_ID, trackId); + bundle.putString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME, fileName); + 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 new file mode 100644 index 000000000..5278db9dd --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportServiceResultReceiver.java @@ -0,0 +1,38 @@ +package de.dennisguse.opentracks.io.file.importer; + +import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; + +/** + * 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_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 Receiver receiver; + + public ImportServiceResultReceiver(Handler handler, Receiver receiver) { + super(handler); + this.receiver = receiver; + } + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (receiver != null) { + 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 new file mode 100644 index 000000000..67a6e2a9e --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportViewModel.java @@ -0,0 +1,119 @@ +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 de.dennisguse.opentracks.R; +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 ImportServiceResultReceiver resultReceiver; + private Summary summary; + private boolean cancel = false; + private List filesToImport = new ArrayList<>(); + + public ImportViewModel(@NonNull Application application) { + super(application); + resultReceiver = new ImportServiceResultReceiver(new Handler(), this); + summary = new Summary(); + } + + public LiveData getImportData(DocumentFile documentFile) { + if (importData == null) { + importData = new MutableLiveData<>(); + loadData(documentFile); + } + return importData; + } + + public void cancel() { + cancel = true; + } + + private void loadData(DocumentFile documentFile) { + List fileList = FileUtils.getFiles(documentFile); + summary.totalCount = fileList.size(); + for (DocumentFile df : fileList) { + filesToImport.add(df); + } + 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"); + } + + 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)); + break; + case ImportServiceResultReceiver.RESULT_CODE_IMPORTED: + summary.successCount++; + break; + case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS: + summary.existsCount++; + break; + default: + throw new RuntimeException(TAG + ": import service result code invalid: " + resultCode); + } + + importData.postValue(summary); + importNextFile(); + } + + public class Summary { + private int totalCount; + private int successCount; + private int existsCount; + private int errorCount; + private 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 getFileErrors() { + return fileErrors; + } + } +} diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java index 106039d2b..edd544f35 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java @@ -74,7 +74,13 @@ public class KmzTrackImporter implements TrackImporter { return null; } - trackId = findAndParseKmlFile(inputStream); + try { + trackId = findAndParseKmlFile(inputStream); + } catch (Exception e) { + cleanImport(context, importTrackId); + throw e; + } + if (trackId == null) { cleanImport(context, importTrackId); return null; @@ -184,9 +190,15 @@ public class KmzTrackImporter implements TrackImporter { zipInputStream.closeEntry(); } return trackId; + } catch (ImportParserException e) { + Log.e(TAG, "Unable to import file", e); + throw e; + } catch (ImportAlreadyExistsException e) { + Log.e(TAG, "Unable to import file", e); + throw e; } catch (IOException e) { Log.e(TAG, "Unable to import file", e); - return null; + throw new ImportParserException(e); } } @@ -242,11 +254,17 @@ public class KmzTrackImporter implements TrackImporter { * @param zipInputStream the zip input stream * @return the imported track id or -1L */ - private Track.Id parseKml(ZipInputStream zipInputStream) throws IOException { + private Track.Id parseKml(ZipInputStream zipInputStream) { KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context); try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) { return kmlFileTrackImporter.importFile(byteArrayInputStream); + } catch (ImportParserException e) { + throw e; + } catch (ImportAlreadyExistsException e) { + throw e; + } catch (Exception e) { + throw new ImportParserException(e); } } diff --git a/src/main/java/de/dennisguse/opentracks/util/ExportUtils.java b/src/main/java/de/dennisguse/opentracks/util/ExportUtils.java index bca6f2152..058a40da2 100644 --- a/src/main/java/de/dennisguse/opentracks/util/ExportUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/ExportUtils.java @@ -32,27 +32,6 @@ public class ExportUtils { } } - public static List getAllFiles(Context context, Uri directoryUri) { - List fileNames = new ArrayList<>(); - final ContentResolver resolver = context.getContentResolver(); - final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(directoryUri, DocumentsContract.getDocumentId(directoryUri)); - - try { - Cursor c = resolver.query( - childrenUri, - new String[] { DocumentsContract.Document.COLUMN_DISPLAY_NAME }, - null,null, null - ); - while (c.moveToNext()) { - fileNames.add(c.getString(0)); - } - } catch (Exception e) { - Log.w(TAG, "Failed query: " + e); - } - - return fileNames; - } - public static boolean exportTrack(Context context, TrackFileFormat trackFileFormat, DocumentFile directory, Track track) { TrackExporter trackExporter = trackFileFormat.newTrackExporter(context); @@ -81,6 +60,27 @@ public class ExportUtils { return filesName.contains(getExportFileNameByTrackId(trackId, trackFileFormatExtension)); } + public static List getAllFiles(Context context, Uri directoryUri) { + List fileNames = new ArrayList<>(); + final ContentResolver resolver = context.getContentResolver(); + final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(directoryUri, DocumentsContract.getDocumentId(directoryUri)); + + try { + Cursor c = resolver.query( + childrenUri, + new String[] { DocumentsContract.Document.COLUMN_DISPLAY_NAME }, + null,null, null + ); + while (c.moveToNext()) { + fileNames.add(c.getString(0)); + } + } catch (Exception e) { + Log.w(TAG, "Failed query: " + e); + } + + return fileNames; + } + private static Uri getExportDocumentFileUri(Context context, Track.Id trackId, TrackFileFormat trackFileFormat, DocumentFile directory) { String exportFileName = getExportFileNameByTrackId(trackId, trackFileFormat.getExtension()); Uri exportDocumentFileUri = findFile(context, directory.getUri(), exportFileName); diff --git a/src/main/res/layout/import_progress_activity.xml b/src/main/res/layout/import_progress_activity.xml new file mode 100644 index 000000000..e0acf821b --- /dev/null +++ b/src/main/res/layout/import_progress_activity.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +