[Libreoffice-commits] core.git: Branch 'feature/android-remote-ng' - 5 commits - android/sdremote

Artur Dryomov artur.dryomov at gmail.com
Sat Jun 29 17:31:20 PDT 2013


 android/sdremote/src/org/libreoffice/impressremote/PairingActivity.java                         |    2 
 android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothClient.java           |  125 -----
 android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothFinder.java           |  145 ------
 android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothServerConnection.java |   76 +++
 android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothServersFinder.java    |  151 ++++++
 android/sdremote/src/org/libreoffice/impressremote/communication/Client.java                    |  168 -------
 android/sdremote/src/org/libreoffice/impressremote/communication/CommandsTransmitter.java       |   87 +++
 android/sdremote/src/org/libreoffice/impressremote/communication/CommunicationService.java      |  200 ++++++--
 android/sdremote/src/org/libreoffice/impressremote/communication/MessagesListener.java          |   17 
 android/sdremote/src/org/libreoffice/impressremote/communication/MessagesReceiver.java          |  174 +++++++
 android/sdremote/src/org/libreoffice/impressremote/communication/NetworkClient.java             |  181 --------
 android/sdremote/src/org/libreoffice/impressremote/communication/Protocol.java                  |   19 
 android/sdremote/src/org/libreoffice/impressremote/communication/Receiver.java                  |  149 ------
 android/sdremote/src/org/libreoffice/impressremote/communication/Server.java                    |    9 
 android/sdremote/src/org/libreoffice/impressremote/communication/ServerConnection.java          |   22 
 android/sdremote/src/org/libreoffice/impressremote/communication/ServerFinder.java              |  226 ----------
 android/sdremote/src/org/libreoffice/impressremote/communication/ServersFinder.java             |   21 
 android/sdremote/src/org/libreoffice/impressremote/communication/TcpServerConnection.java       |   65 ++
 android/sdremote/src/org/libreoffice/impressremote/communication/TcpServersFinder.java          |  198 ++++++++
 android/sdremote/src/org/libreoffice/impressremote/communication/Transmitter.java               |   79 ---
 20 files changed, 992 insertions(+), 1122 deletions(-)

New commits:
commit 4dd2740196037bf7463b0814bcbe37e80221ac08
Author: Artur Dryomov <artur.dryomov at gmail.com>
Date:   Sun Jun 30 03:25:02 2013 +0300

    Change the existing code to use new classes.
    
    * Remove old classes, their functionality was moved to more suitable
      places.
    * Update existing ones to use new schema.
    
    Change-Id: Ic525fd4682051317dc717dedb6d08b97f11c0b09

