<NME> GpxExport.java
<BEF> package cgeo.geocaching.export;

import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.R;
import cgeo.geocaching.Settings;
import cgeo.geocaching.Waypoint;
import cgeo.geocaching.activity.ActivityMixin;
import cgeo.geocaching.activity.Progress;
import cgeo.geocaching.cgCache;
import cgeo.geocaching.cgData;
import cgeo.geocaching.enumerations.CacheAttribute;
import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.utils.ShareUtils;
import cgeo.geocaching.utils.UriUtils;

import android.app.Activity;
import android.app.Dialog;
import android.net.Uri;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

public class GpxExport extends AbstractExport {

    private String fileName = "geocache.gpx"; // used in tests
    private String title = null;

    public GpxExport() {
        super(R.string.export_gpx);
    }

    public void export(@NonNull final List<Geocache> caches, @Nullable final Activity activity, @Nullable final String title) {
        if (StringUtils.isNotBlank(title)) {
            this.title = title.replace("/", "_");
        }
        export(caches, activity);
    }

    @Override
    public void export(@NonNull final List<Geocache> caches, @Nullable final Activity activity) {
        final String[] geocodes = getGeocodes(caches);
        calculateFileName(geocodes);

        } else {
            // Show configuration dialog
            new ExportOptionsDialog(caches, activity).show();
        }
    }

    /**
     * A dialog to allow the user to set options for the export.
     *
     * Currently available option is: opening of share menu after successful export
     */
    private class ExportOptionsDialog extends AlertDialog {
        public ExportOptionsDialog(final List<cgCache> caches, final Activity activity) {
            super(activity);

            View layout = activity.getLayoutInflater().inflate(R.layout.gpx_export_dialog, null);
            setView(layout);

            final TextView text = (TextView) layout.findViewById(R.id.info);
            text.setText(getString(R.string.export_gpx_info, Settings.getGpxExportDir()));

            final CheckBox shareOption = (CheckBox) layout.findViewById(R.id.share);

            shareOption.setChecked(Settings.getShareAfterExport());

            shareOption.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Settings.setShareAfterExport(shareOption.isChecked());
                }
            });

            layout.findViewById(R.id.export).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    dismiss();
                    new ExportTask(caches, activity).execute((Void) null);
                }
            });
        }
    }

    private class ExportTask extends AsyncTask<Void, Integer, File> {
        final CheckBox includeFoundStatus = layout.findViewById(R.id.include_found_status);
        includeFoundStatus.setChecked(Settings.getIncludeFoundStatus());

        builder.setPositiveButton(R.string.export, (dialog, which) -> {
            Settings.setIncludeFoundStatus(includeFoundStatus.isChecked());
            dialog.dismiss();
            new ExportTask(activity).execute(geocodes);
        });

        return builder.create();
    }

    private static String[] getGeocodes(final List<Geocache> caches) {
        return Geocache.getGeocodes(caches).toArray(new String[caches.size()]);
    }

    protected class ExportTask extends AsyncTaskWithProgress<String, Uri> {

        /**
         * Instantiates and configures the task for exporting field notes.
         *
         * @param activity optional: Show a progress bar and toasts
         */
        public ExportTask(final Activity activity) {
            super(activity, getProgressTitle());
        }

        @Override
        protected Uri doInBackgroundInternal(final String[] geocodes) {
            final List<String> allGeocodes = new ArrayList<>(Arrays.asList(geocodes));

            setMessage(CgeoApplication.getInstance().getResources().getQuantityString(R.plurals.cache_counts, allGeocodes.size(), allGeocodes.size()));

            final Uri uri = ContentStorage.get().create(PersistableFolder.GPX, fileName);
            if (uri == null) {
                return null;
            }

            BufferedWriter writer = null;
            try (OutputStream os = ContentStorage.get().openForWrite(uri)) {
                if (os == null) {
                    return null;
                }

                writer = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
                new GpxSerializer().writeGPX(allGeocodes, writer, ExportTask.this::publishProgress);
            } catch (final IOException e) {
                Log.e("GpxExport.ExportTask export", e);
                // delete partial GPX file on error
                ContentStorage.get().delete(uri);

                return null;
            } finally {
                IOUtils.closeQuietly(writer);
            }

            return uri;
        }

        @Override
        protected void onPostExecuteInternal(final Uri uri) {
            final Activity activityLocal = activity;
            if (activityLocal != null) {
                if (uri != null) {
                    ShareUtils.shareOrDismissDialog(activityLocal, uri, "application/xml", R.string.export, getName() + ' ' + activityLocal.getString(R.string.export_exportedto) + ": " + UriUtils.toUserDisplayableString(uri));
                } else {
                    ActivityMixin.showToast(activityLocal, activityLocal.getString(R.string.export_failed));
                }
            }
        }

    }
}

