info.snowhow.plugin.RecorderService.java Source code

Java tutorial

Introduction

Here is the source code for info.snowhow.plugin.RecorderService.java

Source

/*
 * MIT License
 * (c) 2013 bernd@snowhow.info
 *
*/
package info.snowhow.plugin;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.util.Log;
import android.os.Environment;
import android.os.Message;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.widget.Toast;
import android.view.WindowManager;

import java.io.RandomAccessFile;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.net.InetSocketAddress;
import java.net.Inet4Address;
import java.net.InetAddress;

import android.content.BroadcastReceiver;

import android.app.NotificationManager;
import android.app.Notification;
import android.support.v4.app.NotificationCompat;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.IntentService;
import android.app.Service;
import android.os.IBinder;
import android.os.Binder;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;

public class RecorderService extends Service {
    private static final String GPS_TRACK_VERSION = "0.2.1";

    private static final String LOG_TAG = "GPSTrack";
    private static final long MINIMUM_DISTANCE_CHANGE_FOR_UPDATES = 10; // in Meters
    private static final long MINIMUM_TIME_BETWEEN_UPDATES = 5000; // in Milliseconds
    private static final long MINIMUM_TIME_BETWEEN_UPDATES_FAST = 1000; // in Milliseconds
    private static final long SPEED_LIMIT = 5; // in meter per second

    protected float minimumPrecision = 0;
    protected long distanceChange = MINIMUM_DISTANCE_CHANGE_FOR_UPDATES;
    protected long updateTime = MINIMUM_TIME_BETWEEN_UPDATES;
    protected long updateTimeFast = MINIMUM_TIME_BETWEEN_UPDATES_FAST;
    protected long speedLimit = SPEED_LIMIT;
    protected int speedChangeDelay = 0;
    private static final long SPEED_CHANGE_COUNT = 5;

    protected boolean recording = false;
    protected boolean firstPoint = true;

    protected LocationManager locationManager;
    protected int locations = 0;
    public NotificationManager nm;
    public NotificationCompat.Builder note;
    protected RandomAccessFile myWriter;
    protected long start_ts = System.currentTimeMillis();

    protected MyLocationListener mgpsll, mnetll;
    protected String tf;
    protected String ifString = "snowhow_gpstrack_intent";
    protected int runningID;
    protected SharedPreferences sharedPref;
    protected SharedPreferences.Editor editor;
    public GPSServer gpss;
    protected Location lastLoc;
    protected boolean goingFast = false;
    protected boolean adaptiveRecording = false;
    protected String applicationName = "snowhow";

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

    private final IBinder mBinder = new LocalBinder();

    @Override
    public void onCreate() {
        Log.d(LOG_TAG, "onCreate called in service");
        mgpsll = new MyLocationListener();
        mnetll = new MyLocationListener();
        sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        editor = sharedPref.edit();
        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        Log.d(LOG_TAG, "locationManager initialized, starting intent");

        try {
            PackageManager packageManager = this.getPackageManager();
            PackageInfo packageInfo = packageManager.getPackageInfo(this.getPackageName(), 0);
            applicationName = packageManager.getApplicationLabel(packageInfo.applicationInfo).toString();
        } catch (android.content.pm.PackageManager.NameNotFoundException e) {
            /* do nothing, fallback is used as name */
        }

        registerReceiver(RecorderServiceBroadcastReceiver, new IntentFilter(ifString));
        startGPSS();
    }

