de.tubs.ibr.dtn.service.DaemonService.java Source code

Java tutorial

Introduction

Here is the source code for de.tubs.ibr.dtn.service.DaemonService.java

Source

/*
 * DaemonService.java
 * 
 * Copyright (C) 2013 IBR, TU Braunschweig
 *
 * Written-by: Dominik Schrmann <dominik@dominikschuermann.de>
 *               Johannes Morgenroth <morgenroth@ibr.cs.tu-bs.de>
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 */

package de.tubs.ibr.dtn.service;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import android.widget.Toast;
import de.tubs.ibr.dtn.DTNService;
import de.tubs.ibr.dtn.DaemonState;
import de.tubs.ibr.dtn.R;
import de.tubs.ibr.dtn.api.DTNSession;
import de.tubs.ibr.dtn.api.Node;
import de.tubs.ibr.dtn.api.Registration;
import de.tubs.ibr.dtn.daemon.NeighborActivity;
import de.tubs.ibr.dtn.daemon.Preferences;
import de.tubs.ibr.dtn.daemon.api.SelectNeighborActivity;
import de.tubs.ibr.dtn.stats.ConvergenceLayerStatsEntry;
import de.tubs.ibr.dtn.stats.StatsDatabase;
import de.tubs.ibr.dtn.stats.StatsEntry;
import de.tubs.ibr.dtn.swig.DaemonRunLevel;
import de.tubs.ibr.dtn.swig.NativeStats;
import de.tubs.ibr.dtn.swig.StringVec;

public class DaemonService extends Service {
    private static final String ACTION_INITIALIZE = "de.tubs.ibr.dtn.action.INITIALIZE";

    public static final String ACTION_STARTUP = "de.tubs.ibr.dtn.action.STARTUP";
    public static final String ACTION_SHUTDOWN = "de.tubs.ibr.dtn.action.SHUTDOWN";
    public static final String ACTION_RESTART = "de.tubs.ibr.dtn.action.RESTART";

    public static final String ACTION_NETWORK_CHANGED = "de.tubs.ibr.dtn.action.NETWORK_CHANGED";

    public static final String ACTION_UPDATE_NOTIFICATION = "de.tubs.ibr.dtn.action.UPDATE_NOTIFICATION";
    public static final String ACTION_INITIATE_CONNECTION = "de.tubs.ibr.dtn.action.INITIATE_CONNECTION";
    public static final String ACTION_CLEAR_STORAGE = "de.tubs.ibr.dtn.action.CLEAR_STORAGE";

    public static final String ACTION_STORE_STATS = "de.tubs.ibr.dtn.action.STORE_STATS";

    public static final String ACTION_START_DISCOVERY = "de.tubs.ibr.dtn.action.START_DISCOVERY";
    public static final String ACTION_STOP_DISCOVERY = "de.tubs.ibr.dtn.action.STOP_DISCOVERY";
    public static final String EXTRA_DISCOVERY_DURATION = "de.tubs.ibr.dtn.intent.DISCOVERY_DURATION";

    public static final String PREFERENCE_NAME = "de.tubs.ibr.dtn.service_prefs";

    private final String TAG = "DaemonService";

    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;

    private int MSG_WHAT_STOP_DISCOVERY = 1;

    // session manager for all active sessions
    private SessionManager mSessionManager = null;

    // the P2P manager used for wifi direct control
    private P2pManager mP2pManager = null;

    // the daemon process
    private DaemonProcess mDaemonProcess = null;

    // indicates if a notification is visible
    private Boolean mShowNotification = false;

    // statistic database
    private StatsDatabase mStatsDatabase = null;
    private Date mStatsLastAction = null;

    // global discovery state
    private Boolean mDiscoveryState = false;

    // This is the object that receives interactions from clients. See
    // RemoteService for a more complete example.
    private final DTNService.Stub mBinder = new LocalDTNService();

    public class LocalDTNService extends DTNService.Stub {
        @Override
        public DaemonState getState() throws RemoteException {
            return DaemonService.this.mDaemonProcess.getState();
        }

        @Override
        public List<Node> getNeighbors() throws RemoteException {
            return DaemonService.this.mDaemonProcess.getNeighbors();
        }

