net.mceoin.cominghome.LocationService.java Source code

Java tutorial

Introduction

Here is the source code for net.mceoin.cominghome.LocationService.java

Source

/*
 * Copyright 2014 Randy McEoin
 *
 * 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 net.mceoin.cominghome;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.LocationClient;

import net.mceoin.cominghome.cloud.trackETA;
import net.mceoin.cominghome.history.HistoryUpdate;

import java.util.List;

public class LocationService extends Service implements GooglePlayServicesClient.ConnectionCallbacks,
        GooglePlayServicesClient.OnConnectionFailedListener {
    private static final String TAG = LocationService.class.getSimpleName();
    private static final boolean debug = false;

    private static final int NOTIFICATION_TRACKING = 1;

    private LocationManager locationManager = null;

    private long minUpdateTime;
    private static final long MAX_UPDATE_TIME = 120 * 60 * 1000L;
    private float minUpdateDistance;

    private Location myLocation;

    LocationManager lm;
    LocationHelper loc;

    long timeAtLastUpdate = 0;

    int secondsToSleep = 30;
    boolean runBackgroundThread;
    boolean checkinRequested = false;
    boolean trackingETA = false;
    private long lastTimeTrackETA;

    private static final int MAXSLEEP_WHILE_NOT_MOVING = 30 * 60;
    private static final int MAXSLEEP_WHILE_MOVING = 5 * 60;

    public static final String LOCATION_CHANGED = "net.mceoin.cominghome.LocationService.LocationChanged";
    public static final String TRACKING = "net.mceoin.cominghome.LocationService.Tracking";
    public static final String TRACKING_TYPE = "net.mceoin.cominghome.LocationService.TrackingType";
    public static final String TRACKING_START = "net.mceoin.cominghome.LocationService.TrackingStart";
    public static final String TRACKING_STOP = "net.mceoin.cominghome.LocationService.TrackingStop";

    LocationClient mLocationClient;

    public static boolean isRunning(Context context) {
        return MainActivity.isMyServiceRunning(context, LocationService.class);
    }

    public static void startService() {
        AppController.getInstance().startService(
                new Intent(AppController.getInstance().getApplicationContext(), LocationService.class));
    }

    public LocationService() {
    }

    private BroadcastReceiver mTrackingReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (debug)
                Log.d(TAG, "Tracking receiver");
            String type = intent.getStringExtra(TRACKING_TYPE);
            if (type.equals(TRACKING_START)) {
                if (backgroundThread != null) {
                    if (debug)
                        Log.d(TAG, "interrupting background thread");
                    secondsToSleep = 30;
                    trackingETA = true;
                    backgroundThread.interrupt();
                    sendTrackingNotification(context);
                } else {
                    Log.e(TAG, "no background thread");
                }
            } else if (type.equals(TRACKING_STOP)) {
                if (backgroundThread != null) {
                    if (debug)
                        Log.d(TAG, "interrupting background thread");
                    secondsToSleep = 12 * 60 * 60;
                    trackingETA = false;
                    backgroundThread.interrupt();
                    clearNotification(context);
                } else {
                    if (debug)
                        Log.d(TAG, "no background thread");
                }
            }
        }
    };

    @Override
    public void onConnected(Bundle bundle) {
        if (debug)
            Log.d(TAG, "onConnected()");
        mLocationClient.connect();
    }

    @Override
    public void onDisconnected() {
        if (debug)
            Log.d(TAG, "onDisconnected()");
        mLocationClient.disconnect();
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        if (debug)
            Log.d(TAG, "onConnectionFailed()");

    }

    public class LocationBinder extends Binder {
        //        LocationService getService() {
        //            return LocationService.this;
        //        }
    }

    private final IBinder mBinder = new LocationBinder();

    @Override
    public IBinder onBind(Intent intent) {
        if (debug)
            Log.d(TAG, "onBind()");
        servicesConnected();
        return mBinder;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (debug)
            Log.d(TAG, "onStartCommand()");
        startBackgroundTask();
        mLocationClient = new LocationClient(getApplicationContext(), this, this);
        //        mLocationClient.connect();
        LocalBroadcastManager.getInstance(this).registerReceiver(mTrackingReceiver, new IntentFilter(TRACKING));
        trackingETA = true;
        return Service.START_STICKY;
    }

    @Override
    public void onDestroy() {
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mTrackingReceiver);
        runBackgroundThread = false;
        if (backgroundThread != null) {
            backgroundThread.interrupt();
        }
        super.onDestroy();
    }

    Thread backgroundThread = null;

    private void startBackgroundTask() {
        runBackgroundThread = true;
        // Start a background thread and begin the processing.
        // This moves the time consuming operation to a child thread.
        backgroundThread = new Thread(null, doBackgroundThreadProcessing, "LocationChecker");
        backgroundThread.start();

    }

    //Runnable that executes the background processing method.
    private Runnable doBackgroundThreadProcessing = new Runnable() {
        public void run() {
            backgroundThreadProcessing();
        }
    };

    private void backgroundThreadProcessing() {
        secondsToSleep = 30;
        while (runBackgroundThread) {
            if (locationManager == null) {
                initLocationManager();
            }
            try {
                if (debug)
                    Log.d(TAG, "sleeping " + secondsToSleep + " seconds");
                Thread.sleep(secondsToSleep * 1000);
                if (secondsToSleep < MAXSLEEP_WHILE_NOT_MOVING) {
                    secondsToSleep += 15;
                }
            } catch (InterruptedException e) {
                //                e.printStackTrace();
                if (debug)
                    Log.d(TAG, "wake up!");
            }
            Criteria criteria = new Criteria();
            criteria.setAccuracy(Criteria.ACCURACY_FINE);
            String provider = locationManager.getBestProvider(criteria, true);

            //            provider = LocationManager.GPS_PROVIDER;
            Location location = locationManager.getLastKnownLocation(provider);
            if (mLocationClient.isConnected()) {
                if (debug)
                    Log.d(TAG, "mLocationClient is connected");
                location = mLocationClient.getLastLocation();
            }
            if (debug)
                Log.d(TAG, "location=" + location);
            if (myLocation == null) {
                myLocation = location;
                broadcastLocationChanged(myLocation);
            } else {
                float dist = distFrom(myLocation, location);
                if (debug)
                    Log.d(TAG, "dist=" + dist);
                if ((checkinRequested) || (dist > 20)) {
                    myLocation = location;
                    broadcastLocationChanged(myLocation);
                    checkinRequested = false;
                    if (secondsToSleep > MAXSLEEP_WHILE_MOVING) {
                        // if we're moving, don't sleep too much
                        secondsToSleep = MAXSLEEP_WHILE_MOVING;
                    }
                }
            }

        }
    }

    public class TrackLocation extends Thread {
        //        String latString, lngString = null;

        public TrackLocation(Context context) {
            loc = new LocationHelper();
            lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
            setName("TrackLocation");
        }

        public void run() {
            Looper.prepare();
            minUpdateTime = 30 * 1000L;
            /*
             * minimum distance between location updates, in meters
             */
            minUpdateDistance = 200.0f;
            //            lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, minUpdateTime, minUpdateDistance, loc);
            timeAtLastUpdate = System.currentTimeMillis();
            Looper.loop();
        }
    }

    /**
     * Calculate distance in meters between two points.
     *
     * @param lat1 Latitude for point 1
     * @param lng1 Longitude for point 1
     * @param lat2 Latitude for point 2
     * @param lng2 Longitude for point 2
     * @return distance in meters
     */
    public static float distFrom(double lat1, double lng1, double lat2, double lng2) {
        double earthRadius = 3958.75;
        double dLat = Math.toRadians(lat2 - lat1);
        double dLng = Math.toRadians(lng2 - lng1);
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(Math.toRadians(lat1))
                * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        double dist = earthRadius * c;

        int meterConversion = 1609;

        return (float) (dist * meterConversion);
    }

    public static float distFrom(Location location1, Location location2) {
        return distFrom(location1.getLatitude(), location1.getLongitude(), location2.getLatitude(),
                location2.getLongitude());
    }

    public class LocationHelper implements LocationListener {

        @Override
        public void onLocationChanged(Location location) {
            if (debug)
                Log.d(TAG, "onLocationChanged(" + location + ")");
            if (location != null) {
                if (myLocation != null) {
                    float distance = distFrom(myLocation.getLatitude(), myLocation.getLongitude(),
                            location.getLatitude(), location.getLongitude());
                    if (debug)
                        Log.d(TAG, "distance=" + distance);
                }
                myLocation = location;
                long minTimeBetweenUpdate = 50 * 1000L;
                if (System.currentTimeMillis() > (timeAtLastUpdate + minTimeBetweenUpdate)) {
                    broadcastLocationChanged(myLocation);
                }
                if (minUpdateTime < MAX_UPDATE_TIME) {
                    minUpdateTime += 30 * 1000L;
                    if (debug)
                        Log.d(TAG, "increase minUpdateTime to " + minUpdateTime);
                    lm.removeUpdates(loc);
                    lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, minUpdateTime, minUpdateDistance, loc);
                }
            }
        }

        @Override
        public void onProviderDisabled(String provider) {
        }

        @Override
        public void onProviderEnabled(String provider) {
        }

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

        }
    }

    private void broadcastLocationChanged(Location location) {
        if (debug)
            Log.d(TAG, "broadcastLocationChanged(" + location + ")");
        if (location == null)
            return;
        timeAtLastUpdate = System.currentTimeMillis();
        Intent intent = new Intent(LOCATION_CHANGED);
        intent.putExtra("latitude", location.getLatitude());
        intent.putExtra("longitude", location.getLongitude());
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);

        long currentTimeMillis = System.currentTimeMillis();
        long timeSinceLastUpdate = currentTimeMillis - lastTimeTrackETA;
        // make sure it's been at least 2 minutes since the last update before sending up to the cloud
        if (timeSinceLastUpdate > (2 * 60 * 1000)) {
            new trackETA(getApplicationContext(), location.getLatitude(), location.getLongitude()).execute();
        }
        lastTimeTrackETA = currentTimeMillis;
        HistoryUpdate.add(getApplicationContext(), "location changed: latitude=" + location.getLatitude());
    }

    private void initLocationManager() {
        Context context = getApplicationContext();
        if (context == null) {
            if (debug)
                Log.d(TAG, "no context!");
            return;
        }
        locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        List<String> providers = locationManager.getProviders(true);
        if (debug)
            Log.d(TAG, "providers=" + providers);
        new TrackLocation(getApplicationContext()).start();

    }

    private boolean servicesConnected() {
        // Check that Google Play services is available
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        // If Google Play services is available
        if (ConnectionResult.SUCCESS == resultCode) {
            if (debug)
                Log.d(TAG, "Google Play services is available.");
            // Continue
            return true;
            // Google Play services was not available for some reason
        } else {
            if (debug)
                Log.d(TAG, "Google Play services is not available.");
            return false;
        }
    }

    /**
     * Posts a notification in the notification bar when a transition is detected.
     * If the user clicks the notification, control goes to the main Activity.
     *
     */
    public static void sendTrackingNotification(Context context) {

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        boolean notifications = prefs.getBoolean(MainActivity.PREFS_NOTIFICATIONS, true);

        if (!notifications) {
            if (debug)
                Log.d(TAG, "notifications are turned off");
            return;
        }

        // Create an explicit content Intent that starts the main Activity
        Intent notificationIntent = new Intent(context, MainActivity.class);

        // Construct a task stack
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);

        // Adds the main Activity to the task stack as the parent
        stackBuilder.addParentStack(MainActivity.class);

        // Push the content Intent onto the stack
        stackBuilder.addNextIntent(notificationIntent);

        // Get a PendingIntent containing the entire back stack
        PendingIntent notificationPendingIntent = stackBuilder.getPendingIntent(0,
                PendingIntent.FLAG_UPDATE_CURRENT);

        // Get a notification builder that's compatible with platform versions >= 4
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);

        // Set the notification contents
        builder.setSmallIcon(R.drawable.home)
                .setContentTitle(context.getString(R.string.tracking_notification_title)).setOngoing(true)
                .setContentText(context.getString(R.string.tracking_notification_text))
                .setContentIntent(notificationPendingIntent);

        // Get an instance of the Notification manager
        NotificationManager mNotificationManager = (NotificationManager) context
                .getSystemService(Context.NOTIFICATION_SERVICE);

        // Issue the notification
        mNotificationManager.notify(NOTIFICATION_TRACKING, builder.build());
    }

    private static void clearNotification(Context context) {

        // look up the notification manager service
        NotificationManager mNotificationManager = (NotificationManager) context
                .getSystemService(Context.NOTIFICATION_SERVICE);
        mNotificationManager.cancel(NOTIFICATION_TRACKING);
    }

    public static void sendTrackingStop(Context context) {
        if (LocationService.isRunning(context)) {
            if (debug)
                Log.d(TAG, "LocationService is running, ensure tracking is stopped");
            Intent intent = new Intent(LocationService.TRACKING);
            intent.putExtra(LocationService.TRACKING_TYPE, LocationService.TRACKING_STOP);
            LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
        }
    }
}