com.androzic.navigation.NavigationService.java Source code

Java tutorial

Introduction

Here is the source code for com.androzic.navigation.NavigationService.java

Source

/*
 * Androzic - android navigation client that uses OziExplorer maps (ozf2, ozfx3).
 * Copyright (C) 2010-2013 Andrey Novikov <http://andreynovikov.info/>
 * 
 * This file is part of Androzic application.
 * 
 * Androzic 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 3 of the License, or
 * (at your option) any later version.
 * 
 * Androzic 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 Androzic. If not, see <http://www.gnu.org/licenses/>.
 */

package com.androzic.navigation;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.location.Location;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.androzic.Androzic;
import com.androzic.HSIActivity;
import com.androzic.MainActivity;
import com.androzic.R;
import com.androzic.data.MapObject;
import com.androzic.data.Route;
import com.androzic.location.ILocationListener;
import com.androzic.location.ILocationService;
import com.androzic.location.LocationService;
import com.androzic.route.RouteDetails;
import com.androzic.util.Geo;

public class NavigationService extends BaseNavigationService implements OnSharedPreferenceChangeListener {
    private static final String TAG = "Navigation";
    private static final int NOTIFICATION_ID = 24163;

    private Androzic application;

    private ILocationService locationService = null;
    protected Location lastKnownLocation;

    private PendingIntent contentIntent;

    private int routeProximity = 200;
    private boolean useTraverse = true;

    /**
     * Active route waypoint
     */
    public MapObject navWaypoint = null;
    /**
     * Previous route waypoint
     */
    public MapObject prevWaypoint = null;
    /**
     * Active route
     */
    public Route navRoute = null;

    public int navDirection = 0;
    /**
     * Active route waypoint index
     */
    public int navCurrentRoutePoint = -1;
    private double navRouteDistance = -1;

    /**
     * Distance to active waypoint
     */
    public int navProximity = 0;
    public double navDistance = 0.0;
    public double navBearing = 0.0;
    public long navTurn = 0;
    public double navVMG = 0.0;
    public int navETE = Integer.MAX_VALUE;
    public double navCourse = 0.0;
    public double navXTK = Double.NEGATIVE_INFINITY;

    private long tics = 0;
    private float[] vmgav = null;
    private double avvmg = 0.0;

