<NME> GPXImporter.java
<BEF> package cgeo.geocaching.files;

import cgeo.geocaching.R;
import cgeo.geocaching.activity.ActivityMixin;
import cgeo.geocaching.cgCache;
import cgeo.geocaching.cgSearch;
import cgeo.geocaching.cgeoapplication;
import cgeo.geocaching.activity.AbstractActivity;
import cgeo.geocaching.activity.IAbstractActivity;
import cgeo.geocaching.activity.Progress;
import cgeo.geocaching.utils.CancellableHandler;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

public class GPXImporter {
    static final int IMPORT_STEP_START = 0;
    static final int IMPORT_STEP_READ_FILE = 1;
    static final int IMPORT_STEP_READ_WPT_FILE = 2;
    static final int IMPORT_STEP_FINISHED = 5;
    static final int IMPORT_STEP_FINISHED_WITH_ERROR = 6;
    static final int IMPORT_STEP_CANCEL = 7;
    static final int IMPORT_STEP_CANCELED = 8;
    static final int IMPORT_STEP_STATIC_MAPS_SKIPPED = 9;

    public static final String WAYPOINTS_FILE_SUFFIX = "-wpts";
    public static final String WAYPOINTS_FILE_SUFFIX_AND_EXTENSION = WAYPOINTS_FILE_SUFFIX + FileUtils.GPX_FILE_EXTENSION;

    private static final List<String> GPX_MIME_TYPES = Arrays.asList("text/xml", "application/xml");
    private static final List<String> ZIP_MIME_TYPES = Arrays.asList("application/zip", "application/x-compressed", "application/x-zip-compressed", "application/x-zip", "application/octet-stream");

    private final Progress progress = new Progress(true);

    private final int listId;
    private final Activity fromActivity;
    private Resources res;
    private int listId;
    private IAbstractActivity fromActivity;
    private boolean closeOnFinish = false;

    public GPXImporter(final IAbstractActivity fromActivity, final int listId) {
        this.listId = listId;
        this.fromActivity = fromActivity;
        res = ((Activity) fromActivity).getResources();
    }

