Cara Membuat Aplikasi Kontrol Slide Presentasi via Bluetooth dengan Java Desktop dan Android

Yudi Setiawan 24 Maret 2017

Cara Membuat Aplikasi Kontrol Slide Presentasi via Bluetooth dengan Java Desktop dan Android

Pengantar

Bluetooth merupakan salah satu teknologi wireless yang sampai saat ini fitur tersebut masih ada di smartphone yang menandakan bahwa Bluetooth masih banyak dipakai. Saya masih ingat betul bahwa ketika saya SMP masih ada perangkat yang bernama infra red dan ketika itu memang Bluetooth masih jarang ada di beberapa handphone. Yang saya ingat dari infra red ialah kalau mau mengirim file dari satu perangkat ke perangkat lain maka, infra red dari kedua perangkat harus saling berdekatan atau bahkan di himpit karena, perangkat infra red masih memiliki kelemahannya yaitu, jarak. Selang beberapa tahun kemudian, barulah hadir yang namanya Bluetooth dan sampai saat ini Bluetooth masih ada bahkan bukan cuma di smartphone saja melainkan di komputer dan laptop juga ada. Selain itu, Bluetooth juga disediakan dalam bentuk portable menjadi USB Bluetooth. Seperti yang kita ketahui bahwa Bluetooth bukan hanya digunakan untuk mengirim file namun, lebih dari itu seperti, kita bisa menghubungkan headset dengan Bluetooth, Sharing internet dengan Bluetooth dan masih banyak lagi. Pada tutorial ini, saya akan mencoba membuat aplikasi sederhana via Bluetooth untuk menggeser slide presentasi dari perangkat Android ke Komputer/Laptop.

Persiapan

Pada tutorial ini, kita akan membuat 2 aplikasi yaitu, Aplikasi Server di Java Desktop dan Aplikasi Client di Android. Jadi, untuk persiapannya akan saya bagi menjadi dua bagian.

Persiapan Aplikasi Server

  1. Siapkan sebuah IDE untuk projek Java seperti Netbeans, Eclipse, JCreator atau Intellij IDEA.
  2. JDK versi terbaru.
  3. SIapkan dua buah library yaitu, bluecove-2.1.1-SNAPSHOT.jar dan bluecove-gpl-2.1.1-SNAPSHOT.jar yang bisa Anda unduh di http://snapshot.bluecove.org/distribution/download/2.1.1-SNAPSHOT/ dan pastikan Anda unduh versi yang terbaru.

Persiapan Aplikasi Client

  1. Siapkan Android Studio dengan versi terbaru yang bisa Anda unduh di http://tools.android.com/download/studio/canary/latest
  2. Dan beberapa library dependency lainnya yang nanti akan saya jelaskan di tahap pembuatan projeknya.

Pembuatan Aplikasi Server

  1. Langkah pertama, silakan Anda buat projek baru di IDE Anda masing-masing dengan nama Codepolitan-AplikasiKontrolSlidePresentasi-server.
  2. Selanjutnya, silakan Anda masukkan library untuk server yang sudah Anda siapkan tadi di awal ke IDE Anda masing-masing. Caranya, nggak akan saya jelaskan di sini ya karena saya anggap Anda sudah mahir menggunakan IDE Anda masing-masing. Kalau tidak tahu, silakan cari di google ya caranya.
  3. Pada aplikasi ada 3 file java yang akan dibuat yaitu, MainFrame.java sebagai layout utama si aplikasi, ProcessConnectionThread.java sebagai pengolah command yang diberikan si client dan WaitThread.java sebagai connector antara aplikasi server dengan aplikasi client.
  4. Sekarang, silakan Anda buat file pertama yaitu, ProcessConnectionThread.java dan silakan isi dengan kode program berikut.
package com.ysn;

import java.awt.Robot;
import java.io.InputStream;
import javax.microedition.io.StreamConnection;

import com.sun.glass.events.KeyEvent;

public class ProcessConnectionThread implements Runnable {

