com.marekscholtz.bluetify.utilities.BluetoothChatService.java Source code

Java tutorial

Introduction

Here is the source code for com.marekscholtz.bluetify.utilities.BluetoothChatService.java

Source

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.marekscholtz.bluetify.utilities;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.support.v4.content.ContextCompat;
import android.util.Base64;

import com.marekscholtz.bluetify.R;
import com.marekscholtz.bluetify.activities.ChatActivity;
import com.marekscholtz.bluetify.entities.ChatMessage;
import com.marekscholtz.bluetify.entities.Friend;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.UUID;

import io.realm.Realm;
import io.realm.RealmConfiguration;

public class BluetoothChatService extends Service {

    private static final String NAME_SECURE = "BluetoothChatSecure";
    private static final UUID UUID_SECURE = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
    private BluetoothAdapter mBluetoothAdapter;
    private Handler mHandler;
    private AcceptThread mAcceptThread;
    private ConnectThread mConnectThread;
    private ConnectedThread mConnectedThread;
    private int mState;
    private static SharedPreferences mSharedPreferences;
    private final BluetoothChatServiceBinder mBluetoothChatServiceBinder = new BluetoothChatServiceBinder();

    public static final int STATE_NONE = 0;
    public static final int STATE_LISTEN = 1;
    public static final int STATE_CONNECTING = 2;
    public static final int STATE_CONNECTED = 3;

    public BluetoothChatService() {
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mState = STATE_NONE;
    }

    public class BluetoothChatServiceBinder extends Binder {
        public BluetoothChatService getService() {
            return BluetoothChatService.this;
        }
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        accept();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        if (mConnectThread != null) {
            mConnectThread.cancel();
            mConnectThread = null;
        }
        if (mConnectedThread != null) {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }
        if (mAcceptThread != null) {
            mAcceptThread.cancel();
            mAcceptThread = null;
        }
        super.onDestroy();
    }

    public void setHandler(Handler handler) {
        mHandler = handler;
    }

    public void unsetHandler() {
        mHandler = null;
    }

    public int getState() {
        return mState;
    }

    public void accept() {
        if (mAcceptThread != null) {
            mAcceptThread.cancel();
            mAcceptThread = null;
        }
        if (mConnectThread != null) {
            mConnectThread.cancel();
            mConnectThread = null;
        }
        if (mConnectedThread != null) {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }
        mAcceptThread = new AcceptThread();
        mAcceptThread.start();
    }

    public synchronized void connect(String macAddress) {
        if (mAcceptThread != null) {
            mAcceptThread.cancel();
            mAcceptThread = null;
        }
        if (mConnectThread != null) {
            mConnectThread.cancel();
            mConnectThread = null;
        }
        if (mConnectedThread != null) {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }
        mConnectThread = new ConnectThread(mBluetoothAdapter.getRemoteDevice(macAddress));
        mConnectThread.start();
    }

    private synchronized void connected(BluetoothSocket socket) {
        if (mAcceptThread != null) {
            mAcceptThread.cancel();
            mAcceptThread = null;
        }
        if (mConnectThread != null) {
            mConnectThread.cancel();
            mConnectThread = null;
        }
        if (mConnectedThread != null) {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }
        mConnectedThread = new ConnectedThread(socket);
        mConnectedThread.start();
        BluetoothAdapter bluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
                .getAdapter();

        if (mSharedPreferences == null) {
            mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        }

        byte[] usernameBytes = ("&u" + mSharedPreferences.getString("username", bluetoothAdapter.getName()) + "&p")
                .getBytes();
        if (mSharedPreferences.getString("profile_photo", null) == null) {
            write(usernameBytes);
        } else {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            try {
                outputStream.write(usernameBytes);
                outputStream
                        .write(Base64.decode(mSharedPreferences.getString("profile_photo", null), Base64.DEFAULT));
            } catch (IOException e) {
                e.printStackTrace();
            }
            write(outputStream.toByteArray());
        }
    }

