0

I'm new to Android/Glass development, but I've started by tackling what seems to be a very challenging task. I am trying to send String messages over bluetooth to and from Glass and my computer.

I have a Service for the bluetooth functionalities, and I have a binder class nested within that service. When I try to call one of the binder's methods, however, the app crashes.

The crash occurs on this line:

mBluetoothService.requestConnect();

Causing this error:

java.lang.NoClassDefFoundError: com.riverlab.glassbluetooth.BluetoothService$RemoteConnectionBinder

Running this code:

MainActivity.java: Handles the menu items

package com.riverlab.glassbluetooth;

import com.example.glassbluetooth.R;
import com.keyboardr.glassremote.client.RemoteMessenger;
import com.keyboardr.glassremote.client.RemoteMessengerService;

import android.os.Bundle;
import android.os.IBinder;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends Activity {

    private boolean mResumed;
    private GlassBluetoothService.MainBinder mService;
    private BluetoothService.RemoteConnectionBinder mBluetoothService;

    //Name is deceptive, this binder is for the voice control
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName arg0, IBinder binderService) {
            if(binderService instanceof GlassBluetoothService.MainBinder) {
                mService = (GlassBluetoothService.MainBinder) binderService;
                openOptionsMenu();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            // TODO Auto-generated method stub
        }
    };

    //Connection for the bluetooth binder
    private ServiceConnection mBluetoothConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName arg0, IBinder binderService) {
            if(binderService instanceof RemoteMessengerService.RemoteConnectionBinder){
                mBluetoothService = (BluetoothService.RemoteConnectionBinder) binderService;
                openOptionsMenu();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            // TODO Auto-generated method stub
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(mService == null) {
            bindService(new Intent(this, GlassBluetoothService.class), mConnection, 0);
        }

        if (mBluetoothService == null)
        {
            bindService(new Intent(this, BluetoothService.class), mBluetoothConnection, 0);
        }

    }

    @Override
    protected void onResume() {
        super.onResume();
        mResumed = true;
    }

    @Override
    protected void onPause() {
        super.onPause();
        mResumed = false;
    }

    @Override
    public void openOptionsMenu() {
        if (mResumed && mService != null) {
            super.openOptionsMenu();
        }
    }

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

    @Override
    public void onDestroy() {
        super.onDestroy();
        finish();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.read_connection_status:
                mService.speakConnectionStatus();
                finish();
                return true;
            case R.id.connect:
                mBluetoothService.requestConnect();
                finish();
                return true;
            case R.id.stop:
                stopService(new Intent(this, GlassBluetoothService.class));
                stopService(new Intent(this, BluetoothService.class));
                finish();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onOptionsMenuClosed(Menu menu) {
        super.onOptionsMenuClosed(menu);

        unbindService(mConnection);
    }
}

BluetoothService.java: Handles the Bluetooth

package com.riverlab.glassbluetooth;

import java.util.UUID;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.speech.tts.TextToSpeech;

import com.example.glassbluetooth.R;
import com.keyboardr.glassremote.client.RemoteMessenger;
import com.keyboardr.glassremote.client.RemoteMessengerImpl;
import com.keyboardr.glassremote.client.StringRemoteMessengerService;
import com.keyboardr.glassremote.client.RemoteMessenger.Callback;
import com.keyboardr.glassremote.client.RemoteMessengerService.RemoteConnectionBinder;
import com.keyboardr.glassremote.common.receiver.MessageReceiver;
import com.keyboardr.glassremote.common.receiver.StringMessageReader;
import com.keyboardr.glassremote.common.sender.MessageSender;
import com.keyboardr.glassremote.common.sender.StringMessageSender;

public class BluetoothService extends Service
{
    private final RemoteConnectionBinder mBinder = new RemoteConnectionBinder();

    private RemoteMessenger<String, String> mMessenger;

    public class RemoteConnectionBinder extends Binder implements
            RemoteMessenger<String, String> {

        @Override
        public void setCallback(Callback<? super String> callback) {
            mMessenger.setCallback(callback);
        }

        @Override
        public boolean isConnected() {
            return mMessenger.isConnected();
        }

        @Override
        public void requestConnect() {
            mMessenger.requestConnect();
        }

        @Override
        public void disconnect() {
            mMessenger.disconnect();
        }

        @Override
        public void sendMessage(String arg0) throws IllegalStateException {
            // TODO Auto-generated method stub

        }

    }


    public void onCreate()
    {
        super.onCreate();
        mMessenger = new RemoteMessengerImpl<String, String>(UUID.randomUUID(), new StringMessageSender(), new StringMessageReader());
    }

    public void onStartCommand()
    {
        mMessenger.requestConnect();
        while (!mMessenger.isConnected());
        mMessenger.sendMessage("Connection Succesful");
    }

    @Override
    public void onDestroy() {
        mMessenger.disconnect();
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        mMessenger.setCallback(null);
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        mMessenger.setCallback(null);
        mMessenger.disconnect();
        return super.onUnbind(intent);
    }

}

Both files are in the same package. I added three jar files to my buildpath, they don't seem to be related to the issue but let me know if that might cause an issue. I am using the Eclipse ADT Build: v22.3.0-887826 on Ubuntu 12.04

Thank you in advance for your help! I can't figure this one out.

EDIT: Here is RemoteMessenger

package com.keyboardr.glassremote.client;

import java.lang.ref.WeakReference;
import java.util.UUID;

import android.bluetooth.BluetoothDevice;

import com.keyboardr.glassremote.common.receiver.MessageReceiver;
import com.keyboardr.glassremote.common.receiver.StringMessageReader;
import com.keyboardr.glassremote.common.sender.MessageSender;
import com.keyboardr.glassremote.common.sender.StringMessageSender;

/**
 * Interface for communicating with the remote server. There should be a
 * <code>MessageService&lt;R, S></code> running on that remote server.
 * 
 * @author Joshua Brown
 * 
 * @param <S>
 *            the type of messages this <code>RemoteMessenger</code> will send
 *            to the remote server. The corresponding
 *            <code>MessageService</code> on the remote server should receive
 *            messages as <code>S</code> or some superclass thereof.
 * @param <R>
 *            the type of messages this <code>RemoteMessenger</code> will
 *            receive from the remote server. The corresponding
 *            <code>MessageService</code> on the remote server should send
 *            messages as <code>R</code> or some subclass thereof.
 */
public interface RemoteMessenger<S, R> {

    /**
     * Callback interface for <code>RemoteMessenger</code>. All callbacks will
     * be called on the main thread.
     * 
     * @param <M>
     *            message type to receive
     */
    public static interface Callback<M> {
        /**
         * The <code>RemoteMessenger</code> has connected to
         * <code>remoteDevice</code> and is able to send and receive messages
         * 
         * @param remoteDevice
         *            the <code>BluetoothDevice</code> this
         *            <code>RemoteMessenger</code> has connected to
         */
        public void onConnected(BluetoothDevice remoteDevice);

        /**
         * The <code>RemoteMessenger</code> has failed to connect to any
         * devices. Messages cannot be sent or received
         */
        public void onConnectionFailed();

        /**
         * The <code>RemoteMessenger</code> has disconnected from
         * <code>remoteDevice</code> and can no longer send or receive messages
         * 
         * @param remoteDevice
         *            the <code>BluetoothDevice</code> this
         *            <code>RemoteMessenger</code> was connected to
         */
        public void onDisconnected(BluetoothDevice remoteDevice);

        /**
         * The <code>RemoteMessenger</code> has received a message from the
         * remote server
         * 
         * @param message
         *            the <code>M</code> message received from the remote server
         */
        public void onReceiveMessage(M message);
    }

    /**
     * Sets callback destination. Implementations should use
     * {@link WeakReference WeakReferences} to ensure the
     * <code>RemoteMessenger</code> does not inadvertently keep the
     * <code>Callback</code> from being GCed
     * 
     * @param callback
     */
    public void setCallback(Callback<? super R> callback);

    /**
     * Checks if this <code>RemoteMessenger</code> is connected to a remote
     * server
     * 
     * @return <code>true</code> iff this <code>RemoteMessenger</code> can send
     *         and receive messages from a remote server
     */
    public boolean isConnected();

    /**
     * Attempts to connect to a remote server. Calls
     * {@link Callback#onConnected(BluetoothDevice) onConnected()} callback if
     * successful or {@link Callback#onConnectionFailed() onConnectionFailed()}
     * if unsuccessful
     */
    public void requestConnect();

    /**
     * Disconnects from the remote server. Calls
     * {@link Callback#onDisconnected(BluetoothDevice) onDisconnected()} when
     * finished
     */
    public void disconnect();

    /**
     * Sends a <code>S</code> message to the remote server. May be called from
     * the main thread
     * 
     * @param message
     *            the <code>S</code> message to send
     * @throws IllegalStateException
     *             If the <code>RemoteMessenger</code> is not connected
     */
    public void sendMessage(S message) throws IllegalStateException;

    public static class Factory {

        private Factory() {
        }

        /**
         * Gets an instance of <code>RemoteMessenger</code> that sends and
         * receives <code>String</code> messages<br/>
         * <br/>
         * <b>Note:</b> Messages are separated by <code>'\n'</code> characters
         * 
         * @param uuid
         *            a <code>UUID</code> shared between the remote server and
         *            this client. UUIDs can be obtained at <a
         *            href="http://www.uuidgenerator.net/">http
         *            ://www.uuidgenerator.net/</a> and instantiated using
         *            {@link UUID#fromString(String)}.
         * @return a <code>RemoteMessenger</code> that sends and receives
         *         <code>String</code> messages
         */
        public static RemoteMessenger<String, String> getStringRemoteMessenger(
                UUID uuid) {
            return new RemoteMessengerImpl<String, String>(uuid,
                    new StringMessageSender(), new StringMessageReader());
        }

        /**
         * Get an instance of <code>RemoteMessenger</code> that sends
         * <code>S</code> and receives <code>R</code> messages
         * 
         * @param uuid
         *            a <code>UUID</code> shared between the remote server and
         *            this client. UUIDs can be obtained at <a
         *            href="http://www.uuidgenerator.net/">http
         *            ://www.uuidgenerator.net/</a> and instantiated using
         *            {@link UUID#fromString(String)}.
         * @param sender
         *            the <code>MessageSender</code> providing the
         *            implementation for sending <code>S</code> messages
         * @param receiver
         *            the <code>MessageReceiver</code> providing the
         *            implementation for receiving <code>R</code> messages
         */
        public static <S, R> RemoteMessenger<S, R> getRemoteMessenger(
                UUID uuid, MessageSender<S> sender, MessageReceiver<R> receiver) {
            return new RemoteMessengerImpl<S, R>(uuid, sender, receiver);
        }

    }

}

RoboCop87
  • 825
  • 1
  • 8
  • 21
  • What is `RemoteMessenger`? – CommonsWare Feb 11 '14 at 21:53
  • @CommonsWare, I have included the interface file in my question. Thanks! – RoboCop87 Feb 12 '14 at 01:06
  • Hmmm... OK, I thought maybe that was from some other JAR or something. When you say that you "added three jar files to my buildpath", do you really mean that you copied them into `libs/` and did not change anything else? If so, that should be fine. If, instead, you really mean that you modified the Eclipse build path manually, that's wrong. – CommonsWare Feb 12 '14 at 01:09
  • @CommonsWare you are actually correct, it comes from one of the 3 jars that I have imported. That just happens to be the source code behind the jar. As far as how I added them, I right clicked on my project, selected Build Path, Add External Archive, and then I selected my jars – RoboCop87 Feb 12 '14 at 01:11

1 Answers1

1

As far as how I added them, I right clicked on my project, selected Build Path, Add External Archive, and then I selected my jars

That is not how you add JARs to an Android project. Please undo that, then create a libs/ directory in your project (if it does not already exist), then copy the JARs into libs/. That not only will automatically add them to your compile-time build path, but it will also have their contents be packaged in the APK file. Your approach solves the compile-time build path, but then the JARs are not packaged in the APK and are not available at runtime.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491