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

import cgeo.geocaching.R;
import cgeo.geocaching.activity.ActivityMixin;
import cgeo.geocaching.activity.Progress;
import cgeo.geocaching.ui.TextParam;
import cgeo.geocaching.ui.dialog.SimpleDialog;
import cgeo.geocaching.utils.DisposableHandler;
import cgeo.geocaching.utils.FileUtils;
import cgeo.geocaching.utils.Log;

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);

    static final int IMPORT_STEP_STATIC_MAPS_SKIPPED = 9;

    public static final String GPX_FILE_EXTENSION = ".gpx";
    public static final String ZIP_FILE_EXTENSION = ".zip";
    public static final String WAYPOINTS_FILE_SUFFIX = "-wpts";
    public static final String WAYPOINTS_FILE_SUFFIX_AND_EXTENSION = WAYPOINTS_FILE_SUFFIX + GPX_FILE_EXTENSION;
        this.listId = listId;
        this.fromActivity = fromActivity;
        this.importFinishedHandler = importFinishedHandler;
        importStepHandler = new ImportStepHandler(this, fromActivity);
    }

    /**
     * Import GPX file. Currently supports *.gpx, *.zip (containing gpx files, e.g. PQ queries), *.ggz or *.loc files.
     *
     * @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 file from URI.
     *
     * @param uri URI of the file to import
     */
    public void importGPX(final Uri uri, @Nullable final String mimeType, @Nullable final String pathName) {
        final ContentResolver contentResolver = fromActivity.getContentResolver();

        Log.i("importGPX: " + uri + ", mimetype=" + mimeType);

        FileType fileType = new FileTypeDetector(uri, contentResolver)
                .getFileType();

        if (fileType == FileType.UNKNOWN) {
            fileType = getFileTypeFromPathName(pathName);
        }
        if (fileType == FileType.UNKNOWN) {
            fileType = getFileTypeFromMimeType(mimeType);
        }
        if (fileType == FileType.UNKNOWN && uri != null) {
            fileType = getFileTypeFromPathName(uri.toString());
        }

        final AbstractImportThread importer = getImporterFromFileType(uri, contentResolver,
                fileType);
        // Google search says: there is no solution for this problem
        // Gmail doesn't work at all, see #967
        if (mimeType == null) {
            if (StringUtils.endsWithIgnoreCase(uri.getPath(), GPX_FILE_EXTENSION)) {
                mimeType = "application/xml";
            } else {
                // if we can't determine a better type, default to zip import

    @NonNull
    private static FileType getFileTypeFromPathName(
            final String pathName) {

        Log.i("importGPX: " + uri + ", mimetype=" + mimeType);
        if (GPX_MIME_TYPES.contains(mimeType)) {
            new ImportGpxAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
        } else if (ZIP_MIME_TYPES.contains(mimeType)) {
            new ImportGpxZipAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
        } else {
    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:
        }
    }

    static abstract class ImportGpxThread extends ImportThread {

        protected ImportGpxThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
                    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);
        }
    }
}

<MSG> Merge pull request #2863 from campbeb/locIntentImport

Fix #2862 - Support LOC file import via intent
<DFF> @@ -49,6 +49,7 @@ public class GPXImporter {
     static final int IMPORT_STEP_STATIC_MAPS_SKIPPED = 9;
 
     public static final String GPX_FILE_EXTENSION = ".gpx";
+    public static final String LOC_FILE_EXTENSION = ".loc";
     public static final String ZIP_FILE_EXTENSION = ".zip";
     public static final String WAYPOINTS_FILE_SUFFIX = "-wpts";
     public static final String WAYPOINTS_FILE_SUFFIX_AND_EXTENSION = WAYPOINTS_FILE_SUFFIX + GPX_FILE_EXTENSION;
@@ -101,7 +102,7 @@ public class GPXImporter {
         // Google search says: there is no solution for this problem
         // Gmail doesn't work at all, see #967
         if (mimeType == null) {
-            if (StringUtils.endsWithIgnoreCase(uri.getPath(), GPX_FILE_EXTENSION)) {
+            if (StringUtils.endsWithIgnoreCase(uri.getPath(), GPX_FILE_EXTENSION) || StringUtils.endsWithIgnoreCase(uri.getPath(), LOC_FILE_EXTENSION)) {
                 mimeType = "application/xml";
             } else {
                 // if we can't determine a better type, default to zip import
@@ -112,7 +113,11 @@ public class GPXImporter {
 
         Log.i("importGPX: " + uri + ", mimetype=" + mimeType);
         if (GPX_MIME_TYPES.contains(mimeType)) {
-            new ImportGpxAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
+            if (StringUtils.endsWithIgnoreCase(uri.getPath(), LOC_FILE_EXTENSION)) {
+                new ImportLocAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
+            } else {
+                new ImportGpxAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
+            }
         } else if (ZIP_MIME_TYPES.contains(mimeType)) {
             new ImportGpxZipAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
         } else {
@@ -207,6 +212,30 @@ public class GPXImporter {
         }
     }
 
+    static class ImportLocAttachmentThread extends ImportThread {
+        private final Uri uri;
+        private ContentResolver contentResolver;
+
+        public ImportLocAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+            super(listId, importStepHandler, progressHandler);
+            this.uri = uri;
+            this.contentResolver = contentResolver;
+        }
+
+        @Override
+        protected Collection<Geocache> doImport() throws IOException, ParserException {
+            Log.i("Import LOC from uri: " + uri);
+            importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, -1));
+            InputStream is = contentResolver.openInputStream(uri);
+            LocParser parser = new LocParser(listId);
+            try {
+                return parser.parse(is, progressHandler);
+            } finally {
+                is.close();
+            }
+        }
+    }
+
     static abstract class ImportGpxThread extends ImportThread {
 
         protected ImportGpxThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {

Merge pull request #2863 from campbeb/locIntentImport