de.tubs.ibr.dtn.chat.service.ChatService.java Source code

Java tutorial

Introduction

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

Source

/*
 * ChatService.java
 * 
 * Copyright (C) 2011 IBR, TU Braunschweig
 *
 * Written-by: 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.chat.service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Locale;
import java.util.StringTokenizer;

import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
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 de.tubs.ibr.dtn.DTNService;
import de.tubs.ibr.dtn.api.Block;
import de.tubs.ibr.dtn.api.Bundle;
import de.tubs.ibr.dtn.api.Bundle.ProcFlags;
import de.tubs.ibr.dtn.api.BundleID;
import de.tubs.ibr.dtn.api.DTNClient;
import de.tubs.ibr.dtn.api.DTNClient.Session;
import de.tubs.ibr.dtn.api.DataHandler;
import de.tubs.ibr.dtn.api.GroupEndpoint;
import de.tubs.ibr.dtn.api.Registration;
import de.tubs.ibr.dtn.api.ServiceNotAvailableException;
import de.tubs.ibr.dtn.api.SessionConnection;
import de.tubs.ibr.dtn.api.SessionDestroyedException;
import de.tubs.ibr.dtn.api.SingletonEndpoint;
import de.tubs.ibr.dtn.api.TransferMode;
import de.tubs.ibr.dtn.chat.MainActivity;
import de.tubs.ibr.dtn.chat.R;
import de.tubs.ibr.dtn.chat.core.Buddy;
import de.tubs.ibr.dtn.chat.core.Message;
import de.tubs.ibr.dtn.chat.core.Roster;

public class ChatService extends IntentService {

    public enum Debug {
        NOTIFICATION, BUDDY_ADD, SEND_PRESENCE
    }

    private static final String TAG = "ChatService";

    public static final String EXTRA_BUDDY_ID = "de.tubs.ibr.dtn.chat.BUDDY_ID";
    public static final String EXTRA_TEXT_BODY = "de.tubs.ibr.dtn.chat.TEXT_BODY";
    public static final String EXTRA_DISPLAY_NAME = "de.tubs.ibr.dtn.chat.DISPLAY_NAME";
    public static final String EXTRA_PRESENCE = "de.tubs.ibr.dtn.chat.EXTRA_PRESENCE";
    public static final String EXTRA_STATUS = "de.tubs.ibr.dtn.chat.EXTRA_STATUS";

    // mark a specific bundle as delivered
    public static final String MARK_DELIVERED_INTENT = "de.tubs.ibr.dtn.chat.MARK_DELIVERED";
    public static final String REPORT_DELIVERED_INTENT = "de.tubs.ibr.dtn.chat.REPORT_DELIVERED";

    public static final String ACTION_NEW_MESSAGE = "de.tubs.ibr.dtn.chat.ACTION_NEW_MESSAGE";
    public static final String ACTION_PRESENCE_ALARM = "de.tubs.ibr.dtn.chat.PRESENCE_ALARM";
    public static final String ACTION_SEND_MESSAGE = "de.tubs.ibr.dtn.chat.SEND_MESSAGE";
    public static final String ACTION_REFRESH_PRESENCE = "de.tubs.ibr.dtn.chat.REFRESH_PRESENCE";

    private static final int MESSAGE_NOTIFICATION = 1;
    public static final String ACTION_OPENCHAT = "de.tubs.ibr.dtn.chat.OPENCHAT";
    public static final GroupEndpoint PRESENCE_GROUP_EID = new GroupEndpoint("dtn://chat.dtn/presence");
    private Registration _registration = null;
    private ServiceError _service_error = ServiceError.NO_ERROR;

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

    // local roster with the connection to the database
    private Roster roster = null;

    // DTN client to talk with the DTN service
    private DTNClient _client = null;

    public ChatService() {
        super(TAG);
    }

    private DataHandler _data_handler = new DataHandler() {
        ByteArrayOutputStream stream = null;
        Bundle current;
        Long flags = 0L;

        public void startBundle(Bundle bundle) {
            this.current = bundle;
            this.flags = 0L;

            if (bundle.get(Bundle.ProcFlags.DTNSEC_STATUS_CONFIDENTIAL)) {
                this.flags |= Message.FLAG_ENCRYPTED;
            }

            if (bundle.get(Bundle.ProcFlags.DTNSEC_STATUS_VERIFIED)) {
                this.flags |= Message.FLAG_SIGNED;
            }
        }

        public void endBundle() {
            de.tubs.ibr.dtn.api.BundleID received = new de.tubs.ibr.dtn.api.BundleID(this.current);

            // run the queue and delivered process asynchronously
            Intent i = new Intent(ChatService.this, ChatService.class);
            i.setAction(MARK_DELIVERED_INTENT);
            i.putExtra("bundleid", received);
            startService(i);

            this.current = null;
        }

        public TransferMode startBlock(Block block) {
            // ignore messages with a size larger than 8k
            if ((block.length > 8196) || (block.type != 1))
                return TransferMode.NULL;

            // create a new bytearray output stream
            stream = new ByteArrayOutputStream();

            return TransferMode.SIMPLE;
        }

        public void endBlock() {
            if (stream != null) {
                String msg = new String(stream.toByteArray());
                stream = null;

                if (current.getDestination().equals(PRESENCE_GROUP_EID)) {
                    eventNewPresence(current.getSource(), current.getTimestamp().getDate(), msg, flags);
                } else {
                    eventNewMessage(current.getSource(), current.getTimestamp().getDate(), msg, flags);
                }
            }
        }

        public void payload(byte[] data) {
            if (stream == null)
                return;
            // write data to the stream
            try {
                stream.write(data);
            } catch (IOException e) {
                Log.e(TAG, "error on writing payload", e);
            }
        }

        public ParcelFileDescriptor fd() {
            return null;
        }

        public void progress(long current, long length) {
        }

        private void eventNewPresence(SingletonEndpoint source, Date created, String payload, Long flags) {
            Log.i(TAG, "Presence received from " + source);

            // buddy info
            String nickname = null;
            String presence = null;
            String status = null;
            String voiceeid = null;
            String language = null;
            String country = null;

            StringTokenizer tokenizer = new StringTokenizer(payload, "\n");
            while (tokenizer.hasMoreTokens()) {
                String data = tokenizer.nextToken();

                // search for the delimiter
                int delimiter = data.indexOf(':');

                // if the is no delimiter, ignore the line
                if (delimiter == -1)
                    return;

                // split the keyword and data pair
                String keyword = data.substring(0, delimiter);
                String value = data.substring(delimiter + 1, data.length()).trim();

                if (keyword.equalsIgnoreCase("Presence")) {
                    presence = value;
                } else if (keyword.equalsIgnoreCase("Nickname")) {
                    nickname = value;
                } else if (keyword.equalsIgnoreCase("Status")) {
                    status = value;
                } else if (keyword.equalsIgnoreCase("Voice")) {
                    voiceeid = value;
                } else if (keyword.equalsIgnoreCase("Language")) {
                    language = value;
                } else if (keyword.equalsIgnoreCase("Country")) {
                    country = value;
                }
            }

            if (nickname != null) {
                getRoster().updatePresence(source.toString(), created, presence, nickname, status, voiceeid,
                        language, country, flags);
            }
        }

        private void eventNewMessage(SingletonEndpoint source, Date created, String payload, Long flags) {
            if (source == null) {
                Log.e(TAG, "message source is null!");
            }

            // create a new message
            Long msgId = getRoster().createMessage(source.toString(), created, new Date(), true, payload, flags);

            // retrieve message object
            Message msg = getRoster().getMessage(msgId);

            // get buddy object
            Buddy b = getRoster().getBuddy(msg.getBuddyId());

            // create a notification
            createNotification(b, msg);

            // create a status bar notification
            Log.i(TAG, "New message received!");
        }
    };

    /**
     * Class for clients to access.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with
     * IPC.
     */
    public class LocalBinder extends Binder {
        public ChatService getService() {
            return ChatService.this;
        }
    }

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

    @Override
    public void onCreate() {
        // call onCreate of the super-class
        super.onCreate();

        // create a new client object
        _client = new DTNClient(new SessionConnection() {
            @Override
            public void onSessionConnected(Session session) {
                // respect user settings
                if (PreferenceManager.getDefaultSharedPreferences(ChatService.this)
                        .getBoolean("checkBroadcastPresence", false)) {
                    // register scheduled presence update
                    PresenceGenerator.activate(ChatService.this);
                }

                // register own data handler for incoming bundles
                session.setDataHandler(_data_handler);
            }

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

        // create a roster object
        this.roster = new Roster();
        this.roster.open(this);

        // create registration
        _registration = new Registration("chat");
        _registration.add(PRESENCE_GROUP_EID);

        try {
            _client.initialize(this, _registration);
            _service_error = ServiceError.NO_ERROR;
        } catch (ServiceNotAvailableException e) {
            _service_error = ServiceError.SERVICE_NOT_FOUND;
        } catch (SecurityException ex) {
            _service_error = ServiceError.PERMISSION_NOT_GRANTED;
        }

        Log.i(TAG, "service created.");
    }

    public ServiceError getServiceError() {
        return _service_error;
    }

    @Override
    public void onDestroy() {
        // close the roster (plus db connection)
        this.roster.close();

        // destroy DTN client
        _client.terminate();

        // clear all variables
        this.roster = null;
        _client = null;

        super.onDestroy();

        Log.i(TAG, "service destroyed.");
    }

    public synchronized Roster getRoster() {
        return this.roster;
    }

    @SuppressWarnings("deprecation")
    private void showNotification(Intent intent) {
        int defaults = 0;

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        if (prefs.getBoolean("vibrateOnMessage", true)) {
            defaults |= Notification.DEFAULT_VIBRATE;
        }

        Long buddyId = intent.getLongExtra(EXTRA_BUDDY_ID, -1L);
        String displayName = intent.getStringExtra(EXTRA_DISPLAY_NAME);
        String textBody = intent.getStringExtra(EXTRA_TEXT_BODY);

        CharSequence tickerText = getString(R.string.new_message_from) + " " + displayName;
        CharSequence contentTitle = getString(R.string.new_message);
        CharSequence contentText = displayName + ":\n" + textBody;

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);

        // forward intent to the activity
        intent.setClass(this, MainActivity.class);

        // Adds the intent to the main view
        stackBuilder.addNextIntent(intent);
        // Gets a PendingIntent containing the entire back stack
        PendingIntent contentIntent = stackBuilder.getPendingIntent(buddyId.intValue(),
                PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setContentTitle(contentTitle);
        builder.setContentText(contentText);
        builder.setSmallIcon(R.drawable.ic_message);
        builder.setTicker(tickerText);
        builder.setDefaults(defaults);
        builder.setWhen(System.currentTimeMillis());
        builder.setContentIntent(contentIntent);
        builder.setLights(0xffff0000, 300, 1000);
        builder.setSound(
                Uri.parse(prefs.getString("ringtoneOnMessage", "content://settings/system/notification_sound")));
        builder.setAutoCancel(true);

        Notification notification = builder.getNotification();

        NotificationManager mNotificationManager = (NotificationManager) getSystemService(
                Context.NOTIFICATION_SERVICE);
        mNotificationManager.notify(buddyId.toString(), MESSAGE_NOTIFICATION, notification);

        if (prefs.getBoolean("ttsWhenOnHeadset", false)) {
            AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

            if (am.isBluetoothA2dpOn() || am.isWiredHeadsetOn()) {
                // speak the notification
                Intent tts_intent = new Intent(this, TTSService.class);
                tts_intent.setAction(TTSService.INTENT_SPEAK);
                tts_intent.putExtra("speechText", tickerText + ": " + textBody);
                startService(tts_intent);
            }
        }
    }

    private void createNotification(Buddy b, Message msg) {
        Intent intent = new Intent(ACTION_NEW_MESSAGE);
        intent.putExtra(EXTRA_BUDDY_ID, b.getId());
        intent.putExtra(EXTRA_DISPLAY_NAME, b.getNickname());
        intent.putExtra(EXTRA_TEXT_BODY, msg.getPayload());

        // send intent to activity or broadcast receiver for notification
        sendOrderedBroadcast(intent, null);
    }

    public void clearNotification(Long buddyId) {
        if (buddyId == null)
            return;

        // clear notification
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(
                Context.NOTIFICATION_SERVICE);
        mNotificationManager.cancel(buddyId.toString(), MESSAGE_NOTIFICATION);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        String action = intent.getAction();

        // create a task to process concurrently
        if (ACTION_PRESENCE_ALARM.equals(action)) {
            SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ChatService.this);

            // check if the screen is active
            PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
            Boolean screenOn = pm.isScreenOn();

            String presence_tag = preferences.getString("presencetag", "auto");
            String presence_nick = preferences.getString("editNickname", "Nobody");
            String presence_text = preferences.getString("statustext", "");

            if (presence_tag.equals("auto")) {
                if (screenOn) {
                    presence_tag = "chat";
                } else {
                    presence_tag = "away";
                }
            }

            Log.i(TAG, "push out presence; " + presence_tag);
            actionRefreshPresence(presence_tag, presence_nick, presence_text);

            Editor edit = preferences.edit();
            edit.putLong("lastpresenceupdate", (new Date().getTime()));
            edit.commit();
        }
        // create a task to check for messages
        else if (de.tubs.ibr.dtn.Intent.RECEIVE.equals(action)) {
            try {
                while (_client.getSession().queryNext())
                    ;
            } catch (SessionDestroyedException e) {
                Log.e(TAG, "Can not query for bundle", e);
            } catch (InterruptedException e) {
                Log.e(TAG, "Can not query for bundle", e);
            }
        } else if (MARK_DELIVERED_INTENT.equals(action)) {
            actionMarkDelivered(intent);
        } else if (REPORT_DELIVERED_INTENT.equals(action)) {
            actionReportDelivered(intent);
        } else if (ACTION_SEND_MESSAGE.equals(action)) {
            Long buddyId = intent.getLongExtra(ChatService.EXTRA_BUDDY_ID, -1);
            String text = intent.getStringExtra(ChatService.EXTRA_TEXT_BODY);

            // abort if there is no buddyId
            if (buddyId < 0)
                return;

            actionSendMessage(buddyId, text);
        } else if (ACTION_REFRESH_PRESENCE.equals(action)) {
            String presence = intent.getStringExtra(ChatService.EXTRA_PRESENCE);
            String nickname = intent.getStringExtra(ChatService.EXTRA_DISPLAY_NAME);
            String status = intent.getStringExtra(ChatService.EXTRA_STATUS);

            actionRefreshPresence(presence, nickname, status);
        } else if (ACTION_NEW_MESSAGE.equals(action)) {
            showNotification(intent);
        }
    }

    private void actionMarkDelivered(Intent intent) {
        BundleID bundleid = intent.getParcelableExtra("bundleid");
        if (bundleid == null) {
            Log.e(TAG, "Intent to mark a bundle as delivered, but no bundle ID given");
            return;
        }

        try {
            _client.getSession().delivered(bundleid);
        } catch (Exception e) {
            Log.e(TAG, "Can not mark bundle as delivered.", e);
        }
    }

    private void actionReportDelivered(Intent intent) {
        SingletonEndpoint source = intent.getParcelableExtra("source");
        BundleID bundleid = intent.getParcelableExtra("bundleid");

        if (bundleid == null) {
            Log.e(TAG, "Intent to mark a bundle as delivered, but no bundle ID given");
            return;
        }

        synchronized (this.roster) {
            // report delivery to the roster
            getRoster().reportDelivery(source, bundleid);
        }
    }

    private void actionSendMessage(Long buddyId, String text) {
        try {
            Session s = _client.getSession();

            Long msgId = getRoster().createMessage(buddyId, new Date(), new Date(), false, text, 0L);

            // load buddy from roster
            Buddy buddy = getRoster().getBuddy(buddyId);

            // create a new bundle
            Bundle b = new Bundle();

            b.setDestination(new SingletonEndpoint(buddy.getEndpoint()));

            String lifetime = PreferenceManager.getDefaultSharedPreferences(this).getString("messageduration",
                    "259200");
            b.setLifetime(Long.parseLong(lifetime));

            // set status report requests
            b.set(ProcFlags.REQUEST_REPORT_OF_BUNDLE_DELIVERY, true);
            b.setReportto(SingletonEndpoint.ME);

            // request encryption for this message
            b.set(ProcFlags.DTNSEC_REQUEST_ENCRYPT, true);

            // request signing of the message
            b.set(ProcFlags.DTNSEC_REQUEST_SIGN, true);

            synchronized (this.roster) {
                // send out the message
                BundleID ret = s.send(b, text.getBytes());

                if (ret == null) {
                    Log.e(TAG, "could not send the message");
                } else {
                    Log.d(TAG, "Bundle sent, BundleID: " + ret.toString());
                }

                // update message into the database
                getRoster().reportSent(msgId, ret.toString());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (SessionDestroyedException e) {
            e.printStackTrace();
        }
    }

    public void actionRefreshPresence(String presence, String nickname, String status) {
        try {
            Session s = _client.getSession();

            String presence_message = "Presence: " + presence + "\n" + "Nickname: " + nickname + "\n" + "Status: "
                    + status + "\n" + "Language: " + Locale.getDefault().getLanguage() + "\n" + "Country: "
                    + Locale.getDefault().getCountry();

            try {
                if (Utils.isVoiceRecordingSupported(this)) {
                    DTNService dtns = _client.getDTNService();
                    if (dtns != null) {
                        presence_message += "\n" + "Voice: " + dtns.getEndpoint() + "/dtalkie";
                    }
                }
            } catch (RemoteException e) {
            }

            // create a new bundle
            Bundle b = new Bundle();

            // set destination to group endpoint
            b.setDestination(ChatService.PRESENCE_GROUP_EID);

            // set lifetime to one hour
            b.setLifetime(3600L);

            // request signing of the message
            b.set(ProcFlags.DTNSEC_REQUEST_SIGN, true);

            BundleID ret = s.send(b, presence_message.getBytes());

            if (ret == null) {
                Log.e(TAG, "could not send the message");
            } else {
                Log.d(TAG, "Presence sent, BundleID: " + ret.toString());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (SessionDestroyedException e) {
            e.printStackTrace();
        }
    }

    public void startDebug(Debug d) {
        String debug_source = "dtn://debug/chat";

        switch (d) {
        case NOTIFICATION:
            // create a new message
            Long msgId = getRoster().createMessage(debug_source, new Date(), new Date(), true, "Hello World", 0L);

            // retrieve message object
            Message msg = getRoster().getMessage(msgId);

            // get buddy object
            Buddy b = getRoster().getBuddy(msg.getBuddyId());

            // create a notification
            createNotification(b, msg);
            break;
        case BUDDY_ADD:
            getRoster().updatePresence(debug_source + "/" + String.valueOf((new Date()).getTime()), new Date(),
                    "online", "Debug Buddy", "Hello World", "dtn://test/dtalkie", "en", "gb", 0L);
            break;

        case SEND_PRESENCE:
            // wake-up the chat service and queue a send presence task
            Intent i = new Intent(this, ChatService.class);
            i.setAction(ACTION_PRESENCE_ALARM);
            startService(i);
            break;
        }
    }
}