    private void showNoGPSAlert() {
        Log.i(LOG_TAG, "No GPS available --- show Dialog");
        AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
        alertDialogBuilder.setMessage("GPS is disabled on your device. Would you like to enable it?")
                .setCancelable(false).setPositiveButton("GPS Settings", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        Intent callGPSSettingIntent = new Intent(
                                android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                        callGPSSettingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(callGPSSettingIntent);
                    }
                });
        alertDialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                stopRecording();
                dialog.cancel();
            }
        });
        AlertDialog alert = alertDialogBuilder.create();
        alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        alert.show();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(LOG_TAG, "Received start id " + startId + ": " + intent);
        if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
            showNoGPSAlert();
        }
        runningID = startId;
        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.

        if (intent == null) {
            tf = sharedPref.getString("runningTrackFile", "");
            Log.w(LOG_TAG, "Intent is null, trying to continue to write to file " + tf + " lm " + locationManager);
            minimumPrecision = sharedPref.getFloat("runningPrecision", 0);
            distanceChange = sharedPref.getLong("runningDistanceChange", MINIMUM_DISTANCE_CHANGE_FOR_UPDATES);
            updateTime = sharedPref.getLong("runningUpdateTime", MINIMUM_TIME_BETWEEN_UPDATES);
            updateTimeFast = sharedPref.getLong("runningUpdateTimeFast", MINIMUM_TIME_BETWEEN_UPDATES_FAST);
            speedLimit = sharedPref.getLong("runningSpeedLimit", SPEED_LIMIT);
            adaptiveRecording = sharedPref.getBoolean("adaptiveRecording", false);
            int count = sharedPref.getInt("count", 0);
            if (count > 0) {
                firstPoint = false;
            }
            if (tf == null || tf == "") {
                Log.e(LOG_TAG, "No trackfile found ... exit clean here");
                cleanUp();
                return START_NOT_STICKY;
            }
        } else {
            tf = intent.getStringExtra("fileName");
            Log.d(LOG_TAG, "FILENAME for recording is " + tf);
            minimumPrecision = intent.getFloatExtra("precision", 0);
            distanceChange = intent.getLongExtra("distance_change", MINIMUM_DISTANCE_CHANGE_FOR_UPDATES);
            updateTime = intent.getLongExtra("update_time", MINIMUM_TIME_BETWEEN_UPDATES);
            updateTimeFast = intent.getLongExtra("update_time_fast", MINIMUM_TIME_BETWEEN_UPDATES_FAST);
            speedLimit = intent.getLongExtra("speed_limit", SPEED_LIMIT);
            adaptiveRecording = intent.getBooleanExtra("adaptiveRecording", false);
            editor.putString("runningTrackFile", tf);
            editor.putFloat("runningPrecision", minimumPrecision);
            editor.putLong("runningDistanceChange", distanceChange);
            editor.putLong("runningUpdateTime", updateTime);
            editor.putLong("runningUpdateTimeFast", updateTimeFast);
            editor.putLong("runningSpeedLimit", speedLimit);
            editor.putBoolean("adaptiveRecording", adaptiveRecording);
            editor.commit();
        }

        Intent cordovaMainIntent;
        try {
            PackageManager packageManager = this.getPackageManager();
            cordovaMainIntent = packageManager.getLaunchIntentForPackage(this.getPackageName());
            Log.d(LOG_TAG, "got cordovaMainIntent " + cordovaMainIntent);
            if (cordovaMainIntent == null) {
                throw new PackageManager.NameNotFoundException();
            }
        } catch (PackageManager.NameNotFoundException e) {
            cordovaMainIntent = new Intent();
        }
        PendingIntent pend = PendingIntent.getActivity(this, 0, cordovaMainIntent, 0);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(ifString), 0);
        NotificationCompat.Action stop = new NotificationCompat.Action.Builder(android.R.drawable.ic_delete,
                "Stop recording", pendingIntent).build();
        note = new NotificationCompat.Builder(this).setContentTitle(applicationName + " GPS tracking")
                .setSmallIcon(android.R.drawable.ic_menu_mylocation).setOngoing(true).setContentIntent(pend)
                .setContentText("No location yet.");
        note.addAction(stop);

        nm = (NotificationManager) getSystemService(Activity.NOTIFICATION_SERVICE);
        nm.notify(0, note.build());

        recording = true;
        Log.d(LOG_TAG, "recording in handleIntent");
        try { // create directory first, if it does not exist
            File trackFile = new File(tf).getParentFile();
            if (!trackFile.exists()) {
                trackFile.mkdirs();
                Log.d(LOG_TAG, "done creating path for trackfile: " + trackFile);
            }
            Log.d(LOG_TAG, "going to create RandomAccessFile " + tf);
            myWriter = new RandomAccessFile(tf, "rw");
            if (intent != null) { // start new file
                // myWriter.setLength(0);    // delete all contents from file
                String trackName = intent.getStringExtra("trackName");
                String trackHead = initTrack(trackName).toString();
                myWriter.write(trackHead.getBytes());
                // we want to write JSON manually for streamed writing
                myWriter.seek(myWriter.length() - 1);
                myWriter.write(",\"coordinates\":[]}".getBytes());
            }
        } catch (IOException e) {
            Log.d(LOG_TAG, "io error. cannot write to file " + tf);
        }
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, updateTime, distanceChange, mgpsll);
        if (locationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER)) {
            locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, updateTime, distanceChange,
                    mnetll);
        }
        return START_STICKY;
    }

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

    public void handleMessage(Message msg) {
        Log.i(LOG_TAG, "handleMessage running...");
    }

    @Override
    public void onDestroy() {
        // Cancel the persistent notification.
        nm.cancel(0);
        // Tell the user we stopped.
        Toast.makeText(this, applicationName + ": GPS logging stopped", Toast.LENGTH_SHORT).show();
    }

    public void cleanUp() {
        try {
            gpss.sendString("{ \"type\": \"status\", \"msg\": \"disconnect\" }");
            Log.d(LOG_TAG, "stopping gpss ...");
            gpss.stop(1000);
            Log.d(LOG_TAG, "stopped gpss ...");
        } catch (Exception e) {
            Log.d(LOG_TAG, "unable to stop gpss");
            Log.d(LOG_TAG, "stack", e);
        }
        locationManager.removeUpdates(mgpsll);
        locationManager.removeUpdates(mnetll);
        locationManager = null;
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        editor.clear();
        editor.commit();
        unregisterReceiver(RecorderServiceBroadcastReceiver);
        stopSelfResult(runningID);
        Log.i(LOG_TAG, "stopped service ...");
    }

    public void writeFile() {
        try {
            // myWriter.flush();
            myWriter.close();
        } catch (IOException e) {
            Log.d(LOG_TAG, "io error. close");
        }
    }

    public void deleteFile() {
        try {
            myWriter.close();
            File delFile = new File(tf);
            Log.d(LOG_TAG, "deleting file " + tf);
            delFile.delete();
        } catch (IOException e) {
            Log.d(LOG_TAG, "io delete error. delete");
        }
    }

    public JSONObject initTrack(String trackName) {
        JSONObject obj = new JSONObject();
        JSONObject prop = new JSONObject();
        try {
            prop.put("user", "anonymous@snowhow.info");
            prop.put("start_ts", start_ts);
            prop.put("name", trackName);
            prop.put("givenName", trackName);
            prop.put("localname", "snowhow_" + start_ts + ".json");
            prop.put("rec_type", "gpstrack_plugin");
            prop.put("plugin_version", GPS_TRACK_VERSION);
            obj.put("type", "LineString");
            obj.put("properties", prop);
        } catch (JSONException e) {
            Log.d(LOG_TAG, "jsonObject error: " + e);
        }
        return obj;
    }

    public void startGPSS() {
        Log.d(LOG_TAG, "starting WS Server on Port 8887");
        try {
            gpss = new GPSServer(8887, this);
            gpss.start();
            Log.d(LOG_TAG, "starting WS Server on : " + gpss.getAddress());
            Log.d(LOG_TAG, "Gpssss is " + gpss);
            gpss.sendString("{ \"type\": \"start\", \"msg\": \"start on " + gpss.getAddress() + "\" }");
        } catch (Exception e) {
            Log.d(LOG_TAG, "ERROR starting WS Server on Port 8887");
            Log.d(LOG_TAG, "bad:", e);
        }
    }

    public void stopRecording() {
        nm.cancel(0);
        if (locations == 0) { // no locations recorded, delete file
            deleteFile();
            cleanUp();
            Log.i(LOG_TAG, "cleaned up, file deleted (was empty)");
            return;
        }
        writeFile();
        cleanUp();
        Log.i(LOG_TAG, "cleaned up, file saved");
    }

    public String getTrackFilename() {
        return tf;
    }

    private BroadcastReceiver RecorderServiceBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (locations == 0) { // no locations recorded, delete file
                deleteFile();
                cleanUp();
                Log.i(LOG_TAG, "cleaned up, file deleted (was empty)");
                return;
            }
            writeFile();
            cleanUp();
            Log.i(LOG_TAG, "cleaned up, file saved");
        }
    };

    private class MyLocationListener implements LocationListener {

        private boolean firstGPSfix = false;

        public MyLocationListener() {
            Log.d(LOG_TAG, "MyLocationListener started successfully");
        }

        public void onLocationChanged(Location location) {
            float speed = location.getSpeed();
            String speedType = "gps";
            if (recording != true) {
                return;
            }
            if (minimumPrecision > 0 && location.getAccuracy() > minimumPrecision) {
                Log.d(LOG_TAG, "precision of position not good enough: " + location.getAccuracy() + "m, required: "
                        + minimumPrecision + "m");
                return;
            }
            if (location.getProvider().equals(LocationManager.GPS_PROVIDER) && firstGPSfix == false) {
                Log.d(LOG_TAG, "removed network LocationListener");
                Toast.makeText(RecorderService.this, applicationName + ": GPS logging started", Toast.LENGTH_SHORT)
                        .show();
                firstGPSfix = true;
                locationManager.removeUpdates(mnetll);
            }
            Log.d(LOG_TAG, "adaptiveRecording: " + adaptiveRecording);
            if (lastLoc != null && adaptiveRecording == true) {
                if (speed == 0) {
                    long timeDiff = (location.getTime() - lastLoc.getTime()) / 1000;
                    speed = lastLoc.distanceTo(location) / timeDiff;
                    speedType = "calc";
                    Log.d(LOG_TAG, "speed calc from lastLoc " + timeDiff + " makes speed " + speed);
                }
                if (speed > SPEED_LIMIT && goingFast == false) { // faster than 5 m/s, switch to faster GPS interval
                    speedChangeDelay++;
                    if (speedChangeDelay > SPEED_CHANGE_COUNT) {
                        speedChangeDelay = 0;
                        Log.d(LOG_TAG, "travelling fast --- switch to fast update");
                        if (gpss != null) {
                            gpss.sendString("{ \"type\": \"status\", \"msg\": \"fastUpdate\", \"interval\": "
                                    + updateTimeFast + "}");
                        }
                        locationManager.removeUpdates(mgpsll);
                        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, updateTimeFast,
                                distanceChange, mgpsll);
                        goingFast = true;
                    }
                } else if (speed <= SPEED_LIMIT && goingFast == true) {
                    speedChangeDelay++;
                    if (speedChangeDelay > SPEED_CHANGE_COUNT) {
                        speedChangeDelay = 0;
                        Log.d(LOG_TAG, "travelling slow --- switch to slow update");
                        if (gpss != null) {
                            gpss.sendString("{ \"type\": \"status\", \"msg\": \"slowUpdate\", \"interval\": "
                                    + updateTime + "}");
                        }
                        locationManager.removeUpdates(mgpsll);
                        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, updateTime,
                                distanceChange, mgpsll);
                        goingFast = false;
                    }
                }
            }
            lastLoc = location;
            locations = sharedPref.getInt("count", 0);
            locations += 1;
            editor.putInt("count", locations);
            editor.commit();
            long gpsInterval = (goingFast) ? (updateTimeFast / 1000) : (updateTime / 1000);
            note.setContentText(
                    "recording (" + locations + " points, " + gpsInterval + " secs tracking interval).");
            try {
                String locString = "[" + location.getLongitude() + "," + location.getLatitude() + ","
                        + location.getAltitude() + "," + location.getTime() + "," + location.getAccuracy() + ",\""
                        + location.getProvider() + "\"" + "," + gpsInterval + "," + speed + "," + adaptiveRecording
                        + "]";
                int pointCount = sharedPref.getInt("count", 0);
                String gpssString = "{\"type\":\"coords\",\"coords\":" + locString + ",\"pointCount\":" + pointCount
                        + ",\"speed\":" + speed + ",\"speedType\":\"" + speedType + "\"," + "\"interval\":"
                        + gpsInterval + "}";
                if (gpss != null) {
                    gpss.sendString(gpssString);
                } else {
                    startGPSS();
                    Log.d(LOG_TAG, "started new GPSS");
                    gpss.sendString(gpssString);
                }
                locString += "]}";
                myWriter.seek(myWriter.length() - 2); // remove last 2 byte
                if (firstPoint == true) {
                    firstPoint = false;
                } else {
                    locString = "," + locString;
                }
                myWriter.write(locString.getBytes());
            } catch (IOException e) {
                Log.d(LOG_TAG, "io error writing pos");
            }
            nm.notify(0, note.build());
        }

        public void onStatusChanged(String s, int i, Bundle b) {
        }

        public void onProviderDisabled(String s) {
        }

        public void onProviderEnabled(String s) {
        }
    }
}