        @Override
        public DTNSession getSession(String sessionKey) throws RemoteException {
            int caller = Binder.getCallingUid();
            String[] packageNames = DaemonService.this.getPackageManager().getPackagesForUid(caller);

            ClientSession cs = mSessionManager.getSession(packageNames, sessionKey);
            if (cs == null)
                return null;
            return cs.getBinder();
        }

        @Override
        public String[] getVersion() throws RemoteException {
            return DaemonService.this.mDaemonProcess.getVersion();
        }

        @Override
        public String getEndpoint() throws RemoteException {
            return Preferences.getEndpoint(DaemonService.this);
        }

        public DaemonService getLocal() {
            return DaemonService.this;
        }

        @Override
        public Bundle getSelectNeighborIntent() throws RemoteException {
            Bundle ret = new Bundle();
            Intent intent = new Intent(DaemonService.this, SelectNeighborActivity.class);
            PendingIntent pi = PendingIntent.getActivity(DaemonService.this, 0, intent,
                    PendingIntent.FLAG_ONE_SHOT);
            ret.putParcelable(de.tubs.ibr.dtn.Intent.EXTRA_PENDING_INTENT, pi);
            return ret;
        }
    };

    public boolean isP2pActive() {
        if (mP2pManager == null)
            return false;
        return mP2pManager.isActive();
    }

    public NativeStats getStats() {
        return mDaemonProcess.getStats();
    }

    public StatsDatabase getStatsDatabase() {
        return mStatsDatabase;
    }

