com.esri.cordova.geolocation.controllers.GPSController.java Source code

Java tutorial

Introduction

Here is the source code for com.esri.cordova.geolocation.controllers.GPSController.java

Source

/**
 * @author Andy Gup
 *
 * Copyright 2016 Esri
 * 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 com.esri.cordova.geolocation.controllers;

import android.content.Context;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.location.OnNmeaMessageListener;
import android.location.GpsStatus.NmeaListener;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;

import com.esri.cordova.geolocation.model.Coordinate;
import com.esri.cordova.geolocation.model.InitStatus;
import com.esri.cordova.geolocation.model.LocationDataBuffer;
import com.esri.cordova.geolocation.utils.ErrorMessages;
import com.esri.cordova.geolocation.utils.JSONHelper;
import com.esri.cordova.geolocation.utils.GPSLocation;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.PluginResult;
import java.util.ArrayList;

public final class GPSController implements Runnable {

    private static LocationManager _locationManager = null;
    private static LocationListener _locationListenerGPSProvider = null;
    private static OnNmeaMessageListener _nmeaListener = null;
    private static GpsStatus.NmeaListener _nmeaStatusListener = null;
    private static GpsStatus.Listener _gpsStatusListener = null;

    private static CallbackContext _callbackContext; // Threadsafe
    private static CordovaInterface _cordova;

    private static long _minDistance = 0;
    private static long _minTime = 0;
    private static boolean _buffer = false;
    private static int _bufferSize = 0;
    private static boolean _returnCache = false;
    private static boolean _returnSatelliteData = false;
    private static boolean _returnNMEAData = false;
    private static boolean _returnLocationData = false;
    private static LocationDataBuffer _locationDataBuffer = null;
    private static GPSLocation gpsloc = new GPSLocation();

    private static final String TAG = "GeolocationPlugin";
    private ArrayList<String> nmeaMessages = new ArrayList<String>();
    private ArrayList<String> parsingErrors = new ArrayList<String>();
    private ArrayList<String> parsedTypes = new ArrayList<String>();

    public GPSController(CordovaInterface cordova, CallbackContext callbackContext, long minDistance, long minTime,
            boolean returnCache, boolean returnSatelliteData, boolean returnNMEAData, boolean returnLocationData,
            boolean buffer, int bufferSize) {
        _cordova = cordova;
        _callbackContext = callbackContext;
        _minDistance = minDistance;
        _minTime = minTime;
        _returnCache = returnCache;
        _returnSatelliteData = returnSatelliteData;
        _buffer = buffer;
        _bufferSize = bufferSize;
        _returnNMEAData = returnNMEAData;
        _returnLocationData = returnLocationData;
    }

    public void run() {
        // Reference: http://developer.android.com/reference/android/os/Process.html#THREAD_PRIORITY_BACKGROUND
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);

        // We are running a Looper to allow the Cordova CallbackContext to be passed within the Thread as a message.
        if (Looper.myLooper() == null) {
            _locationManager = (LocationManager) _cordova.getActivity().getSystemService(Context.LOCATION_SERVICE);
            Looper.prepare();
            startLocation();
            Looper.loop();
        }
    }

    public void startLocation() {

        if (!Thread.currentThread().isInterrupted()) {
            Log.i(TAG, "Available location providers: " + _locationManager.getAllProviders().toString());

            Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread thread, Throwable throwable) {
                    Log.e(TAG, "Failing gracefully after detecting an uncaught exception on GPSController thread. "
                            + throwable.getMessage());
                    sendCallback(PluginResult.Status.ERROR, JSONHelper.errorJSON("TEST",
                            "Failing gracefully after detecting an uncaught exception on GPSController thread. "
                                    + thread.toString() + " - " + throwable.getMessage() + "Stacktrace: "
                                    + throwable.getStackTrace().toString()));
                    /*  sendCallback(PluginResult.Status.ERROR,
                        JSONHelper.errorJSON(LocationManager.GPS_PROVIDER, ErrorMessages.UNCAUGHT_THREAD_EXCEPTION()));*/
                    stopLocation();
                }
            });

            if (_buffer) {
                _locationDataBuffer = new LocationDataBuffer(_bufferSize);
            }

            final InitStatus gpsListener = setLocationListenerGPSProvider();
            //final InitStatus nmeaListener = new InitStatus(); // setNMEAProvider();
            //  final InitStatus nmeaListener = setNMEAProvider();
            InitStatus nmeaListener = new InitStatus();
            if (_returnNMEAData) {
                nmeaListener = setNMEAListener();
            }

            InitStatus satelliteListener = new InitStatus();
            if (_returnSatelliteData) {
                satelliteListener = setGPSStatusListener();
            }

            if (!gpsListener.success || !satelliteListener.success || !nmeaListener.success) {
                if (!nmeaListener.success) {
                    if (nmeaListener.exception == null) {
                        sendCallback(PluginResult.Status.ERROR, JSONHelper.errorJSON("NMEA", nmeaListener.error));
                    } else {
                        // Handle system exceptions
                        sendCallback(PluginResult.Status.ERROR,
                                JSONHelper.errorJSON("NMEA", nmeaListener.exception));
                    }
                }
                if (!gpsListener.success) {
                    if (gpsListener.exception == null) {
                        // Handle custom error messages
                        sendCallback(PluginResult.Status.ERROR,
                                JSONHelper.errorJSON(LocationManager.GPS_PROVIDER, gpsListener.error));
                    } else {
                        // Handle system exceptions
                        sendCallback(PluginResult.Status.ERROR,
                                JSONHelper.errorJSON(LocationManager.GPS_PROVIDER, gpsListener.exception));
                    }
                }
            } else {
                // Return cache immediate if requested, otherwise wait for a location provider
                if (_returnCache) {

                    Location location = null;

                    try {
                        location = _locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                    } catch (SecurityException exc) {
                        Log.e(TAG, exc.getMessage());
                    }

                    final String parsedLocation;

                    // If the provider is disabled or currently unavailable then null is returned
                    // Some devices will return null if the GPS is still warming up and hasn't gotten
                    // a full signal lock yet.
                    if (location != null) {
                        parsedLocation = JSONHelper.locationJSON(LocationManager.GPS_PROVIDER, location, true);
                        sendCallback(PluginResult.Status.OK, parsedLocation);
                    }
                }
            }
        } else {
            Log.e(TAG, "Not starting GPSController due to thread interrupt.");
        }
    }

    /**
     * Full stop using brute force. Works with many Android versions.
     */
    public void stopLocation() {

        if (_locationManager != null) {
            Log.d(TAG, "Attempting to stop gps geolocation");

            if (_gpsStatusListener != null) {
                _locationManager.removeGpsStatusListener(_gpsStatusListener);
                _gpsStatusListener = null;
            }

            if (_locationListenerGPSProvider != null) {

                try {
                    _locationManager.removeUpdates(_locationListenerGPSProvider);
                } catch (SecurityException exc) {
                    Log.e(TAG, exc.getMessage());
                }

                _locationListenerGPSProvider = null;
            }

            if (_nmeaListener != null) {

                try {
                    _locationManager.removeNmeaListener(_nmeaListener);
                } catch (SecurityException exc) {
                    Log.e(TAG, exc.getMessage());
                }

                _nmeaListener = null;
            }

            _locationManager = null;

            // Clear all elements from the buffer
            if (_locationDataBuffer != null) {
                _locationDataBuffer.clear();
            }

            try {
                Thread.currentThread().interrupt();
            } catch (SecurityException exc) {
                Log.e(TAG, exc.getMessage());
                sendCallback(PluginResult.Status.ERROR, JSONHelper.errorJSON(LocationManager.GPS_PROVIDER,
                        ErrorMessages.FAILED_THREAD_INTERRUPT()));
            }
        } else {
            Log.d(TAG, "GPS location already stopped");
        }
    }

    /**
     * Callback handler for this Class
     * @param status Message status
     * @param message Any message
     */
    private static void sendCallback(PluginResult.Status status, String message) {
        if (!Thread.currentThread().isInterrupted()) {
            final PluginResult result = new PluginResult(status, message);
            result.setKeepCallback(true);
            _callbackContext.sendPluginResult(result);
        }
    }

    private static InitStatus setGPSStatusListener() {

        // IMPORTANT: The GpsStatus.Listener Interface is deprecated at API 24.
        // Reference: https://developer.android.com/reference/android/location/package-summary.html
        _gpsStatusListener = new GpsStatus.Listener() {

            @Override
            public void onGpsStatusChanged(int event) {
                Log.d(TAG, "GPS status changed.");

                // Ignore if GPS_EVENT_STARTED or GPS_EVENT_STOPPED
                if (!Thread.currentThread().isInterrupted()
                        && (event == GpsStatus.GPS_EVENT_FIRST_FIX || event == GpsStatus.GPS_EVENT_SATELLITE_STATUS)
                        && _locationManager != null) {
                    sendCallback(PluginResult.Status.OK,
                            JSONHelper.satelliteDataJSON(_locationManager.getGpsStatus(null)));
                }
            }
        };

        final InitStatus status = new InitStatus();

        final Boolean gpsProviderEnabled = _locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);

        if (gpsProviderEnabled) {
            try {
                _locationManager.addGpsStatusListener(_gpsStatusListener);
            }
            // if the ACCESS_FINE_LOCATION permission is not present
            catch (SecurityException exc) {
                status.success = false;
                status.exception = exc.getMessage();
            }
        } else {
            //GPS not enabled
            status.success = false;
            status.error = ErrorMessages.GPS_UNAVAILABLE();
        }

        return status;
    }

    /* Android 5x */
    private InitStatus setNMEAListener() {
        final InitStatus status = new InitStatus();
        try {
            _nmeaStatusListener = new GpsStatus.NmeaListener() {

                public void onNmeaReceived(long timestamp, String message) {
                    if (!Thread.currentThread().isInterrupted()) {
                        try {

                            /* Parsing NMEA Data to Object */
                            if (gpsloc.getUTC(message) != null) {
                                if (!gpsloc.checkUTC(gpsloc.getUTC(message))) {
                                    /* Auswerten des Objektes und zurcksenden! */
                                    String loc = gpsloc.getLocation(parsingErrors, parsedTypes);
                                    if (loc != null) {
                                        sendCallback(PluginResult.Status.OK,
                                                JSONHelper.nmeaJSON("NMEA", loc, timestamp));
                                    }
                                    //   parsingErrors = new ArrayList<String>();
                                    //   parsedTypes = new ArrayList<String>();
                                    gpsloc.clear();
                                }
                            }
                            /* Gehrt noch zur Serie */
                            String mt = null;
                            try {
                                mt = gpsloc.messageType(message);
                                //parsedTypes.add(mt);
                            } catch (Exception exc) {
                                sendCallback(PluginResult.Status.ERROR, JSONHelper.errorJSON("NMEA",
                                        "Could not get Message type" + exc.getMessage() + "- " + message));
                            }

                            try {
                                if (mt != null && !mt.isEmpty()) {
                                    mt = mt.toUpperCase();
                                    //parsedTypes.add(mt);
                                    if (mt.equalsIgnoreCase("GST")) {
                                        gpsloc.parseGST(message);
                                        if (gpsloc.parseError()) {
                                            parsingErrors.add(gpsloc.getError());
                                        }
                                    } else if (mt.equalsIgnoreCase("GGA")) {
                                        gpsloc.parseGGA(message);
                                        if (gpsloc.parseError()) {
                                            parsingErrors.add(gpsloc.getError());
                                        }
                                    } else if (mt.equalsIgnoreCase("VTG")) {
                                        gpsloc.parseVTG(message);
                                        if (gpsloc.parseError()) {
                                            parsingErrors.add(gpsloc.getError());
                                        }
                                    } else if (mt.equalsIgnoreCase("ZDA")) {
                                        gpsloc.parseZDA(message);
                                        if (gpsloc.parseError()) {
                                            parsingErrors.add(gpsloc.getError());
                                        }
                                    } else if (mt.equalsIgnoreCase("GSA")) {
                                        gpsloc.parseGSA(message);
                                        if (gpsloc.parseError()) {
                                            parsingErrors.add(gpsloc.getError());
                                        }
                                    }
                                }
                            } catch (Exception exc) {
                                /*sendCallback(PluginResult.Status.ERROR,
                                  JSONHelper.errorJSON("NMEA", "Could not parse"
                                + exc.getMessage() + "- "+message));*/
                            }

                        } catch (Exception exc) {
                            /*sendCallback(PluginResult.Status.ERROR,
                               JSONHelper.errorJSON("NMEA", "Meine Ausgabe - vielleicht mehr info"
                                       + exc.getMessage()));*/
                        }
                    }
                }
            };
        } catch (Exception ex) {
            status.success = false;
            status.exception = ex.getMessage();
        }

        final Boolean gpsProviderEnabled = _locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);

        if (gpsProviderEnabled) {
            try {
                Log.d(TAG, "Starting NMEA");
                // Register the listener with the Location Manager to receive location updates
                _locationManager.addNmeaListener(_nmeaStatusListener);
            } catch (SecurityException exc) {
                Log.e(TAG, "Unable to start NMEA listener. " + exc.getMessage());
                status.success = false;
                status.exception = exc.getMessage();
            }
        } else {
            status.success = false;
            status.error = ErrorMessages.GPS_UNAVAILABLE();
        }

        return status;
    }

    /* Fr Android 6) */
    private InitStatus setNMEAProvider() {
        final InitStatus status = new InitStatus();
        try {
            _nmeaListener = new OnNmeaMessageListener() {

                public void onNmeaMessage(String message, long timestamp) {
                    //   if(!Thread.currentThread().isInterrupted()){
                    try {
                        nmeaMessages.add(message);
                        if (nmeaMessages.size() > 100) {
                            String ausgabeStr = "[";
                            for (String ausgabe : nmeaMessages) {
                                if (!ausgabeStr.equalsIgnoreCase("[")) {
                                    ausgabeStr += ",";
                                }
                                ausgabeStr += "'" + ausgabe + "'";
                            }
                            sendCallback(PluginResult.Status.OK,
                                    JSONHelper.nmeaJSON("NMEA", ausgabeStr, timestamp));
                        }
                    } catch (Exception exc) {
                        sendCallback(PluginResult.Status.ERROR, JSONHelper.errorJSON("NMEA",
                                "Meine Ausgabe - vielleicht mehr info" + exc.getMessage()));
                    }
                    //   }
                }
            };
        } catch (Exception ex) {
            status.success = false;
            status.exception = ex.getMessage();
        }

        final Boolean gpsProviderEnabled = _locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);

        if (gpsProviderEnabled) {
            try {
                Log.d(TAG, "Starting NMEA");
                // Register the listener with the Location Manager to receive location updates
                //    _locationManager.addNmeaListener(_nmeaListener);
            } catch (SecurityException exc) {
                Log.e(TAG, "Unable to start NMEA listener. " + exc.getMessage());
                status.success = false;
                status.exception = exc.getMessage();
            }
        } else {
            status.success = false;
            status.error = ErrorMessages.GPS_UNAVAILABLE();
        }

        return status;
    }

    private InitStatus setLocationListenerGPSProvider() {
        _locationListenerGPSProvider = new LocationListener() {

            public void onLocationChanged(Location location) {
                if (_returnLocationData) {
                    if (_buffer && !Thread.currentThread().isInterrupted()) {
                        final Coordinate coordinate = new Coordinate();
                        coordinate.latitude = location.getLatitude();
                        coordinate.longitude = location.getLongitude();
                        coordinate.accuracy = location.getAccuracy();

                        // Get the size of the buffer
                        final int size = _locationDataBuffer.add(coordinate);

                        final Coordinate center = _locationDataBuffer.getGeographicCenter();
                        sendCallback(PluginResult.Status.OK,
                                JSONHelper.locationJSON(LocationManager.GPS_PROVIDER, location, false, _buffer,
                                        center.latitude, center.longitude, center.accuracy, size));

                    } else {
                        sendCallback(PluginResult.Status.OK,
                                JSONHelper.locationJSON(LocationManager.GPS_PROVIDER, location, false));
                    }
                }
            }

            public void onStatusChanged(String provider, int status, Bundle extras) {
                switch (status) {
                case LocationProvider.OUT_OF_SERVICE:
                    // Reference: https://developer.android.com/reference/android/location/LocationProvider.html#OUT_OF_SERVICE
                    Log.d(TAG, "Location Status Changed: " + ErrorMessages.GPS_OUT_OF_SERVICE().message);
                    sendCallback(PluginResult.Status.ERROR,
                            JSONHelper.errorJSON(LocationManager.GPS_PROVIDER, ErrorMessages.GPS_OUT_OF_SERVICE()));

                    break;
                case LocationProvider.TEMPORARILY_UNAVAILABLE:
                    Log.d(TAG, "Location Status Changed: " + ErrorMessages.GPS_UNAVAILABLE().message);
                    sendCallback(PluginResult.Status.ERROR,
                            JSONHelper.errorJSON(LocationManager.GPS_PROVIDER, ErrorMessages.GPS_UNAVAILABLE()));
                    break;
                case LocationProvider.AVAILABLE:
                    Log.d(TAG, "Location Status Changed: GPS Available");
                    break;
                }
            }

            public void onProviderEnabled(String provider) {
                startLocation();
            }

            public void onProviderDisabled(String provider) {
                stopLocation();
            }
        };

        final InitStatus status = new InitStatus();
        final Boolean gpsProviderEnabled = _locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);

        if (gpsProviderEnabled) {

            try {
                Log.d(TAG, "Starting LocationManager.GPS_PROVIDER");
                // Register the listener with the Location Manager to receive location updates
                _locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, _minTime, _minDistance,
                        _locationListenerGPSProvider);
            } catch (SecurityException exc) {
                Log.e(TAG, "Unable to start GPS provider. " + exc.getMessage());
                status.success = false;
                status.exception = exc.getMessage();
            }
        } else {
            Log.w(TAG, ErrorMessages.GPS_UNAVAILABLE().message);
            //GPS not enabled
            status.success = false;
            status.error = ErrorMessages.GPS_UNAVAILABLE();
        }

        return status;
    }
}