    /**
     *
     * @param file the file to import
     */
    public void importGPX(final File file) {
        if (StringUtils.endsWithIgnoreCase(file.getName(), FileUtils.GPX_FILE_EXTENSION)) {
            new ImportGpxFileThread(file, listId, importStepHandler, progressHandler).start();
        } else if (StringUtils.endsWithIgnoreCase(file.getName(), FileUtils.ZIP_FILE_EXTENSION) || StringUtils.endsWithIgnoreCase(file.getName(), FileUtils.COMPRESSED_GPX_FILE_EXTENSION)) {
            new ImportGpxZipFileThread(file, listId, importStepHandler, progressHandler).start();
        } else {
            new ImportLocFileThread(file, listId, importStepHandler, progressHandler).start();
        }
    }

    }

    /**
     * Import GPX provided via intent of import activity that instantiated this GPXImporter.
     */
    public void importGPX() {
        closeOnFinish = true;
        final ContentResolver contentResolver = ((Activity) fromActivity).getContentResolver();
        final Intent intent = ((Activity) fromActivity).getIntent();
        final Uri uri = intent.getData();

        String mimeType = intent.getType();
        if (mimeType == null) {
            mimeType = contentResolver.getType(uri);
        }
        // if mimetype can't be determined (e.g. for emulators email app), use a default
        // TODO: check if problem occurs with gmail as well
        if (mimeType == null) {
            mimeType = "application/zip";
        }
        if (fileType == FileType.UNKNOWN && uri != null) {
            fileType = getFileTypeFromPathName(uri.toString());
        }

        } else if (StringUtils.equalsIgnoreCase("application/zip", mimeType)) {
            new ImportGpxZipAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
        } else {
            // should not happen, GPXImportActivity accepts only mime types handled above
            closeActivity();
        }
    }

        }
    }

    @NonNull
    private static FileType getFileTypeFromPathName(
            final String pathName) {
        if (StringUtils.endsWithIgnoreCase(pathName, FileUtils.GPX_FILE_EXTENSION)) {
            return FileType.GPX;
        }

        if (StringUtils.endsWithIgnoreCase(pathName, FileUtils.LOC_FILE_EXTENSION)) {
            return FileType.LOC;
        }
        return FileType.UNKNOWN;
    }

    @NonNull
    private static FileType getFileTypeFromMimeType(
            final String mimeType) {
        if (GPX_MIME_TYPES.contains(mimeType)) {
            return FileType.GPX;
        }
        if (ZIP_MIME_TYPES.contains(mimeType)) {
            return FileType.ZIP;
        }
        return FileType.UNKNOWN;
    }

    private AbstractImportThread getImporterFromFileType(final Uri uri,
                                                         final ContentResolver contentResolver, final FileType fileType) {

        switch (fileType) {
            case ZIP:
                return new ImportGpxZipAttachmentThread(uri, contentResolver,
                        listId, importStepHandler, progressHandler);
            case GPX:
                return new ImportGpxAttachmentThread(uri, contentResolver, listId,
                        importStepHandler, progressHandler);
            case LOC:
                return new ImportLocAttachmentThread(uri, contentResolver, listId,
                        importStepHandler, progressHandler);
            default:
                return null;
        }
    }

    /**
     * Import GPX provided via intent of activity that instantiated this
     * GPXImporter.
     */
    public void importGPX() {
        final Intent intent = fromActivity.getIntent();
        final Uri uri = intent.getData();
        final String mimeType = intent.getType();
        importGPX(uri, mimeType, null);
    }

    private static final class ProgressHandler extends DisposableHandler {
        private final WeakReference<Progress> progressRef;

        ProgressHandler(final Progress progress) {
            progressRef = new WeakReference<>(progress);
        }

        @Override
        public void handleRegularMessage(final Message msg) {
            final Progress progress = progressRef.get();
            if (progress != null) {
                progress.setProgress(msg.arg1);
            }
        }
    }

    private static final class ImportStepHandler extends Handler {
        private final Activity fromActivity;
        private final Handler importFinishedHandler;
        private final Progress progress;
        private final WeakReference<DisposableHandler> progressHandlerRef;
        private final Resources res;

        ImportStepHandler(final GPXImporter importer, final Activity fromActivity) {
            this.fromActivity = importer.fromActivity;
            this.importFinishedHandler = importer.importFinishedHandler;
            this.progress = importer.progress;
            progressHandlerRef = new WeakReference<>(importer.progressHandler);
            res = fromActivity.getResources();
        }

        private void disposeProgressHandler() {
            final DisposableHandler progressHandler = progressHandlerRef.get();
            if (progressHandler != null) {
                progressHandler.dispose();
            }
        }

        private void importFinished() {
            if (importFinishedHandler != null) {
                importFinishedHandler.sendEmptyMessage(0);
            }
        }

        @Override
        public void handleMessage(final Message msg) {
            switch (msg.what) {
                case IMPORT_STEP_START:
                    final Message cancelMessage = obtainMessage(IMPORT_STEP_CANCEL);
                    progress.show(fromActivity, res.getString(R.string.gpx_import_title_reading_file), res.getString(R.string.gpx_import_loading_caches_with_filename, msg.obj), ProgressDialog.STYLE_HORIZONTAL, cancelMessage);
                    break;

                case IMPORT_STEP_READ_FILE:
                case IMPORT_STEP_READ_WPT_FILE:
                    progress.setMessage(res.getString(msg.arg1, msg.obj));
                    progress.setMaxProgressAndReset(msg.arg2);
                    break;

                case IMPORT_STEP_STATIC_MAPS_SKIPPED:
                    progress.dismiss();
                    disposeProgressHandler();
                    SimpleDialog.of(fromActivity).setTitle(R.string.gpx_import_title_caches_imported).setMessage(TextParam.text(res.getString(R.string.gpx_import_static_maps_skipped) + ", " + res.getQuantityString(R.plurals.gpx_import_caches_imported_with_filename, msg.arg1, msg.arg1, msg.obj))).show();
                    importFinished();
                    break;

                case IMPORT_STEP_FINISHED:
                    progress.dismiss();
                    SimpleDialog.of(fromActivity).setMessage(R.string.gpx_import_title_caches_imported).setMessage(TextParam.text(res.getQuantityString(R.plurals.gpx_import_caches_imported_with_filename, msg.arg1, msg.arg1, msg.obj))).show();
                    importFinished();
                    break;

                case IMPORT_STEP_FINISHED_WITH_ERROR:
                    progress.dismiss();
                    SimpleDialog.of(fromActivity).setMessage(R.string.gpx_import_title_caches_import_failed).setMessage(TextParam.text(res.getString(msg.arg1) + "\n\n" + msg.obj)).show();
                    importFinished();
                    break;

                case IMPORT_STEP_CANCEL:
                    progress.dismiss();
                    disposeProgressHandler();
                    break;

                case IMPORT_STEP_CANCELED:
                    ActivityMixin.showShortToast(fromActivity, res.getString(R.string.gpx_import_canceled_with_filename, msg.obj));
                    importFinished();
                    break;

                default:
                    break;
            }
        }
    }

    /**
     * @param gpxfile the gpx file
     * @return the expected file name of the waypoints file
     */
    static String getWaypointsFileNameForGpxFile(final File gpxfile) {
        if (gpxfile == null || !gpxfile.canRead()) {
            return null;
        }
        final File dir = gpxfile.getParentFile();
        final String[] filenameList = dir.list();
        if (filenameList == null) {
            return null;
        }
        final String gpxFileName = gpxfile.getName();
        for (final String filename : filenameList) {
            if (!StringUtils.containsIgnoreCase(filename, WAYPOINTS_FILE_SUFFIX)) {
                continue;
            }
            final String expectedGpxFileName = StringUtils.substringBeforeLast(filename, WAYPOINTS_FILE_SUFFIX)
                    + StringUtils.substringAfterLast(filename, WAYPOINTS_FILE_SUFFIX);
            if (gpxFileName.equals(expectedGpxFileName)) {
                return filename;
            }
        }
        return null;
    }

    protected void importFinished() {
        if (importFinishedHandler != null) {
            importFinishedHandler.sendEmptyMessage(0);
        }
    }
}
                case IMPORT_STEP_FINISHED:
                    progress.dismiss();
                    fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), msg.arg1 + " " + res.getString(R.string.gpx_import_caches_imported));
                    closeActivity();
                    break;

                case IMPORT_STEP_FINISHED_WITH_ERROR:
                    progress.dismiss();
                    fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_import_failed), res.getString(msg.arg1) + "\n\n" + msg.obj);
                    closeActivity();
                    break;

                case IMPORT_STEP_CANCEL:

                case IMPORT_STEP_CANCELED:
                    fromActivity.showShortToast(res.getString(R.string.gpx_import_canceled));
                    closeActivity();
                    break;
            }
        }
        }
    }

    protected void closeActivity() {
        if (closeOnFinish) {
            ((AbstractActivity) fromActivity).finish();
        }
    }
}

