A few weeks ago I attended the Android developer conference, AnDevCon in San Francisco. One of the registration giveaways was this bluetooth remote control from Zeemote. It’s very small, and right now only a few games support it. I wanted to experiment with it a little bit, to see what else it might be useful for, and wanted to write a quick demo program. Since I learned about Fragments at the conference, I though I’d use this as an excuse to include them in my project.
One Fragment will display indicators for the 4 buttons on the controller. The other will represent the position of the joystick.
Zeemote does offer an SDK for interfacing with the device, and some code samples to help get started. You have to register by giving them your email address, then you can download the SDK.
Now, here’s my walkthrough for creating this demo app, using Eclipse.
Create a new project. I’ll call mine “ZeemoteDemo”, and create an activity called MainActivity. So I can support both my tablet and phone I’m going to target API 11 (version 3.0) but set the minimum SDK to API 8 (version 2.2).
Note: Fragments don’t officially show up in the Android SDK until Honeycomb, but they have made the Android Support Library (aka Android Compatibility Library) so you can make apps compatible back to Cupcake. You can see some more details and recommendations from the Android team on their Guide to Supporting Tablets and Handhelds.
In the project, create a folder called “libs”, and copy these files from the Zeemote SDK:
zc-strings-1.6.0.jar
ZControllerLib-android-1.6.0.jar
ZControllerLib-android-ui-1.6.0.jar
ZControllerLib-common-1.6.0.jar
To add the Android Support Library, copy android-support-v4.jar into the libs directory. It can be found in the android-sdk folder, under extras/android/support. Select all 5 files in the libs directory, right-click, and select Build Path > Add to Build Path. This will add a new folder called Referenced Libraries and allow you to use them in your code.
In AndroidManifest.xml, give the app permissions to use Bluetooth:
<uses-permission android:name="android.permission.BLUETOOTH"/>
If you want to be able to turn Bluetooth on and off from within the app, you should also add android.permission.BLUETOOTH_ADMIN but I’m not going to worry about that in this project.
In the layout file res/layout/main.xml, set up two fragments. Rather than specify how tall they are, I’m using the layout_weight property to set the joystick fragment to 90% of the screen and the button fragment to 10%.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <fragment class = "com.randomrobotics.zeemotedemo.JoystickFragment" android:id = "@+id/joyFragment" android:layout_width = "fill_parent" android:layout_height = "0dp" android:layout_weight = "9"/> <fragment class = "com.randomrobotics.zeemotedemo.ButtonFragment" android:id = "@+id/btnFragment" android:layout_width = "fill_parent" android:layout_height = "0dp" android:layout_weight = "1"/> </LinearLayout>
Now create the fragment classes:
ButtonFragment extends Fragment
JoystickFragment extends Fragment
Change MainActivity to extend FragmentActivity (this will require the import android.support.v4.app.FragmentActivity)
Create a layout for each fragment:
buttonfragmentlayout.xml is a LinearLayout (horizontal) with 4 ToggleButtons, set to equal width across the screen, to take up as much vertical space given (only 10% of the screen), and to have names that can be easily accessed.
Joystickfragmentlayout.xml is a LinearLayout, but we have to create a custom view to allow drawing on a background image. It’s actually pretty simple, we’re going to start with an ImageView and a crosshair background, but add some code so we can draw a circle to represent where the joystick position is.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.randomrobotics.zeemotedemo.JoystickView android:id = "@+id/joystickView" android:background="#CCCCCCCC" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>
Create a new class JoystickView that extends ImageView.
I added a simple crosshair to the background (you could also just put an image of a crosshair in the background), and a routine to draw a red dot where the joystick is. There are a multitude of ways this could be implemented, this is just one example. Based on the size of the View (found with getMeasuredWidth() and getMeasuredHeight() and the fact that the joystick position is scaled -100 to +100, I can easily put the target on the background. I’m also using these measured values frequently for the size of various items (like circle radius, etc) so that it scales well on different screen sizes.
public class JoystickView extends ImageView { private float x = 0, y = 0; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // draw the crosshair drawCrosshair(canvas); // draw the joystick position drawJoystick(canvas); } // Change the current joystick position (scaled from -100 to +100) public void updateJoystickPosition(int _x, int _y) { x = (float)_x; y = (float)_y; // invalidate the View so that it is redrawn this.invalidate(); } // Draw a red dot to indicate the joystick position private void drawJoystick(Canvas canvas) { // find the center point of the canvas float ctr_x = getMeasuredWidth() / 2; float ctr_y = getMeasuredHeight() / 2; float target_x = (x / 100) * ctr_x + ctr_x; float target_y = (y / 100) * ctr_y + ctr_y; // set the circle radius relative to the screen size float circle_radius = (float) ((0.05) * Math.min(ctr_x, ctr_y)); Paint p1 = new Paint(); p1.setColor(Color.RED); canvas.drawCircle(target_x, target_y, circle_radius, p1); } // Draw a crosshair on the background private void drawCrosshair(Canvas canvas) { // find the center point of the canvas float ctr_x = getMeasuredWidth() / 2; float ctr_y = getMeasuredHeight() / 2; // find the smaller dimension float dim = Math.min(ctr_x, ctr_y); Paint p2 = new Paint(); p2.setColor(Color.DKGRAY); p2.setStyle(Paint.Style.STROKE); p2.setStrokeWidth(4); // draw the inner circle canvas.drawCircle(ctr_x, ctr_y, (float) (0.25* dim), p2); // draw the outermost circle canvas.drawCircle(ctr_x, ctr_y, (float)(0.75 * dim), p2); // draw the horizontal and vertical lines canvas.drawLine(0, ctr_y, getMeasuredWidth(), ctr_y, p2); canvas.drawLine(ctr_x, 0, ctr_x, getMeasuredHeight(), p2); } }
Make the Fragment display the proper view by overriding onCreateView
For the Button fragment:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.buttonfragmentlayout, container); }
For the Joystick fragment
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.joystickfragmentlayout, container); }
You should be able to run this and test that the basic fragment functionality works. The red dot should be centered on the crosshair.
Now it’s time to actually talk to the Zeemote
in MainActivity, import the library com.zeemote.zc.*
Create a Controller object, and change your MainActivity declaration so that it implements IStatusListener, IJoystickListener, IButtonListener
In onCreate, add this as the listener for all three:
// Create the Zeemote Controller (1 is the first controller found) controller = new Controller(1); // Establish this class as the listener for the events controller.addStatusListener(this); controller.addJoystickListener(this); controller.addButtonListener(this);
Eclipse will show errors, prompting you to override buttonPressed, buttonReleased, joystickMoved, batteryUpdate, connected, and disconnected.
First, let’s establish connection to the device.
Zeemote has supplied a class to connect, called ControllerAndroidUi. This includes an Activity for enumerating the devices and connecting. Add this activity to the AndroidManifest:
<activity android:name=”com.zeemote.zc.ui.android.ControllerAndroidUi$Activity” />
From the MainActivity, create a ControllerAndroidUi called controllerUi. From the onCreate method, instantiate it and call startConnectionProcess(). (For now we’ll just do this in the onCreate method, but later it would be better to check to see if we’re already connected, and put it in onResume)
This starts monitoring the controller on a separate thread to report the events we’re listening for (buttonPressed, joystickMoved, etc)
In the joystickMoved event handler, you can easily get a scaled representation of the joystick position with this code:
Handler ViewUpdateHandler = new Handler(){ public void handleMessage(Message msg){ } };
HandleMessage is something that can get called across threads, and is how we will report activity from the controller thread to the main UI thread. We’ll send three types of messages: when the joystick moves, when a button is pressed, and when a button is released.
So, in the joystickMoved handler, we’ll create a message and include some identifying information about the type and pass along the X and Y values:
public void joystickMoved(JoystickEvent e) { int x = e.getScaledX(-100, 100); int y = e.getScaledY(-100, 100); Message m = new Message(); m.what = JOYSTICK_UPDATE; m.arg1 = x; m.arg2 = y; this.ViewUpdateHandler.sendMessage(m); }
The call to sendMessage will push the Message over to the main UI thread and cause ViewUpdateHandler.handleMessage to receive it.
We can handle button events in a similar fashion, sending a message type and the ID of the button:
@Override public void buttonPressed(ButtonEvent e) { Message m = new Message(); m.what = BUTTON_PRESSED; m.arg1 = e.getButtonID(); ViewUpdateHandler.sendMessage(m); } @Override public void buttonReleased(ButtonEvent e) { Message m = new Message(); m.what = BUTTON_RELEASED; m.arg1 = e.getButtonID(); ViewUpdateHandler.sendMessage(m); }
Now the handler looks like:
Handler ViewUpdateHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case (JOYSTICK_UPDATE): { int x = msg.arg1; int y = msg.arg2; jView.updateJoystickPosition(x, y); break; } case (BUTTON_PRESSED): { ToggleButton btn = (ToggleButton) findViewById(getButtonView(msg.arg1)); btn.setChecked(true); break; } case (BUTTON_RELEASED): { ToggleButton btn = (ToggleButton) findViewById(getButtonView(msg.arg1)); btn.setChecked(false); break; } } super.handleMessage(msg); } };
You’ll note a call to getButtonView(msg.arg1). This is just a subroutine to relate the Button ID (integer 0 through 3) to the corresponding ToggleButton View:
private int getButtonView(int buttonGameAction) { int id = 0; switch (buttonGameAction) { case (0): { id = R.id.btnA; break; } case (1): { id = R.id.btnB; break; } case (2): { id = R.id.btnC; break; } case (3): { id = R.id.btnD; break; } } return id; }
Before running, turn on the Zeemote and Pair it with your Android device (the bluetooth password is 0000).
Now, when you run the app, it will automatically run the ControllerAndroidUi connection activity, which will scan for devices, and can be set to AutoConnect to your Zeemote.
Then it should run, sending updates to the JoystickView to report joystick position, and lighting up the buttons whenever a button is pressed.
If the Android device changes orientation, the app will crash, because we put the connection dialog in the onCreate method, without ever disconnecting.
For a quick workaround, we’ll add some code to onPause and onResume to establish a connection only if it’s not already connected.
Since the connection takes place in a separate Activity, this Activity will get paused during the connection attempt, so we’ll use a boolean to record whether or not we’re currently trying to connect:
private boolean tryingToConnect = false; @Override protected void onPause() { super.onPause(); try { if (!tryingToConnect){ controller.disconnect(); } } catch (IOException e) { e.printStackTrace(); } } @Override protected void onResume() { super.onResume(); if (!controller.isConnected()){ tryingToConnect = true; controllerUi.startConnectionProcess(); } }
And we’ll return the tryingToConnect variable to false in the event handler for connected:
@Override public void connected(ControllerEvent e) { tryingToConnect = false; }
This isn’t a very efficient way of doing it, but at least it’ll keep the app running for now.
Great site Travis, curious why you don’t post anymore? We’d love to see some new stuff!
Very nice article. It would be really great if you could tell me where I could find these libraries:
zc-strings-1.6.0.jar
ZControllerLib-android-1.6.0.jar
ZControllerLib-android-ui-1.6.0.jar
ZControllerLib-common-1.6.0.jar
Thanks in advance.