    public void connectionError() {
        mState = STATE_NONE;
        if (mHandler != null) {
            mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mState, -1).sendToTarget();
        }
        BluetoothChatService.this.accept();
    }

    public void write(byte[] out) {
        ConnectedThread connectedThread;
        synchronized (this) {
            if (mState != STATE_CONNECTED)
                return;
            connectedThread = mConnectedThread;
        }
        connectedThread.write(out);
    }

    private class AcceptThread extends Thread {

        private final BluetoothServerSocket mmServerSocket;
        private String mSocketType;

        private AcceptThread() {
            BluetoothServerSocket tmp = null;
            mSocketType = "Secure";
            try {
                tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE, UUID_SECURE);
            } catch (IOException e) {
                e.printStackTrace();
            }
            mmServerSocket = tmp;
            mState = STATE_LISTEN;
            if (mHandler != null) {
                mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mState, -1).sendToTarget();
            }
        }

        public void run() {
            setName("AcceptThread" + mSocketType);
            BluetoothSocket socket;
            while (mState != STATE_CONNECTED) {
                try {
                    socket = mmServerSocket.accept();
                } catch (IOException e) {
                    break;
                }
                if (socket != null) {
                    synchronized (BluetoothChatService.this) {
                        switch (mState) {
                        case STATE_LISTEN:
                        case STATE_CONNECTING:
                            connected(socket);
                            break;
                        case STATE_NONE:
                        case STATE_CONNECTED:
                            try {
                                socket.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            break;
                        }
                    }
                }
            }
        }

        private void cancel() {
            try {
                mmServerSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private String mSocketType;

        private ConnectThread(BluetoothDevice device) {
            BluetoothSocket tmp = null;
            mSocketType = "Secure";
            try {
                tmp = device.createRfcommSocketToServiceRecord(UUID_SECURE);
            } catch (IOException e) {
                e.printStackTrace();
            }
            mmSocket = tmp;
            mState = STATE_CONNECTING;
            if (mHandler != null) {
                mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mState, -1).sendToTarget();
            }
        }

        public void run() {
            setName("ConnectThread" + mSocketType);
            mBluetoothAdapter.cancelDiscovery();
            try {
                mmSocket.connect();
            } catch (IOException e) {
                try {
                    mmSocket.close();
                } catch (IOException e2) {
                    e.printStackTrace();
                }
                connectionError();
                return;
            }

            synchronized (BluetoothChatService.this) {
                mConnectThread = null;
            }
            connected(mmSocket);
        }

        private void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;
        private String mmPreviousCommand;

        private ConnectedThread(BluetoothSocket socket) {
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }

            mmInStream = tmpIn;
            mmOutStream = tmpOut;
            mState = STATE_CONNECTED;
            if (mHandler != null) {
                mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mState, -1).sendToTarget();
            }
        }

        public void run() {
            byte[] messageBytes = new byte[1048576];
            int bytes;
            while (mState == STATE_CONNECTED) {
                try {
                    bytes = mmInStream.read(messageBytes);
                    Realm.init(getApplicationContext());
                    RealmConfiguration config = new RealmConfiguration.Builder().deleteRealmIfMigrationNeeded()
                            .build();
                    Realm realm = Realm.getInstance(config);
                    Friend friend = realm.where(Friend.class)
                            .equalTo("mMacAddress", mmSocket.getRemoteDevice().getAddress()).findFirst();
                    String command = new String(Arrays.copyOfRange(messageBytes, 0, 2));
                    realm.beginTransaction();
                    if ("&u".equals(command)) {
                        mmPreviousCommand = "&u";
                        friend.setName(new String(
                                Arrays.copyOfRange(messageBytes, 2, new String(messageBytes).indexOf("&p"))));
                        if (new String(messageBytes).indexOf("&p") + 2 < bytes) {
                            friend.setProfilePhoto(Arrays.copyOfRange(messageBytes,
                                    new String(messageBytes).indexOf("&p") + 2, bytes));
                        } else {
                            friend.setProfilePhoto(null);
                        }
                        if (mHandler != null) {
                            mHandler.obtainMessage(Constants.USERNAME_CHANGED, friend.getName()).sendToTarget();
                        }
                    } else if ("&t".equals(command)) {
                        mmPreviousCommand = command;
                        ChatMessage chatMessage = realm.createObject(ChatMessage.class, getNextPrimaryKey(realm));
                        chatMessage.setMacAddress(mmSocket.getRemoteDevice().getAddress());
                        chatMessage.setPicture(false);
                        if (!"t&".equals(new String(Arrays.copyOfRange(messageBytes, bytes - 2, bytes)))) {
                            chatMessage.setContent(Arrays.copyOfRange(messageBytes, 2, bytes));
                        } else {
                            chatMessage.setContent(Arrays.copyOfRange(messageBytes, 2, bytes - 2));
                            if (mSharedPreferences.getBoolean("notification_enabled", false)) {
                                showNotification(friend, chatMessage);
                            }
                        }
                        chatMessage.setSent(false);
                        chatMessage.setDate(new Date());
                    } else if ("&i".equals(command)) {
                        mmPreviousCommand = command;
                        ChatMessage chatMessage = realm.createObject(ChatMessage.class, getNextPrimaryKey(realm));
                        chatMessage.setMacAddress(mmSocket.getRemoteDevice().getAddress());
                        chatMessage.setPicture(true);
                        if (!"i&".equals(new String(Arrays.copyOfRange(messageBytes, bytes - 2, bytes)))) {
                            chatMessage.setContent(Arrays.copyOfRange(messageBytes, 2, bytes));
                        } else {
                            byte[] bitmapBytes = Arrays.copyOfRange(messageBytes, 2, bytes - 2);
                            Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length);
                            Calendar calendar = Calendar.getInstance();
                            calendar.setTime(chatMessage.getDate());
                            String bitmapFileName = String.valueOf(calendar.get(Calendar.YEAR)) + "-"
                                    + calendar.get(Calendar.MONTH) + "-" + calendar.get(Calendar.DAY_OF_MONTH) + " "
                                    + calendar.get(Calendar.HOUR_OF_DAY) + "." + calendar.get(Calendar.MINUTE) + "."
                                    + calendar.get(Calendar.SECOND) + " " + friend.getName() + ".png";
                            File bitmapPathFile = new File(
                                    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                                    bitmapFileName);
                            FileOutputStream fileOutputStream = new FileOutputStream(bitmapPathFile);
                            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
                            chatMessage.setContent(bitmapPathFile.toURI().toString().getBytes());
                            if (mSharedPreferences.getBoolean("notification_enabled", false)) {
                                showNotification(friend, chatMessage);
                            }
                        }
                        chatMessage.setSent(false);
                        chatMessage.setDate(new Date());
                    } else if ("&u".equals(mmPreviousCommand)) {
                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                        try {
                            byteArrayOutputStream.write(friend.getProfilePhoto());
                            byteArrayOutputStream.write(Arrays.copyOfRange(messageBytes, 0, bytes));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        friend.setProfilePhoto(byteArrayOutputStream.toByteArray());
                    } else if ("&t".equals(mmPreviousCommand)) {
                        ChatMessage chatMessage = realm.where(ChatMessage.class).findAll().last();
                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                        byteArrayOutputStream.write(chatMessage.getContent());
                        if (!"t&".equals(new String(Arrays.copyOfRange(messageBytes, bytes - 2, bytes)))) {
                            try {
                                byteArrayOutputStream.write(Arrays.copyOfRange(messageBytes, 0, bytes));
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            chatMessage.setContent(byteArrayOutputStream.toByteArray());
                        } else {
                            try {
                                byteArrayOutputStream.write(Arrays.copyOfRange(messageBytes, 0, bytes - 2));
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            chatMessage.setContent(byteArrayOutputStream.toByteArray());
                            if (mSharedPreferences.getBoolean("notification_enabled", false)) {
                                showNotification(friend, chatMessage);
                            }
                        }
                    } else if ("&i".equals(mmPreviousCommand)) {
                        ChatMessage chatMessage = realm.where(ChatMessage.class).findAll().last();
                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                        byteArrayOutputStream.write(chatMessage.getContent());
                        if (!"i&".equals(new String(Arrays.copyOfRange(messageBytes, bytes - 2, bytes)))) {
                            try {
                                byteArrayOutputStream.write(Arrays.copyOfRange(messageBytes, 0, bytes));
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            chatMessage.setContent(byteArrayOutputStream.toByteArray());
                        } else {
                            try {
                                byteArrayOutputStream.write(Arrays.copyOfRange(messageBytes, 0, bytes - 2));
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            Bitmap bitmap = BitmapFactory.decodeByteArray(byteArrayOutputStream.toByteArray(), 0,
                                    byteArrayOutputStream.size());
                            Calendar calendar = Calendar.getInstance();
                            calendar.setTime(chatMessage.getDate());
                            String bitmapFileName = String.valueOf(calendar.get(Calendar.YEAR)) + "-"
                                    + calendar.get(Calendar.MONTH) + "-" + calendar.get(Calendar.DAY_OF_MONTH) + " "
                                    + calendar.get(Calendar.HOUR_OF_DAY) + "." + calendar.get(Calendar.MINUTE) + "."
                                    + calendar.get(Calendar.SECOND) + " " + friend.getName() + ".png";
                            File bitmapPathFile = new File(
                                    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                                    bitmapFileName);
                            FileOutputStream fileOutputStream = new FileOutputStream(bitmapPathFile);
                            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
                            chatMessage.setContent(bitmapPathFile.toURI().toString().getBytes());
                            if (mSharedPreferences.getBoolean("notification_enabled", false)) {
                                showNotification(friend, chatMessage);
                            }
                        }
                    }
                    realm.commitTransaction();
                } catch (IOException e) {
                    connectionError();
                    break;
                }
            }
        }

        private void write(byte[] buffer) {
            try {
                mmOutStream.write(buffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public String getConnectedMacAddress() {
        if (mConnectedThread != null) {
            return mConnectedThread.mmSocket.getRemoteDevice().getAddress();
        } else {
            return null;
        }
    }

    public Long getNextPrimaryKey(Realm realm) {
        try {
            return realm.where(ChatMessage.class).max("mId").longValue() + 1;
        } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
            return 1L;
        }
    }

    private void showNotification(Friend friend, ChatMessage chatMessage) {
        if (mHandler == null) {
            Intent clickIntent = new Intent(getApplicationContext(), ChatActivity.class);
            clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            clickIntent.putExtra("mac_address", friend.getMacAddress());
            PendingIntent clickPendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, clickIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT);
            NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext())
                    .setSmallIcon(R.drawable.ic_logo_white_24dp).setPriority(Notification.PRIORITY_HIGH)
                    .setColor(ContextCompat.getColor(getApplicationContext(), R.color.colorPrimary))
                    .setContentTitle(friend.getName()).setContentIntent(clickPendingIntent).setAutoCancel(true);

            if (mSharedPreferences.getBoolean("notification_sound", false)) {
                notificationBuilder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI);
            }

            if (mSharedPreferences.getBoolean("notification_vibration", false)) {
                notificationBuilder.setVibrate(new long[] { 0, 250, 250, 250 });
            }

            if (mSharedPreferences.getBoolean("notification_light", false)) {
                notificationBuilder.setLights(0xff0082fc, 1000, 1000);
            }

            if (!chatMessage.hasPicture()) {
                notificationBuilder.setContentText(new String(chatMessage.getContent()));
            } else {
                if (chatMessage.isSent()) {
                    notificationBuilder.setContentText(
                            getApplicationContext().getResources().getString(R.string.you_sent_an_image) + ".");
                } else {
                    notificationBuilder.setContentText(friend.getName() + " "
                            + getApplicationContext().getResources().getString(R.string.sent_you_an_image) + ".");
                }
            }

            if (friend.getProfilePhoto() != null) {
                byte[] profilePhotoBytes = friend.getProfilePhoto();
                Bitmap profilePhotoBitmap = BitmapFactory.decodeByteArray(friend.getProfilePhoto(), 0,
                        profilePhotoBytes.length);
                notificationBuilder.setLargeIcon(profilePhotoBitmap);
            }

            //TODO reply in notification
            //            if (android.os.Build.VERSION_CODES.N <= android.os.Build.VERSION.SDK_INT) {
            //                PendingIntent replyPendingIntent =
            //                        PendingIntent.getActivity(
            //                                getApplicationContext(),
            //                                0,
            //                                new Intent("REPLY"),
            //                                PendingIntent.FLAG_UPDATE_CURRENT
            //                        );
            //                RemoteInput remoteInput = new RemoteInput.Builder("message_text")
            //                        .setLabel(getString(R.string.write_a_message))
            //                        .build();
            //
            //                NotificationCompat.Action action = new NotificationCompat.Action.Builder(R.drawable.ic_send_white_24dp,
            //                        getString(R.string.reply), replyPendingIntent)
            //                        .addRemoteInput(remoteInput)
            //                        .build();
            //
            //                getApplicationContext().registerReceiver(new NotificationBroadcastReceiver(), new IntentFilter("REPLY"));
            //                notificationBuilder.addAction(action);
            //            }

            NotificationManager mNotificationManager = (NotificationManager) getApplicationContext()
                    .getSystemService(Context.NOTIFICATION_SERVICE);
            mNotificationManager.notify(0, notificationBuilder.build());
        } else {
            Vibrator vibrator = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
            vibrator.vibrate(125);
        }
    }

    public static class BluetoothBroadcastReceiver extends BroadcastReceiver {

        private Realm mRealm;

        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_ON) {
                context.startService(new Intent(context, BluetoothChatService.class));
                mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
            } else if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_TURNING_OFF) {
                Realm.init(context);
                RealmConfiguration config = new RealmConfiguration.Builder().deleteRealmIfMigrationNeeded().build();
                mRealm = Realm.getInstance(config);
                mRealm.beginTransaction();
                for (Friend friend : mRealm.where(Friend.class).findAll()) {
                    friend.setOnline(false);
                }
                mRealm.commitTransaction();
                context.stopService(new Intent(context, BluetoothChatService.class));
            }
        }
    }

    public class NotificationBroadcastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
            if (remoteInput != null) {
                BluetoothChatService.this.write(("&t" + remoteInput.getString("message_text") + "&t").getBytes());
                unregisterReceiver(this);
            }
        }
    }
}