    private Runnable mCollectStats = new Runnable() {
        @Override
        public void run() {
            final Intent storeStatsIntent = new Intent(DaemonService.this, DaemonService.class);
            storeStatsIntent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_STORE_STATS);
            startService(storeStatsIntent);
        }
    };

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

    @SuppressLint("HandlerLeak")
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            Intent intent = (Intent) msg.obj;
            onHandleIntent(intent, msg.arg1);
        }
    }

    /**
     * Incoming Intents are handled here
     * 
     * @param intent
     */
    @SuppressWarnings("deprecation")
    public void onHandleIntent(Intent intent, int startId) {
        String action = intent.getAction();

        if (ACTION_STARTUP.equals(action)) {
            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

            // only start if the daemon is offline or in error state
            // and if the daemon switch is on
            if ((mDaemonProcess.getState().equals(DaemonState.OFFLINE)
                    || mDaemonProcess.getState().equals(DaemonState.ERROR))
                    && prefs.getBoolean(Preferences.KEY_ENABLED, true)) {
                // start-up the daemon
                mDaemonProcess.start();

                final Intent storeStatsIntent = new Intent(this, DaemonService.class);
                storeStatsIntent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_STORE_STATS);
                startService(storeStatsIntent);
            }
        } else if (ACTION_SHUTDOWN.equals(action)) {
            // stop main loop
            mDaemonProcess.stop();
        } else if (ACTION_RESTART.equals(action)) {
            Integer level = intent.getIntExtra("runlevel", 0);

            // restart the daemon into the given runlevel
            mDaemonProcess.restart(level, new DaemonProcess.OnRestartListener() {
                @Override
                public void OnStop(DaemonRunLevel previous, DaemonRunLevel next) {
                    if (next.swigValue() < DaemonRunLevel.RUNLEVEL_API.swigValue()
                            && previous.swigValue() >= DaemonRunLevel.RUNLEVEL_API.swigValue()) {
                        // shutdown the session manager
                        mSessionManager.destroy();
                    }
                }

                @Override
                public void OnStart(DaemonRunLevel previous, DaemonRunLevel next) {
                    if (previous.swigValue() < DaemonRunLevel.RUNLEVEL_API.swigValue()
                            && next.swigValue() >= DaemonRunLevel.RUNLEVEL_API.swigValue()) {
                        // re-initialize the session manager
                        mSessionManager.initialize();
                    }
                }

                @Override
                public void OnReloadConfiguration() {
                }
            });

            final Intent storeStatsIntent = new Intent(this, DaemonService.class);
            storeStatsIntent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_STORE_STATS);
            startService(storeStatsIntent);
        } else if (ACTION_UPDATE_NOTIFICATION.equals(action)) {
            if (intent.hasExtra("text")) {
                // update state text in the notification
                updateNotification(intent.getStringExtra("text"));
            } else {
                // update state text in the notification
                updateNotification(null);
            }
        } else if (de.tubs.ibr.dtn.Intent.REGISTER.equals(action)) {
            final Registration reg = (Registration) intent.getParcelableExtra("registration");
            final PendingIntent pi = (PendingIntent) intent.getParcelableExtra("app");

            mSessionManager.register(pi.getTargetPackage(), reg);

        } else if (de.tubs.ibr.dtn.Intent.UNREGISTER.equals(action)) {
            final PendingIntent pi = (PendingIntent) intent.getParcelableExtra("app");

            mSessionManager.unregister(pi.getTargetPackage());
        } else if (ACTION_INITIATE_CONNECTION.equals(action)) {
            if (intent.hasExtra("endpoint")) {
                mDaemonProcess.initiateConnection(intent.getStringExtra("endpoint"));
            }
        } else if (ACTION_CLEAR_STORAGE.equals(action)) {
            mDaemonProcess.clearStorage();
            Toast.makeText(this, R.string.toast_storage_cleared, Toast.LENGTH_SHORT).show();
        } else if (ACTION_NETWORK_CHANGED.equals(action)) {
            // This intent tickle the service if something has changed in the
            // network configuration. If the service was enabled but terminated
            // before,
            // it will be started now.

            final Intent storeStatsIntent = new Intent(this, DaemonService.class);
            storeStatsIntent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_STORE_STATS);
            startService(storeStatsIntent);

            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

            // if discovery is configured as "smart"
            if ("smart".equals(prefs.getString(Preferences.KEY_DISCOVERY_MODE, "smart"))) {
                // enable discovery for 2 minutes
                final Intent discoIntent = new Intent(DaemonService.this, DaemonService.class);
                discoIntent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_START_DISCOVERY);
                discoIntent.putExtra(EXTRA_DISCOVERY_DURATION, 120L);
                startService(discoIntent);
            }
        } else if (ACTION_STORE_STATS.equals(action)) {
            // cancel the next scheduled collection
            mServiceHandler.removeCallbacks(mCollectStats);

            if (mStatsLastAction != null) {
                Calendar now = Calendar.getInstance();
                now.roll(Calendar.MINUTE, -1);
                if (mStatsLastAction.before(now.getTime())) {
                    refreshStats();
                }
            } else {
                refreshStats();
            }

            // schedule next collection in 15 minutes
            mServiceHandler.postDelayed(mCollectStats, 900000);
        } else if (ACTION_INITIALIZE.equals(action)) {
            // initialize the daemon service
            initialize();
        } else if (ACTION_START_DISCOVERY.equals(action)) {
            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
            String discoMode = prefs.getString(Preferences.KEY_DISCOVERY_MODE, "smart");
            boolean stayOn = "on".equals(discoMode) && mDiscoveryState;

            // create stop discovery intent
            Intent stopIntent = new Intent(this, DaemonService.class);
            stopIntent.setAction(DaemonService.ACTION_STOP_DISCOVERY);

            if (intent.hasExtra(EXTRA_DISCOVERY_DURATION) && !stayOn) {
                Long duration = intent.getLongExtra(EXTRA_DISCOVERY_DURATION, 120);

                Message msg = mServiceHandler.obtainMessage();
                msg.what = MSG_WHAT_STOP_DISCOVERY;
                msg.arg1 = startId;
                msg.obj = stopIntent;
                mServiceHandler.sendMessageDelayed(msg, duration * 1000);

                Log.i(TAG, "Discovery stop scheduled in " + duration + " seconds.");
            } else {
                mServiceHandler.removeMessages(MSG_WHAT_STOP_DISCOVERY);
                Log.i(TAG, "Scheduled discovery stop removed.");
            }

            // only start discovery if not active
            if (!mDiscoveryState) {
                // start P2P discovery and enable IPND
                mDaemonProcess.startDiscovery();

                // start Wi-Fi P2P discovery
                if (mP2pManager != null)
                    mP2pManager.startDiscovery();

                // set global discovery state
                mDiscoveryState = true;

                // request notification update
                requestNotificationUpdate(getResources().getString(R.string.ticker_discovery_started));
            }
        } else if (ACTION_STOP_DISCOVERY.equals(action)) {
            // only stop discovery if active
            if (mDiscoveryState) {
                // stop P2P discovery and disable IPND
                mDaemonProcess.stopDiscovery();

                // stop Wi-Fi P2P discovery
                if (mP2pManager != null)
                    mP2pManager.stopDiscovery();

                // set global discovery state
                mDiscoveryState = false;

                // request notification update
                requestNotificationUpdate(getResources().getString(R.string.ticker_discovery_stopped));
            }

            // remove all stop discovery messages
            mServiceHandler.removeMessages(MSG_WHAT_STOP_DISCOVERY);
            Log.i(TAG, "Scheduled discovery stop removed.");
        }

        // stop the daemon if it should be offline
        if (mDaemonProcess.getState().equals(DaemonState.OFFLINE) && (startId != -1))
            stopSelf(startId);
    }

    private void refreshStats() {
        NativeStats nstats = mDaemonProcess.getStats();

        // query the daemon stats and store them in the database
        mStatsDatabase.put(new StatsEntry(nstats));

        StringVec tags = nstats.getTags();
        for (int i = 0; i < tags.size(); i++) {
            ConvergenceLayerStatsEntry e = new ConvergenceLayerStatsEntry(nstats, tags.get(i), i);
            mStatsDatabase.put(e);
        }

        mStatsLastAction = new Date();
    }

    public DaemonService() {
        super();
    }

    @Override
    public void onCreate() {
        super.onCreate();

        // open statistic database
        mStatsDatabase = new StatsDatabase(this);
        mStatsLastAction = null;

        // create daemon main thread
        mDaemonProcess = new DaemonProcess(this, mProcessHandler);

        /*
         * incoming Intents will be processed by ServiceHandler and queued in
         * HandlerThread
         */
        HandlerThread thread = new HandlerThread("DaemonService_IntentThread");
        thread.start();
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);

        // create a session manager
        mSessionManager = new SessionManager(this);

        // create P2P Manager
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            mP2pManager = new P2pManager(this);
            mP2pManager.create();
        }

        // start initialization of the daemon process
        final Intent intent = new Intent(this, DaemonService.class);
        intent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_INITIALIZE);

        // queue the initialization job as the first job of the handler
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = -1; // invalid startId (this never leads to a stop of the
                       // service)
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    /**
     * Initialize the daemon service This should be the first job of the service
     * after creation
     */
    private void initialize() {
        // initialize the basic daemon
        mDaemonProcess.initialize();

        // restore registrations
        mSessionManager.initialize();

        if (Log.isLoggable(TAG, Log.DEBUG))
            Log.d(TAG, "DaemonService created");

        // start daemon if enabled
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

        // listen to preference changes
        prefs.registerOnSharedPreferenceChangeListener(_pref_listener);

        if (prefs.getBoolean(Preferences.KEY_ENABLED, true)) {
            // startup the daemon process
            final Intent intent = new Intent(this, DaemonService.class);
            intent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_STARTUP);
            startService(intent);
        }
    }

    /**
     * Called on stopSelf() or stopService()
     */
    @Override
    public void onDestroy() {
        // unlisten to preference changes
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        prefs.unregisterOnSharedPreferenceChangeListener(_pref_listener);

        // disable P2P manager
        if (mP2pManager != null)
            mP2pManager.destroy();

        // stop looper that handles incoming intents
        mServiceLooper.quit();

        // close all sessions
        mSessionManager.destroy();

        // shutdown daemon completely
        mDaemonProcess.destroy();
        mDaemonProcess = null;

        // dereference P2P Manager
        mP2pManager = null;

        // remove notification
        NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        nm.cancel(1);

        // close statistic database
        mStatsDatabase.close();

        // call super method
        super.onDestroy();
    }

    @Override
    public void onStart(Intent intent, int startId) {
        /*
         * If no explicit intent is given start as ACTION_STARTUP. When this
         * service crashes, Android restarts it without an Intent. Thus
         * ACTION_STARTUP is executed!
         */
        if (intent == null || intent.getAction() == null) {
            Log.d(TAG, "intent == null or intent.getAction() == null -> default to ACTION_STARTUP");

            intent = new Intent(ACTION_STARTUP);
        }

        String action = intent.getAction();

        if (Log.isLoggable(TAG, Log.DEBUG))
            Log.d(TAG, "Received start id " + startId + ": " + intent);
        if (Log.isLoggable(TAG, Log.DEBUG))
            Log.d(TAG, "Intent Action: " + action);

        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return START_STICKY;
    }

    private DaemonProcessHandler mProcessHandler = new DaemonProcessHandler() {

        @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
        @Override
        public void onStateChanged(DaemonState state) {
            Log.d(TAG, "DaemonState: " + state);

            // prepare broadcast intent
            Intent broadcastIntent = new Intent();
            broadcastIntent.setAction(de.tubs.ibr.dtn.Intent.STATE);
            broadcastIntent.putExtra("state", state.name());
            broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);

            // request notification update
            requestNotificationUpdate(null);

            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(DaemonService.this);

            switch (state) {
            case ERROR:
                break;

            case OFFLINE:
                if (prefs.getBoolean(Preferences.KEY_P2P_ENABLED, true)) {
                    if (mP2pManager != null)
                        mP2pManager.setEnabled(false);
                }

                // disable foreground service only if the daemon has been
                // switched off
                if (!prefs.getBoolean(Preferences.KEY_ENABLED, true)) {
                    // mark the notification as invisible
                    mShowNotification = false;

                    // stop foreground service
                    stopForeground(true);

                    // stop service
                    stopSelf();
                }

                // if discovery is configured as some kind of active
                if (!"off".equals(prefs.getString(Preferences.KEY_DISCOVERY_MODE, "smart"))) {
                    // disable discovery
                    final Intent discoIntent = new Intent(DaemonService.this, DaemonService.class);
                    discoIntent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_STOP_DISCOVERY);
                    startService(discoIntent);
                }

                break;

            case ONLINE:
                // mark the notification as visible
                mShowNotification = true;

                // create initial notification
                Notification n = buildNotification(null);

                // turn this to a foreground service (kill-proof)
                startForeground(1, n);

                if (prefs.getBoolean(Preferences.KEY_P2P_ENABLED, false)) {
                    if (mP2pManager != null)
                        mP2pManager.setEnabled(true);
                }

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
                    // wake-up all apps in stopped-state when going online
                    broadcastIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
                }

                // if discovery is configured as "smart"
                if ("smart".equals(prefs.getString(Preferences.KEY_DISCOVERY_MODE, "smart"))) {
                    // enable discovery for 2 minutes
                    final Intent discoIntent = new Intent(DaemonService.this, DaemonService.class);
                    discoIntent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_START_DISCOVERY);
                    discoIntent.putExtra(EXTRA_DISCOVERY_DURATION, 120L);
                    startService(discoIntent);
                }
                // if discovery is configured as "on"
                else if ("on".equals(prefs.getString(Preferences.KEY_DISCOVERY_MODE, "smart"))) {
                    // enable discovery
                    final Intent discoIntent = new Intent(DaemonService.this, DaemonService.class);
                    discoIntent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_START_DISCOVERY);
                    startService(discoIntent);
                }
                break;

            case SUSPENDED:
                break;
            case UNKOWN:
                break;
            default:
                break;

            }

            // broadcast state change
            sendBroadcast(broadcastIntent);
        }

        @Override
        public void onNeighborhoodChanged() {
            requestNotificationUpdate(null);
        }

        @Override
        public void onEvent(Intent intent) {
            sendBroadcast(intent);
        }

    };

    private void requestNotificationUpdate(String text) {
        // request notification update
        final Intent i = new Intent(DaemonService.this, DaemonService.class);
        i.setAction(ACTION_UPDATE_NOTIFICATION);

        if (text != null) {
            i.putExtra("text", text);
        }

        startService(i);
    }

    private void updateNotification(String stateText) {
        NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        // update the notification only if it is visible
        if (mShowNotification) {
            nm.notify(1, buildNotification(stateText));
        }
    }

    @SuppressWarnings("deprecation")
    private Notification buildNotification(String stateText) {
        String content = Preferences.getEndpoint(DaemonService.this);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);

        builder.setContentTitle(getResources().getString(R.string.service_name));
        builder.setContentText(content);

        builder.setSmallIcon(R.drawable.ic_notification);
        builder.setOngoing(true);
        builder.setOnlyAlertOnce(true);
        builder.setWhen(0);
        if (stateText != null)
            builder.setTicker(stateText);

        List<Node> neighbors = mDaemonProcess.getNeighbors();
        builder.setNumber(neighbors.size());

        // create intent for the neighbor list
        Intent showNeighborsIntent = new Intent(this, NeighborActivity.class);
        showNeighborsIntent.setAction("android.intent.action.MAIN");
        showNeighborsIntent.addCategory("android.intent.category.LAUNCHER");

        if (mDiscoveryState) {
            // create intent to stop discovery
            Intent stopDiscoveryIntent = new Intent(this, DaemonService.class);
            stopDiscoveryIntent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_STOP_DISCOVERY);
            PendingIntent piDisco = PendingIntent.getService(this, 0, stopDiscoveryIntent, 0);
            builder.addAction(R.drawable.ic_action_discovery_stop, getString(R.string.stop_discovery), piDisco);
        } else {
            // create intent to start discovery
            Intent startDiscoveryIntent = new Intent(this, DaemonService.class);
            startDiscoveryIntent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_START_DISCOVERY);

            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
            if ("smart".equals(prefs.getString(Preferences.KEY_DISCOVERY_MODE, "smart"))) {
                startDiscoveryIntent.putExtra(DaemonService.EXTRA_DISCOVERY_DURATION, 120L);
            }

            PendingIntent piDisco = PendingIntent.getService(this, 0, startDiscoveryIntent, 0);
            builder.addAction(R.drawable.ic_action_discovery, getString(R.string.start_discovery), piDisco);
        }

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(NeighborActivity.class);
        stackBuilder.addNextIntent(showNeighborsIntent);
        PendingIntent contentIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); // PendingIntent.getActivity(this, 0, showNeighborsIntent, 0);
        builder.setContentIntent(contentIntent);

        return builder.getNotification();
    }

    private SharedPreferences.OnSharedPreferenceChangeListener _pref_listener = new SharedPreferences.OnSharedPreferenceChangeListener() {

        @Override
        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

            if (Preferences.KEY_P2P_ENABLED.equals(key)) {
                if (sharedPreferences.getBoolean(key, false)
                        && mDaemonProcess.getState().equals(DaemonState.ONLINE)) {
                    if (mP2pManager != null)
                        mP2pManager.setEnabled(true);
                } else {
                    if (mP2pManager != null)
                        mP2pManager.setEnabled(false);
                }
            } else if (Preferences.KEY_DISCOVERY_MODE.equals(key)) {
                final String disco_mode = sharedPreferences.getString(key, "smart");

                // if discovery is configured as "on"
                if ("on".equals(disco_mode)) {
                    // enable discovery
                    final Intent discoIntent = new Intent(DaemonService.this, DaemonService.class);
                    discoIntent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_START_DISCOVERY);
                    startService(discoIntent);
                }
                // if discovery is configured as "off"
                else if ("off".equals(disco_mode)) {
                    // disable discovery
                    final Intent discoIntent = new Intent(DaemonService.this, DaemonService.class);
                    discoIntent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_STOP_DISCOVERY);
                    startService(discoIntent);
                }
                // if discovery is configured as "smart"
                else if ("smart".equals(disco_mode)) {
                    // enable discovery for 2 minutes
                    final Intent discoIntent = new Intent(DaemonService.this, DaemonService.class);
                    discoIntent.setAction(de.tubs.ibr.dtn.service.DaemonService.ACTION_START_DISCOVERY);
                    discoIntent.putExtra(EXTRA_DISCOVERY_DURATION, 120L);
                    startService(discoIntent);
                }
            }
        }

    };
}