org.freedesktop.geoclueshare.LocationService.java Source code

Java tutorial

Introduction

Here is the source code for org.freedesktop.geoclueshare.LocationService.java

Source

package org.freedesktop.geoclueshare;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

/*
 * Copyright (C) 2015 Ankit (Verma)
 *
 * GeoclueShare is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * GeoclueShare is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along
 * with GeoclueShare; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Author: Ankit (Verma) <ankitstarski@gmail.com>
 */

/**
 * <p>
 * {@code LocationService} is responsible for fetching and handling location data from the GPS in
 * the background.
 * </p>
 * FIXME: It's just a dummy class yet. Add functionality.
 */
public class LocationService extends Service implements LocationListener, GpsStatus.NmeaListener {

    private static final String TAG = "LocationService";
    private LocationManager locationManager;
    private NetworkListener networkListener;
    private String ggaSentence;
    private NotificationCompat.Builder builder;

    /**
     * The unique identifier for current Android device.
     */
    public static String deviceId;

    /**
     * The that value goes into the `accuracy` feild of mDNS service's TXT record.
     */
    public static String accuracy = "exact";

    /**
     * The minimum time between updates in milliseconds.
     */
    private static final long MIN_TIME_BW_UPDATES_GPS = 1000;

    /**
     * The minimum distance to change Updates in meters.
     */
    private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 1;

    /**
     * The code for stopping Location updates.
     */
    public static final int MESSAGE_STOP_GPS = 0;

    /**
     * The code for starting Location updates.
     */
    public static final int MESSAGE_START_GPS = 1;

    /**
     * The max allowed age of last known location.
     */
    public static final int LAST_KNOWN_LOCATION_MAX_AGE = 24 * 60 * 60 * 1000;

    /**
     * Notification id.
     */
    private static final int NOTIFICATION_ID = 007;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "Service started");
        createNotification();

        Zeroconf.attainLock(this);

        locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);

        deviceId = Settings.Secure.getString(getApplicationContext().getContentResolver(),
                Settings.Secure.ANDROID_ID);

        Handler handler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message message) {
                switch (message.what) {
                case MESSAGE_START_GPS:
                    startGps();
                    Log.d(TAG, "GPS start");
                    Location loc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);

                    if (loc != null && System.currentTimeMillis() - loc.getTime() > LAST_KNOWN_LOCATION_MAX_AGE)
                        loc = null;

                    if (loc != null)
                        NetworkListener.sendData(getGgaFromLocation(loc));
                    break;
                case MESSAGE_STOP_GPS:
                    stopGps();
                    Log.d(TAG, "GPS stop");
                }
            }
        };

        networkListener = new NetworkListener(handler);
        networkListener.execute();

        return Service.START_STICKY;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void unbindService(ServiceConnection conn) {
        super.unbindService(conn);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        stopGps();
        networkListener.cancel(true);
        Zeroconf.releaseLock();

        Log.d(TAG, "Service destroyed");
        removeNotification();
    }

    @Override
    public void onNmeaReceived(long timestamp, String nmea) {
        String nmeaTimestamp = nmea.split(",")[1];
        if (nmea.startsWith("$GPGGA") && nmeaTimestamp.matches("[0-9]{6}")) {
            NetworkListener.sendData(nmea);
            ggaSentence = null;
        } else if (nmea.startsWith("$GPGGA")) {
            if (ggaSentence == null || ggaSentence.length() == 0)
                return;
            nmeaTimestamp = ggaSentence.split(",")[1];

            if (!nmeaTimestamp.matches("[0-9]{6}"))
                return;
            NetworkListener.sendData(ggaSentence);
            ggaSentence = null;
        }
    }

    private void startGps() {
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, MIN_TIME_BW_UPDATES_GPS,
                MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
        locationManager.addNmeaListener(this);
    }

    private void stopGps() {
        locationManager.removeUpdates(this);
        locationManager.removeNmeaListener(this);
    }

    @Override
    public void onLocationChanged(Location location) {
        ggaSentence = getGgaFromLocation(location);
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {

    }

    @Override
    public void onProviderEnabled(String provider) {

    }

    @Override
    public void onProviderDisabled(String provider) {
        if (provider.equals(LocationManager.GPS_PROVIDER)) {
            MainActivity.promptForLocation();
        }
    }

    private String getGgaFromLocation(Location location) {
        String gga;
        Boolean hasAltitude = location.hasAltitude();

        Date date = new Date(location.getTime());
        DateFormat format = new SimpleDateFormat("HHmmss");
        format.setTimeZone(TimeZone.getTimeZone("UTC"));
        String time = format.format(date);

        if (hasAltitude) {
            gga = "$GPGGA,%s,%s,%s,1,,%.1f,%.1f,M,,M,,";
            gga = String.format(gga, time, getLatitudeString(location.getLatitude()),
                    getLongitudeString(location.getLongitude()), getHdopFromAccuracy(location.getAccuracy()),
                    location.getAltitude());
        } else {
            gga = "$GPGGA,%s,%s,%s,1,,%.1f,,M,,M,,";
            gga = String.format(gga, time, getLatitudeString(location.getLatitude()),
                    getLongitudeString(location.getLongitude()), getHdopFromAccuracy(location.getAccuracy()));
        }

        gga = addChecksumToGga(gga);

        return gga;
    }

    private double getHdopFromAccuracy(double accuracy) {
        /* FIXME: These are rough estimates based on the information given in the link below:
         *        http://en.wikipedia.org/wiki/Dilution_of_precision_%28GPS%29#Meaning_of_DOP_Values
         */
        if (accuracy <= 0.5)
            return 0.5;
        else if (accuracy <= 1.0)
            return 1.5;
        else if (accuracy <= 3.0)
            return 3.5;
        else if (accuracy <= 50.0)
            return 7.5;
        else if (accuracy <= 100.0)
            return 15.0;
        else
            return 30.0;
    }

    private String getLatitudeString(double lat) {
        String latStr = "%02d%06.3f,%s";

        int degrees = (int) Math.abs(lat);
        double minutes = Math.abs((lat - (int) lat) * 60);
        String symbol = lat >= 0 ? "N" : "S";

        latStr = String.format(latStr, degrees, minutes, symbol);
        return latStr;
    }

    private String getLongitudeString(double lon) {
        String lonStr = "%03d%06.3f,%s";

        int degrees = (int) Math.abs(lon);
        double minutes = Math.abs((lon - (int) lon) * 60);
        String symbol = lon >= 0 ? "E" : "W";

        lonStr = String.format(lonStr, degrees, minutes, symbol);
        return lonStr;
    }

    private String addChecksumToGga(String gga) {
        int checksum = 0;

        for (int i = 1, n = gga.length(); i < n; i++) {
            checksum ^= (int) gga.charAt(i);
        }

        return String.format("%s*%02X", gga, checksum);
    }

    private void createNotification() {
        Bitmap icon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);

        builder = new NotificationCompat.Builder(this).setSmallIcon(R.mipmap.ic_location_share).setLargeIcon(icon)
                .setContentTitle(getString(R.string.notif_title)).setContentText(getString(R.string.notif_content))
                .setOngoing(true);

        Intent resultIntent = new Intent(this, MainActivity.class);
        resultIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);

        PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        builder.setContentIntent(resultPendingIntent);

        NotificationManager notoficationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        notoficationManager.notify(NOTIFICATION_ID, builder.build());
    }

    private void removeNotification() {
        NotificationManager notoficationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        notoficationManager.cancel(NOTIFICATION_ID);
    }
}