<MSG> Merge branch 'master' of github.com:cgeo/c-geo-opensource into upstream

<DFF> @@ -4,10 +4,10 @@ import cgeo.geocaching.LogEntry;
 import cgeo.geocaching.R;
 import cgeo.geocaching.Settings;
 import cgeo.geocaching.Waypoint;
-import cgeo.geocaching.activity.ActivityMixin;
-import cgeo.geocaching.activity.Progress;
 import cgeo.geocaching.cgCache;
 import cgeo.geocaching.cgData;
+import cgeo.geocaching.activity.ActivityMixin;
+import cgeo.geocaching.activity.Progress;
 import cgeo.geocaching.enumerations.CacheAttribute;
 import cgeo.geocaching.enumerations.LoadFlags;
 import cgeo.geocaching.geopoint.Geopoint;
@@ -20,7 +20,9 @@ import org.xmlpull.v1.XmlSerializer;
 
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.Dialog;
 import android.app.ProgressDialog;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -58,44 +60,40 @@ class GpxExport extends AbstractExport {
 
         } else {
             // Show configuration dialog
-            new ExportOptionsDialog(caches, activity).show();
+            getExportDialog(caches, activity).show();
         }
     }
 
-    /**
-     * A dialog to allow the user to set options for the export.
-     *
-     * Currently available option is: opening of share menu after successful export
-     */
-    private class ExportOptionsDialog extends AlertDialog {
-        public ExportOptionsDialog(final List<cgCache> caches, final Activity activity) {
-            super(activity);
+    private Dialog getExportDialog(final List<cgCache> caches, final Activity activity) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
 
-            View layout = activity.getLayoutInflater().inflate(R.layout.gpx_export_dialog, null);
-            setView(layout);
+        View layout = activity.getLayoutInflater().inflate(R.layout.gpx_export_dialog, null);
+        builder.setView(layout);
 
-            final TextView text = (TextView) layout.findViewById(R.id.info);
-            text.setText(getString(R.string.export_gpx_info, Settings.getGpxExportDir()));
+        final TextView text = (TextView) layout.findViewById(R.id.info);
+        text.setText(getString(R.string.export_gpx_info, Settings.getGpxExportDir()));
 
-            final CheckBox shareOption = (CheckBox) layout.findViewById(R.id.share);
+        final CheckBox shareOption = (CheckBox) layout.findViewById(R.id.share);
 
-            shareOption.setChecked(Settings.getShareAfterExport());
+        shareOption.setChecked(Settings.getShareAfterExport());
 
-            shareOption.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    Settings.setShareAfterExport(shareOption.isChecked());
-                }
-            });
+        shareOption.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Settings.setShareAfterExport(shareOption.isChecked());
+            }
+        });
 
-            layout.findViewById(R.id.export).setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    dismiss();
-                    new ExportTask(caches, activity).execute((Void) null);
-                }
-            });
-        }
+        builder.setPositiveButton(R.string.export, new DialogInterface.OnClickListener() {
+
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                dialog.dismiss();
+                new ExportTask(caches, activity).execute((Void) null);
+            }
+        });
+
+        return builder.create();
     }
 
     private class ExportTask extends AsyncTask<Void, Integer, File> {

Merge branch 'master' of github.com:cgeo/c-geo-opensource into upstream