    @Override
    public void onCreate() {
        application = Androzic.getApplication();
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        onSharedPreferenceChanged(sharedPreferences, getString(R.string.pref_navigation_proximity));
        onSharedPreferenceChanged(sharedPreferences, getString(R.string.pref_navigation_traverse));
        sharedPreferences.registerOnSharedPreferenceChangeListener(this);

        Log.i(TAG, "Service started");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null) {
            Intent activity = new Intent(this, MainActivity.class)
                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
            String action = intent.getAction();
            if (action == null)
                return 0;
            Bundle extras = intent.getExtras();
            if (action.equals(NAVIGATE_MAPOBJECT)) {
                MapObject mo = new MapObject();
                mo.name = extras.getString(EXTRA_NAME);
                mo.latitude = extras.getDouble(EXTRA_LATITUDE);
                mo.longitude = extras.getDouble(EXTRA_LONGITUDE);
                mo.proximity = extras.getInt(EXTRA_PROXIMITY);
                activity.putExtra(MainActivity.LAUNCH_ACTIVITY, HSIActivity.class);
                contentIntent = PendingIntent.getActivity(this, NOTIFICATION_ID, activity,
                        PendingIntent.FLAG_CANCEL_CURRENT);
                navigateTo(mo);
            }
            if (action.equals(NAVIGATE_MAPOBJECT_WITH_ID)) {
                long id = extras.getLong(EXTRA_ID);
                MapObject mo = application.getMapObject(id);
                if (mo == null)
                    return 0;
                activity.putExtra(MainActivity.LAUNCH_ACTIVITY, HSIActivity.class);
                contentIntent = PendingIntent.getActivity(this, NOTIFICATION_ID, activity,
                        PendingIntent.FLAG_CANCEL_CURRENT);
                navigateTo(mo);
            }
            if (action.equals(NAVIGATE_ROUTE)) {
                int index = extras.getInt(EXTRA_ROUTE_INDEX);
                int dir = extras.getInt(EXTRA_ROUTE_DIRECTION, DIRECTION_FORWARD);
                int start = extras.getInt(EXTRA_ROUTE_START, -1);
                activity.putExtra(MainActivity.SHOW_FRAGMENT, RouteDetails.class);
                activity.putExtra("index", index);
                contentIntent = PendingIntent.getActivity(this, NOTIFICATION_ID, activity,
                        PendingIntent.FLAG_CANCEL_CURRENT);
                Route route = application.getRoute(index);
                if (route == null)
                    return 0;
                navigateTo(route, dir);
                if (start != -1)
                    setRouteWaypoint(start);
            }
        }
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        disconnect();
        clearNavigation();
        PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
        super.onDestroy();
        Log.i(TAG, "Service stopped");
    }

    private final IBinder binder = new LocalBinder();

    public class LocalBinder extends Binder {
        public NavigationService getService() {
            return NavigationService.this;
        }
    }

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

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (getString(R.string.pref_navigation_proximity).equals(key)) {
            routeProximity = Integer
                    .parseInt(sharedPreferences.getString(key, getString(R.string.def_navigation_proximity)));
        }
        if (getString(R.string.pref_navigation_traverse).equals(key)) {
            useTraverse = sharedPreferences.getBoolean(key,
                    getResources().getBoolean(R.bool.def_navigation_traverse));
        }
    }

    private void connect() {
        bindService(new Intent(this, LocationService.class), locationConnection, BIND_AUTO_CREATE);
    }

    private void disconnect() {
        if (locationService != null) {
            locationService.unregisterLocationCallback(locationListener);
            unbindService(locationConnection);
            locationService = null;
        }
    }

    public void stopNavigation() {
        clearNavigation();
        updateNavigationState(STATE_STOPED);
        stopForeground(true);
        disconnect();
    }

    private void clearNavigation() {
        navWaypoint = null;
        prevWaypoint = null;
        navRoute = null;

        navDirection = 0;
        navCurrentRoutePoint = -1;

        navProximity = routeProximity;
        navDistance = 0.0;
        navBearing = 0.0;
        navTurn = 0;
        navVMG = 0.0;
        navETE = Integer.MAX_VALUE;
        navCourse = 0.0;
        navXTK = Double.NEGATIVE_INFINITY;

        vmgav = null;
        avvmg = 0.0;
    }

    public boolean isNavigating() {
        return navWaypoint != null;
    }

    public boolean isNavigatingViaRoute() {
        return navRoute != null;
    }

    private void navigateTo(final MapObject waypoint) {
        clearNavigation();
        connect();
        startForeground(NOTIFICATION_ID, getNotification());

        vmgav = new float[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

        navWaypoint = waypoint;
        navProximity = navWaypoint.proximity > 0 ? navWaypoint.proximity : routeProximity;
        updateNavigationState(STATE_STARTED);
        if (lastKnownLocation != null)
            calculateNavigationStatus(lastKnownLocation, 0, 0);
    }

    private void navigateTo(final Route route, final int direction) {
        clearNavigation();
        connect();
        startForeground(NOTIFICATION_ID, getNotification());

        vmgav = new float[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

        navRoute = route;
        navDirection = direction;
        navCurrentRoutePoint = navDirection == 1 ? 1 : navRoute.length() - 2;

        navWaypoint = navRoute.getWaypoint(navCurrentRoutePoint);
        prevWaypoint = navRoute.getWaypoint(navCurrentRoutePoint - navDirection);
        navProximity = navWaypoint.proximity > 0 ? navWaypoint.proximity : routeProximity;
        navRouteDistance = -1;
        navCourse = Geo.bearing(prevWaypoint.latitude, prevWaypoint.longitude, navWaypoint.latitude,
                navWaypoint.longitude);
        updateNavigationState(STATE_STARTED);
        if (lastKnownLocation != null)
            calculateNavigationStatus(lastKnownLocation, 0, 0);
    }

    public void setRouteWaypoint(int waypoint) {
        navCurrentRoutePoint = waypoint;
        navWaypoint = navRoute.getWaypoint(navCurrentRoutePoint);
        int prev = navCurrentRoutePoint - navDirection;
        if (prev >= 0 && prev < navRoute.length())
            prevWaypoint = navRoute.getWaypoint(prev);
        else
            prevWaypoint = null;
        navProximity = navWaypoint.proximity > 0 ? navWaypoint.proximity : routeProximity;
        navRouteDistance = -1;
        navCourse = prevWaypoint == null ? 0.0
                : Geo.bearing(prevWaypoint.latitude, prevWaypoint.longitude, navWaypoint.latitude,
                        navWaypoint.longitude);
        updateNavigationState(STATE_NEXTWPT);
    }

    public MapObject getNextRouteWaypoint() {
        try {
            return navRoute.getWaypoint(navCurrentRoutePoint + navDirection);
        } catch (IndexOutOfBoundsException e) {
            return null;
        }
    }

    public void nextRouteWaypoint() throws IndexOutOfBoundsException {
        navCurrentRoutePoint += navDirection;
        navWaypoint = navRoute.getWaypoint(navCurrentRoutePoint);
        prevWaypoint = navRoute.getWaypoint(navCurrentRoutePoint - navDirection);
        navProximity = navWaypoint.proximity > 0 ? navWaypoint.proximity : routeProximity;
        navRouteDistance = -1;
        navCourse = Geo.bearing(prevWaypoint.latitude, prevWaypoint.longitude, navWaypoint.latitude,
                navWaypoint.longitude);
        updateNavigationState(STATE_NEXTWPT);
    }

    public void prevRouteWaypoint() throws IndexOutOfBoundsException {
        navCurrentRoutePoint -= navDirection;
        navWaypoint = navRoute.getWaypoint(navCurrentRoutePoint);
        int prev = navCurrentRoutePoint - navDirection;
        if (prev >= 0 && prev < navRoute.length())
            prevWaypoint = navRoute.getWaypoint(prev);
        else
            prevWaypoint = null;
        navProximity = navWaypoint.proximity > 0 ? navWaypoint.proximity : routeProximity;
        navRouteDistance = -1;
        navCourse = prevWaypoint == null ? 0.0
                : Geo.bearing(prevWaypoint.latitude, prevWaypoint.longitude, navWaypoint.latitude,
                        navWaypoint.longitude);
        updateNavigationState(STATE_NEXTWPT);
    }

    public boolean hasNextRouteWaypoint() {
        if (navRoute == null)
            return false;
        boolean hasNext = false;
        if (navDirection == DIRECTION_FORWARD)
            hasNext = (navCurrentRoutePoint + navDirection) < navRoute.length();
        if (navDirection == DIRECTION_REVERSE)
            hasNext = (navCurrentRoutePoint + navDirection) >= 0;
        return hasNext;
    }

    public boolean hasPrevRouteWaypoint() {
        if (navRoute == null)
            return false;
        boolean hasPrev = false;
        if (navDirection == DIRECTION_FORWARD)
            hasPrev = (navCurrentRoutePoint - navDirection) >= 0;
        if (navDirection == DIRECTION_REVERSE)
            hasPrev = (navCurrentRoutePoint - navDirection) < navRoute.length();
        return hasPrev;
    }

    public int navRouteCurrentIndex() {
        return navDirection == DIRECTION_FORWARD ? navCurrentRoutePoint
                : navRoute.length() - navCurrentRoutePoint - 1;
    }

    /**
     * Calculates distance between current route waypoint and last route waypoint.
     * @return distance left
     */
    public double navRouteDistanceLeft() {
        if (navRouteDistance < 0) {
            navRouteDistance = navRouteDistanceLeftTo(navRoute.length() - 1);
        }
        return navRouteDistance;
    }

    /**
     * Calculates distance between current route waypoint and route waypoint with specified index.
     * Method honors navigation direction.
     * @param index
     * @return distance left
     */
    public double navRouteDistanceLeftTo(int index) {
        int current = navRouteCurrentIndex();
        int progress = index - current;

        if (progress <= 0)
            return 0.0;

        double distance = 0.0;
        if (navDirection == DIRECTION_FORWARD)
            distance = navRoute.distanceBetween(navCurrentRoutePoint, index);
        if (navDirection == DIRECTION_REVERSE)
            distance = navRoute.distanceBetween(navRoute.length() - index - 1, navCurrentRoutePoint);

        return distance;
    }

    public int navRouteWaypointETE(int index) {
        if (index == 0)
            return 0;
        int ete = Integer.MAX_VALUE;
        if (avvmg > 0) {
            int i = navDirection == DIRECTION_FORWARD ? index : navRoute.length() - index - 1;
            int j = i - navDirection;
            MapObject w1 = navRoute.getWaypoint(i);
            MapObject w2 = navRoute.getWaypoint(j);
            double distance = Geo.distance(w1.latitude, w1.longitude, w2.latitude, w2.longitude);
            ete = (int) Math.round(distance / avvmg / 60);
        }
        return ete;
    }

    /**
     * Calculates route ETE.
     * @param distance route distance
     * @return route ETE
     */
    public int navRouteETE(double distance) {
        int eta = Integer.MAX_VALUE;
        if (avvmg > 0) {
            eta = (int) Math.round(distance / avvmg / 60);
        }
        return eta;
    }

    public int navRouteETETo(int index) {
        double distance = navRouteDistanceLeftTo(index);
        if (distance <= 0.0)
            return 0;

        return navRouteETE(distance);
    }

    private void calculateNavigationStatus(Location loc, float smoothspeed, float avgspeed) {
        double distance = Geo.distance(loc.getLatitude(), loc.getLongitude(), navWaypoint.latitude,
                navWaypoint.longitude);
        double bearing = Geo.bearing(loc.getLatitude(), loc.getLongitude(), navWaypoint.latitude,
                navWaypoint.longitude);
        double track = loc.getBearing();

        // turn
        long turn = Math.round(bearing - track);
        if (Math.abs(turn) > 180) {
            turn = turn - (long) (Math.signum(turn)) * 360;
        }

        // vmg
        double vmg = Geo.vmg(smoothspeed, Math.abs(turn));

        // ete
        float curavvmg = (float) Geo.vmg(avgspeed, Math.abs(turn));
        if (avvmg == 0.0 || tics % 10 == 0) {
            for (int i = vmgav.length - 1; i > 0; i--) {
                avvmg += vmgav[i];
                vmgav[i] = vmgav[i - 1];
            }
            avvmg += curavvmg;
            vmgav[0] = curavvmg;
            avvmg = avvmg / vmgav.length;
        }

        int ete = Integer.MAX_VALUE;
        if (avvmg > 0)
            ete = (int) Math.round(distance / avvmg / 60);

        double xtk = Double.NEGATIVE_INFINITY;

        if (navRoute != null) {
            boolean hasNext = hasNextRouteWaypoint();
            if (distance < navProximity) {
                if (hasNext) {
                    nextRouteWaypoint();
                    return;
                } else {
                    updateNavigationState(STATE_REACHED);
                    stopNavigation();
                    return;
                }
            }

            if (prevWaypoint != null) {
                double dtk = Geo.bearing(prevWaypoint.latitude, prevWaypoint.longitude, navWaypoint.latitude,
                        navWaypoint.longitude);
                xtk = Geo.xtk(distance, dtk, bearing);

                if (xtk == Double.NEGATIVE_INFINITY) {
                    if (useTraverse && hasNext) {
                        double cxtk2 = Double.NEGATIVE_INFINITY;
                        MapObject nextWpt = getNextRouteWaypoint();
                        if (nextWpt != null) {
                            double dtk2 = Geo.bearing(nextWpt.latitude, nextWpt.longitude, navWaypoint.latitude,
                                    navWaypoint.longitude);
                            cxtk2 = Geo.xtk(0, dtk2, bearing);
                        }

                        if (cxtk2 != Double.NEGATIVE_INFINITY) {
                            nextRouteWaypoint();
                            return;
                        }
                    }
                }
            }
        }

        tics++;

        if (distance != navDistance || bearing != navBearing || turn != navTurn || vmg != navVMG || ete != navETE
                || xtk != navXTK) {
            navDistance = distance;
            navBearing = bearing;
            navTurn = turn;
            navVMG = vmg;
            navETE = ete;
            navXTK = xtk;
            updateNavigationStatus();
        }
    }

    private Notification getNotification() {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setContentIntent(contentIntent);
        builder.setSmallIcon(R.drawable.ic_stat_navigation);
        builder.setContentTitle(getText(R.string.notif_nav_short));
        if (navWaypoint != null) {
            builder.setWhen(System.currentTimeMillis());
            builder.setContentText(String.format((String) getText(R.string.notif_nav_to), navWaypoint.name));
        } else {
            builder.setWhen(0);
            builder.setContentText(getText(R.string.notif_nav_started));
        }
        builder.setGroup("androzic");
        builder.setCategory(NotificationCompat.CATEGORY_SERVICE);
        builder.setPriority(NotificationCompat.PRIORITY_LOW);
        builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
        builder.setColor(getResources().getColor(R.color.theme_accent_color));
        return builder.build();
    }

    private void updateNavigationState(final int state) {
        if (state != STATE_STOPED && state != STATE_REACHED) {
            NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            nm.notify(NOTIFICATION_ID, getNotification());
        }
        sendBroadcast(new Intent(BROADCAST_NAVIGATION_STATE).putExtra("state", state));
        Log.d(TAG, "State dispatched");
    }

    private void updateNavigationStatus() {
        sendBroadcast(new Intent(BROADCAST_NAVIGATION_STATUS));
        Log.d(TAG, "Status dispatched");
    }

    private ServiceConnection locationConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            locationService = (ILocationService) service;
            locationService.registerLocationCallback(locationListener);
            Log.i(TAG, "Location service connected");
        }

        public void onServiceDisconnected(ComponentName className) {
            locationService = null;
            Log.i(TAG, "Location service disconnected");
        }
    };

    private ILocationListener locationListener = new ILocationListener() {
        @Override
        public void onGpsStatusChanged(String provider, int status, int fsats, int tsats) {
        }

        @Override
        public void onLocationChanged(Location loc, boolean continous, boolean geoid, float smoothspeed,
                float avgspeed) {
            Log.d(TAG, "Location arrived");
            lastKnownLocation = loc;

            if (navWaypoint != null)
                calculateNavigationStatus(loc, smoothspeed, avgspeed);
        }

        @Override
        public void onProviderChanged(String provider) {
        }

        @Override
        public void onProviderDisabled(String provider) {
        }

        @Override
        public void onProviderEnabled(String provider) {
        }
    };
}