    private StreamConnection mConnection;
    private static final int EXIT_CMD = -1;
    private static final int KEY_LEFT = 1;
    private static final int KEY_RIGHT = 2;

    public ProcessConnectionThread(StreamConnection connection) {
        mConnection = connection;
    }

    public void run() {
        // TODO Auto-generated method stub
        try {
            InputStream inputStream = mConnection.openInputStream();
            System.out.println("Waiting for input");
            MainFrame.taKeterangan.append("Waiting for input\n");
            while (true) {
                int command = inputStream.read();
                if (command == EXIT_CMD) {
                    System.out.println("Finish process");
                    MainFrame.taKeterangan.append("Finish process\n");
                    break;
                }
                processCommand(command);
                System.out.println("Command: " + command);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void processCommand(int command) {
        try {
            Robot robot = new Robot();
            switch (command) {
                case KEY_RIGHT:
                    robot.keyPress(KeyEvent.VK_RIGHT);
                    robot.keyRelease(KeyEvent.VK_RIGHT);
                    System.out.println("Command: Right");
                    MainFrame.taKeterangan.append("Command: Right\n");
                    break;
                case KEY_LEFT:
                    robot.keyPress(KeyEvent.VK_LEFT);
                    robot.keyRelease(KeyEvent.VK_LEFT);
                    System.out.println("Command: Left");
                    MainFrame.taKeterangan.append("Command: Left\n");
                    break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Selanjutnya, buat file yang kedua yaitu, WaitThread.java dan isi dengan kode program berikut.

package com.ysn;

import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.UUID;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;

public class WaitThread implements Runnable {

    public WaitThread() {

    }

    public void run() {
        // TODO Auto-generated method stub
        waitForConnection();
    }

    //	waiting for connection from devices
    private void waitForConnection() {
        LocalDevice localDevice = null;
        StreamConnectionNotifier notifier;
        StreamConnection connection = null;
        try {
            localDevice = LocalDevice.getLocalDevice();
            localDevice.setDiscoverable(DiscoveryAgent.GIAC);
            UUID uuid = new UUID("0f2b61c18be240e6ab90e735818da0a7", false);
            String url = "btspp://localhost:" + uuid.toString() + ";name=RemoteNotifier";
            notifier = (StreamConnectionNotifier) Connector.open(url);
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }

        //	waiting for connection
        while (true) {
            try {
                System.out.println("Waiting for connection . . .");
                MainFrame.taKeterangan.append("Waiting for connection . . .\n");
                connection = notifier.acceptAndOpen();
                Thread processThread = new Thread(new ProcessConnectionThread(connection));
                processThread.start();
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }
        }
    }

}

Pada kode program diatas, kita menggunakan thread baru untuk menunggu koneksi baru dari si client. Selanjutnya, buat file MainFrame.java dan isi dengan kode program berikut.

package com.ysn;

import java.awt.AWTException;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;

public class MainFrame extends JFrame implements ActionListener {

    private JPanel contentPane;
    public static JTextArea taKeterangan;
    private JButton btnAction;
    private JLabel lblStatus;
    private PopupMenu popupMenu;
    private TrayIcon trayIcon;
    private MenuItem menuItemExit;
    private MenuItem menuItemOpenWindow;

    private Thread thread;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    MainFrame frame = new MainFrame();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the frame.
     */
    public MainFrame() {
        setAlwaysOnTop(true);
        setResizable(false);
        setTitle("Codepolitan - Aplikasi Kontrol Slide Presentasi Server");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 450, 258);
        setLocationRelativeTo(null);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);

        taKeterangan = new JTextArea();
        taKeterangan.setEditable(false);
        taKeterangan.setBounds(10, 11, 424, 160);
        contentPane.add(taKeterangan);

        btnAction = new JButton("Start/Stop");
        btnAction.setBounds(10, 182, 89, 23);
        contentPane.add(btnAction);

        lblStatus = new JLabel("Status: Off");
        lblStatus.setHorizontalAlignment(SwingConstants.LEFT);
        lblStatus.setBounds(360, 182, 64, 23);
        contentPane.add(lblStatus);

        popupMenu = new PopupMenu();
        menuItemExit = new MenuItem("Exit");
        menuItemOpenWindow = new MenuItem("Open Window");
        popupMenu.add(menuItemExit);
        popupMenu.add(menuItemOpenWindow);
        Image iconTray = Toolkit.getDefaultToolkit().getImage("D:/icon_tray.png");
        trayIcon = new TrayIcon(iconTray, "Codepolitan - Aplikasi Kontrol Slide Presentasi Server", popupMenu);
        trayIcon.setImageAutoSize(true);

        thread = new Thread(new WaitThread());

        setEventHandler();
    }

    public void setEventHandler() {
        //	menuItemExit
        menuItemExit.addActionListener(this);

        //	menuItemOpenWindow
        menuItemOpenWindow.addActionListener(this);

        //	btnAction
        btnAction.addActionListener(this);

        //	window
        this.addWindowListener(new WindowListener() {

            public void windowActivated(WindowEvent arg0) {
                // TODO Auto-generated method stub

            }

            public void windowClosed(WindowEvent arg0) {
                // TODO Auto-generated method stub

            }

            public void windowClosing(WindowEvent arg0) {
                // TODO Auto-generated method stub

            }

            public void windowDeactivated(WindowEvent arg0) {
                // TODO Auto-generated method stub

            }

            public void windowDeiconified(WindowEvent arg0) {
                // TODO Auto-generated method stub

            }

            public void windowIconified(WindowEvent arg0) {
                // TODO Auto-generated method stub
                try {
                    SystemTray.getSystemTray().add(trayIcon);
                    setVisible(false);
                } catch (AWTException e) {
                    e.printStackTrace();
                }
            }

            public void windowOpened(WindowEvent arg0) {
                // TODO Auto-generated method stub

            }

        });
    }

    public void actionPerformed(ActionEvent ae) {
        // TODO Auto-generated method stub
        if (ae.getSource() == btnAction) {
            if (lblStatus.getText().equalsIgnoreCase("Status: On")) {
                lblStatus.setText("Status: Off");
                thread.stop();
            } else {
                lblStatus.setText("Status: On");
                thread.start();
            }
        } else if (ae.getSource() == menuItemExit) {
            int questionToQuit = JOptionPane.showConfirmDialog(null, "Are you sure to exit this app?", "Confirm", JOptionPane.YES_NO_OPTION);
            if (questionToQuit == JOptionPane.YES_OPTION) {
                System.exit(1);
            }
        } else if (ae.getSource() == menuItemOpenWindow) {
            setVisible(true);
            setState(Frame.NORMAL);
            SystemTray.getSystemTray().remove(trayIcon);
        }
    }
}

Di MainFrame.java bisa Anda lihat bahwa disinilah letak main nya yaitu sebagai layout dan menggunakan thread yang sudah kita buat tadi. Dan kira-kira seperti inilah output dari aplikasi server yang kita buat. Aplikasi Server

Pembuatan Aplikasi Client

  1. Buka aplikasi Android Studio dan buat projek baru dengan nama Codepolitan-AplikasiKontrolSlidePresentasi-client.
  2. Selanjutnya, setting file build.gradle(Module: App) menjadi seperti berikut.
apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.ysn.codepolitan_aplikasikontrolslidepresentasi"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.1.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.0-beta4'
    compile 'com.android.support:design:25.1.0'
    testCompile 'junit:junit:4.12'
    compile 'io.reactivex:rxjava:1.2.7'
    compile 'io.reactivex:rxandroid:1.2.1'
}

Pada library dependency diatas, bisa kita lihat bahwa kita ada menggunakan library support design dan RxJava.

Pembuatan Layout, Model, dan MainActivty

Pada layout activity_main.xml silakan isi dengan kode layout berikut.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/coordinator_layout_activity_main"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    tools:context="com.ysn.codepolitan_aplikasikontrolslidepresentasi.MainActivity>

    <ListView
        android:id="@+id/list_view_devices"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/floating_action_button_action_activity_main"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_alignParentBottom="true"
        app:elevation="4dp"
        app:fabSize="normal"
        app:srcCompat="@drawable/ic_screen_share_white_24dp" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/floating_action_button_previous_activity_main"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginEnd="@dimen/activity_horizontal_margin"
        android:layout_marginRight="@dimen/activity_horizontal_margin"
        android:layout_toLeftOf="@+id/floating_action_button_action_activity_main"
        android:layout_toStartOf="@+id/floating_action_button_action_activity_main"
        android:visibility="gone"
        app:elevation="4dp"
        app:fabSize="normal"
        app:srcCompat="@drawable/ic_keyboard_arrow_left_white_24dp" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/floating_action_button_next_activity_main"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="@dimen/activity_horizontal_margin"
        android:layout_marginStart="@dimen/activity_horizontal_margin"
        android:layout_toEndOf="@+id/floating_action_button_action_activity_main"
        android:layout_toRightOf="@+id/floating_action_button_action_activity_main"
        android:visibility="gone"
        app:elevation="4dp"
        app:fabSize="normal"
        app:srcCompat="@drawable/ic_keyboard_arrow_right_white_24dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/text_view_status"
        android:layout_centerInParent="true"
        android:text="Please, select menu options and choose a action ''Scan New Device'' or ''Paired Devices''"
        android:textColor="@android:color/darker_gray"
        android:gravity="center"
        android:textSize="18sp" />

</RelativeLayout>

Pada layout, kita menggunakan ListView dan FloatingActionButton Kemudian, kita buat class model dengan nama Devices.java dan isi dengan kode program berikut.

package com.ysn.codepolitan_aplikasikontrolslidepresentasi;

import android.bluetooth.BluetoothDevice;

/**
 * Created by root on 20/03/17.
 */

public class Devices {
    private String deviceName;
    private BluetoothDevice bluetoothDevice;

    public Devices(String deviceName, BluetoothDevice bluetoothDevice) {
        this.deviceName = deviceName;
        this.bluetoothDevice = bluetoothDevice;
    }

    public String getDeviceName() {
        return deviceName;
    }

    public void setDeviceName(String deviceName) {
        this.deviceName = deviceName;
    }

    public BluetoothDevice getBluetoothDevice() {
        return bluetoothDevice;
    }

    public void setBluetoothDevice(BluetoothDevice bluetoothDevice) {
        this.bluetoothDevice = bluetoothDevice;
    }

    @Override
    public String toString() {
        return deviceName;
    }
}

Di class model Devices kita hanya mendeklarasikan devices name dan BluetoothDevice. Sebelum masuk ke MainActivity.java harap buat terlebih dahulu file menu_main.xml dan letakkan di direktori res-menu. Dan isi dengan kode program berikut.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >
    <item
        android:id="@+id/item_scan_new_device"
        android:title="Scan New Device"
        app:showAsAction="never"
        />
    <item
        android:id="@+id/item_paired_device"
        android:title="Paired Devices"
        app:showAsAction="never"
        />
</menu>

Pada menu, kita hanya membuat 2 item saja yaitu, Scan New Device dan Paired Devices. Kemudian, ubah kode program pada file MainActivity.java menjadi seperti berikut.

package com.ysn.codepolitan_aplikasikontrolslidepresentasi;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Set;
import java.util.UUID;

import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "MainActivityTAG";
    private static final int NEXT = 2, PREVIOUS = 1;
    private ListView listViewDevices;
    private TextView textViewStatus;
    private FloatingActionButton floatingActionButtonAction;
    private FloatingActionButton floatingActionButtonNext;
    private FloatingActionButton floatingActionButtonPrevious;

    private final int request_enable_bt = 243;
    public boolean isConnected = false;
    private BluetoothAdapter bluetoothAdapter = null;
    private ArrayAdapter<Devices> arrayAdapterDevices = null;

    private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                Devices devices = new Devices(bluetoothDevice.getName(), bluetoothDevice);
                arrayAdapterDevices.add(devices);
            }
            if (arrayAdapterDevices.getCount() > 0) {
                textViewStatus.setVisibility(View.GONE);
                listViewDevices.setVisibility(View.VISIBLE);
            } else {
                textViewStatus.setVisibility(View.VISIBLE);
                listViewDevices.setVisibility(View.GONE);
                textViewStatus.setText("Oops... New Devices not detected :(");
            }
        }
    };

