org.cowboycoders.cyclisimo.turbo.TurboService.java Source code

Java tutorial

Introduction

Here is the source code for org.cowboycoders.cyclisimo.turbo.TurboService.java

Source

/*
*    Copyright (c) 2013, Will Szumski
*    Copyright (c) 2013, Doug Szumski
*
*    This file is part of Cyclismo.
*
*    Cyclismo 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.
*
*    Cyclismo 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 Cyclismo.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.cowboycoders.cyclisimo.turbo;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;

import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.cowboycoders.ant.Node;
import org.cowboycoders.ant.interfaces.AndroidAntTransceiver;
import org.cowboycoders.ant.interfaces.AntRadioPoweredOffException;
import org.cowboycoders.ant.interfaces.AntRadioServiceNotInstalledException;
import org.cowboycoders.ant.interfaces.ServiceAlreadyClaimedException;
import org.cowboycoders.cyclisimo.Constants;
import org.cowboycoders.cyclisimo.R;
import org.cowboycoders.cyclisimo.TrackEditActivity;
import org.cowboycoders.cyclisimo.content.Bike;
import org.cowboycoders.cyclisimo.content.CyclismoProviderUtils;
import org.cowboycoders.cyclisimo.content.MyTracksProviderUtils;
import org.cowboycoders.cyclisimo.content.User;
import org.cowboycoders.cyclisimo.services.TrackRecordingServiceConnection;
import org.cowboycoders.cyclisimo.util.IntentUtils;
import org.cowboycoders.cyclisimo.util.PreferencesUtils;
import org.cowboycoders.cyclisimo.util.TrackRecordingServiceConnectionUtils;
import org.cowboycoders.cyclisimo.util.UnitConversions;
import org.cowboycoders.location.LatLongAlt;
import org.cowboycoders.turbotrainers.CourseTracker;
import org.cowboycoders.turbotrainers.Mode;
import org.cowboycoders.turbotrainers.Parameters;
import org.cowboycoders.turbotrainers.TooFewAntChannelsAvailableException;
import org.cowboycoders.turbotrainers.TurboCommunicationException;
import org.cowboycoders.turbotrainers.TurboTrainerDataListener;
import org.cowboycoders.turbotrainers.TurboTrainerInterface;
import org.cowboycoders.turbotrainers.bushido.brake.BushidoBrake;
import org.cowboycoders.turbotrainers.bushido.brake.PidBrakeController;
import org.cowboycoders.turbotrainers.bushido.brake.SpeedResistanceMapper;
import org.cowboycoders.turbotrainers.bushido.headunit.BushidoHeadunit;

public class TurboService extends Service {

    private static final int DEFAULT_BIKE_WEIGHT = 7;

    private static final int DEFAULT_USER_WEIGHT = 60;

    private static final int RESULT_ERROR = 0;

    private Binder turboBinder = new TurboBinder();

    //relocate to R
    public static final int NOTIFCATION_ID_STARTUP = 0;

    public static final int NOTIFCATION_ID_SHUTDOWN = 1;

    public static String TAG = "TurboService";

    public static String COURSE_TRACK_ID = "COURSE_TRACK_ID";

    public static double TARGET_TRACKPOINT_DISTANCE_METRES = 5;

    protected AntLoggerImpl antLogger;

    private final static String MOCK_LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;// LocationManager.NETWORK_PROVIDER;

    private static int GPS_ACCURACY = 5; // m

    // private List<LatLongAlt> latLongAlts;

    private boolean running = false;

    private float scaleFactor = 1.0f; //multiply positive gradients by this value

    private AndroidAntTransceiver transceiver;

    // private double distanceBetweenPoints;

    // private double lastSubmittedDistance = 0;

    // private int currentLatLongIndex = 1;

    private static String WAKE_LOCK = TurboService.class.getSimpleName();

    private boolean mIsBound = false;

    private boolean attachAntLogger = false;

    private Node antNode;

    // Notification that we are running
    public static int ONGOING_NOTIFICATION = 1786;

    double lastRecordedSpeed = 0.0; // kmh

    private TurboTrainerInterface turboTrainer;

    private Lock parameterBuilderLock = new ReentrantLock();

    private Parameters.Builder parameterBuilder;

    public void setParameterBuilder(Parameters.Builder builder) {
        try {
            parameterBuilderLock.lock();
            parameterBuilder = builder;
        } finally {
            parameterBuilderLock.unlock();
        }

    }

    TurboTrainerDataListener dataListener = new TurboTrainerDataListener() {

        boolean updating = false;
        Lock updatingLock = new ReentrantLock();

        @Override
        public void onSpeedChange(double speed) { // synchronized to keep speed in

            // alignment with distance    

            Intent intent = new Intent(getString(R.string.sensor_data_speed_kmh));
            intent.putExtra(getString(R.string.sensor_data_double_value), speed);
            sendBroadcast(intent);

            try {
                updatingLock.lock();
                if (updating)
                    return;
                updating = true;
            } finally {
                updatingLock.unlock();
            }

            lastRecordedSpeed = speed;
            Log.v(TAG, "new speed: " + speed);

            try {
                updatingLock.lock();
                updating = false;
            } finally {
                updatingLock.unlock();
            }

        }

        @Override
        public void onPowerChange(double power) {
            Intent intent = new Intent(getString(R.string.sensor_data_power));
            intent.putExtra(getString(R.string.sensor_data_double_value), power);
            sendBroadcast(intent);
        }

        @Override
        public void onCadenceChange(double cadence) {
            Log.d(TAG, "cadence: " + cadence);
            Intent intent = new Intent(getString(R.string.sensor_data_cadence));
            intent.putExtra(getString(R.string.sensor_data_double_value), cadence);
            sendBroadcast(intent);
        }

        @Override
        public void onDistanceChange(double distance) {
            // synchronized to keep speed in alignment with distance : may need
            // changing if threads are queueing and order becomes
            // unpredictable
            try {
                updatingLock.lock();
                if (updating)
                    return;
                updating = true;
            } finally {
                updatingLock.unlock();
            }

            Log.d(TAG, "distance:" + distance);
            LatLongAlt currentLocation = courseTracker.getNearestLocation(distance);
            updateLocation(currentLocation);
            double gradient = courseTracker.getCurrentGradient(); // returns 0.0 if
                                                                  // finished for warm
                                                                  // down
            if (gradient > 0) {
                gradient = gradient * scaleFactor;
            }

            try {
                parameterBuilderLock.lock();
                turboTrainer.setParameters(parameterBuilder.buildTargetSlope(gradient));
            } finally {
                parameterBuilderLock.unlock();
            }

            Log.d(TAG, "New Gradient: " + gradient);
            if (courseTracker.hasFinished()) {
                doFinish();
            }

            try {
                updatingLock.lock();
                updating = false;
            } finally {
                updatingLock.unlock();
            }

        }

        @Override
        public void onHeartRateChange(double heartRate) {
            if (heartRate > 0.) {
                Intent intent = new Intent(getString(R.string.sensor_data_heart_rate));
                intent.putExtra(getString(R.string.sensor_data_double_value), heartRate);
                sendBroadcast(intent);
            }
        }

    };

    private WakeLock wakeLock;

    private CourseTracker courseTracker;

    private long recordingTrackId;

    private SharedPreferences preferences;

    private String selectedTurboTrainer;

    public void doFinish() {
        preferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
        // if (recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
        Intent intent = IntentUtils.newIntent(getBaseContext(), TrackEditActivity.class)
                .putExtra(TrackEditActivity.EXTRA_TRACK_ID, recordingTrackId)
                .putExtra(TrackEditActivity.EXTRA_NEW_TRACK, true).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        getApplication().startActivity(intent);
        TrackRecordingServiceConnectionUtils.stopRecording(this, trackRecordingServiceConnection, false);
        // }
        this.stopSelf();
    }

    void doUnbindService() {
        if (mIsBound) {
            unbindService(mConnection);
            mIsBound = false;
        }
    }

    /*
     * (non-Javadoc)
     * @see android.app.Service#onStartCommand(android.content.Intent, int, int)
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        return START_STICKY;
    }

    public synchronized void start(final long trackId, final Context context) {
        if (running) {
            return;
        }
        running = true;

        preferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);

        preferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);

        syncScaleFactor();

        this.selectedTurboTrainer = preferences.getString(context.getString(R.string.turbotrainer_selected),
                context.getString(R.string.turbotrainer_tacx_bushido_headunit_value));

        Log.d(TAG, selectedTurboTrainer);

        // accessing database so should be put into a task
        double userWeight;
        double bikeWeight;
        Bike selectedBike;

        CyclismoProviderUtils providerUtils = MyTracksProviderUtils.Factory.getCyclimso(context);
        User currentUser = providerUtils
                .getUser(PreferencesUtils.getLong(context, R.string.settings_select_user_current_selection_key));
        if (currentUser != null) {
            userWeight = currentUser.getWeight();
            selectedBike = providerUtils.getBike(
                    PreferencesUtils.getLong(context, R.string.settings_select_bike_current_selection_key));
        } else {
            Log.w(TAG, "using default user weight");
            userWeight = DEFAULT_USER_WEIGHT; //kg
            selectedBike = null;
        }
        if (selectedBike != null) {
            bikeWeight = selectedBike.getWeight();
        } else {
            Log.w(TAG, "using default bike weight");
            bikeWeight = DEFAULT_BIKE_WEIGHT; //kg
        }
        setParameterBuilder(new Parameters.Builder(userWeight, bikeWeight));
        Log.d(TAG, "user weight: " + userWeight);
        Log.d(TAG, "bike weight: " + bikeWeight);

        attachAntLogger = PreferencesUtils.getBoolean(context, R.string.settings_ant_diagnostic_logging_state_key,
                false);

        wakeLock.acquire();

        recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key);

        List<LatLongAlt> latLongAlts = null;

        context.getClass();
        CourseLoader cl = new CourseLoader(context, trackId);
        try {
            latLongAlts = cl.getLatLongAlts();
        } catch (InterruptedException e) {
            String error = "interrupted whilst loading course";
            Log.e(TAG, error);
            handleException(e, "Error loading course", true, NOTIFCATION_ID_STARTUP);
        }

        latLongAlts = org.cowboycoders.location.LocationUtils.interpolatePoints(latLongAlts,
                TARGET_TRACKPOINT_DISTANCE_METRES);

        this.courseTracker = new CourseTracker(latLongAlts);

        Log.d(TAG, "latlong length: " + latLongAlts.size());

        enableMockLocations();

        // start in background as otherwise it is destroyed in onDestory() before we
        // can disconnect
        startServiceInBackround();

        doBindService();

        registerRecordingReceiver();

        Intent notificationIntent = new Intent(this, TurboService.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

        Notification noti = new NotificationCompat.Builder(this)
                .setContentTitle(getString(R.string.turbo_mode_service_running_notification_title))
                .setContentText(getString(R.string.turbo_mode_service_running_notification_text))
                .setSmallIcon(R.drawable.track_bike).setContentIntent(pendingIntent).setOngoing(true).build(); // build api 16 only ;/

        startForeground(ONGOING_NOTIFICATION, noti);

        // currentLatLongIndex = latLongAlts.size() -3; puts in near end so we can
        // test ending

    }

    private static interface ErrorProneCall {
        void call() throws Exception;
    }

    private static void retryErrorProneCall(ErrorProneCall errorProneCall, int maxRetries) throws Exception {
        int retries = 0;
        while (true) {
            try {
                errorProneCall.call();
                break;
            } catch (Exception e) {
                if (++retries >= maxRetries) {
                    throw e;
                }
            }
        }
    }

    private ErrorProneCall startTurbo = new ErrorProneCall() {

        @Override
        public void call() throws TurboCommunicationException, InterruptedException, TimeoutException {
            turboTrainer.start();
        }

    };

    private ServiceConnection mConnection = new ServiceConnection() {

        public void onServiceConnected(ComponentName className, IBinder binder) {
            AntHubService s = ((AntHubService.LocalBinder) binder).getService();
            antNode = s.getNode();
            if (attachAntLogger) {
                antLogger = new AntLoggerImpl(TurboService.this);
                antNode.registerAntLogger(antLogger);
            }
            transceiver = s.getTransceiver();
            TurboService.this.turboTrainer = getTurboTrainer();

            new Thread() {
                public void run() {
                    try {
                        turboTrainer.setMode(Mode.TARGET_SLOPE);
                        // slight hack to get around timeout errors on my tablet 
                        retryErrorProneCall(startTurbo, 10);
                        turboTrainer.registerDataListener(dataListener);
                        //turboTrainer.start();
                        //turboTrainer.registerDataListener(dataListener);
                        // if
                        // (TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(TurboService.this))
                        // {
                        // TrackRecordingServiceConnectionUtils.resumeTrack(trackRecordingServiceConnection);
                        // }
                        // notify receivers that we have started up
                        Intent intent = new Intent()
                                .setAction(TurboService.this.getString(R.string.turbo_service_action_course_start));
                        sendBroadcast(intent);
                        updateLocation(courseTracker.getNearestLocation(0.0));
                    } catch (Exception e) {
                        handleException(e, "Error initiliasing turbo trainer", true, NOTIFCATION_ID_STARTUP);
                    }
                }
            }.start();

            Toast.makeText(TurboService.this, "Connected to AntHub service", Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            Toast.makeText(TurboService.this, "Disconnected from AntHub service", Toast.LENGTH_SHORT).show();
        }

    };

    private TrackRecordingServiceConnection trackRecordingServiceConnection;

    private boolean networkProviderEnabled;

    void doBindService() {
        bindService(new Intent(this, AntHubService.class), mConnection, Context.BIND_AUTO_CREATE);
        mIsBound = true;
    }

    protected TurboTrainerInterface getTurboTrainer() {
        if (selectedTurboTrainer == getString(R.string.turbotrainer_tacx_bushido_headunit_value)) {
            return new BushidoHeadunit(antNode);
        } else if (selectedTurboTrainer == getString(
                R.string.turbotrainer_tacx_bushido_brake_headunit_simulation_value)) {
            SpeedResistanceMapper mapper = new SpeedResistanceMapper();
            return new BushidoBrake(antNode, mapper);
        } else if (selectedTurboTrainer == getString(R.string.turbotrainer_tacx_bushido_brake_pid_control_value)) {
            PidBrakeController pid = new PidBrakeController();
            return new BushidoBrake(antNode, pid);
        }

        return null;
    }

    private void startServiceInBackround() {
        Intent intent = new Intent(this, AntHubService.class);
        this.startService(intent);
    }

    private boolean enableLocationProvider(String provider) {
        LocationManager locationManager = (LocationManager) getApplicationContext()
                .getSystemService(Context.LOCATION_SERVICE);
        if (locationManager.isProviderEnabled(provider)) {
            try {
                locationManager.addTestProvider(provider, "requiresNetwork" == "", "requiresSatellite" == "",
                        "requiresCell" == "", "hasMonetaryCost" == "", "supportsAltitude" == "",
                        "supportsSpeed" == "", "supportsBearing" == "", android.location.Criteria.POWER_LOW,
                        android.location.Criteria.ACCURACY_FINE);

                locationManager.setTestProviderEnabled(provider, true);
                locationManager.setTestProviderStatus(provider, LocationProvider.AVAILABLE, null,
                        System.currentTimeMillis());
            } catch (SecurityException e) {
                handleException(e, "Error enabling location provider", true, NOTIFCATION_ID_STARTUP);
                return false;
            }
        } else {
            return false;
        }

        return true;
    }

    private void enableMockLocations() {
        enableLocationProvider(MOCK_LOCATION_PROVIDER);
        // enable any alternatives otherwise : could provide a last known location
        networkProviderEnabled = enableLocationProvider(LocationManager.NETWORK_PROVIDER);
    }

    private void disableMockLocations() {
        disableLocationProvider(MOCK_LOCATION_PROVIDER);
        if (networkProviderEnabled)
            disableLocationProvider(LocationManager.NETWORK_PROVIDER);
    }

    private void disableLocationProvider(String provider) {
        LocationManager locationManager = (LocationManager) getApplicationContext()
                .getSystemService(Context.LOCATION_SERVICE);
        if (locationManager.isProviderEnabled(provider)) {
            try {
                // is this the same as the below? probably
                locationManager.setTestProviderEnabled(provider, false);
                locationManager.clearTestProviderEnabled(provider);
                locationManager.clearTestProviderLocation(provider);
                locationManager.clearTestProviderStatus(provider);
                locationManager.removeTestProvider(provider);
            } catch (SecurityException e) {
                // ignore 
            }
        }
    }

    private synchronized void updateLocation(LatLongAlt pos) {
        LocationManager locationManager = (LocationManager) getApplicationContext()
                .getSystemService(Context.LOCATION_SERVICE);
        if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
            try {
                float locSpeed = (float) (lastRecordedSpeed / UnitConversions.MS_TO_KMH);
                final long timestamp = System.currentTimeMillis();
                Log.v(TAG, "location timestamp: " + timestamp);
                Location loc = new Location(MOCK_LOCATION_PROVIDER);
                Log.d(TAG, "alt: " + pos.getAltitude());
                Log.d(TAG, "lat: " + pos.getLatitude());
                Log.d(TAG, "long: " + pos.getLongitude());
                loc.setLatitude(pos.getLatitude());
                loc.setLongitude(pos.getLongitude());
                loc.setAltitude(pos.getAltitude());
                loc.setTime(timestamp);
                loc.setSpeed(locSpeed);
                loc.setAccuracy(GPS_ACCURACY);
                locationManager.setTestProviderLocation(MOCK_LOCATION_PROVIDER, loc);
                Log.e(TAG, "updated location");
            } catch (SecurityException e) {
                handleException(e, "Error updating location", true, NOTIFCATION_ID_STARTUP);
            }

            return;
        }
        Log.e(TAG, "no gps provider");

    }

    @Override
    public synchronized void onDestroy() {
        unregisterRecordingReceiver();
        disableMockLocations();
        running = false;

        new Thread() {
            public void run() {
                shutDownTurbo();
            }
        }.start();

        // no longer need ant
        this.doUnbindService();

        super.onDestroy();
    }

    // this takes time
    private boolean shutDownTurbo() {
        boolean shutDownSuccess = true;
        if (turboTrainer != null) {
            try {
                turboTrainer.unregisterDataListener(dataListener);
                turboTrainer.stop();
            } catch (Exception e) {
                handleException(e, "Error shutting down turbo", false, NOTIFCATION_ID_SHUTDOWN);
                shutDownSuccess = false;
            }

            String shutdownMessage;
            if (shutDownSuccess) {
                shutdownMessage = "Shutdown turbo trainer sucessfully";
            } else {
                shutdownMessage = "Error shutting down turbo trainer";
            }

            // NOU IN UI
            // Toast.makeText(this.getBaseContext(), shutdownMessage,
            // Toast.LENGTH_LONG).show();
            Log.i(TAG, shutdownMessage);

        }
        Intent intent = new Intent().setAction(this.getString(R.string.anthub_action_shutdown));
        sendBroadcast(intent);
        wakeLock.release();
        // trackRecordingServiceConnection.unbind();
        return shutDownSuccess;

    }

    public class TurboBinder extends Binder {
        public TurboService getService() {
            return TurboService.this;
        }
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return turboBinder;
    }

    /*
     * (non-Javadoc)
     * @see android.app.Service#onCreate()
     */
    @Override
    public void onCreate() {
        super.onCreate();
        trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
        // trackRecordingServiceConnection.startAndBind();
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        this.wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, TurboService.WAKE_LOCK);
    }

    // can th is be null
    private final Runnable bindChangedCallback = new Runnable() {
        @Override
        public void run() {
            // After binding changes (is available), update the total time in
            // trackController.
        }
    };

    // start track controller stuff

    private final BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(TRACK_STOPPED_ACTION)) {
                TurboService.this.doFinish();
            } else if (action.equals(TRACK_PAUSED_ACTION)) {
                // paused
            }
        }
    };

    private static String TRACK_STOPPED_ACTION;
    private static String TRACK_PAUSED_ACTION;

    public void registerRecordingReceiver() {
        TRACK_STOPPED_ACTION = TurboService.this.getString(R.string.track_stopped_broadcast_action);
        TRACK_PAUSED_ACTION = TurboService.this.getString(R.string.track_paused_broadcast_action);
        IntentFilter filter = new IntentFilter();
        filter.addAction(TRACK_STOPPED_ACTION);
        filter.addAction(TRACK_PAUSED_ACTION);

        registerReceiver(receiver, filter);
    }

    public void unregisterRecordingReceiver() {
        unregisterReceiver(receiver);
    }

    // probably should show an activity with detail?
    // toDO: move strings to R
    private void handleException(Exception e, String title, boolean finish, int id) {
        title = title == null ? "An Error occured communicating with your turbotrainer" : title;
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.track_bike).setContentTitle(title);
        Notification notification = null;

        Log.e(TAG, "Exception thrown in " + TurboService.class.getSimpleName() + ": ", e);
        String exceptionMessage = e.getMessage() == null ? "" : e.getMessage();
        if (e instanceof ServiceAlreadyClaimedException) {
            if (transceiver != null) {
                transceiver.requestForceClaimInterface(getString(R.string.app_name));
            }
            mBuilder.setContentText("Someone has already claimed the interface : attempting to force claim");
            notification = mBuilder.build();

        } else if (e instanceof AntRadioServiceNotInstalledException) {

            mBuilder.setContentText("AntRadioSerivce not installed. Please install.");
            notification = mBuilder.build();

        } else if (e instanceof TooFewAntChannelsAvailableException) {
            mBuilder.setContentText("Too few ant channels available");
            notification = mBuilder.build();

        } else if (e instanceof AntRadioPoweredOffException) {
            mBuilder.setContentText("Ant radio is powered off");
            notification = mBuilder.build();

        } else if (e instanceof Exception) { //too long : just show message?
            StringBuilder message = new StringBuilder();
            Throwable cause = e.getCause();
            message.append("Exception: " + e.getMessage());
            if (exceptionMessage != "") {
                message.append("\n" + "Message : " + exceptionMessage);
            }
            if (cause != null) {
                message.append("\nCaused by: ");
                message.append(cause.toString());
                if (cause.getMessage() != null) {
                    message.append(" : " + cause.getMessage());
                }
                while ((cause = cause.getCause()) != null) {
                    message.append(", ");
                    message.append(cause.toString());
                    if (cause.getMessage() != null) {
                        message.append(" : " + cause.getMessage());
                    }
                }
            }
            mBuilder.setContentText(message);
            notification = mBuilder.build();
        }

        NotificationManager mNotificationManager = (NotificationManager) getSystemService(
                Context.NOTIFICATION_SERVICE);

        mNotificationManager.notify(id, notification);

        if (finish) {
            Intent intent = new Intent().setAction(this.getString(R.string.turbo_service_action_exception_thrown));
            sendBroadcast(intent);
            doFinish();
        }

    }

    /*
     * Note that sharedPreferenceChangeListenr cannot be an anonymous inner class.
     * Anonymous inner class will get garbage collected.
     */
    private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
        @Override
        public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
            if (key == getString(R.string.settings_turbotrainer_generic_scale_factor_key)) {
                syncScaleFactor();
            } else if (key == getString(R.string.turbotrainer_selected)) {
                selectedTurboTrainer = preferences.getString(getString(R.string.turbotrainer_selected),
                        getString(R.string.turbotrainer_tacx_bushido_headunit_value));
            }

        }

    };

    private void syncScaleFactor() {
        scaleFactor = Float.parseFloat(
                preferences.getString(getString(R.string.settings_turbotrainer_generic_scale_factor_key), "1.0"));
    }

    // if
    // (context.getString(R.string.track_paused_broadcast_action).equals(action)
    // ||
    // context.getString(R.string.track_resumed_broadcast_action).equals(action)
    // ||
    // context.getString(R.string.track_started_broadcast_action).equals(action)
    // ||
    // context.getString(R.string.track_stopped_broadcast_action).equals(action)
    // ||
    // context.getString(R.string.track_update_broadcast_action).equals(action))
    //

}