diff --git a/android/sdremote/src/org/libreoffice/impressremote/PairingActivity.java b/android/sdremote/src/org/libreoffice/impressremote/PairingActivity.java
index 0b0624e..45222c0 100644
--- a/android/sdremote/src/org/libreoffice/impressremote/PairingActivity.java
+++ b/android/sdremote/src/org/libreoffice/impressremote/PairingActivity.java
@@ -70,8 +70,6 @@ public class PairingActivity extends SherlockActivity {
 
     @Override
     public void onBackPressed() {
-        mCommunicationService.getClient().closeConnection();
-
         Intent aIntent = new Intent(this, SelectorActivity.class);
         aIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
         startActivity(aIntent);
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothClient.java b/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothClient.java
deleted file mode 100644
index 8da48bb..0000000
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothClient.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This file is part of the LibreOffice project.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-package org.libreoffice.impressremote.communication;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.UUID;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothSocket;
-import android.content.Intent;
-import android.support.v4.content.LocalBroadcastManager;
-
-public class BluetoothClient extends Client {
-    // Standard UUID for the Serial Port Profile.
-    // https://www.bluetooth.org/en-us/specification/assigned-numbers-overview/service-discovery
-    private static final String STANDARD_SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB";
-
-    private final boolean mBluetoothWasEnabled;
-
-    private BluetoothSocket mSocket;
-
-    public BluetoothClient(Server aServer, CommunicationService aCommunicationService, Receiver aReceiver, boolean aBluetoothWasEnabled) {
-        super(aServer, aCommunicationService, aReceiver);
-
-        mBluetoothWasEnabled = aBluetoothWasEnabled;
-
-        if (!mBluetoothWasEnabled) {
-            BluetoothAdapter.getDefaultAdapter().enable();
-        }
-    }
-
-    @Override
-    protected void setUpServerConnection() {
-        mSocket = buildServerConnection();
-    }
-
-    private BluetoothSocket buildServerConnection() {
-        try {
-            BluetoothDevice aBluetoothServer = BluetoothAdapter
-                .getDefaultAdapter()
-                .getRemoteDevice(mServer.getAddress());
-
-            BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
-
-            BluetoothSocket aSocket = aBluetoothServer
-                .createRfcommSocketToServiceRecord(
-                    UUID.fromString(STANDARD_SPP_UUID));
-
-            aSocket.connect();
-
-            return aSocket;
-        } catch (IOException e) {
-            throw new RuntimeException("Unable to connect to Bluetooth host.");
-        }
-    }
-
-    protected InputStream buildMessagesStream() {
-        try {
-            return mSocket.getInputStream();
-        } catch (IOException e) {
-            throw new RuntimeException("Unable to open messages stream.");
-        }
-    }
-
-    protected OutputStream buildCommandsStream() {
-        try {
-            return mSocket.getOutputStream();
-        } catch (IOException e) {
-            throw new RuntimeException("Unable to open commands stream.");
-        }
-    }
-
-    @Override
-    public void closeConnection() {
-        try {
-            mSocket.close();
-        } catch (IOException e) {
-            throw new RuntimeException("Unable to close Bluetooth socket.");
-        }
-    }
-
-    protected void onDisconnect() {
-        if (!mBluetoothWasEnabled) {
-            BluetoothAdapter.getDefaultAdapter().disable();
-        }
-    }
-
-    @Override
-    public void validating() throws IOException {
-        String aMessage = mMessagesReader.readLine();
-
-        if (!aMessage.equals(Protocol.Messages.PAIRED)) {
-            return;
-        }
-
-        while (mMessagesReader.readLine().length() != 0) {
-            // Get rid of extra lines
-        }
-
-        callSuccessfulPairing();
-
-        startListening();
-    }
-
-    private void callSuccessfulPairing() {
-        Intent aSuccessfulPairingIntent = new Intent(
-            CommunicationService.MSG_PAIRING_SUCCESSFUL);
-
-        LocalBroadcastManager.getInstance(mCommunicationService)
-            .sendBroadcast(aSuccessfulPairingIntent);
-    }
-}
-
-/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/Client.java b/android/sdremote/src/org/libreoffice/impressremote/communication/Client.java
deleted file mode 100644
index 10fa657..0000000
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/Client.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This file is part of the LibreOffice project.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-package org.libreoffice.impressremote.communication;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-
-import android.content.Intent;
-import android.text.TextUtils;
-
-public abstract class Client implements Runnable {
-    protected final BufferedReader mMessagesReader;
-    protected final OutputStream mCommandsStream;
-
-    protected String mPin = "";
-    protected String mName = "";
-
-    private static Client latestInstance = null;
-
-    protected final Server mServer;
-    protected final CommunicationService mCommunicationService;
-    protected final Receiver mReceiver;
-
-    protected Client(Server aServer, CommunicationService aCommunicationService, Receiver aReceiver) {
-        mServer = aServer;
-        mName = aServer.getName();
-        mCommunicationService = aCommunicationService;
-        mReceiver = aReceiver;
-        latestInstance = this;
-
-        setUpServerConnection();
-
-        mMessagesReader = buildMessagesReader(buildMessagesStream());
-        mCommandsStream = buildCommandsStream();
-    }
-
-    protected abstract void setUpServerConnection();
-
-    private BufferedReader buildMessagesReader(InputStream aMessagesStream) {
-        try {
-            return new BufferedReader(
-                new InputStreamReader(aMessagesStream, Protocol.CHARSET));
-        } catch (UnsupportedEncodingException e) {
-            throw new RuntimeException("Unable to create messages reader.");
-        }
-    }
-
-    protected abstract InputStream buildMessagesStream();
-
-    protected abstract OutputStream buildCommandsStream();
-
-    public static String getPin() {
-        if (latestInstance == null) {
-            return "";
-        }
-
-        return latestInstance.mName;
-    }
-
-    public static String getName() {
-        if (latestInstance == null) {
-            return "";
-        }
-
-        return latestInstance.mName;
-    }
-
-    protected void startListening() {
-        Thread aListeningThread = new Thread(this);
-
-        aListeningThread.start();
-    }
-
-    @Override
-    public void run() {
-        listen();
-    }
-
-    private void listen() {
-        try {
-            while (true) {
-                List<String> aMessage = readMessage();
-
-                if (aMessage == null) {
-                    return;
-                }
-
-                mReceiver.parseCommand(aMessage);
-            }
-        } catch (IOException e) {
-            // TODO: stream couldn't be opened
-            e.printStackTrace();
-        } finally {
-            onDisconnect();
-        }
-    }
-
-    private List<String> readMessage() throws IOException {
-        List<String> aMessage = new ArrayList<String>();
-
-        String aMessageParameter = mMessagesReader.readLine();
-
-        while ((aMessageParameter != null) && (!TextUtils
-            .isEmpty(aMessageParameter))) {
-            aMessage.add(aMessageParameter);
-
-            aMessageParameter = mMessagesReader.readLine();
-        }
-
-        if (aMessageParameter == null) {
-            startReconnection();
-
-            return null;
-        }
-
-        return aMessage;
-    }
-
-    private void startReconnection() {
-        Intent aReconnectionIntent = new Intent(
-            mCommunicationService.getApplicationContext(),
-            ReconnectionActivity.class);
-        aReconnectionIntent.putExtra("server", mServer);
-        aReconnectionIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        mCommunicationService.getApplicationContext()
-            .startActivity(aReconnectionIntent);
-    }
-
-    /**
-     * Called after the Client disconnects. Can be extended to allow for
-     * cleaning up bluetooth properties etc.
-     */
-    protected void onDisconnect() {
-    }
-
-    /**
-     * Send a valid command to the Server.
-     */
-    public void sendCommand(String aCommand) {
-        try {
-            mCommandsStream.write(aCommand.getBytes(Protocol.CHARSET));
-        } catch (UnsupportedEncodingException e) {
-            throw new RuntimeException("UTF-8 must be used for commands.");
-        } catch (IOException e) {
-            // I.e. connection closed. This will be dealt with by the listening
-            // loop.
-        }
-    }
-
-    public abstract void closeConnection();
-
-    public abstract void validating() throws IOException;
-}
-
-/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/CommunicationService.java b/android/sdremote/src/org/libreoffice/impressremote/communication/CommunicationService.java
index 273362c..8678286 100644
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/CommunicationService.java
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/CommunicationService.java
@@ -8,7 +8,6 @@
  */
 package org.libreoffice.impressremote.communication;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -26,9 +25,8 @@ import android.preference.PreferenceManager;
 import android.support.v4.content.LocalBroadcastManager;
 
 import org.libreoffice.impressremote.Preferences;
-import org.libreoffice.impressremote.communication.Server.Protocol;
 
-public class CommunicationService extends Service implements Runnable {
+public class CommunicationService extends Service implements Runnable, MessagesListener {
     public static enum State {
         DISCONNECTED, SEARCHING, CONNECTING, CONNECTED
     }
@@ -62,14 +60,9 @@ public class CommunicationService extends Service implements Runnable {
 
     private final IBinder mBinder = new CBinder();
 
-    private Transmitter mTransmitter;
-
-    private Client mClient;
-
-    private final Receiver mReceiver = new Receiver(this);
-
     private final ServersFinder mTcpServersFinder = new TcpServersFinder(this);
-    private final ServersFinder mBluetoothServersFinder = new BluetoothServersFinder(this);
+    private final ServersFinder mBluetoothServersFinder = new BluetoothServersFinder(
+        this);
 
     private Thread mThread = null;
 
@@ -98,7 +91,7 @@ public class CommunicationService extends Service implements Runnable {
     }
 
     public String getPairingDeviceName() {
-        return Client.getName();
+        return getDeviceName();
     }
 
     @Override
@@ -133,42 +126,76 @@ public class CommunicationService extends Service implements Runnable {
         }
     }
 
+    private ServerConnection mServerConnection;
+
+    private MessagesReceiver mMessagesReceiver;
+    private CommandsTransmitter mCommandsTransmitter;
+
     private void closeConnection() {
-        mClient.closeConnection();
-        mClient = null;
+        mServerConnection.close();
 
         mState = State.DISCONNECTED;
     }
 
     private void openConnection() {
-        try {
-            mClient = buildClient();
-            mClient.validating();
+        mServerConnection = buildServerConnection();
 
-            mTransmitter = new Transmitter(mClient);
+        mMessagesReceiver = new MessagesReceiver(mServerConnection, this);
+        mCommandsTransmitter = new CommandsTransmitter(mServerConnection);
 
-            mState = State.CONNECTED;
-        } catch (IOException e) {
-            connectionFailed();
-        }
+        pairWithServer();
+
+        mState = State.CONNECTED;
     }
 
-    private Client buildClient() {
+    private ServerConnection buildServerConnection() {
         switch (mServerDesired.getProtocol()) {
-            case NETWORK:
-                return new NetworkClient(mServerDesired, this, mReceiver);
+            case TCP:
+                return new TcpServerConnection(mServerDesired);
 
             case BLUETOOTH:
-                return new BluetoothClient(mServerDesired, this, mReceiver,
-                    mBluetoothPreviouslyEnabled);
+                return new BluetoothServerConnection(mServerDesired);
 
             default:
                 throw new RuntimeException("Unknown desired protocol.");
         }
     }
 
+    private void pairWithServer() {
+        if (mServerDesired.getProtocol() == Server.Protocol.BLUETOOTH) {
+            return;
+        }
+
+        mCommandsTransmitter.pair(getDeviceName(), loadPin());
+
+        startPairingActivity();
+    }
+
+    private void startPairingActivity() {
+        Intent aPairingIntent = new Intent(MSG_PAIRING_STARTED);
+        aPairingIntent.putExtra("PIN", loadPin());
+
+        LocalBroadcastManager.getInstance(this).sendBroadcast(aPairingIntent);
+    }
+
+    private String loadPin() {
+        if (Preferences.doContain(this,
+            Preferences.Locations.AUTHORIZED_REMOTES,
+            mServerDesired.getAddress())) {
+            return Preferences
+                .getString(this, Preferences.Locations.AUTHORIZED_REMOTES,
+                    mServerDesired.getAddress());
+        }
+
+        String aPin = Protocol.Pin.generate();
+
+        Preferences.set(this, Preferences.Locations.AUTHORIZED_REMOTES,
+            mServerDesired.getAddress(), aPin);
+
+        return aPin;
+    }
+
     private void connectionFailed() {
-        mClient = null;
         mState = State.DISCONNECTED;
         Intent aIntent = new Intent(
             CommunicationService.STATUS_CONNECTION_FAILED);
@@ -254,8 +281,8 @@ public class CommunicationService extends Service implements Runnable {
         mThread = null;
     }
 
-    public Transmitter getTransmitter() {
-        return mTransmitter;
+    public CommandsTransmitter getTransmitter() {
+        return mCommandsTransmitter;
     }
 
     public List<Server> getServers() {
@@ -269,7 +296,7 @@ public class CommunicationService extends Service implements Runnable {
     }
 
     public SlideShow getSlideShow() {
-        return mReceiver.getSlideShow();
+        return mSlideShow;
     }
 
     void loadServersFromPreferences() {
@@ -282,7 +309,7 @@ public class CommunicationService extends Service implements Runnable {
 
         for (Entry<String, String> aServerEntry : aStoredMap.entrySet()) {
             mManualServers.put(aServerEntry.getKey(), new Server(
-                Protocol.NETWORK, aServerEntry.getKey(),
+                Server.Protocol.TCP, aServerEntry.getKey(),
                 aServerEntry.getValue(), 0));
         }
     }
@@ -295,8 +322,9 @@ public class CommunicationService extends Service implements Runnable {
             if (aServer.equals(aAddress))
                 return;
         }
-        mManualServers.put(aAddress, new Server(Protocol.NETWORK, aAddress,
-            aName, 0));
+        mManualServers
+            .put(aAddress, new Server(Server.Protocol.TCP, aAddress,
+                aName, 0));
         if (aRemember) {
 
             Preferences
@@ -312,8 +340,92 @@ public class CommunicationService extends Service implements Runnable {
             aServer.getAddress());
     }
 
-    public Client getClient() {
-        return mClient;
+    @Override
+    public void onPinValidation() {
+        startPinValidation();
+    }
+
+    private void startPinValidation() {
+        Intent aPairingIntent = new Intent(STATUS_PAIRING_PINVALIDATION);
+        aPairingIntent.putExtra("PIN", loadPin());
+        aPairingIntent.putExtra("SERVERNAME", mServerDesired.getName());
+
+        LocalBroadcastManager.getInstance(this).sendBroadcast(aPairingIntent);
+    }
+
+    @Override
+    public void onSuccessfulPairing() {
+        callSuccessfulPairing();
+    }
+
+    private void callSuccessfulPairing() {
+        Intent aSuccessfulPairingIntent = new Intent(MSG_PAIRING_SUCCESSFUL);
+
+        LocalBroadcastManager.getInstance(this).sendBroadcast(
+            aSuccessfulPairingIntent);
+    }
+
+    private SlideShow mSlideShow;
+
+    @Override
+    public void onSlideShowStart(int aSlidesCount, int aCurrentSlideIndex) {
+        mSlideShow = new SlideShow();
+        mSlideShow.setSlidesCount(aSlidesCount);
+        mSlideShow.setCurrentSlideIndex(aCurrentSlideIndex);
+
+        Intent aStatusConnectedSlideShowRunningIntent = new Intent(
+            STATUS_CONNECTED_SLIDESHOW_RUNNING);
+        Intent aSlideChangedIntent = new Intent(MSG_SLIDE_CHANGED);
+        aSlideChangedIntent.putExtra("slide_number", aCurrentSlideIndex);
+
+        LocalBroadcastManager.getInstance(this)
+            .sendBroadcast(aStatusConnectedSlideShowRunningIntent);
+        LocalBroadcastManager.getInstance(this)
+            .sendBroadcast(aSlideChangedIntent);
+    }
+
+    @Override
+    public void onSlideShowFinish() {
+        mSlideShow = new SlideShow();
+
+        Intent aStatusConnectedNoSlideShowIntent = new Intent(
+            STATUS_CONNECTED_NOSLIDESHOW);
+
+        LocalBroadcastManager.getInstance(this)
+            .sendBroadcast(aStatusConnectedNoSlideShowIntent);
+    }
+
+    @Override
+    public void onSlideChanged(int aCurrentSlideIndex) {
+        mSlideShow.setCurrentSlideIndex(aCurrentSlideIndex);
+
+        Intent aSlideChangedIntent = new Intent(MSG_SLIDE_CHANGED);
+        aSlideChangedIntent.putExtra("slide_number", aCurrentSlideIndex);
+
+        LocalBroadcastManager.getInstance(this)
+            .sendBroadcast(aSlideChangedIntent);
+    }
+
+    @Override
+    public void onSlidePreview(int aSlideIndex, byte[] aPreview) {
+        mSlideShow.setSlidePreview(aSlideIndex, aPreview);
+
+        Intent aSlidePreviewChangedIntent = new Intent(MSG_SLIDE_PREVIEW);
+        aSlidePreviewChangedIntent.putExtra("slide_number", aSlideIndex);
+
+        LocalBroadcastManager.getInstance(this)
+            .sendBroadcast(aSlidePreviewChangedIntent);
+    }
+
+    @Override
+    public void onSlideNotes(int aSlideIndex, String aNotes) {
+        mSlideShow.setSlideNotes(aSlideIndex, aNotes);
+
+        Intent aSlideNotesChangedIntent = new Intent(MSG_SLIDE_NOTES);
+        aSlideNotesChangedIntent.putExtra("slide_number", aSlideIndex);
+
+        LocalBroadcastManager.getInstance(this)
+            .sendBroadcast(aSlideNotesChangedIntent);
     }
 }
 
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/NetworkClient.java b/android/sdremote/src/org/libreoffice/impressremote/communication/NetworkClient.java
deleted file mode 100644
index 4381480..0000000
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/NetworkClient.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This file is part of the LibreOffice project.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-package org.libreoffice.impressremote.communication;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.util.Random;
-
-import android.content.Context;
-import android.content.Intent;
-import android.support.v4.content.LocalBroadcastManager;
-
-import org.libreoffice.impressremote.Preferences;
-
-public class NetworkClient extends Client {
-    private Socket mSocket;
-
-    private final String mPin;
-
-    public NetworkClient(Server aServer, CommunicationService aCommunicationService, Receiver aReceiver) {
-        super(aServer, aCommunicationService, aReceiver);
-
-        mPin = loadPin();
-
-        startPairingActivity();
-        startPairing();
-    }
-
-    private String loadPin() {
-        Context aContext = mCommunicationService.getApplicationContext();
-
-        if (Preferences
-            .doContain(aContext, Preferences.Locations.AUTHORIZED_REMOTES,
-                mServer.getName())) {
-            return Preferences
-                .getString(aContext, Preferences.Locations.AUTHORIZED_REMOTES,
-                    mServer.getName());
-        }
-
-        String aPin = generatePin();
-
-        Preferences.set(aContext, Preferences.Locations.AUTHORIZED_REMOTES,
-            mServer.getName(), aPin);
-
-        return aPin;
-    }
-
-    private String generatePin() {
-        return String.format("%04d", generatePinNumber());
-    }
-
-    private int generatePinNumber() {
-        Random aRandomGenerator = new Random();
-
-        int aMaximumPin = (int) Math.pow(10, Protocol.PIN_NUMBERS_COUNT) - 1;
-
-        return aRandomGenerator.nextInt(aMaximumPin);
-    }
-
-    private void startPairingActivity() {
-        Intent aPairingIntent = new Intent(
-            CommunicationService.MSG_PAIRING_STARTED);
-        aPairingIntent.putExtra("PIN", mPin);
-
-        LocalBroadcastManager.getInstance(mCommunicationService)
-            .sendBroadcast(aPairingIntent);
-    }
-
-    private void startPairing() {
-        // TODO: get the proper name
-        String aPhoneName = CommunicationService.getDeviceName();
-
-        sendCommand(Protocol.Commands
-            .prepareCommand(Protocol.Commands.PAIR_WITH_SERVER, aPhoneName, mPin));
-    }
-
-    @Override
-    protected void setUpServerConnection() {
-        mSocket = buildServerConnection();
-    }
-
-    private Socket buildServerConnection() {
-        try {
-            return new Socket(mServer.getAddress(),
-                Protocol.Ports.CLIENT_CONNECTION);
-        } catch (UnknownHostException e) {
-            throw new RuntimeException("Unable to connect to unknown host.");
-        } catch (IOException e) {
-            e.printStackTrace();
-            throw new RuntimeException("Unable to connect to host.");
-        }
-    }
-
-    @Override
-    protected InputStream buildMessagesStream() {
-        try {
-            return mSocket.getInputStream();
-        } catch (IOException e) {
-            throw new RuntimeException("Unable to open messages stream.");
-        }
-    }
-
-    @Override
-    protected OutputStream buildCommandsStream() {
-        try {
-            return mSocket.getOutputStream();
-        } catch (IOException e) {
-            throw new RuntimeException("Unable to open commands stream.");
-        }
-    }
-
-    @Override
-    public void closeConnection() {
-        try {
-            mSocket.close();
-        } catch (IOException e) {
-            throw new RuntimeException("Unable to close network socket.");
-        }
-    }
-
-    @Override
-    public void validating() throws IOException {
-        String aMessage = mMessagesReader.readLine();
-
-        if (aMessage == null) {
-            throw new RuntimeException(
-                "End of stream reached before any data received.");
-        }
-
-        while (!aMessage.equals(Protocol.Messages.PAIRED)) {
-            if (aMessage.equals(Protocol.Messages.VALIDATING)) {
-                startPinValidation();
-
-                while (mMessagesReader.readLine().length() != 0) {
-                    // Read off empty lines
-                }
-
-                aMessage = mMessagesReader.readLine();
-            } else {
-                return;
-            }
-        }
-
-        callSuccessfulPairing();
-
-        while (mMessagesReader.readLine().length() != 0) {
-            // Get rid of extra lines
-        }
-
-        startListening();
-    }
-
-    private void startPinValidation() {
-        Intent aPairingIntent = new Intent(
-            CommunicationService.STATUS_PAIRING_PINVALIDATION);
-        aPairingIntent.putExtra("PIN", mPin);
-        aPairingIntent.putExtra("SERVERNAME", mServer.getName());
-
-        LocalBroadcastManager.getInstance(mCommunicationService)
-            .sendBroadcast(aPairingIntent);
-    }
-
-    private void callSuccessfulPairing() {
-        Intent aSuccessfulPairingIntent = new Intent(
-            CommunicationService.MSG_PAIRING_SUCCESSFUL);
-
-        LocalBroadcastManager.getInstance(mCommunicationService)
-            .sendBroadcast(aSuccessfulPairingIntent);
-    }
-}
-
-/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/Protocol.java b/android/sdremote/src/org/libreoffice/impressremote/communication/Protocol.java
index 6f40e54..c276ec9 100644
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/Protocol.java
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/Protocol.java
@@ -8,6 +8,8 @@
  */
 package org.libreoffice.impressremote.communication;
 
+import java.util.Random;
+
 import android.text.TextUtils;
 
 final class Protocol {
@@ -16,7 +18,20 @@ final class Protocol {
 
     public static final String CHARSET = "UTF-8";
 
-    public static final int PIN_NUMBERS_COUNT = 4;
+    public static final class Pin {
+        private Pin() {
+        }
+
+        private static final int NUMBERS_COUNT = 4;
+
+        public static String generate() {
+            Random aRandomGenerator = new Random();
+            int aMaximumPin = (int) Math.pow(10, NUMBERS_COUNT) - 1;
+            int aPinNumber = aRandomGenerator.nextInt(aMaximumPin);
+
+            return String.format("%04d", aPinNumber);
+        }
+    }
 
     public static final class Ports {
         private Ports() {
@@ -31,7 +46,6 @@ final class Protocol {
         }
 
         public static final String SERVER_SEARCH = "239.0.0.1";
-        public static final String SERVER_LOCAL_FOR_EMULATOR = "10.0.2.2";
     }
 
     public static final class Messages {
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/Receiver.java b/android/sdremote/src/org/libreoffice/impressremote/communication/Receiver.java
deleted file mode 100644
index 1ecb53e..0000000
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/Receiver.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This file is part of the LibreOffice project.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-package org.libreoffice.impressremote.communication;
-
-import java.util.List;
-
-import android.content.Context;
-import android.content.Intent;
-import android.support.v4.content.LocalBroadcastManager;
-import android.util.Base64;
-
-public class Receiver {
-    private final Context mContext;
-
-    private SlideShow mSlideShow;
-
-    public Receiver(Context aContext) {
-        this.mContext = aContext;
-        this.mSlideShow = new SlideShow();
-    }
-
-    public SlideShow getSlideShow() {
-        return mSlideShow;
-    }
-
-    public boolean isSlideShowRunning() {
-        return mSlideShow.getSlidesCount() > 0;
-    }
-
-    public void parseCommand(List<String> aInstruction) {
-        if (aInstruction.isEmpty()) {
-            return;
-        }
-
-        String aCommand = aInstruction.get(0);
-
-        if (aCommand.equals(Protocol.Messages.SLIDESHOW_STARTED)) {
-            startSlideShow(aInstruction);
-            return;
-        }
-
-        if (aCommand.equals(Protocol.Messages.SLIDESHOW_FINISHED)) {
-            finishSlideShow();
-            return;
-        }
-
-        if (mSlideShow == null) {
-            return;
-        }
-
-        if (aCommand.equals(Protocol.Messages.SLIDE_UPDATED)) {
-            setUpCurrentSlide(aInstruction);
-            return;
-        }
-
-        if (aCommand.equals(Protocol.Messages.SLIDE_PREVIEW)) {
-            setUpSlidePreview(aInstruction);
-            return;
-        }
-
-        if (aCommand.equals(Protocol.Messages.SLIDE_NOTES)) {
-            setUpSlideNotes(aInstruction);
-        }
-    }
-
-    private void startSlideShow(List<String> aInstruction) {
-        int aSlideShowSlidesCount = Integer.parseInt(aInstruction.get(1));
-        int aCurrentSlideIndex = Integer.parseInt(aInstruction.get(2));
-
-        mSlideShow.setSlidesCount(aSlideShowSlidesCount);
-        mSlideShow.setCurrentSlideIndex(aCurrentSlideIndex);
-
-        Intent aStatusConnectedSlideShowRunningIntent = new Intent(
-            CommunicationService.STATUS_CONNECTED_SLIDESHOW_RUNNING);
-        Intent aSlideChangedIntent = new Intent(
-            CommunicationService.MSG_SLIDE_CHANGED);
-        aSlideChangedIntent.putExtra("slide_number", aCurrentSlideIndex);
-
-        LocalBroadcastManager.getInstance(mContext)
-            .sendBroadcast(aStatusConnectedSlideShowRunningIntent);
-        LocalBroadcastManager.getInstance(mContext)
-            .sendBroadcast(aSlideChangedIntent);
-    }
-
-    private void finishSlideShow() {
-        this.mSlideShow = new SlideShow();
-
-        Intent aStatusConnectedNoSlideShowIntent = new Intent(
-            CommunicationService.STATUS_CONNECTED_NOSLIDESHOW);
-
-        LocalBroadcastManager.getInstance(mContext)
-            .sendBroadcast(aStatusConnectedNoSlideShowIntent);
-    }
-
-    private void setUpCurrentSlide(List<String> aInstruction) {
-        int aCurrentSlideIndex = Integer.parseInt(aInstruction.get(1));
-
-        mSlideShow.setCurrentSlideIndex(aCurrentSlideIndex);
-
-        Intent aSlideChangedIntent = new Intent(
-            CommunicationService.MSG_SLIDE_CHANGED);
-        aSlideChangedIntent.putExtra("slide_number", aCurrentSlideIndex);
-
-        LocalBroadcastManager.getInstance(mContext)
-            .sendBroadcast(aSlideChangedIntent);
-    }
-
-    private void setUpSlidePreview(List<String> aInstruction) {
-        int aSlideIndex = Integer.parseInt(aInstruction.get(1));
-        String aImageAsString = aInstruction.get(2);
-
-        byte[] aImage = Base64.decode(aImageAsString, Base64.DEFAULT);
-        mSlideShow.setSlidePreview(aSlideIndex, aImage);
-
-        Intent aSlidePreviewChangedIntent = new Intent(
-            CommunicationService.MSG_SLIDE_PREVIEW);
-        aSlidePreviewChangedIntent.putExtra("slide_number", aSlideIndex);
-
-        LocalBroadcastManager.getInstance(mContext)
-            .sendBroadcast(aSlidePreviewChangedIntent);
-    }
-
-    private void setUpSlideNotes(List<String> aInstruction) {
-        int aSlideIndex = Integer.parseInt(aInstruction.get(1));
-        StringBuilder aNotesBuilder = new StringBuilder();
-        for (int aNoteIndex = 2; aNoteIndex < aInstruction
-            .size(); aNoteIndex++) {
-            aNotesBuilder.append(aInstruction.get(aNoteIndex));
-        }
-        String aNotes = aNotesBuilder.toString();
-
-        mSlideShow.setSlideNotes(aSlideIndex, aNotes);
-
-        Intent aSlideNotesChangedIntent = new Intent(
-            CommunicationService.MSG_SLIDE_NOTES);
-        aSlideNotesChangedIntent.putExtra("slide_number", aSlideIndex);
-
-        LocalBroadcastManager.getInstance(mContext)
-            .sendBroadcast(aSlideNotesChangedIntent);
-    }
-}
-
-/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/Server.java b/android/sdremote/src/org/libreoffice/impressremote/communication/Server.java
index 7edf63c..c523f61 100644
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/Server.java
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/Server.java
@@ -15,7 +15,7 @@ public class Server implements Parcelable {
     private static final int SPECIAL_PARCELABLE_OBJECTS_BITMASK = 0;
 
     public static enum Protocol {
-        NETWORK, BLUETOOTH
+        TCP, BLUETOOTH
     }
 
     private final Protocol mProtocol;
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/TcpServersFinder.java b/android/sdremote/src/org/libreoffice/impressremote/communication/TcpServersFinder.java
index 53a14c3..ea5e0b5 100644
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/TcpServersFinder.java
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/TcpServersFinder.java
@@ -126,7 +126,7 @@ public class TcpServersFinder implements ServersFinder, Runnable {
 
         String aFoundServerHostname = aSearchResultScanner.nextLine();
 
-        Server aFoundServer = new Server(Server.Protocol.NETWORK,
+        Server aFoundServer = new Server(Server.Protocol.TCP,
             aSearchResultPacket.getAddress().getHostAddress(),
             aFoundServerHostname);
 
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/Transmitter.java b/android/sdremote/src/org/libreoffice/impressremote/communication/Transmitter.java
deleted file mode 100644
index 520a0e9..0000000
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/Transmitter.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This file is part of the LibreOffice project.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-package org.libreoffice.impressremote.communication;
-
-import android.graphics.Color;
-
-/**
- * Interface to send commands to the server.
- */
-public class Transmitter {
-    private final Client mClient;
-
-    public Transmitter(Client aClient) {
-        this.mClient = aClient;
-    }
-
-    public void performNextTransition() {
-        mClient.sendCommand(Protocol.Commands
-            .prepareCommand(Protocol.Commands.TRANSITION_NEXT));
-    }
-
-    public void performPreviousTransition() {
-        mClient.sendCommand(Protocol.Commands
-            .prepareCommand(Protocol.Commands.TRANSITION_PREVIOUS));
-    }
-
-    public void setCurrentSlide(int slideIndex) {
-        mClient.sendCommand(Protocol.Commands
-            .prepareCommand(Protocol.Commands.GOTO_SLIDE,
-                Integer.toString(slideIndex)));
-    }
-
-    /**
-     * Blank the screen to the default colour (set server-side), which is
-     * generally black. This is slightly faster than using
-     * <code> setUpBlankScreen( colour ) </code>.
-     */
-    public void setUpBlankScreen() {
-        mClient.sendCommand(Protocol.Commands
-            .prepareCommand(Protocol.Commands.PRESENTATION_BLANK_SCREEN));
-    }
-
-    /**
-     * Set the screen to a specific colour. Only use if a non default colour is
-     * needed.
-     *
-     * @param aColor blank screen color
-     */
-    public void setUpBlankScreen(Color aColor) {
-        // FIXME: check how to get colour in integer form.
-
-        mClient.sendCommand(Protocol.Commands
-            .prepareCommand(Protocol.Commands.PRESENTATION_BLANK_SCREEN,
-                aColor.toString()));
-    }
-
-    public void resumePresentation() {
-        mClient.sendCommand(Protocol.Commands
-            .prepareCommand(Protocol.Commands.PRESENTATION_RESUME));
-    }
-
-    public void startPresentation() {
-        mClient.sendCommand(Protocol.Commands
-            .prepareCommand(Protocol.Commands.PRESENTATION_START));
-    }
-
-    public void stopPresentation() {
-        mClient.sendCommand(Protocol.Commands
-            .prepareCommand(Protocol.Commands.PRESENTATION_STOP));
-    }
-}
-
-/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
commit 63ba696acf8c28c84931c99413fb12c6378c3cc5
Author: Artur Dryomov <artur.dryomov at gmail.com>
Date:   Sun Jun 30 03:20:59 2013 +0300

    Add CommandsTransmitter and MessagesReceiver classes.
    
    These classes consist of the code from Client, Receiver and Transmitter
    classes. The main goal is to combine all actions at suitable plases.
    
    Change-Id: Ic90f1c0a47a31bd32d57f409fe24a60f3b0686e1

diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/CommandsTransmitter.java b/android/sdremote/src/org/libreoffice/impressremote/communication/CommandsTransmitter.java
new file mode 100644
index 0000000..1947021
--- /dev/null
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/CommandsTransmitter.java
@@ -0,0 +1,87 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.libreoffice.impressremote.communication;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+
+public class CommandsTransmitter {
+    private final BufferedWriter mCommandsWriter;
+
+    public CommandsTransmitter(ServerConnection aServerConnection) {
+        mCommandsWriter = buildCommandsWriter(aServerConnection);
+    }
+
+    private BufferedWriter buildCommandsWriter(ServerConnection aServerConnection) {
+        try {
+            OutputStream aCommandsStream = aServerConnection.buildCommandsStream();
+
+            return new BufferedWriter(
+                new OutputStreamWriter(aCommandsStream, Protocol.CHARSET));
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("Unable to create commands writer.");
+        }
+    }
+
+    public void pair(String aDeviceName, String aPin) {
+        writeCommand(Protocol.Commands
+            .prepareCommand(Protocol.Commands.PAIR_WITH_SERVER, aDeviceName,
+                aPin));
+    }
+
+    private void writeCommand(String aCommand) {
+        try {
+            mCommandsWriter.write(aCommand);
+            mCommandsWriter.flush();
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to write command.");
+        }
+    }
+
+    public void performNextTransition() {
+        writeCommand(Protocol.Commands
+            .prepareCommand(Protocol.Commands.TRANSITION_NEXT));
+    }
+
+    public void performPreviousTransition() {
+        writeCommand(Protocol.Commands
+            .prepareCommand(Protocol.Commands.TRANSITION_PREVIOUS));
+    }
+
+    public void setCurrentSlide(int slideIndex) {
+        writeCommand(Protocol.Commands
+            .prepareCommand(Protocol.Commands.GOTO_SLIDE,
+                Integer.toString(slideIndex)));
+    }
+
+    public void setUpBlankScreen() {
+        writeCommand(Protocol.Commands
+            .prepareCommand(Protocol.Commands.PRESENTATION_BLANK_SCREEN));
+    }
+
+    public void resumePresentation() {
+        writeCommand(Protocol.Commands
+            .prepareCommand(Protocol.Commands.PRESENTATION_RESUME));
+    }
+
+    public void startPresentation() {
+        writeCommand(Protocol.Commands
+            .prepareCommand(Protocol.Commands.PRESENTATION_START));
+    }
+
+    public void stopPresentation() {
+        writeCommand(Protocol.Commands
+            .prepareCommand(Protocol.Commands.PRESENTATION_STOP));
+    }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/MessagesListener.java b/android/sdremote/src/org/libreoffice/impressremote/communication/MessagesListener.java
new file mode 100644
index 0000000..7bc098b
--- /dev/null
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/MessagesListener.java
@@ -0,0 +1,17 @@
+package org.libreoffice.impressremote.communication;
+
+public interface MessagesListener {
+    public void onPinValidation();
+
+    public void onSuccessfulPairing();
+
+    public void onSlideShowStart(int aSlidesCount, int aCurrentSlideIndex);
+
+    public void onSlideShowFinish();
+
+    public void onSlideChanged(int aCurrentSlideIndex);
+
+    public void onSlidePreview(int aSlideIndex, byte[] aPreview);
+
+    public void onSlideNotes(int aSlideIndex, String aNotes);
+}
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/MessagesReceiver.java b/android/sdremote/src/org/libreoffice/impressremote/communication/MessagesReceiver.java
new file mode 100644
index 0000000..b187e24
--- /dev/null
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/MessagesReceiver.java
@@ -0,0 +1,174 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.libreoffice.impressremote.communication;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.text.TextUtils;
+import android.util.Base64;
+
+public class MessagesReceiver implements Runnable {
+    private final BufferedReader mMessagesReader;
+
+    private final MessagesListener mMessagesListener;
+
+    private final Thread mMessagesListenerThread;
+
+    public MessagesReceiver(ServerConnection aServerConnection, MessagesListener aMessagesListener) {
+        mMessagesReader = buildMessagesReader(aServerConnection);
+
+        mMessagesListener = aMessagesListener;
+
+        mMessagesListenerThread = new Thread(this);
+        mMessagesListenerThread.start();
+    }
+
+    private BufferedReader buildMessagesReader(ServerConnection aServerConnection) {
+        try {
+            InputStream aMessagesStream = aServerConnection.buildMessagesStream();
+
+            return new BufferedReader(
+                new InputStreamReader(aMessagesStream, Protocol.CHARSET));
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("Unable to create messages reader.");
+        }
+    }
+
+    @Override
+    public void run() {
+        while (true) {
+            List<String> aMessage = readMessage();
+
+            if (aMessage == null) {
+                return;
+            }
+
+            parseMessage(aMessage);
+        }
+    }
+
+    private List<String> readMessage() {
+        List<String> aMessage = new ArrayList<String>();
+
+        String aMessageParameter = readMessageParameter();
+
+        while (true) {
+            if (aMessageParameter == null) {
+                break;
+            }
+
+            if (TextUtils.isEmpty(aMessageParameter)) {
+                break;
+            }
+
+            aMessage.add(aMessageParameter);
+
+            aMessageParameter = readMessageParameter();
+        }
+
+        if (aMessageParameter == null) {
+            return null;
+        }
+
+        return aMessage;
+    }
+
+    private String readMessageParameter() {
+        try {
+            return mMessagesReader.readLine();
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to read message parameter.");
+        }
+    }
+
+    private void parseMessage(List<String> aMessage) {
+        if (aMessage.isEmpty()) {
+            return;
+        }
+
+        String aMessageType = aMessage.get(0);
+
+        if (Protocol.Messages.VALIDATING.equals(aMessageType)) {
+            mMessagesListener.onPinValidation();
+            return;
+        }
+
+        if (Protocol.Messages.PAIRED.equals(aMessageType)) {
+            mMessagesListener.onSuccessfulPairing();
+            return;
+        }
+
+        if (Protocol.Messages.SLIDESHOW_STARTED.equals(aMessageType)) {
+            int aSlidesCount = parseSlidesCount(aMessage, 1);
+            int aCurrentSlideIndex = parseSlideIndex(aMessage, 2);
+
+            mMessagesListener.onSlideShowStart(aSlidesCount, aCurrentSlideIndex);
+            return;
+        }
+
+        if (Protocol.Messages.SLIDESHOW_FINISHED.equals(aMessageType)) {
+            mMessagesListener.onSlideShowFinish();
+            return;
+        }
+
+        if (Protocol.Messages.SLIDE_UPDATED.equals(aMessageType)) {
+            int aCurrentSlideIndex = parseSlideIndex(aMessage, 1);
+
+            mMessagesListener.onSlideChanged(aCurrentSlideIndex);
+            return;
+        }
+
+        if (Protocol.Messages.SLIDE_PREVIEW.equals(aMessageType)) {
+            int aSlideIndex = parseSlideIndex(aMessage, 1);
+            byte[] aSlidePreview = parseSlidePreview(aMessage, 2);
+
+            mMessagesListener.onSlidePreview(aSlideIndex, aSlidePreview);
+            return;
+        }
+
+        if (Protocol.Messages.SLIDE_NOTES.equals(aMessageType)) {
+            int aSlideIndex = parseSlideIndex(aMessage, 1);
+            String aSlideNotes = parseSlideNotes(aMessage, 2);
+
+            mMessagesListener.onSlideNotes(aSlideIndex, aSlideNotes);
+        }
+    }
+
+    private int parseSlidesCount(List<String> aMessage, int aMessageParameterIndex) {
+        return Integer.parseInt(aMessage.get(aMessageParameterIndex));
+    }
+
+    private int parseSlideIndex(List<String> aMessage, int aMessageParameterIndex) {
+        return Integer.parseInt(aMessage.get(aMessageParameterIndex));
+    }
+
+    private byte[] parseSlidePreview(List<String> aMessage, int aMessageParameterIndex) {
+        String aPreviewAsString = aMessage.get(aMessageParameterIndex);
+
+        return Base64.decode(aPreviewAsString, Base64.DEFAULT);
+    }
+
+    private String parseSlideNotes(List<String> aMessage, int aMessageParameterIndex) {
+        StringBuilder aNotesBuilder = new StringBuilder();
+
+        for (int aNoteIndex = aMessageParameterIndex; aNoteIndex < aMessage.size(); aNoteIndex++) {
+            aNotesBuilder.append(aMessage.get(aNoteIndex));
+        }
+
+        return aNotesBuilder.toString();
+    }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
commit a446d4cc6833af51f05787bcc54f66ac29967a81
Author: Artur Dryomov <artur.dryomov at gmail.com>
Date:   Sun Jun 30 03:09:22 2013 +0300

    Add the ServerConnection interface and its Bluetooth and TCP implementations.
    
    Basically code of these classes consists of Client implementations. The
    new classes only provide connection and access to messages and commands
    streams.
    
    Change-Id: I683b58dc764d309c47bb46c98663bcb1986a197b

diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothServerConnection.java b/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothServerConnection.java
new file mode 100644
index 0000000..baf75e5
--- /dev/null
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothServerConnection.java
@@ -0,0 +1,76 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.libreoffice.impressremote.communication;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.UUID;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+
+public class BluetoothServerConnection implements ServerConnection {
+    // Standard UUID for the Serial Port Profile.
+    // https://www.bluetooth.org/en-us/specification/assigned-numbers-overview/service-discovery
+    private static final String STANDARD_SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB";
+
+    private final BluetoothSocket mServerConnection;
+
+    public BluetoothServerConnection(Server aServer) {
+        mServerConnection = buildServerConnection(aServer);
+    }
+
+    private BluetoothSocket buildServerConnection(Server aServer) {
+        try {
+            BluetoothDevice aBluetoothServer = BluetoothAdapter
+                .getDefaultAdapter().getRemoteDevice(aServer.getAddress());
+
+            BluetoothSocket aServerConnection = aBluetoothServer
+                .createRfcommSocketToServiceRecord(
+                    UUID.fromString(STANDARD_SPP_UUID));
+
+            aServerConnection.connect();
+
+            return aServerConnection;
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to connect to Bluetooth host.");
+        }
+    }
+
+    @Override
+    public InputStream buildMessagesStream() {
+        try {
+            return mServerConnection.getInputStream();
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to open messages stream.");
+        }
+    }
+
+    @Override
+    public OutputStream buildCommandsStream() {
+        try {
+            return mServerConnection.getOutputStream();
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to open commands stream.");
+        }
+    }
+
+    @Override
+    public void close() {
+        try {
+            mServerConnection.close();
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to close server connection.");
+        }
+    }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/ServerConnection.java b/android/sdremote/src/org/libreoffice/impressremote/communication/ServerConnection.java
new file mode 100644
index 0000000..fced5a0
--- /dev/null
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/ServerConnection.java
@@ -0,0 +1,22 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.libreoffice.impressremote.communication;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public interface ServerConnection {
+    public InputStream buildMessagesStream();
+
+    public OutputStream buildCommandsStream();
+
+    public void close();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/TcpServerConnection.java b/android/sdremote/src/org/libreoffice/impressremote/communication/TcpServerConnection.java
new file mode 100644
index 0000000..a559a50
--- /dev/null
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/TcpServerConnection.java
@@ -0,0 +1,65 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.libreoffice.impressremote.communication;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+public class TcpServerConnection implements ServerConnection {
+    private final Socket mServerConnection;
+
+    public TcpServerConnection(Server aServer) {
+        mServerConnection = buildServerConnection(aServer);
+    }
+
+    private Socket buildServerConnection(Server aServer) {
+        try {
+            String aServerAddress = aServer.getAddress();
+            int aServerPort = Protocol.Ports.CLIENT_CONNECTION;
+
+            return new Socket(aServerAddress, aServerPort);
+        } catch (UnknownHostException e) {
+            throw new RuntimeException("Unable to connect to unknown host.");
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to connect to host.");
+        }
+    }
+
+    @Override
+    public InputStream buildMessagesStream() {
+        try {
+            return mServerConnection.getInputStream();
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to open messages stream.");
+        }
+    }
+
+    @Override
+    public OutputStream buildCommandsStream() {
+        try {
+            return mServerConnection.getOutputStream();
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to open commands stream.");
+        }
+    }
+
+    @Override
+    public void close() {
+        try {
+            mServerConnection.close();
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to close server connection.");
+        }
+    }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
commit 6024be55fa7ec481fcefc037ea55a5d55566169a
Author: Artur Dryomov <artur.dryomov at gmail.com>
Date:   Tue Jun 25 02:38:01 2013 +0300

    Refactor servers finders.
    
    * Add a common ServersFinder interface.
    * Rename finders to match their responsibility.
    
    Change-Id: Ib414ce2ba8315558695c80ca47d43d98f64298c9

diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothFinder.java b/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothServersFinder.java
similarity index 55%
rename from android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothFinder.java
rename to android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothServersFinder.java
index 4ed99a1..93a1f84 100644
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothFinder.java
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothServersFinder.java
@@ -8,8 +8,9 @@
  */
 package org.libreoffice.impressremote.communication;
 
-import java.util.Collection;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import android.bluetooth.BluetoothAdapter;
@@ -23,104 +24,84 @@ import android.support.v4.content.LocalBroadcastManager;
 
 import org.libreoffice.impressremote.communication.Server.Protocol;
 
-public class BluetoothFinder extends BroadcastReceiver {
-    // TODO: add removal of cached items
+public class BluetoothServersFinder extends BroadcastReceiver implements ServersFinder, Runnable {
+    private static final int SEARCH_DELAY_IN_MILLISECONDS = 1000 * 10;
+
     private final Context mContext;
 
     private final Map<String, Server> mServers;
 
-    public BluetoothFinder(Context aContext) {
+    public BluetoothServersFinder(Context aContext) {
         mContext = aContext;
 
         mServers = new HashMap<String, Server>();
     }
 
+    @Override
     public void startSearch() {
         if (!isBluetoothAvailable()) {
             return;
         }
 
-        BluetoothAdapter.getDefaultAdapter().startDiscovery();
+        if (BluetoothAdapter.getDefaultAdapter().isDiscovering()) {
+            return;
+        }
+
+        setUpSearchResultsReceiver();
 
-        registerSearchResultsReceiver();
+        BluetoothAdapter.getDefaultAdapter().startDiscovery();
     }
 
     private boolean isBluetoothAvailable() {
         return BluetoothAdapter.getDefaultAdapter() != null;
     }
 
-    private void registerSearchResultsReceiver() {
-        IntentFilter aIntentFilter = new IntentFilter(
+    private void setUpSearchResultsReceiver() {
+        IntentFilter aSearchResultsFilter = new IntentFilter(
             BluetoothDevice.ACTION_FOUND);
-        aIntentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
-        aIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
-
-        mContext.registerReceiver(this, aIntentFilter);
-    }
-
-    public void stopSearch() {
-        if (!isBluetoothAvailable()) {
-            return;
-        }
-
-        BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
-
-        unregisterSearchResultsReceiver();
-    }
+        aSearchResultsFilter
+            .addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
 
-    private void unregisterSearchResultsReceiver() {
-        mContext.unregisterReceiver(this);
-    }
-
-    public Collection<Server> getServers() {
-        return mServers.values();
+        mContext.registerReceiver(this, aSearchResultsFilter);
     }
 
     @Override
     public void onReceive(Context aContext, Intent aIntent) {
-        if (aIntent.getAction().equals(BluetoothDevice.ACTION_FOUND)) {
-            BluetoothDevice aBluetoothDevice = (BluetoothDevice) aIntent
-                .getExtras().get(BluetoothDevice.EXTRA_DEVICE);
-
-            if (aBluetoothDevice == null) {
-                return;
-            }
+        if (BluetoothDevice.ACTION_FOUND.equals(aIntent.getAction())) {
+            BluetoothDevice aBluetoothDevice = aIntent
+                .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
 
-            createServer(aBluetoothDevice);
+            addServer(buildServer(aBluetoothDevice));
 
             callUpdatingServersList();
 
             return;
         }
 
-        if (aIntent.getAction()
-            .equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
-            startDiscoveryDelayed();
+        if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED
+            .equals(aIntent.getAction())) {
 
-            return;
-        }
-
-        if (aIntent.getAction()
-            .equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
             startDiscoveryDelayed();
         }
     }
 
-    private void createServer(BluetoothDevice aBluetoothDevice) {
+    private void addServer(Server aServer) {
+        mServers.put(aServer.getAddress(), aServer);
+    }
+
+    private Server buildServer(BluetoothDevice aBluetoothDevice) {
         String aServerAddress = aBluetoothDevice.getAddress();
         String aServerName = aBluetoothDevice.getName();
 
-        Server aServer = new Server(Protocol.BLUETOOTH, aServerAddress,
-            aServerName, System.currentTimeMillis());
-        mServers.put(aServerAddress, aServer);
+        return new Server(Protocol.BLUETOOTH, aServerAddress, aServerName);
     }
 
     private void callUpdatingServersList() {
-        Intent aServersListChangedIntent = new Intent(
+        Intent aServersListUpdatedIntent = new Intent(
             CommunicationService.MSG_SERVERLIST_CHANGED);
 
         LocalBroadcastManager.getInstance(mContext)
-            .sendBroadcast(aServersListChangedIntent);
+            .sendBroadcast(aServersListUpdatedIntent);
     }
 
     private void startDiscoveryDelayed() {
@@ -133,12 +114,37 @@ public class BluetoothFinder extends BroadcastReceiver {
         }
 
         Handler aHandler = new Handler();
-        aHandler.postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                // Looping, huh?
-            }
-        }, 1000 * 15);
+        aHandler.postDelayed(this, SEARCH_DELAY_IN_MILLISECONDS);
+    }
+
+    @Override
+    public void run() {
+        BluetoothAdapter.getDefaultAdapter().startDiscovery();
+    }
+
+    @Override
+    public void stopSearch() {
+        if (!isBluetoothAvailable()) {
+            return;
+        }
+
+        tearDownSearchResultsReceiver();
+
+        BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
+    }
+
+    private void tearDownSearchResultsReceiver() {
+        try {
+            mContext.unregisterReceiver(this);
+        } catch (IllegalArgumentException e) {
+            // Receiver not registered.
+            // Fixed in Honeycomb: Android’s issue #6191.
+        }
+    }
+
+    @Override
+    public List<Server> getServers() {
+        return new ArrayList<Server>(mServers.values());
     }
 }
 
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/CommunicationService.java b/android/sdremote/src/org/libreoffice/impressremote/communication/CommunicationService.java
index d87ef6f..273362c 100644
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/CommunicationService.java
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/CommunicationService.java
@@ -68,8 +68,8 @@ public class CommunicationService extends Service implements Runnable {
 
     private final Receiver mReceiver = new Receiver(this);
 
-    private final ServerFinder mNetworkFinder = new ServerFinder(this);
-    private final BluetoothFinder mBluetoothFinder = new BluetoothFinder(this);
+    private final ServersFinder mTcpServersFinder = new TcpServersFinder(this);
+    private final ServersFinder mBluetoothServersFinder = new BluetoothServersFinder(this);
 
     private Thread mThread = null;
 
@@ -180,19 +180,19 @@ public class CommunicationService extends Service implements Runnable {
             .getDefaultSharedPreferences(this);
         boolean bEnableWifi = aPref.getBoolean("option_enablewifi", false);
         if (bEnableWifi)
-            mNetworkFinder.startSearch();
+            mTcpServersFinder.startSearch();
         BluetoothAdapter aAdapter = BluetoothAdapter.getDefaultAdapter();
         if (aAdapter != null) {
             mBluetoothPreviouslyEnabled = aAdapter.isEnabled();
             if (!mBluetoothPreviouslyEnabled)
                 aAdapter.enable();
-            mBluetoothFinder.startSearch();
+            mBluetoothServersFinder.startSearch();
         }
     }
 
     public void stopSearch() {
-        mNetworkFinder.stopSearch();
-        mBluetoothFinder.stopSearch();
+        mTcpServersFinder.stopSearch();
+        mBluetoothServersFinder.stopSearch();
         BluetoothAdapter aAdapter = BluetoothAdapter.getDefaultAdapter();
         if (aAdapter != null) {
             if (!mBluetoothPreviouslyEnabled) {
@@ -204,8 +204,8 @@ public class CommunicationService extends Service implements Runnable {
     public void connectTo(Server aServer) {
         synchronized (mConnectionVariableMutex) {
             if (mState == State.SEARCHING) {
-                mNetworkFinder.stopSearch();
-                mBluetoothFinder.stopSearch();
+                mTcpServersFinder.stopSearch();
+                mBluetoothServersFinder.stopSearch();
                 mState = State.DISCONNECTED;
             }
             mServerDesired = aServer;
@@ -261,8 +261,8 @@ public class CommunicationService extends Service implements Runnable {
     public List<Server> getServers() {
         List<Server> aServers = new ArrayList<Server>();
 
-        aServers.addAll(mNetworkFinder.getServers());
-        aServers.addAll(mBluetoothFinder.getServers());
+        aServers.addAll(mTcpServersFinder.getServers());
+        aServers.addAll(mBluetoothServersFinder.getServers());
         aServers.addAll(mManualServers.values());
 
         return aServers;
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/Protocol.java b/android/sdremote/src/org/libreoffice/impressremote/communication/Protocol.java
index 6081669..6f40e54 100644
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/Protocol.java
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/Protocol.java
@@ -40,6 +40,7 @@ final class Protocol {
 
         public static final String PAIRED = "LO_SERVER_SERVER_PAIRED";
         public static final String VALIDATING = "LO_SERVER_VALIDATING_PIN";
+        public static final String ADVERTISE = "LOREMOTE_ADVERTISE";
 
         public static final String SLIDESHOW_STARTED = "slideshow_started";
         public static final String SLIDESHOW_FINISHED = "slideshow_finished";
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/Server.java b/android/sdremote/src/org/libreoffice/impressremote/communication/Server.java
index dd2bc5b..7edf63c 100644
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/Server.java
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/Server.java
@@ -36,6 +36,13 @@ public class Server implements Parcelable {
         this.mTimeDiscovered = aTimeDiscovered;
     }
 
+    protected Server(Protocol aProtocol, String aAddress, String aName) {
+        this.mProtocol = aProtocol;
+        this.mAddress = aAddress;
+        this.mName = aName;
+        this.mTimeDiscovered = System.currentTimeMillis();
+    }
+
     public Protocol getProtocol() {
         return mProtocol;
     }
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/ServerFinder.java b/android/sdremote/src/org/libreoffice/impressremote/communication/ServerFinder.java
deleted file mode 100644
index 9ac7037..0000000
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/ServerFinder.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This file is part of the LibreOffice project.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-package org.libreoffice.impressremote.communication;
-
-import java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.DatagramSocket;
-import java.net.InetAddress;
-import java.net.SocketException;
-import java.net.SocketTimeoutException;
-import java.net.UnknownHostException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-import android.content.Context;
-import android.content.Intent;
-import android.support.v4.content.LocalBroadcastManager;
-
-public class ServerFinder implements Runnable {
-    private final Context mContext;
-
-    private DatagramSocket mSocket = null;
-    private Thread mListenerThread;
-    private boolean mFinishRequested;
-
-    private final Map<String, Server> mServers;
-
-    public ServerFinder(Context aContext) {
-        mContext = aContext;
-
-        mSocket = null;
-        mListenerThread = null;
-        mFinishRequested = false;
-
-        mServers = new HashMap<String, Server>();
-    }
-
-    public void startSearch() {
-        if (mSocket != null) {
-            return;
-        }
-
-        mFinishRequested = false;
-
-        if (mListenerThread == null) {
-            mListenerThread = new Thread(this);
-        }
-
-        mListenerThread.start();
-    }
-
-    @Override
-    public void run() {
-        addLocalServerForEmulator();
-
-        long aStartSearchTime = 0;
-
-        setUpSearchSocket();
-
-        while (!mFinishRequested) {
-            if (System
-                .currentTimeMillis() - aStartSearchTime > 1000 * 15) {
-                sendSearchCommand();
-
-                aStartSearchTime = System.currentTimeMillis();
-
-                removeStaleServers();
-            }
-
-            listenForServer();
-        }
-    }
-
-    /**
-     * Check whether we are on an emulator and add it's host to the list of
-     * servers if so (although we do not know whether libo is running on
-     * the host).
-     */
-    private void addLocalServerForEmulator() {
-        if (!isLocalServerForEmulatorReachable()) {
-            return;
-        }
-
-        Server aServer = new Server(Server.Protocol.NETWORK,
-            Protocol.Addresses.SERVER_LOCAL_FOR_EMULATOR, "Android Emulator",
-            0);
-
-        mServers.put(aServer.getAddress(), aServer);
-
-        callUpdatingServersList();
-    }
-
-    private boolean isLocalServerForEmulatorReachable() {
-        try {
-            InetAddress aLocalServerAddress = InetAddress
-                .getByName(Protocol.Addresses.SERVER_LOCAL_FOR_EMULATOR);
-
-            return aLocalServerAddress.isReachable(100);
-        } catch (UnknownHostException e) {
-            return false;
-        } catch (IOException e) {
-            return false;
-        }
-    }
-
-    private void callUpdatingServersList() {
-        Intent aIntent = new Intent(
-            CommunicationService.MSG_SERVERLIST_CHANGED);
-        LocalBroadcastManager.getInstance(mContext).sendBroadcast(aIntent);
-    }
-
-    private void setUpSearchSocket() {
-        try {
-            mSocket = new DatagramSocket();
-            mSocket.setSoTimeout(1000 * 10);
-        } catch (SocketException e) {
-            throw new RuntimeException("Unable to open search socket.");
-        }
-    }
-
-    private void sendSearchCommand() {
-        try {
-            mSocket.send(buildSearchPacket());
-        } catch (IOException e) {
-            throw new RuntimeException("Unable to send search packet.");
-        }
-    }
-
-    private DatagramPacket buildSearchPacket() {
-        try {
-            String aSearchCommand = Protocol.Commands
-                .prepareCommand(Protocol.Commands.SEARCH_SERVERS);
-
-            DatagramPacket aSearchPacket = new DatagramPacket(
-                aSearchCommand.getBytes(), aSearchCommand.length());
-            aSearchPacket.setAddress(
-                InetAddress.getByName(Protocol.Addresses.SERVER_SEARCH));
-            aSearchPacket.setPort(Protocol.Ports.SERVER_SEARCH);
-
-            return aSearchPacket;
-        } catch (UnknownHostException e) {
-            throw new RuntimeException("Unable to find address to search.");
-        }
-    }
-
-    private void removeStaleServers() {
-        for (Server aServer : mServers.values()) {
-            if (aServer.mNoTimeout) {
-                continue;
-            }
-
-            if (System.currentTimeMillis()
-                - aServer
-                .getTimeDiscovered() > 60 * 1000) {
-                mServers
-                    .remove(aServer.getAddress());
-                callUpdatingServersList();
-            }
-        }
-    }
-
-    private void listenForServer() {
-        byte[] aBuffer = new byte[500];
-        DatagramPacket aPacket = new DatagramPacket(aBuffer, aBuffer.length);
-
-        try {
-            String aCommand = null;
-            String aName = null;
-            mSocket.receive(aPacket);
-            int i;
-            for (i = 0; i < aBuffer.length; i++) {
-                if (aPacket.getData()[i] == '\n') {
-                    aCommand = new String(aPacket.getData(), 0, i,
-                        Protocol.CHARSET);
-                    break;
-                }
-            }
-            if (i == aBuffer.length || !"LOREMOTE_ADVERTISE".equals(aCommand)) {
-                return;
-            }
-            for (int j = i + 1; j < aBuffer.length; j++) {
-                if (aPacket.getData()[j] == '\n') {
-                    aName = new String(aPacket.getData(), i + 1, j - (i + 1),
-                        Protocol.CHARSET);
-                    break;
-                }
-            }
-            if (aName == null) {
-                return;
-            }
-            Server aServer = new Server(Server.Protocol.NETWORK, aPacket
-                .getAddress().getHostAddress(), aName,
-                System.currentTimeMillis());
-            mServers.put(aServer.getAddress(), aServer);
-
-            callUpdatingServersList();
-        } catch (SocketTimeoutException e) {
-            // Ignore -- we want to timeout to enable checking whether we
-            // should stop listening periodically
-        } catch (IOException e) {
-        }
-
-    }
-
-    public void stopSearch() {
-        if (mListenerThread == null) {
-            return;
-        }
-
-        mFinishRequested = true;
-        mListenerThread = null;
-    }
-
-    public Collection<Server> getServers() {
-        return mServers.values();
-    }
-}
-
-/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/ServersFinder.java b/android/sdremote/src/org/libreoffice/impressremote/communication/ServersFinder.java
new file mode 100644
index 0000000..aba82ab
--- /dev/null
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/ServersFinder.java
@@ -0,0 +1,21 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.libreoffice.impressremote.communication;
+
+import java.util.List;
+
+public interface ServersFinder {
+    public void startSearch();
+
+    public void stopSearch();
+
+    public List<Server> getServers();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/TcpServersFinder.java b/android/sdremote/src/org/libreoffice/impressremote/communication/TcpServersFinder.java
new file mode 100644
index 0000000..53a14c3
--- /dev/null
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/TcpServersFinder.java
@@ -0,0 +1,198 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package org.libreoffice.impressremote.communication;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.v4.content.LocalBroadcastManager;
+import android.text.TextUtils;
+
+public class TcpServersFinder implements ServersFinder, Runnable {
+    private static final int SEARCH_DELAY_IN_MILLISECONDS = 1000 * 10;
+    private static final int BLOCKING_TIMEOUT_IN_MILLISECONDS = 1000 * 10;
+
+    private static final int SEARCH_RESULT_BUFFER_SIZE = 1024;
+
+    private final Context mContext;
+
+    private final Map<String, Server> mServers;
+
+    private DatagramSocket mSearchSocket;
+    private Thread mSearchResultsListenerThread;
+    private boolean mSearchStopRequested;
+
+    public TcpServersFinder(Context aContext) {
+        mContext = aContext;
+
+        mServers = new HashMap<String, Server>();
+    }
+
+    @Override
+    public void startSearch() {
+        if (mSearchResultsListenerThread != null) {
+            return;
+        }
+
+        mSearchStopRequested = false;
+
+        mSearchResultsListenerThread = new Thread(this);
+        mSearchResultsListenerThread.start();
+    }
+
+    @Override
+    public void run() {
+        setUpSearchSocket();
+
+        while (!mSearchStopRequested) {
+            sendSearchCommand();
+
+            listenForSearchResults();
+
+            setUpSearchDelay();
+        }
+
+        tearDownSearchSocket();
+    }
+
+    private void setUpSearchSocket() {
+        try {
+            mSearchSocket = new DatagramSocket();
+            mSearchSocket.setSoTimeout(BLOCKING_TIMEOUT_IN_MILLISECONDS);
+        } catch (SocketException e) {
+            throw new RuntimeException("Unable to open search socket.");
+        }
+    }
+
+    private void sendSearchCommand() {
+        try {
+            mSearchSocket.send(buildSearchPacket());
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to send search packet.");
+        }
+    }
+
+    private DatagramPacket buildSearchPacket() {
+        try {
+            String aSearchCommand = Protocol.Commands
+                .prepareCommand(Protocol.Commands.SEARCH_SERVERS);
+
+            DatagramPacket aSearchPacket = new DatagramPacket(
+                aSearchCommand.getBytes(), aSearchCommand.length());
+            aSearchPacket.setAddress(
+                InetAddress.getByName(Protocol.Addresses.SERVER_SEARCH));
+            aSearchPacket.setPort(Protocol.Ports.SERVER_SEARCH);
+
+            return aSearchPacket;
+        } catch (UnknownHostException e) {
+            throw new RuntimeException("Unable to find address to search.");
+        }
+    }
+
+    private void listenForSearchResults() {
+        DatagramPacket aSearchResultPacket = buildSearchResultPacket();
+
+        String aSearchResult = receiveSearchResult(aSearchResultPacket);
+
+        if (TextUtils.isEmpty(aSearchResult)) {
+            return;
+        }
+
+        Scanner aSearchResultScanner = new Scanner(aSearchResult);
+
+        String aMessage = aSearchResultScanner.nextLine();
+
+        if (!Protocol.Messages.ADVERTISE.equals(aMessage)) {
+            return;
+        }
+
+        String aFoundServerHostname = aSearchResultScanner.nextLine();
+
+        Server aFoundServer = new Server(Server.Protocol.NETWORK,
+            aSearchResultPacket.getAddress().getHostAddress(),
+            aFoundServerHostname);
+
+        addServer(aFoundServer);
+
+        callUpdatingServersList();
+    }
+
+    private DatagramPacket buildSearchResultPacket() {
+        byte[] aSearchResultBuffer = new byte[SEARCH_RESULT_BUFFER_SIZE];
+
+        return new DatagramPacket(
+            aSearchResultBuffer, aSearchResultBuffer.length);
+    }
+
+    private String receiveSearchResult(DatagramPacket aSearchResultPacket) {
+        try {
+            mSearchSocket.receive(aSearchResultPacket);
+
+            return new String(aSearchResultPacket.getData(), Protocol.CHARSET);
+        } catch (SocketTimeoutException e) {
+            return "";
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to receive search result.");
+        }
+    }
+
+    private void addServer(Server aServer) {
+        mServers.put(aServer.getAddress(), aServer);
+    }
+
+    private void callUpdatingServersList() {
+        Intent aServersListUpdatedIntent = new Intent(
+            CommunicationService.MSG_SERVERLIST_CHANGED);
+
+        LocalBroadcastManager.getInstance(mContext)
+            .sendBroadcast(aServersListUpdatedIntent);
+    }
+
+    private void setUpSearchDelay() {
+        try {
+            Thread.sleep(SEARCH_DELAY_IN_MILLISECONDS);
+        } catch (InterruptedException e) {
+            mSearchStopRequested = true;
+        }
+    }
+
+    private void tearDownSearchSocket() {
+        mSearchSocket.close();
+    }
+
+    @Override
+    public void stopSearch() {
+        if (mSearchResultsListenerThread == null) {
+            return;
+        }
+
+        mSearchStopRequested = true;
+
+        mSearchResultsListenerThread = null;
+    }
+
+    @Override
+    public List<Server> getServers() {
+        return new ArrayList<Server>(mServers.values());
+    }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
commit 26a84b3306234a6653fe0d9169e0b0fcec68a44e
Author: Artur Dryomov <artur.dryomov at gmail.com>
Date:   Tue Jun 25 02:34:46 2013 +0300

    Fix possible null pointer exception in the BluetoothClient.
    
    Change-Id: Ib43458bd8cfa9624f6f43610db90686476d9865f

diff --git a/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothClient.java b/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothClient.java
index 4fea919..8da48bb 100644
--- a/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothClient.java
+++ b/android/sdremote/src/org/libreoffice/impressremote/communication/BluetoothClient.java
@@ -26,7 +26,6 @@ public class BluetoothClient extends Client {
 
     private final boolean mBluetoothWasEnabled;
 
-    private final BluetoothAdapter mBluetoothAdapter;
     private BluetoothSocket mSocket;
 
     public BluetoothClient(Server aServer, CommunicationService aCommunicationService, Receiver aReceiver, boolean aBluetoothWasEnabled) {
@@ -34,10 +33,8 @@ public class BluetoothClient extends Client {
 
         mBluetoothWasEnabled = aBluetoothWasEnabled;
 
-        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-
         if (!mBluetoothWasEnabled) {
-            mBluetoothAdapter.enable();
+            BluetoothAdapter.getDefaultAdapter().enable();
         }
     }
 
@@ -48,10 +45,11 @@ public class BluetoothClient extends Client {
 
     private BluetoothSocket buildServerConnection() {
         try {
-            BluetoothDevice aBluetoothServer = mBluetoothAdapter
+            BluetoothDevice aBluetoothServer = BluetoothAdapter
+                .getDefaultAdapter()
                 .getRemoteDevice(mServer.getAddress());
 
-            mBluetoothAdapter.cancelDiscovery();
+            BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
 
             BluetoothSocket aSocket = aBluetoothServer
                 .createRfcommSocketToServiceRecord(
@@ -61,7 +59,7 @@ public class BluetoothClient extends Client {
 
             return aSocket;
         } catch (IOException e) {
-            throw new RuntimeException("Unable to connect to Bluetooth host");
+            throw new RuntimeException("Unable to connect to Bluetooth host.");
         }
     }
 
@@ -92,7 +90,7 @@ public class BluetoothClient extends Client {
 
     protected void onDisconnect() {
         if (!mBluetoothWasEnabled) {
-            mBluetoothAdapter.disable();
+            BluetoothAdapter.getDefaultAdapter().disable();
         }
     }
 


More information about the Libreoffice-commits mailing list