AcceptorI.java

// Copyright (c) ZeroC, Inc.

package com.zeroc.IceBT;

import com.zeroc.Ice.Acceptor;
import com.zeroc.Ice.ReadyCallback;
import com.zeroc.Ice.SocketException;
import com.zeroc.Ice.SocketOperation;
import com.zeroc.Ice.Transceiver;

import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;

import java.io.IOException;
import java.nio.channels.ServerSocketChannel;
import java.util.Stack;
import java.util.UUID;

final class AcceptorI implements Acceptor {
    @Override
    public ServerSocketChannel fd() {
        return null;
    }

    @Override
    public synchronized void setReadyCallback(ReadyCallback callback) {
        _readyCallback = callback;
        notify(); // Notify the acceptor thread
    }

    @Override
    public void close() {
        synchronized (this) {
            _closed = true;
        }

        if (_socket != null) {
            try {
                _socket.close(); // Wakes up the thread blocked in accept().
            } catch (Exception ex) {
                // Ignore.
            }
        }
        if (_thread != null) {
            try {
                _thread.join();
            } catch (Exception ex) {
                // Ignore.
            }
        }
    }

    @Override
    public com.zeroc.Ice.EndpointI listen() {
        UUID uuid = null;
        try {
            uuid = UUID.fromString(_uuid);
        } catch (IllegalArgumentException ex) {
            throw new SocketException(ex);
        }

        try {
            // We always listen using the "secure" method.
            _socket = _instance.bluetoothAdapter().listenUsingRfcommWithServiceRecord(_name, uuid);
        } catch (IOException ex) {
            throw new SocketException(ex);
        }

        // Use a helper thread to perform the blocking accept() calls.
        _thread =
            new Thread() {
                public void run() {
                    runAccept();
                }
            };
        _thread.start();

        return _endpoint;
    }

    @Override
    public synchronized Transceiver accept() {
        if (_exception != null) {
            throw new SocketException(_exception);
        }

        // accept() should only be called when we have at least one socket ready.
        assert (!_pending.isEmpty());

        BluetoothSocket socket = _pending.pop();

        // Update our status with the thread pool.
        _readyCallback.ready(SocketOperation.Read, !_pending.isEmpty());

        return new TransceiverI(_instance, socket, _uuid, _adapterName);
    }

    @Override
    public String protocol() {
        return _instance.protocol();
    }

    @Override
    public String toString() {
        StringBuffer s = new StringBuffer("local address = ");
        s.append(_instance.bluetoothAdapter().getAddress());
        return s.toString();
    }

    @Override
    public String toDetailedString() {
        StringBuffer s = new StringBuffer(toString());
        if (!_name.isEmpty()) {
            s.append("\nservice name = '");
            s.append(_name);
            s.append("'");
        }
        if (!_uuid.isEmpty()) {
            s.append("\nservice uuid = ");
            s.append(_uuid);
        }
        return s.toString();
    }

    AcceptorI(EndpointI endpoint, Instance instance, String adapterName, String uuid, String name) {
        _endpoint = endpoint;
        _instance = instance;
        _adapterName = adapterName;
        _name = name;
        _uuid = uuid;

        _pending = new Stack<BluetoothSocket>();
        _closed = false;
    }

    private void runAccept() {
        synchronized (this) {
            // Wait for the ready callback to be set by the selector.
            while (_readyCallback == null) {
                try {
                    wait();
                } catch (InterruptedException ex) {}
            }
        }

        while (true) {
            try {
                BluetoothSocket socket = _socket.accept();
                synchronized (this) {
                    _pending.push(socket);

                    // Notify the thread pool that we are ready to "read". The thread pool will
                    // invoke accept()
                    // and we can return a new transceiver.
                    _readyCallback.ready(SocketOperation.Read, true);
                }
            } catch (Exception ex) {
                synchronized (this) {
                    if (_closed) {
                        break;
                    }
                    _exception = ex;
                    _readyCallback.ready(SocketOperation.Read, true);
                }
            }
        }

        // Close any remaining incoming sockets that haven't been accepted yet.
        for (BluetoothSocket s : _pending) {
            try {
                s.close();
            } catch (Exception ex) {
                // Ignore.
            }
        }
        _pending.clear();
    }

    private final EndpointI _endpoint;
    private final Instance _instance;
    private final String _adapterName;
    private final String _name;
    private final String _uuid;
    private ReadyCallback _readyCallback;
    private BluetoothServerSocket _socket;
    private final Stack<BluetoothSocket> _pending;
    private Thread _thread;
    private Exception _exception;
    private boolean _closed;
}