diff --git a/src/main/java/de/dennisguse/opentracks/fragments/ConfirmDeleteDialogFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/ConfirmDeleteDialogFragment.java index 6dace2826..2cdccad8f 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/ConfirmDeleteDialogFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/ConfirmDeleteDialogFragment.java @@ -18,7 +18,6 @@ package de.dennisguse.opentracks.fragments; import android.app.Dialog; import android.content.Context; -import android.content.DialogInterface; import android.os.Bundle; import androidx.annotation.NonNull; @@ -69,17 +68,14 @@ public class ConfirmDeleteDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { final long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS); - int titleId; - int messageId; - titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.track_delete_one_confirm_title; - messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message : R.string.track_delete_one_confirm_message; + int titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.track_delete_one_confirm_title; + int messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message : R.string.track_delete_one_confirm_message; return DialogUtils.createConfirmationDialog( - getActivity(), titleId, getString(messageId), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - caller.onConfirmDeleteDone(trackIds); - } - }); + getActivity(), + titleId, + getString(messageId), + (dialog, which) -> caller.onConfirmDeleteDone(trackIds) + ); } /** @@ -89,12 +85,6 @@ public class ConfirmDeleteDialogFragment extends DialogFragment { */ public interface ConfirmDeleteCaller { - /** - * Called when confirm delete is done. - * - * @param trackIds list of track ids to delete. To delete all, set to size 1 - * with trackIds[0] == -1L - */ void onConfirmDeleteDone(long[] trackIds); } } \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/fragments/ImportFinishedDialogFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/ImportFinishedDialogFragment.java new file mode 100644 index 000000000..667506c0a --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/fragments/ImportFinishedDialogFragment.java @@ -0,0 +1,111 @@ +/* + * 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.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.NonNull; +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 new file mode 100644 index 000000000..ac9084428 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/fragments/ImportProgressDialogFragment.java @@ -0,0 +1,224 @@ +/* + * 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_DIRECTORY_URI_KEY = "directory_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; + + /** + * Create a new instance. + */ + public static void showDialog(FragmentManager fragmentManager, Uri documentFileUri) { + Bundle bundle = new Bundle(); + bundle.putParcelable(EXTRA_DIRECTORY_URI_KEY, documentFileUri); + + 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_DIRECTORY_URI_KEY); + DocumentFile documentFile = DocumentFile.fromTreeUri(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 dialog = DialogUtils.createHorizontalProgressDialog( + getContext(), R.string.import_progress_message, + dialogInterface -> { + importThread.interrupt(); + dialogInterface.dismiss(); + }, directoryDisplayName); + if (trackImportSuccessCount == 0) { + dialog.setIndeterminate(true); + } else { + setProgress(); + } + return dialog; + } + + @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 DocumentFile file; + + ImportThread(DocumentFile file) { + this.file = file; + } + + @Override + public void run() { + Log.w(TAG, "" + this); + 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(), -1L); + } else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) { + trackImporter = new KmzTrackImporter(context, -1L, file.getUri()); + } else { + Log.i(TAG, "Unsupported file format."); + return false; + } + + try (InputStream inputStream = context.getContentResolver().openInputStream(file.getUri())) { + return trackImporter.importFile(inputStream) != -1L; + } catch (IOException e) { + Log.e(TAG, "Unable to import file", e); + return false; + } + } + } + + public interface DismissCallback { + 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/importer/ImportActivity.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportActivity.java index f3bb09834..95cdddad9 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,192 +16,53 @@ package de.dennisguse.opentracks.io.file.importer; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ProgressDialog; import android.net.Uri; import android.os.Bundle; -import android.util.Log; -import androidx.documentfile.provider.DocumentFile; +import androidx.annotation.NonNull; import androidx.fragment.app.FragmentActivity; -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.util.DialogUtils; -import de.dennisguse.opentracks.util.FileUtils; +import de.dennisguse.opentracks.fragments.ImportFinishedDialogFragment; +import de.dennisguse.opentracks.fragments.ImportProgressDialogFragment; /** * An activity to import files from the external storage. * * @author Rodrigo Damazio */ -public class ImportActivity extends FragmentActivity { +public class ImportActivity extends FragmentActivity implements ImportProgressDialogFragment.DismissCallback, ImportFinishedDialogFragment.DismissCallback { private static final String TAG = ImportActivity.class.getSimpleName(); public static final String EXTRA_DIRECTORY_URI_KEY = "directory_uri"; - private static final int DIALOG_PROGRESS_ID = 0; - private static final int DIALOG_RESULT_ID = 1; - - private DocumentFile pickedDirectory; - private String directoryDisplayName; - - - private Thread importTask = new ImportThread(); - private ProgressDialog progressDialog; - - private int importedTrackCount; - private int totalTrackCount; + private static final String BUNDLE_ACTIVITY_RECREATED = "activity_recreated"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Uri directoryUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY); - pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri); - directoryDisplayName = FileUtils.getPath(pickedDirectory); - } - - @Override - protected void onStart() { - super.onStart(); - showDialog(DIALOG_PROGRESS_ID); - importTask.start(); - } - - @Override - protected void onPause() { - super.onPause(); - importTask.interrupt(); - } - - @Override - protected Dialog onCreateDialog(int id) { - switch (id) { - case DIALOG_PROGRESS_ID: - progressDialog = DialogUtils.createHorizontalProgressDialog( - this, R.string.import_progress_message, dialog -> { - importTask.interrupt(); - dialog.dismiss(); - finish(); - }, directoryDisplayName); - return progressDialog; - case DIALOG_RESULT_ID: - 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); - } - return new AlertDialog.Builder(this).setCancelable(true).setIcon(iconId) - .setMessage(message).setOnCancelListener(dialogInterface -> { - dialogInterface.dismiss(); - finish(); - }).setPositiveButton(R.string.generic_ok, (dialogInterface, which) -> { - dialogInterface.dismiss(); - finish(); - }).setTitle(titleId).create(); - default: - return null; + if (savedInstanceState == null || !savedInstanceState.getBoolean(BUNDLE_ACTIVITY_RECREATED, false)) { + Uri directoryUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY); + ImportProgressDialogFragment.showDialog(getSupportFragmentManager(), directoryUri); } } - /** - * Invokes when the associated AsyncTask completes. - * - * @param aSuccessCount the number of files successfully imported - * @param aTotalCount the number of files to import - */ - public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount) { + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(BUNDLE_ACTIVITY_RECREATED, true); + } + + @Override + public void onImportFinished(final String directoryDisplayName, final int successCount, final int fileCount) { runOnUiThread(() -> { - importedTrackCount = aSuccessCount; - totalTrackCount = aTotalCount; - removeDialog(DIALOG_PROGRESS_ID); - showDialog(DIALOG_RESULT_ID); + ImportFinishedDialogFragment.showDialog(getSupportFragmentManager(), directoryDisplayName, successCount, fileCount); }); } - /** - * Sets the progress dialog value. - * - * @param number the number of files imported - * @param max the maximum number of files - */ - public void setProgressDialogValue(int number, int max) { - if (progressDialog != null) { - progressDialog.setIndeterminate(false); - progressDialog.setMax(max); - progressDialog.setProgress(Math.min(number, max)); - } - } - - public class ImportThread extends Thread { - - @Override - public void run() { - List files = FileUtils.getFiles(pickedDirectory); - int totalTrackCount = files.size(); - int importedTrackCount = 0; - - for (int i = 0; i < totalTrackCount; i++) { - if (Thread.interrupted()) { - return; - } - if (importFile(files.get(i))) { - importedTrackCount++; - } - setProgressDialogValue(i + 1, totalTrackCount); - } - - onAsyncTaskCompleted(importedTrackCount, totalTrackCount); - } - - /** - * Imports a file. - * - * @param file the file - */ - private boolean importFile(final DocumentFile file) { - TrackImporter trackImporter; - String fileExtension = FileUtils.getExtension(file); - - if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) { - trackImporter = new GpxFileTrackImporter(ImportActivity.this); - } else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) { - trackImporter = new KmlFileTrackImporter(ImportActivity.this, -1L); - } else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) { - trackImporter = new KmzTrackImporter(ImportActivity.this, -1L, file.getUri()); - } else { - Log.i(TAG, "Unsupported file format."); - return false; - } - - try (InputStream inputStream = ImportActivity.this.getContentResolver().openInputStream(file.getUri())) { - return trackImporter.importFile(inputStream) != -1L; - } catch (IOException e) { - Log.e(TAG, "Unable to import file", e); - return false; - } - } + @Override + public void onDismissed() { + finish(); } } 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 dfae0a27f..7c2c9af08 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 @@ -60,7 +60,7 @@ public class KmzTrackImporter implements TrackImporter { * @param importTrackId track id to import to. This should not be -1L so that images in the kmz file can be imported. * @param uriFile URI of the kmz file. */ - KmzTrackImporter(Context context, long importTrackId, Uri uriFile) { + public KmzTrackImporter(Context context, long importTrackId, Uri uriFile) { this.context = context; this.importTrackId = importTrackId; this.uriKmzFile = uriFile; diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java index 2d51d154f..1525d865a 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java @@ -22,7 +22,7 @@ import java.io.InputStream; * * @author Jimmy Shih */ -interface TrackImporter { +public interface TrackImporter { /** * Import a file. diff --git a/src/main/java/de/dennisguse/opentracks/util/FileUtils.java b/src/main/java/de/dennisguse/opentracks/util/FileUtils.java index 6936adca1..8399f00ee 100644 --- a/src/main/java/de/dennisguse/opentracks/util/FileUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/FileUtils.java @@ -309,6 +309,8 @@ public class FileUtils { public static ArrayList getFiles(DocumentFile file) { ArrayList files = new ArrayList<>(); + if (!file.isDirectory()) return files; + for (DocumentFile candidate : file.listFiles()) { if (!candidate.isDirectory()) { files.add(candidate);