biz.bokhorst.bpt.BPTService.java Source code

Java tutorial

Introduction

Here is the source code for biz.bokhorst.bpt.BPTService.java

Source

package biz.bokhorst.bpt;

/*
 Copyright 2011-2015 Marcel Bokhorst
 All Rights Reserved
    
 This program 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.
    
 This program 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 this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Locale;

import biz.bokhorst.bpt.R;

import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.ActivityRecognition;
import com.google.android.gms.location.ActivityRecognitionResult;
import com.google.android.gms.location.DetectedActivity;

import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.SystemClock;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
import android.os.PowerManager;

public class BPTService extends IntentService
        implements LocationListener, GpsStatus.Listener, GoogleApiClient.ConnectionCallbacks {
    // Messages
    public static final int MSG_REPLY = 1;
    public static final int MSG_WAYPOINT = 2;

    private static final SimpleDateFormat TIME_FORMATTER = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());

    // Helpers
    private LocationManager locationManager = null;
    private DatabaseHelper databaseHelper = null;
    private SharedPreferences preferences = null;
    private Vibrator vibrator = null;
    private Handler taskHandler = null;
    private PowerManager.WakeLock wakeLock = null;
    private PendingIntent pendingAlarmIntent = null;
    private AlarmManager alarmManager = null;
    private BPTAlarmReceiver alarmReceiver = null;
    private Messenger clientMessenger = null;
    private final Messenger serverMessenger = new Messenger(new IncomingHandler());
    private NotificationManager notificationManager = null;

    // State
    private boolean bound = false;
    private boolean once = false;
    private boolean waypoint = false;
    private boolean locating = false;
    private boolean locationwait = false;
    private Location bestLocation = null;
    private Date nextTrackTime;

    private static boolean should = false;
    private static String lastActivity = null;
    private static GoogleApiClient activityRecognitionClient = null;

    public BPTService() {
        super("BPTService");
    }

    @Override
    public void onConnected(Bundle arg0) {
        should = true;
        sendActivity(getString(R.string.connected), -1, new Date().getTime());

        long interval = Integer.parseInt(preferences.getString(Preferences.PREF_ACTIVITYRECOGNITIONINTERVAL,
                Preferences.PREF_ACTIVITYRECOGNITIONINTERVAL_DEFAULT)) * 60L * 1000L;
        PendingIntent activityCallbackIntent = PendingIntent.getService(this, 0, new Intent(this, BPTService.class),
                PendingIntent.FLAG_UPDATE_CURRENT);
        ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(activityRecognitionClient, interval,
                activityCallbackIntent);
    }

    @Override
    public void onConnectionSuspended(int arg0) {
        should = true;
        sendActivity(getString(R.string.suspended), -1, new Date().getTime());
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if ("Waypoint".equals(intent.getAction())) {
            once = true;
            waypoint = true;
            taskHandler.post(StartTask);
        } else if ("Update".equals(intent.getAction())) {
            once = true;
            taskHandler.post(StartTask);
        } else if (ActivityRecognitionResult.hasResult(intent)) {
            ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(intent);
            DetectedActivity mostProbableActivity = result.getMostProbableActivity();
            int confidence = mostProbableActivity.getConfidence();
            int activityType = mostProbableActivity.getType();

            should = (activityType != DetectedActivity.STILL);
            String activityName = getNameFromType(activityType);
            lastActivity = activityName;
            sendActivity(activityName, confidence, result.getTime());

            for (DetectedActivity activity : result.getProbableActivities())
                Log.w("BPT", TIME_FORMATTER.format(new Date(result.getTime())) + " Activity "
                        + getNameFromType(activity.getType()) + " " + activity.getConfidence() + " %");
        } else
            sendActivity(intent.getAction(), -1, new Date().getTime());
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Start foreground service
        startForeground(1, getNotification(getString(R.string.Running)));

        // Instantiate helpers
        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        databaseHelper = new DatabaseHelper(this);
        preferences = PreferenceManager.getDefaultSharedPreferences(this);
        PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
        taskHandler = new Handler();
        wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BPT");
        notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        Intent alarmIntent = new Intent("BPT_ALARM");
        pendingAlarmIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
        alarmReceiver = new BPTAlarmReceiver();
        registerReceiver(alarmReceiver, new IntentFilter("BPT_ALARM"));

        bound = true;
        return serverMessenger.getBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        if (bound) {
            bound = false;

            unregisterReceiver(alarmReceiver);
            stopForeground(true);
            alarmManager.cancel(pendingAlarmIntent);
            taskHandler.post(StopTask);
        }
        return super.onUnbind(intent);
    }

    // Handle alarms
    public class BPTAlarmReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            taskHandler.post(StartTask);
        }
    }

    // Handle incoming messages
    @SuppressLint("HandlerLeak")
    private class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.w("BPT", "Message=" + msg.what);
            if (msg.replyTo != null)
                clientMessenger = msg.replyTo;
            once = true;
            waypoint = (msg.what == MSG_WAYPOINT);
            taskHandler.post(StartTask);
        }
    }

    // Helper start location
    protected synchronized void startLocating() {
        // Start activity recognition
        if (activityRecognitionClient == null)
            activityRecognitionClient = new GoogleApiClient.Builder(this).addApi(ActivityRecognition.API)
                    .addConnectionCallbacks(BPTService.this).build();

        // Connect activity recognition
        if (!activityRecognitionClient.isConnected() && !activityRecognitionClient.isConnecting()) {
            sendActivity(getString(R.string.connecting), -1, new Date().getTime());
            activityRecognitionClient.connect();
        }

        // Schedule next alarm
        long interval = Integer.parseInt(
                preferences.getString(Preferences.PREF_TRACKINTERVAL, Preferences.PREF_TRACKINTERVAL_DEFAULT)) * 60L
                * 1000L;
        long alarmTime = SystemClock.elapsedRealtime() + interval;
        alarmManager.cancel(pendingAlarmIntent);
        alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, pendingAlarmIntent);

        // Prepare user feedback
        nextTrackTime = new Date(System.currentTimeMillis() + interval);

        // Use activity recognition?
        boolean recognition = preferences.getBoolean(Preferences.PREF_ACTIVITYRECOGNITION,
                Preferences.PREF_ACTIVITYRECOGNITION_DEFAULT);

        if (recognition && !should)
            sendStage(String.format(getString(R.string.StageStill), TIME_FORMATTER.format(nextTrackTime)));

        if (!locating && (recognition ? should || once : true)) {
            locating = true;
            locationwait = false;
            wakeLock.acquire();

            // Start waiting for fix
            long timeout = Integer.parseInt(
                    preferences.getString(Preferences.PREF_FIXTIMEOUT, Preferences.PREF_FIXTIMEOUT_DEFAULT))
                    * 1000L;
            taskHandler.postDelayed(FixTimeoutTask, timeout);

            // Request location updates
            locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this,
                    taskHandler.getLooper());
            locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this,
                    taskHandler.getLooper());
            locationManager.addGpsStatusListener(this);

            // User feedback
            Date timeoutTime = new Date(System.currentTimeMillis() + timeout);
            boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
            sendStage(String.format(getString(R.string.StageFixWait), TIME_FORMATTER.format(timeoutTime)));
            sendStatus(gpsEnabled ? getString(R.string.On) : getString(R.string.Off));
            sendSatellites(-1, -1);
        }
    }

    // Helper stop locating
    protected synchronized void stopLocating() {
        if (locating) {
            locating = false;
            wakeLock.release();

            // Disable location updates
            locationManager.removeGpsStatusListener(this);
            locationManager.removeUpdates(this);

            // Cancel fix/location tasks
            taskHandler.removeCallbacks(FixTimeoutTask);
            taskHandler.removeCallbacks(LocationWaitTimeoutTask);
        }

        // User feedback
        sendStage(getString(R.string.na));
        sendStatus(getString(R.string.na));
        sendSatellites(-1, -1);
    }

    private final Runnable StartTask = new Runnable() {
        public void run() {
            startLocating();
        }
    };

    private final Runnable StopTask = new Runnable() {
        public void run() {
            stopLocating();
        }
    };

    @Override
    public void onLocationChanged(Location location) {
        if (locating) {
            // Have location: stop fix timeout task
            taskHandler.removeCallbacks(FixTimeoutTask);

            // Get minimum accuracy
            int minAccuracy = Integer.parseInt(
                    preferences.getString(Preferences.PREF_MINACCURACY, Preferences.PREF_MINACCURACY_DEFAULT));

            if (locationwait) {
                // Record best location
                if (location.getAccuracy() <= bestLocation.getAccuracy())
                    bestLocation = location;

                // Check if minimum accuracy reached
                if (location.getAccuracy() < minAccuracy) {
                    // Immediately handle location
                    taskHandler.removeCallbacks(LocationWaitTimeoutTask);
                    taskHandler.post(LocationWaitTimeoutTask);
                }
            } else {
                // Current location is best location (for now)
                bestLocation = location;

                // Start waiting for better location
                locationwait = true;

                // Get maximum wait time
                long wait = Integer.parseInt(
                        preferences.getString(Preferences.PREF_MAXWAIT, Preferences.PREF_MAXWAIT_DEFAULT)) * 1000L;

                // Wait for better location for some time
                taskHandler.postDelayed(LocationWaitTimeoutTask, wait);
                Date waitTime = new Date(System.currentTimeMillis() + wait);

                // User feedback
                sendStage(String.format(getString(R.string.StageTrackWait), TIME_FORMATTER.format(waitTime),
                        minAccuracy));
            }

            // User feedback
            sendLocation(location);
        }
    }

    // Fix wait done
    private final Runnable LocationWaitTimeoutTask = new Runnable() {
        public void run() {
            if (locating) {
                // Stop locating procedure
                stopLocating();

                // Always make track point
                makeTrackpoint(bestLocation, waypoint);

                // Make way point
                if (waypoint) {
                    waypoint = false;
                    makeWaypoint(bestLocation);
                }

                once = false;

                // User feedback
                sendStage(String.format(getString(R.string.StageTracked), Math.round(bestLocation.getAccuracy()),
                        TIME_FORMATTER.format(nextTrackTime),
                        lastActivity == null ? getString(R.string.unknown) : lastActivity));
            }
        }
    };

    // Fix timeout
    private final Runnable FixTimeoutTask = new Runnable() {
        public void run() {
            if (locating) {
                // Stop locating procedure
                stopLocating();

                // User feedback
                sendStage(String.format(getString(R.string.StageFixTimeout), TIME_FORMATTER.format(nextTrackTime)));
            }
        }
    };

    private String getNameFromType(int activityType) {
        switch (activityType) {
        case DetectedActivity.STILL:
            return getString(R.string.still);
        case DetectedActivity.TILTING:
            return getString(R.string.tilting);
        case DetectedActivity.ON_FOOT:
            return getString(R.string.on_foot);
        case DetectedActivity.WALKING:
            return getString(R.string.walking);
        case DetectedActivity.RUNNING:
            return getString(R.string.running);
        case DetectedActivity.ON_BICYCLE:
            return getString(R.string.on_bicycle);
        case DetectedActivity.IN_VEHICLE:
            return getString(R.string.in_vehicle);
        case DetectedActivity.UNKNOWN:
            return getString(R.string.unknown);
        }
        return getString(R.string.undefined);
    }

    // Helper method create track point
    protected void makeTrackpoint(Location location, boolean waypoint) {
        String trackName = preferences.getString(Preferences.PREF_TRACKNAME, Preferences.PREF_TRACKNAME_DEFAULT);

        long minDX = Integer.parseInt(
                preferences.getString(Preferences.PREF_MINDISTANCE, Preferences.PREF_MINDISTANCE_DEFAULT));

        Location lastLocation = databaseHelper.getYoungest(trackName, false);
        if (lastLocation == null || distanceM(lastLocation, location) >= minDX || waypoint) {
            // Register track point
            databaseHelper.insertPoint(trackName, null, location, null, false, lastActivity);

            // User feedback
            sendMessage(BackPackTrack.MSG_UPDATETRACK, null);
            Toast.makeText(this, getString(R.string.TrackpointAdded), Toast.LENGTH_LONG).show();
        }
    }

    // Helper create way point
    private void makeWaypoint(Location location) {
        String trackName = preferences.getString(Preferences.PREF_TRACKNAME, Preferences.PREF_TRACKNAME_DEFAULT);
        int count = databaseHelper.countPoints(trackName, true);
        String name = String.format(Locale.getDefault(), "%03d", count + 1);
        databaseHelper.insertPoint(trackName, null, location, name, true, lastActivity);

        // User feedback
        sendMessage(BackPackTrack.MSG_UPDATETRACK, null);
        String msg = String.format(getString(R.string.WaypointAdded), name);
        Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
        vibrator.vibrate(500);
    }

    public double distanceM(double userLat, double userLng, double venueLat, double venueLng) {
        double latDistance = Math.toRadians(userLat - venueLat);
        double lngDistance = Math.toRadians(userLng - venueLng);
        double a = (Math.sin(latDistance / 2) * Math.sin(latDistance / 2)) + (Math.cos(Math.toRadians(userLat)))
                * (Math.cos(Math.toRadians(venueLat))) * (Math.sin(lngDistance / 2)) * (Math.sin(lngDistance / 2));
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return 6371 * 1000 * c;
    }

    public double distanceM(Location a, Location b) {
        return distanceM(a.getLatitude(), a.getLongitude(), b.getLatitude(), b.getLongitude());
    }

    // GPS disabled
    @Override
    public void onProviderDisabled(String arg0) {
        if (locating)
            sendStatus(getString(R.string.Off));
    }

    // GPS enabled
    @Override
    public void onProviderEnabled(String s) {
        if (locating)
            sendStatus(getString(R.string.On));
    }

    // GPS status changed
    @Override
    public void onStatusChanged(String s, int i, Bundle b) {
        if (locating)
            if (i == LocationProvider.OUT_OF_SERVICE)
                sendStatus(getString(R.string.StatusNoService));
            else if (i == LocationProvider.TEMPORARILY_UNAVAILABLE)
                sendStatus(getString(R.string.StatusUnavailable));
            else if (i == LocationProvider.AVAILABLE)
                sendStatus(getString(R.string.StatusAvailable));
            else
                sendStatus(String.format("Status %d", i));
    }

    // GPS status changed
    @Override
    public void onGpsStatusChanged(int event) {
        if (locating)
            if (event == GpsStatus.GPS_EVENT_SATELLITE_STATUS) {
                if (locationManager != null) {
                    GpsStatus status = locationManager.getGpsStatus(null);
                    if (status != null) {
                        int fix = 0;
                        int count = 0;
                        Iterable<GpsSatellite> sats = status.getSatellites();
                        Iterator<GpsSatellite> satI = sats.iterator();
                        while (satI.hasNext()) {
                            GpsSatellite gpssatellite = satI.next();
                            count++;
                            if (gpssatellite.usedInFix())
                                fix++;
                        }
                        sendSatellites(fix, count);
                    }
                }

            } else {
                if (event == GpsStatus.GPS_EVENT_FIRST_FIX)
                    sendStatus(getString(R.string.GpsFix));
                else if (event == GpsStatus.GPS_EVENT_STARTED)
                    sendStatus(getString(R.string.GpsStarted));
                else if (event == GpsStatus.GPS_EVENT_STOPPED)
                    sendStatus(getString(R.string.GpsStopped));
                else
                    sendStatus(String.format("Event %d", event));
            }
    }

    // And a few helpers to simplify the above logic ;-)

    private void sendStage(String stage) {
        if (!getString(R.string.na).equals(stage)) {
            Notification notification = getNotification(stage);
            notificationManager.notify(1, notification);
        }

        Bundle b = new Bundle();
        b.putString("Stage", stage);
        sendMessage(BackPackTrack.MSG_STAGE, b);
    }

    private Notification getNotification(String text) {
        // Build intent
        Intent toLaunch = new Intent(this, BackPackTrack.class);
        toLaunch.setAction("android.intent.action.MAIN");
        toLaunch.addCategory("android.intent.category.LAUNCHER");
        toLaunch.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

        // Build pending intent
        PendingIntent intentBack = PendingIntent.getActivity(this, 0, toLaunch, PendingIntent.FLAG_UPDATE_CURRENT);

        // Build result intent update
        Intent resultIntentUpdate = new Intent(this, BPTService.class);
        resultIntentUpdate.setAction("Update");

        // Build pending intent waypoint
        PendingIntent pendingIntentUpdate = PendingIntent.getService(this, 2, resultIntentUpdate,
                PendingIntent.FLAG_UPDATE_CURRENT);

        // Build result intent waypoint
        Intent resultIntentWaypoint = new Intent(this, BPTService.class);
        resultIntentWaypoint.setAction("Waypoint");

        // Build pending intent waypoint
        PendingIntent pendingIntentWaypoint = PendingIntent.getService(this, 2, resultIntentWaypoint,
                PendingIntent.FLAG_UPDATE_CURRENT);

        // Build notification
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this);
        notificationBuilder.setSmallIcon(R.drawable.icon);
        notificationBuilder.setContentTitle(getString(R.string.app_name));
        notificationBuilder.setContentText(text);
        notificationBuilder.setContentIntent(intentBack);
        notificationBuilder.setWhen(System.currentTimeMillis());
        notificationBuilder.setAutoCancel(true);
        notificationBuilder.addAction(android.R.drawable.ic_menu_mylocation, getString(R.string.Update),
                pendingIntentUpdate);
        notificationBuilder.addAction(android.R.drawable.ic_menu_add, getString(R.string.Waypoint),
                pendingIntentWaypoint);
        Notification notification = notificationBuilder.build();
        return notification;
    }

    private void sendStatus(String status) {
        Bundle b = new Bundle();
        b.putString("Status", status);
        sendMessage(BackPackTrack.MSG_STATUS, b);
    }

    private void sendSatellites(int fix, int count) {
        Bundle b = new Bundle();
        b.putInt("Fix", fix);
        b.putInt("Count", count);
        sendMessage(BackPackTrack.MSG_SATELLITES, b);
    }

    private void sendLocation(Location location) {
        Bundle b = new Bundle();
        b.putDouble("Latitude", location.getLatitude());
        b.putDouble("Longitude", location.getLongitude());
        b.putDouble("Altitude", location.getAltitude());
        b.putFloat("Speed", location.getSpeed());
        b.putFloat("Accuracy", location.getAccuracy());
        b.putString("Provider", location.getProvider());
        b.putLong("Time", location.getTime());
        sendMessage(BackPackTrack.MSG_LOCATION, b);
    }

    private void sendActivity(String name, int confidence, long time) {
        Bundle b = new Bundle();
        b.putString("Name", name);
        b.putInt("Confidence", confidence);
        b.putLong("Time", time);
        sendMessage(BackPackTrack.MSG_ACTIVITY, b);
    }

    private void sendMessage(int type, Bundle data) {
        try {
            if (clientMessenger != null) {
                try {
                    Message msg = Message.obtain();
                    msg.what = type;
                    if (data != null)
                        msg.setData(data);
                    clientMessenger.send(msg);
                } catch (Exception ex) {
                    Toast.makeText(this, ex.toString(), Toast.LENGTH_LONG).show();
                }
            }
        } catch (Throwable ex) {
            Log.e("BPT", "sendMessage: " + ex);
        }
    }
}