<MSG> Merge pull request #832 from stephanme/importzip

fix #826: import GPX from email/browser without GPXImportActivity
<DFF> @@ -5,7 +5,6 @@ import cgeo.geocaching.Settings;
 import cgeo.geocaching.cgCache;
 import cgeo.geocaching.cgSearch;
 import cgeo.geocaching.cgeoapplication;
-import cgeo.geocaching.activity.AbstractActivity;
 import cgeo.geocaching.activity.IAbstractActivity;
 import cgeo.geocaching.activity.Progress;
 import cgeo.geocaching.utils.CancellableHandler;
@@ -52,12 +51,13 @@ public class GPXImporter {
     private Resources res;
     private int listId;
     private IAbstractActivity fromActivity;
-    private boolean closeOnFinish = false;
+    private Handler importFinishedHandler;
 
-    public GPXImporter(final IAbstractActivity fromActivity, final int listId) {
+    public GPXImporter(final IAbstractActivity fromActivity, final int listId, final Handler importFinishedHandler) {
         this.listId = listId;
         this.fromActivity = fromActivity;
         res = ((Activity) fromActivity).getResources();
+        this.importFinishedHandler = importFinishedHandler;
     }
 
     /**
@@ -77,19 +77,18 @@ public class GPXImporter {
     }
 
     /**
-     * Import GPX provided via intent of import activity that instantiated this GPXImporter.
+     * Import GPX provided via intent of activity that instantiated this GPXImporter.
      */
     public void importGPX() {
-        closeOnFinish = true;
         final ContentResolver contentResolver = ((Activity) fromActivity).getContentResolver();
         final Intent intent = ((Activity) fromActivity).getIntent();
         final Uri uri = intent.getData();
 
         String mimeType = intent.getType();
-        if (mimeType == null) {
-            mimeType = contentResolver.getType(uri);
-        }
         // if mimetype can't be determined (e.g. for emulators email app), use a default
+        // contentResolver.getType(uri) doesn't help but throws exception for emulators email app
+        //   Permission Denial: reading com.android.email.provider.EmailProvider uri
+        // Google search says: there is no solution for this problem
         // TODO: check if problem occurs with gmail as well
         if (mimeType == null) {
             mimeType = "application/zip";
@@ -101,8 +100,7 @@ public class GPXImporter {
         } else if (StringUtils.equalsIgnoreCase("application/zip", mimeType)) {
             new ImportGpxZipAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
         } else {
-            // should not happen, GPXImportActivity accepts only mime types handled above
-            closeActivity();
+            importFinished();
         }
     }
 
@@ -355,13 +353,13 @@ public class GPXImporter {
                 case IMPORT_STEP_FINISHED:
                     progress.dismiss();
                     fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), msg.arg1 + " " + res.getString(R.string.gpx_import_caches_imported));
-                    closeActivity();
+                    importFinished();
                     break;
 
                 case IMPORT_STEP_FINISHED_WITH_ERROR:
                     progress.dismiss();
                     fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_import_failed), res.getString(msg.arg1) + "\n\n" + msg.obj);
-                    closeActivity();
+                    importFinished();
                     break;
 
                 case IMPORT_STEP_CANCEL:
@@ -371,7 +369,7 @@ public class GPXImporter {
 
                 case IMPORT_STEP_CANCELED:
                     fromActivity.showShortToast(res.getString(R.string.gpx_import_canceled));
-                    closeActivity();
+                    importFinished();
                     break;
             }
         }
@@ -386,9 +384,9 @@ public class GPXImporter {
         }
     }
 
-    protected void closeActivity() {
-        if (closeOnFinish) {
-            ((AbstractActivity) fromActivity).finish();
+    protected void importFinished() {
+        if (importFinishedHandler != null) {
+            importFinishedHandler.sendEmptyMessage(0);
         }
     }
 }

Merge pull request #832 from stephanme/importzip