    private Devices devicesSelected;
    private BluetoothDevice bluetoothDevice;
    private BluetoothSocket bluetoothSocket;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadComponent();
        checkBluetoothAdapter();
    }

    private void checkBluetoothAdapter() {
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (bluetoothAdapter == null) {
            showToast("Device not support Bluetooth", Toast.LENGTH_LONG);
        }
        if (!bluetoothAdapter.isEnabled()) {
            Intent intentEnableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intentEnableBluetooth, request_enable_bt);
        }
    }

    @Override
    protected void onResume() {
        isConnected = false;
        super.onResume();
    }

    @Override
    protected void onStop() {
        socketBluetoothClose();
        super.onStop();
    }

    private void socketBluetoothClose() {
        if (bluetoothSocket != null) {
            try {
                bluetoothSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void loadComponent() {
        listViewDevices = (ListView) findViewById(R.id.list_view_devices);
        textViewStatus = (TextView) findViewById(R.id.text_view_status);
        floatingActionButtonAction = (FloatingActionButton) findViewById(R.id.floating_action_button_action_activity_main);
        floatingActionButtonNext = (FloatingActionButton) findViewById(R.id.floating_action_button_next_activity_main);
        floatingActionButtonPrevious = (FloatingActionButton) findViewById(R.id.floating_action_button_previous_activity_main);

        arrayAdapterDevices = new ArrayAdapter<Devices>(this, android.R.layout.simple_list_item_1);
        listViewDevices.setAdapter(arrayAdapterDevices);
        listViewDevices.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
                if (isConnected) {
                    showToast("Before to connect another device, you must disconnected from current device", Toast.LENGTH_LONG);
                } else {
                    devicesSelected = (Devices) adapterView.getAdapter().getItem(position);
                    showToast("Device: " + devicesSelected.getDeviceName() + " selected", Toast.LENGTH_SHORT);
                }
            }
        });
        floatingActionButtonAction.setOnClickListener(this);
        floatingActionButtonNext.setOnClickListener(this);
        floatingActionButtonPrevious.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        if (view == floatingActionButtonAction) {
            if (isConnected) {
                socketBluetoothClose();
                hideActions();
                isConnected = false;
                showToast("You're device has been disconnected from: " + devicesSelected.getDeviceName(), Toast.LENGTH_SHORT);
            } else {
                bluetoothAdapter.cancelDiscovery();
                try {
                    bluetoothDevice = devicesSelected.getBluetoothDevice();
                    UUID uuid = UUID.fromString("0f2b61c1-8be2-40e6-ab90-e735818da0a7");
                    bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(uuid);

                    Observable<BluetoothSocket> observable = Observable.create(new Observable.OnSubscribe<BluetoothSocket>() {
                        @Override
                        public void call(Subscriber<? super BluetoothSocket> subscriber) {
                            subscriber.onNext(bluetoothSocket);
                            subscriber.onCompleted();
                        }
                    });
                    Subscriber<BluetoothSocket> subscriber = new Subscriber<BluetoothSocket>() {
                        @Override
                        public void onCompleted() {
                            Log.d(TAG, "onCompleted");
                            if (bluetoothSocket.isConnected()) {
                                isConnected = true;
                                showActions();
                                showToast("You're device connected to: " + devicesSelected.getDeviceName(), Toast.LENGTH_SHORT);
                                Log.d(TAG, "isConnected");
                            } else {
                                isConnected = false;
                                hideActions();
                                showToast("You're device fail to connect to: " + devicesSelected.getDeviceName(), Toast.LENGTH_SHORT);
                                Log.d(TAG, "isDisconnected");
                            }
                        }

                        @Override
                        public void onError(Throwable e) {
                            e.printStackTrace();
                        }

                        @Override
                        public void onNext(BluetoothSocket bluetoothSocket) {
                            try {
                                bluetoothSocket.connect();
                                Log.d(TAG, "onNext");
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    };
                    observable.subscribeOn(Schedulers.newThread());
                    observable.observeOn(AndroidSchedulers.mainThread());
                    observable.subscribe(subscriber);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } else if (view == floatingActionButtonNext) {
            OutputStream outputStream;
            try {
                outputStream = bluetoothSocket.getOutputStream();
                outputStream.write(NEXT);
                showToast("action: NEXT - Ok", Toast.LENGTH_SHORT);
            } catch (IOException e) {
                e.printStackTrace();
                showToast("action: NEXT - Fail", Toast.LENGTH_SHORT);
            }
        } else if (view == floatingActionButtonPrevious) {
            try {
                OutputStream outputStream;
                outputStream = bluetoothSocket.getOutputStream();
                outputStream.write(PREVIOUS);
                showToast("action: PREVIOUS - Ok", Toast.LENGTH_SHORT);
            } catch (IOException e) {
                e.printStackTrace();
                showToast("action: PREVIOUS - Fail", Toast.LENGTH_SHORT);
            }
        }
    }

    private void hideActions() {
        floatingActionButtonAction.setImageResource(R.drawable.ic_screen_share_white_24dp);
        floatingActionButtonNext.setVisibility(View.GONE);
        floatingActionButtonPrevious.setVisibility(View.GONE);
    }

    private void showActions() {
        floatingActionButtonAction.setImageResource(R.drawable.ic_stop_screen_share_white_24dp);
        floatingActionButtonNext.setVisibility(View.VISIBLE);
        floatingActionButtonPrevious.setVisibility(View.VISIBLE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && requestCode == request_enable_bt) {
            showToast("Bluetooth On", Toast.LENGTH_LONG);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.item_scan_new_device:
                loadScanNewDevices();
                return true;
            case R.id.item_paired_device:
                loadPairedDevices();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void loadPairedDevices() {
        Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
        if (pairedDevices.size() > 0) {
            arrayAdapterDevices.clear();
            for (BluetoothDevice pairedDevice : pairedDevices) {
                Devices devices = new Devices(pairedDevice.getName(), pairedDevice);
                arrayAdapterDevices.add(devices);
            }
            arrayAdapterDevices.notifyDataSetChanged();
            textViewStatus.setVisibility(View.GONE);
            listViewDevices.setVisibility(View.VISIBLE);
        } else {
            textViewStatus.setVisibility(View.VISIBLE);
            listViewDevices.setVisibility(View.GONE);
            textViewStatus.setText("Sorry... You don't have a paired devices :(");
        }
        showToast("Paired Devices is successfully loaded", Toast.LENGTH_SHORT);
    }

    private void loadScanNewDevices() {
        IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        registerReceiver(broadcastReceiver, intentFilter);
        arrayAdapterDevices.clear();
        bluetoothAdapter.startDiscovery();
        showToast("Scan new Devices is start", Toast.LENGTH_SHORT);
    }

    private void showToast(String message, int length) {
        Toast.makeText(this, message, length)
                .show();
    }
}

Jadi, cara kerja si client ialah pada tampilan utama ada 2 menu options yang tersedia yaitu, Scan New Device dan Paired Devices. Ketika salah satu menu options dipilih maka, ListView akan langsung terisi dengan data adapter yang sesuai. Selanjutnya, pilih salah satu device yang ada di ListView dan pilih Floating Action Button yang ada di tengah bawah. Kemudian, proses connect ke server akan dilakukan. Oya, jangan lupa aplikasi server harap dijalankan ketika melakukan pengetesan dan pastikanStatus: On pada aplikasi servernya. Dan apabila koneksi berhasil maka, di bagian client akan menampilkan 2 floating action button yaitu, Next dan Previous. Dan apabila Anda ingin memutuskan koneksinya maka, tekan kembali floating action button yang ada ditengah.

Output


Untuk projeknya sudah saya upload di [github](https://github.com/CoderJava/Control-Slide-Presentation)