Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • danc/MicroCART
  • snawerdt/MicroCART_17-18
  • bbartels/MicroCART_17-18
  • jonahu/MicroCART
4 results
Show changes
Showing
with 0 additions and 2359 deletions
src/vrpn/android_widgets/vrpn_library/res/drawable-mdpi/icon.png

2.51 KiB

<?xml version="1.0" encoding="utf-8"?>
<eu.ensam.ii.vrpn.views.DebugSensors
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="vertical"
android:id="@+id/DebugSensors"
>
<ToggleButton android:text="Save" android:id="@+id/BtnSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</ToggleButton>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns ="*">
<TableRow android:id="@+id/TableRow01"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:text="Sensor" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Rel." android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="X" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Y" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Z" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="L" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
</TableRow>
<TableRow android:id="@+id/TableRow01"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:text="Accelerator" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="" android:id="@+id/AccAccuracy" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="X" android:id="@+id/AccX" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Y" android:id="@+id/AccY" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Z" android:id="@+id/AccZ" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="L" android:id="@+id/AccL" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
</TableRow>
<TableRow android:id="@+id/TableRow01"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:text="AcceleratorN" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="" android:id="@+id/xx" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="X" android:id="@+id/AccXn" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Y" android:id="@+id/AccYn" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Z" android:id="@+id/AccZn" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="L" android:id="@+id/AccLn" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
</TableRow>
<TableRow android:id="@+id/TableRow01"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:text="Magnetic" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="" android:id="@+id/MagAccuracy" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="X" android:id="@+id/MagX" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Y" android:id="@+id/MagY" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Z" android:id="@+id/MagZ" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="L" android:id="@+id/MagL" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
</TableRow>
<TableRow android:id="@+id/TableRow01"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:text="Magnetic" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="" android:id="@+id/xx" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="X" android:id="@+id/MagXn" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Y" android:id="@+id/MagYn" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Z" android:id="@+id/MagZn" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="L" android:id="@+id/MagLn" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
</TableRow>
<TableRow android:id="@+id/TableRow01"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:text="Gyroscope" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="" android:id="@+id/GyrAccuracy" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="X" android:id="@+id/GyrX" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Y" android:id="@+id/GyrY" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Z" android:id="@+id/GyrZ" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
</TableRow>
<TableRow android:id="@+id/TableRow01"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:text="Orientation" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="" android:id="@+id/xxx" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="X" android:id="@+id/OriX" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Y" android:id="@+id/OriY" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Z" android:id="@+id/OriZ" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
</TableRow>
<TableRow android:id="@+id/TableRow01"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:text="Or. remapped" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="" android:id="@+id/xxx" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="X" android:id="@+id/OriRX" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Y" android:id="@+id/OriRY" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Z" android:id="@+id/OriRZ" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
</TableRow>
<TableRow android:id="@+id/TableRow01"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:text="Rotation" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="" android:id="@+id/xxx" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="X" android:id="@+id/RotX" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Y" android:id="@+id/RotY" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="Z" android:id="@+id/RotZ" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
</TableRow>
<TableRow android:id="@+id/TableRow01"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:text="inclination" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<TextView android:text="X" android:id="@+id/Inclination" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
</TableRow>
</TableLayout>
</eu.ensam.ii.vrpn.views.DebugSensors>
<?xml version="1.0" encoding="utf-8"?>
<!--
Layout for the composite widget VrpnSeekBar , backed by the class VrpnSeekBar
The widget includes
- a title line
- a line with min, max and current values of the seekbar
- the seekbar itself
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/vrpn_seekbar"
android:orientation="vertical"
>
<TextView
android:id="@+id/txtSeekBarTitle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Label">
</TextView>
<LinearLayout
android:id="@+id/lytSeekBarValues"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/txtSeekBarMinValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:gravity="bottom|left"
android:layout_weight="1"
android:text="min"
>
</TextView>
<TextView
android:id="@+id/txtSeekBarCurrentValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:textSize="20dip"
android:layout_weight="1"
android:text="Current">
</TextView>
<TextView
android:id="@+id/txtSeekBarMaxValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:gravity="bottom|right"
android:layout_weight="1"
android:text="max">
</TextView>
</LinearLayout>
<SeekBar
android:id="@+id/seekBarWidget"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</SeekBar>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="VrpnSeekBar">
<attr name="hideTitle" format="boolean"/>
<attr name="hideValues" format="boolean"/>
<attr name="defaultValue" format="float"/>
<attr name="minValue" format="float"/>
<attr name="maxValue" format="float"/>
<attr name="title" format="string"/>
<attr name="vrpnAnalog" format="integer"/>
</declare-styleable>
<declare-styleable name="VrpnRadioButton">
<!--
This is the definition : declare the format.
The next ones will be declarations, without the forat
-->
<attr name="vrpnButton" format="integer"/>
</declare-styleable>
<declare-styleable name="VrpnPressButton">
<attr name="vrpnButton"/>
</declare-styleable>
<declare-styleable name="VrpnToggleButton">
<attr name="vrpnButton"/>
</declare-styleable>
<declare-styleable name="VrpnSurface">
<attr name="vrpnAnalogX" format="integer"/>
<attr name="vrpnAnalogY" format="integer"/>
<attr name="leftX" format="float"/>
<attr name="rightX" format="float"/>
<attr name="topY" format="float"/>
<attr name="bottomY" format="float"/>
</declare-styleable>
<declare-styleable name="VrpnSpinner">
<attr name="vrpnAnalog"/>
</declare-styleable>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Hello World!</string>
<string name="app_name">vrpn_library</string>
<string name="DefaultIntentString">eu.ensam.ii.vrpn.STARTUI</string>
</resources>
package eu.ensam.ii.vrpn;
import org.json.JSONArray;
import org.json.JSONException;
import android.util.FloatMath;
/**
* A simple float quaternion
*
* @author Philippe
*
*/
class Quat {
Vec3f v = new Vec3f();
float w = 0.0f;
Quat() {
}
/**
* Computes a rotation quaternion that rotates <code>s</code> to <code>t</code>
*
* @param s
* @param t
*/
void setRotate(final Vec3f s, final Vec3f t) {
// from Real-Time Rendering, 3d ed p 79
final float e = s.dot(t);
final float k = FloatMath.sqrt(2.0f * (1.0f + e));
v = s.cross(t).mul(1.0f / k);
w = k / 2.0f;
}
/**
* Fill the first 4 elements of the JSON array with the values of this quaternion object
*
* Extraneous existing elements of the array are set to null
*
* @param jsonArray the JSON array to be filled
* @throws JSONException
*/
final void fill(JSONArray jsonArray) throws JSONException {
jsonArray.put(0, v.x);
jsonArray.put(1, v.y);
jsonArray.put(2, v.z);
jsonArray.put(3, w);
int i = 4;
while (i < jsonArray.length()) {
jsonArray.put(i++, null);
}
}
}
package eu.ensam.ii.vrpn;
import android.util.FloatMath;
import org.json.JSONArray;
import org.json.JSONException;
/**
* A basic 3-element float vector.
*
* Used for some computations with SensorEvent values
*
* @author Philippe
*
*/
class Vec3f {
public float x, y, z;
Vec3f() {
x = y = z = 0.0f;
}
Vec3f(float _x, float _y, float _z) {
set(_x, _y, _z);
}
void set(float _x, float _y, float _z) {
x = _x;
y = _y;
z = _z;
}
final float dot(final Vec3f v) {
return x*v.x + y*v.y + z*v.z;
}
final Vec3f cross(final Vec3f v) {
Vec3f w = new Vec3f(
y*v.z - z*v.y,
z*v.x - x*v.z,
x*v.y - y*v.x);
return w;
}
/**
* Computes a normalized vector
* @param u
* @return normalized vector value
*/
static Vec3f normalize(final Vec3f u) {
final float norm = u.norm();
Vec3f v = u;
v.x /= norm;
v.y /= norm;
v.z /= norm;
return v;
}
/**
* Normalizes this vector
*/
final void normalize() {
final float norm = norm();
x /= norm;
y /= norm;
z /= norm;
}
/**
* Computes the norm of this vector
*
* @return the float value of the norm of the object
*/
final float norm() {
return FloatMath.sqrt(x*x + y*y + z*z);
}
/**
* Fill the first 3 elements of the JSON array with the values of this Vec3f object
*
* Extraneous existing elements of the array are set to null
*
* @param jsonArray the JSON array to be filled
* @throws JSONException
*/
final void fill(JSONArray jsonArray) throws JSONException {
jsonArray.put(0, x);
jsonArray.put(1, y);
jsonArray.put(2, z);
int i = 3;
while (i < jsonArray.length()) {
jsonArray.put(i++, null);
}
}
Vec3f mul(float k) {
return new Vec3f(x*k, y*k, z*k);
}
}
package eu.ensam.ii.vrpn;
/**
* This class must be used as an {@link android.app.Application Application} for applications that use the Vrpn widgets.
* <p>
* An application that wants to use the Vrpn widgets must define an
* <em>application</em> tag with the name of this class in its manifest :
* </p>
* <ul>
* <li>edit your application <em>AndroidManifext.xml</em> and open the <em>Application</em> tab</li>
* <li>in the <em>Application toggle</em> section , check
* <em>Define an &lt;application&gt; tag ...</em></li>
* <li>in the <em>Application Attributes</em> section, type <em>eu.ensam.ii.vrpn.VrpnApplication</em> as the value of the
* <em>Name</em> attribute or use the <em>Browse...<em>button</li>
* </ul>
*
*/
public class VrpnApplication extends android.app.Application {
/*
* The VrpnApplication object holds and initializes an instance of VrpnClient.
*/
private VrpnClient _vrpnClient;
@Override
public void onCreate() {
_vrpnClient = VrpnClient.getInstance();
_vrpnClient.init(getApplicationContext());
}
}
package eu.ensam.ii.vrpn;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.net.Uri;
import android.util.Log;
/**
* Send updates button, analog and tracker updates to the JsonNet Vrpn server.
*
* <p>
* A application that uses the Vrpn widgets must initialize the unique instance
* of this class by calling {@link #setUri(Context, Uri) setUri} before the
* widgets are used.
* </p>
* <p>
* This class is not thread-safe. The methods <code>sendButton</code> and
* <code>sendAnalog</code> must be called from the main UI thread
* </p>
* <p>
* Currently the messages neither include a timestamp nor a sequence number
* </p>
*
* TODO Add timestamps and sequence numbers Add a method to allow sending TODO
* tracker updates from the widgets
*/
public class VrpnClient implements SensorEventListener {
private static VrpnClient _instance;
private static String LOG_TAG = "VrpnClient";
private SensorManager _sensorManager;
private boolean _isTiltTrackerEnabled = false;
private boolean _isRawGyroscopeEnabled = false;
private boolean _isRawAccelerometerEnabled = false;
private int _tiltTrackerSequenceNumber = 0;
private static final Vec3f Zpos = new Vec3f(0.0f, 0.0f, 1.0f);
float _values[] = new float[3];
/*
* Create the objects once to avoid repeated allocations in frequently
* called functions. As long as the VrpnClient methods that update the
* object are called from the same thread, no problem. This is currently the
* case, since the sensor updates are call on the main and the other updates
* are called from UI widgets callbacks, also on the main UI thread..
*/
private DatagramSocket _socket;
private DatagramPacket _packet;
JSONArray _jsonArray = new JSONArray();
private JSONObject _sensorJsonObject = new JSONObject();
private JSONObject _buttonJsonObject = new JSONObject();
private JSONObject _analogJsonObject = new JSONObject();
private Quat _quat = new Quat();
private Vec3f _v = new Vec3f();
/*
* These Ids must match those declared in vrpn_Tracker_Android.cpp
*/
private static final String MSG_KEY_TYPE = "type";
private static final String MSG_KEY_SEQUENCE_NUMBER = "sn";
private static final String MSG_KEY_TIMESTAMP = "ts";
private static final String MSG_KEY_TRACKER_ID = "id";
private static final String MSG_KEY_TRACKER_QUAT = "quat";
@SuppressWarnings("unused")
private static final String MSG_KEY_TRACKER_POS = "pos";
// Values for MSG_KEY_TRACKER_ID
private static final int MSG_SENSOR_ID_TILT = 0;
@SuppressWarnings("unused")
private static final int MSG_SENSOR_ID_RAW_ACCELEROMETER = 1;
@SuppressWarnings("unused")
private static final int MSG_SENSOR_ID_RAW_GYROSCOPE = 2;
private static final String MSG_KEY_BUTTON_ID = "button";
private static final String MSG_KEY_BUTTON_STATUS = "state";
private static final String MSG_KEY_ANALOG_CHANNEL = "num";
private static final String MSG_KEY_ANALOG_DATA = "data";
private final int MSG_TYPE_TRACKER = 1;
private final int MSG_TYPE_BUTTON = 2;
private final int MSG_TYPE_ANALOG = 3;
/**************************************************************************
*
* Create / Destroy
*
**************************************************************************/
/**
* Returns the unique VrpnClient instance
*/
public static VrpnClient getInstance() {
if (_instance == null) {
_instance = new VrpnClient();
}
return _instance;
}
/**
* Initializes the address and port of the VRPN server.
*
* <p>
* This method must be called before sending any update to the server.
* </p>
* Note that the widgets trigger updates when they they are displayed for
* the first time. So this method must be called before calling
* {@link android.app.Activity#setContentView(int) setContentView} in the
* main {@link android.app.Activity activity}
* {@link android.app.Activity#onCreate setContentView}
* <code>onCreate</code> method
*
* @param host
* a string that is a valid host name or IP address
* @param port
* the port number of the VRPN server
*/
public void setupVrpnServer(InetAddress hostAddress, int port) {
Log.d(LOG_TAG, "server:host = " + hostAddress + ":" + port);
/*
* Initialize the packet destination
*/
_packet.setAddress(hostAddress);
_packet.setPort(port);
}
/**
* Load the target server address and port from the shared preferences
*
* @param context
*/
void init(Context context) {
_sensorManager = (SensorManager) context
.getSystemService(Context.SENSOR_SERVICE);
enableOrDisableSensors();
}
/**
* Send an update to a Vrpn Button.
*
* This update will trigger a button callback on the application side
*
* @param buttonId
* the button number. Invalid button numbers are ignored on this
* side, but may trigger an error message on the Vrpn server
* @param buttonState
* the new status of the button
*/
public void sendButton(int buttonId, boolean buttonState) {
try {
_buttonJsonObject.put(MSG_KEY_TYPE, MSG_TYPE_BUTTON);
_buttonJsonObject.put(MSG_KEY_BUTTON_ID, buttonId);
_buttonJsonObject.put(MSG_KEY_BUTTON_STATUS, buttonState);
} catch (JSONException e1) {
e1.printStackTrace();
}
_sendObject(_buttonJsonObject);
}
/**
* Send an update to a Vrpn Analog channel.
*
* This update will trigger an analog callback on the application side
*
* @param channelId
* The channel number. Invalid channel numbers are ignored on
* this side but, they may trigger an error message on the Vrpn
* server
* @param channelValue
* the data that will be sent on the analog channel
*/
public void sendAnalog(int channelId, double channelValue) {
try {
_analogJsonObject.put(MSG_KEY_TYPE, MSG_TYPE_ANALOG);
_analogJsonObject.put(MSG_KEY_ANALOG_CHANNEL, channelId);
_analogJsonObject.put(MSG_KEY_ANALOG_DATA, channelValue);
} catch (JSONException e1) {
e1.printStackTrace();
}
_sendObject(_analogJsonObject);
}
/**
* Enables or disable the tilt tracker.
*
* The tilt tracker send a tracker update with a quaternion that rotates the
* "earth vertical up" vector to the +Z vector in device coordinates. See
* {@link android.hardware.SensorEvent SensorEvent} for a description of the
* device coordinate system. This quaternion can be used to retrieve the
* roll and pitch of the device.
*
* @param enable
* <code>true</code> enables the tilt tracker updates and
* <code>false</code> disable the tilt tracker updates
*/
public void enableTiltTracker(boolean enable) {
_isTiltTrackerEnabled = enable;
enableOrDisableSensors();
}
/**************************************************************************
*
* Sensorlistener implementation
*
**************************************************************************/
/**
* Called by the system when the sensor accuracy changes
*
* This method is empty and should not be called by widgets
*
*/
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
/**
* Called by the system when a sensor data changes.
*
* This method retrieves the Android sensor object. If an vrpn Tracker is
* active that involves this sensor object this method sends a Vrpn tracker
* update to the appropriate tracker. packages the sensor data into a JSON
* object then send the JSON object onto an UDP socket. The target socket
* endpoint configuration is configured by calling setupVrpnServer
*
*/
@Override
public void onSensorChanged(SensorEvent event) {
final int sensorType = event.sensor.getType();
// retrieve and update the adequate sequence number.
switch (sensorType) {
case Sensor.TYPE_ACCELEROMETER:
if (_isTiltTrackerEnabled) {
buildTiltTrackerObject(event.values, event.timestamp);
_sendObject(_sensorJsonObject);
}
if (_isRawAccelerometerEnabled) {
// To do
}
break;
case Sensor.TYPE_GYROSCOPE:
if (_isRawGyroscopeEnabled) {
// To do
}
break;
default:
return;
}
// Add other trackers here
// Send the data onto the network
}
/****************************************************************************
* Private methods
****************************************************************************/
private VrpnClient() {
try {
_socket = new DatagramSocket();
} catch (SocketException e) {
// Check this device IP network configuration
e.printStackTrace();
}
/*
* There is a single _packet instance to avoid numerous object creation
* in onSensorChanged()
*/
byte dummy[] = { 0 };
_packet = new DatagramPacket(dummy, dummy.length);
// Set the address with anything since _sendObject will be called before
// setUri has been called // why
try {
_packet.setAddress(InetAddress.getLocalHost());
} catch (UnknownHostException e) {
}
}
/**
* Send the string representation of a Json Object.
*
* @param[in] o the json object to send
*/
private void _sendObject(final JSONObject o) {
Log.d(LOG_TAG, o.toString());
// Send the data onto the network
_packet.setData(o.toString().getBytes());
try {
_socket.send(_packet);
} catch (SocketException e) {
// Local network not ready, no route to host, ...
;
} catch (IOException e) {
// TODO Auto-generated catch block : send a notification ?
e.printStackTrace();
}
}
/**
* Build a tilt tracker JSON object
*
* This method updates the _sensorJsonObject object
*
* @param x
* @param y
* @param z
* @param timestamp
*/
private void buildTiltTrackerObject(final float[] values, long timestamp) {
_v.set(values[0], values[1], values[2]);
_v.normalize();
/*
* When the device is at rest, the acceleration vector is -g (earth
* vertical up) in device coordinate system (Z out of the screen, that
* is Z up when the device lays on a table). Compute the rotation +Z -->
* -g
*/
_quat.setRotate(Zpos, _v);
// Build a Json message
try {
_sensorJsonObject.put(MSG_KEY_TYPE, MSG_TYPE_TRACKER);
_sensorJsonObject.put(MSG_KEY_SEQUENCE_NUMBER,
_tiltTrackerSequenceNumber++);
_sensorJsonObject.put(MSG_KEY_TIMESTAMP, timestamp);
_sensorJsonObject.put(MSG_KEY_TRACKER_ID, MSG_SENSOR_ID_TILT);
_quat.fill(_jsonArray);
_sensorJsonObject.put(MSG_KEY_TRACKER_QUAT, _jsonArray);
} catch (JSONException e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
}
}
/**
* Update the device sensor activation status.
*/
private void enableOrDisableSensors() {
final int rate = SensorManager.SENSOR_DELAY_GAME;
final Sensor accelerometer = _sensorManager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
_sensorManager.unregisterListener(this);
if (_isTiltTrackerEnabled) {
_sensorManager.registerListener(this, accelerometer, rate);
} else {
/*
* We are closing down the tilt tracker, Send an update with
* horizontal position
*/
buildTiltTrackerObject(new float[] { 0.0f, 0.0f, 1.0f },
System.nanoTime());
_sendObject(_sensorJsonObject);
}
}
}
package eu.ensam.ii.vrpn;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/**
* A {@link android.widget.Button Button} connected to a VRPN Button.
*
* <p>
* This button is linked to a Vrpn button. It sends sends a <code>true</code>
* update when its status changes to checked and a <code>false</code> update
* when its status changes to unchecked. The status of the VrpnPressButton
* changes when it is pushed, and also when it is released, unlike a
* {@link VrpnToggleButton}.
* </p>
*
* <p>
* In your <code>res/layout.xml</code>, include the widget in
* its parent layout :
* </p>
* <pre>
* &lt;eu.ensam.ii.VrpnPressButton
* android:layout_width="fill_parent"
* android:layout_height="wrap_content"
* app:vrpnButton="@id/MyButton"
* &#47&gt;
* </pre>
*
* <p>
* The root element of the layout must declare the following line just after the
* <code>xmlns:android ... </code>line :
*
* <pre>
* xmlns:app="http://schemas.android.com/apk/res/your.package.name.here"
* </pre>
*
* </p>
*
* <p>
* <strong>Custom XML attributes :</strong>
* </p>
* <ul>
* <li><code>app:vrpnButton</code> : the id of the Vrpn button that will receive
* updated from this widget. This value can be a literal or a resource Id that
* can be kept in a <a href="package-summary.html#vrpnids">separate Vrpn Id
* list</a></li>
* </ul>
*
*/
public class VrpnPressButton extends android.widget.Button implements
android.view.View.OnTouchListener {
private String TAG = "VrpnPressButton";
private int _vrpnButtonId = 0;
public VrpnPressButton(Context context) {
super(context);
init(null);
}
public VrpnPressButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public VrpnPressButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
/**
* Widget initialization
*
* @param attrs
* the AttributeSet that is required to access to the XML custom
* attributes
*/
private void init(AttributeSet attrs) {
// this.onTouch() will be called when this widget is touched
setOnTouchListener(this);
/*
* Get The Vrpn button id from the xml app:vrpnButton attribute The
* custom attribute is declared is in res/values/attrs.xml
*/
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.VrpnRadioButton);
_vrpnButtonId = a.getInt(R.styleable.VrpnRadioButton_vrpnButton, 0);
a.recycle();
// Send a "false" since a PressButton is always initially released
Log.d(TAG, "ctor init : button " + _vrpnButtonId + " released ");
VrpnClient.getInstance().sendButton(_vrpnButtonId, false);
}
/**
* Called when a touch event is dispatched to this VrpnPressButton.
*
* This method sends a <code>true</code> update when the button is pressed
* and a <code>false</code> update when the button is released. when it is
* released. No event is send while the button is pressed.
*
* @see android.view.View.OnTouchListener#onTouch(android.view.View,
* android.view.MotionEvent)
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
final int action = event.getAction();
/*
* Avoid unnecessary processing for irrelevant events Nothing is sent
* while the button is hold down (event=ACTION_MOVE)
*/
if (action != MotionEvent.ACTION_DOWN
&& action != MotionEvent.ACTION_UP
&& action != MotionEvent.ACTION_CANCEL) {
// Mark the event has handled, so it is not be handled by
// ToggleButton::onClick
return true;
}
/*
* Send true when the button is pressed and false when it is released.
*/
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, " button " + _vrpnButtonId + " pressed ");
VrpnClient.getInstance().sendButton(_vrpnButtonId, true);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
Log.d(TAG, " button " + _vrpnButtonId + " released ");
VrpnClient.getInstance().sendButton(_vrpnButtonId, false);
break;
default:
break;
}
// Mark the event has handled, so it is not be handled by
// ToggleButton::onClick
return false;
}
}
package eu.ensam.ii.vrpn;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.CompoundButton;
import android.widget.RadioGroup;
import android.widget.CompoundButton.OnCheckedChangeListener;
/**
* A {@link android.widget.RadioButton RadioButton} that updates a Vrpn Button channel.
*
* <p>
* The <code>VrpnRadioButton</code> is to be included inside a {@link RadioGroup}. The
* <code>VrpnRadioButton</code> will send a <code>true</code> when its state changes to
* checked and a <code> false</code> update when its state changes to unchecked.
* Checking another button of the <code>RadioGroup</code> will uncheck the <code>RadioButton</code>.</p>
* <p>in a <code>RadioGroup</code>, it is possible to include :
* <ul>
* <li>a <code>VrpnRadiobutton</code> and a <code>RadioButton</code>. Checking the <code>RadioButton</code> will uncheck the <code>VrpnRadioButton</code> that will send an update</li>
* <li>several <code>VrpnRadioButton</code> widgets. In this case the checked and the unchecked widgets will send updates</li>
* </ul>
* </p>
* <p>
* In your layout, declare the following :
* </p>
*
* <pre>
* &lt;RadioGroup
* android:id="@+id/RadioGroup01"
* &gt;
* &lt;eu.ensam.ii.vrpn.VrpnRadioButton
* android:text="This way"
* android:checked="true"
* app:vrpnButton="@id/VrpnButtonThisWay"
* /&gt;
* &lt;RadioButton
* android:text="other Way"
* android:checked="false"
* /&gt;
* &lt;/RadioGroup&gt;
* </pre>
*
* <p>
* The root element of the layout must declare the following line just after the
* <code>xmlns:android ... </code>line :
*
* <pre>
* xmlns:app="http://schemas.android.com/apk/res/your.package.name.here"
* </pre>
*
* </p>
*
* <p>
* <strong>Custom XML attributes :</strong>
* </p>
* <ul>
* <li><code>app:vrpnButton</code> : the id of the Vrpn button that will receive
* updated from this widget. This value can be a literal or a resource Id that
* can be kept in a <a href="package-summary.html#vrpnids">separate Vrpn Id
* list</a></li>
* </ul>
*/
public class VrpnRadioButton extends android.widget.RadioButton implements
OnCheckedChangeListener {
private static final String TAG = "VrpnRadioButton";
int _vrpnButtonId = 0;
public VrpnRadioButton(Context context) {
super(context);
init(null);
}
public VrpnRadioButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public VrpnRadioButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
private void init(AttributeSet attrs) {
setOnCheckedChangeListener(this);
// get The Vrpn button id from the xml app:vrpnButton attribute
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.VrpnRadioButton);
_vrpnButtonId = a.getInt(R.styleable.VrpnRadioButton_vrpnButton, 0);
a.recycle();
// Trigger an initial Vrpn update
Log.d(TAG, "ctor init " + _vrpnButtonId + " : " + isChecked());
VrpnClient.getInstance().sendButton(_vrpnButtonId, isChecked());
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Log.d(TAG, "onCheckChanged " + _vrpnButtonId + " : " + isChecked);
// Send the update through the Vrpn service
VrpnClient.getInstance().sendButton(_vrpnButtonId, isChecked());
}
}
package eu.ensam.ii.vrpn;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
/**
* A {@link android.widget.SeekBar SeekBar} that updates a Vrpn Analog channel.
*
* <p>
* The seekbar include a line with the min, max and current
* values of the seekbar, and the seekbar itself.
*
* The seekbar progress is converted into a value in the
* <code>app:minValue</code> .. <code>app:maxValue</code>
* range. This value is then sent out to the Vrpn Analog channel whose
* id is specified in the <code>app:vrpnAnalog</code> attribute. The <code>VrpnSeekBar</code>
* moves in increments that are one hundredth of the value range.
* </p>
* <p>
* In your layout, declare the following (note the app:attributes)
* </p>
*
* <pre>
* &lt;eu.ensam.ii.vrpn.VrpnSeekBar
* android:layout_width="fill_parent"
* android:layout_height="wrap_content"
* app:minValue="-3"
* app:minValue="3"
* app:minValue="0"
* app:vrpnAnalog="@id/MyVrpnAnalogId"
* /&gt;
* </pre>
*
* <p>
* The root element of the layout must declare the following line just after the
* <code>xmlns:android ... </code>line :
*
* <pre>
* xmlns:app="http://schemas.android.com/apk/res/your.package.name.here"
* </pre>
*
* </p>
*
* <p>
* <strong>Custom XML attributes :</strong>
* </p>
* <ul>
* <li><code>app:vrpnAnalog</code> : the id of the Vrpn analog that will receive
* updates from this widget. This value can be a literal or a resource Id that
* can be kept in a <a href="package-summary.html#vrpnids">separate Vrpn Id
* list</a></li>
* <li><code>app:minValue</code> : the numeric value mapped onto the left
* position of the VrpnSeekbar</li>
* <li><code>app:maxValue</code> : the numeric value mapped onto the left
* position of the VrpnSeekbar</li>
* <li><code>app:defaultValue</code> : the initial numeric value of the VrpnSeekbar;</li>
* <li><code>app:hideValues</code> : <code>true</code> will cause the line with
* the minimum, maximum and current values to be hidden, <code>false</code> will cause
* the line to be displayed (default)</li>
* </ul>
*
* @author Philippe
*
*/
public class VrpnSeekBar extends android.widget.LinearLayout implements
OnSeekBarChangeListener {
// Partly adapted from
// http://www.codeproject.com/KB/android/seekbar_preference.aspx
private static final String TAG = "VrpnSeekBar";
private static final float DEFAULT_MIN_VALUE = 0.0f;
private static final float DEFAULT_MAX_VALUE = 100.0f;
private float _minValue;
private float _maxValue;
private float _currentValue;
private TextView _currentValueText;
private CharSequence _title;
private int _vrpnAnalogId;
private SeekBar _seekBar;
private boolean _hideValues;
public VrpnSeekBar(Context context) {
this(context, null);
}
public VrpnSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
// Get the custom attribute values
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.VrpnSeekBar);
_minValue = a
.getFloat(R.styleable.VrpnSeekBar_minValue, DEFAULT_MIN_VALUE);
_maxValue = a
.getFloat(R.styleable.VrpnSeekBar_maxValue, DEFAULT_MAX_VALUE);
_currentValue = a.getFloat(R.styleable.VrpnSeekBar_defaultValue,
(_maxValue + _minValue) / 2.0f);
_title = a.getText(R.styleable.VrpnSeekBar_title);
_vrpnAnalogId = a.getInt(R.styleable.VrpnSeekBar_vrpnAnalog, 0);
boolean hideTitle = a.getBoolean(R.styleable.VrpnSeekBar_hideTitle, false);
_hideValues = a.getBoolean(R.styleable.VrpnSeekBar_hideValues, false);
a.recycle();
// Load the view layout
LayoutInflater li = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
li.inflate(R.layout.vrpn_seekbar, this, true);
// Load or hide the title
TextView titleView = (TextView) findViewById(R.id.txtSeekBarTitle);
if (hideTitle) {
titleView.setVisibility(GONE);
} else {
titleView.setText(_title);
}
// Load or hide the layout with the values
if (_hideValues) {
((LinearLayout) findViewById(R.id.lytSeekBarValues))
.setVisibility(GONE);
} else {
((TextView) findViewById(R.id.txtSeekBarMinValue)).setText(Float
.toString(_minValue));
((TextView) findViewById(R.id.txtSeekBarMaxValue)).setText(Float
.toString(_maxValue));
_currentValueText = (TextView) findViewById(R.id.txtSeekBarCurrentValue);
}
// Setup the SeekBar
_seekBar = (SeekBar) findViewById(R.id.seekBarWidget);
_seekBar.setOnSeekBarChangeListener(this);
// This triggers OnProgressChanged() and a VrpnUpdate
int currentValue = (int) (100.0 * (_currentValue - _minValue) / (_maxValue - _minValue));
_seekBar.setProgress(currentValue);
}
/**
* Called when the progress of the SeekBar changes
*
* Compute the value from the progress and send a VrpnUpdate to the analog
* channel that is associated to the widget with the custom attribute
* app:vrpnAnalog
*/
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// progress is in range 0..100 : map to min..max
_currentValue = (_maxValue - _minValue) / 100 * progress + _minValue;
if (!_hideValues) {
_currentValueText.setText(String.format("%.2f", _currentValue));
}
Log.d(TAG, "onProgressChanged " + _vrpnAnalogId + " progress/value: "
+ progress + " " + _currentValue);
// Send the vrpn update
// Note : The line below throws an exception in the Eclipse GUI preview
VrpnClient.getInstance().sendAnalog(_vrpnAnalogId, _currentValue);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
package eu.ensam.ii.vrpn;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
/**
* A {@link android.widget.Spinner Spinner} that updates a Vrpn Analog channel.
*
* <p>
* Picking a value from the spinner will update a Vrpn Analog channel with the
* position of the item that was picked in the item list. On the application side, the value will be delivered as a <em>double</em>.
* </p>
* <p>
* In your layout, declare the following :
* </p>
*
* <pre>
* &lt;eu.ensam.ii.vrpn.VrpnSpinner
* android:layout_width="fill_parent"
* android:layout_height="wrap_content"
* android:prompt="@string/the_prompt"
* android:entries="@array/the_array"
* app:vrpnAnalog="@id/VrpnAnalogMySpinner"
* /&gt;
* </pre>
*
* In a resource XML file located in <em>res/values</em>, define the prompt that
* will be displayed in the choice dialog, as well as the list of values to choose from.</p><
*
* <pre>
* &lt;string name="the_prompt"&gt;Message displayed when the choice dialog is opened&lt;/string&gt;
* &lt;string-array name="the_array"&gt;
* &lt;item&gt;"Mars"&lt;/item&gt;
* &lt;item&gt;"Venus"&lt;/item&gt;
* &lt;/string-array&gt;
* </pre>
* <p>
* The root element of the layout must declare the following line just after the
* <code>xmlns:android ... </code>line :</p>
* <pre>
* xmlns:app="http://schemas.android.com/apk/res/your.package.name.here"
* </pre>
* <p>
* <strong>Custom XML attributes :</strong>
* </p>
* <ul>
* <li><code>app:vrpnAnalog</code> : the id of the Vrpn analog that will receive
* updated from this widget. This value can be a literal or a resource Id that
* can be kept in a <a href="package-summary.html#vrpnids">separate Vrpn Id
* list</a></li>
* </ul>
*/
public class VrpnSpinner extends android.widget.Spinner implements OnItemSelectedListener {
private static final String TAG = "VrpnSpinner";
private int _vrpnAnalogId;
public VrpnSpinner(Context context) {
super(context);
init(null);
}
public VrpnSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
private void init(AttributeSet attrs) {
// Get the custom attribute values
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.VrpnSpinner);
_vrpnAnalogId = a.getInt(R.styleable.VrpnSpinner_vrpnAnalog, 0);
a.recycle();
setOnItemSelectedListener(this);
setSelection(0);
// Trigger a Vrpn update to get a known initial state
Log.d(TAG, "ctor " + _vrpnAnalogId + " position " + 0);
VrpnClient.getInstance().sendAnalog(_vrpnAnalogId, 0);
}
/**
*
* @see android.widget.AdapterView.OnItemSelectedListener#onItemSelected(android.widget.AdapterView, android.view.View, int, long)
*/
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position,
long id) {
Log.d(TAG, "onItemSelected " + _vrpnAnalogId + " position" + position);
// Send the vrpn update
// Note : The line below throws an exception in the Eclipse GUI preview
VrpnClient.getInstance().sendAnalog(_vrpnAnalogId, position);
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
}
}
package eu.ensam.ii.vrpn;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
/**
* This {@link android.view.SurfaceView SurfaceView} updates two Vrpn Analog channels with the coordinates of the
* Surface point that is touched.
*
* <p>
* Updates are sent as long astouch events occur within the surface. The
* X channel range from
* <code>app:leftX</code> to <code>app:rightX</code>,
* and the Y channel values range from <code>app:topY</code> to
* <code>app:bottomY</code>.
* </p>
*
* <p>
* In your layout, declare the following (note the <code>app:</code> attributes)
* </p>
*
* <pre>
* &lt;eu.ensam.ii.vrpn.VrpnSurface
* android:layout_width="fill_parent"
* android:layout_height="fill_parent"
* app:leftX="-1"
* app:rightX="1"
* app:topY="0"
* app:bottomY="1"
* app:vrpnAnalogX="@id/MyVrpnAnalogX"
* app:vrpnAnalogY="@id/MyVrpnAnalogY"
* /&gt;
* </pre>
*
* <p>
*
* <p>
* The root element of the layout must declare the following line just after the
* <code>xmlns:android ... </code>line :
* <pre>
* xmlns:app="http://schemas.android.com/apk/res/your.package.name.here"
* </pre>
* </p>
* <p>
* <strong>Custom XML attributes :</strong>
* </p>
* <ul>
* <li><code>app:vrpnAnalogX</code> : the id of the Vrpn analog that will
* receive the X position of the touch event from this widget. The update value
* sent to the Vrpn analog ranges from the <code>app:leftX</code> value to the
* <code>app:rightX</code> value.</li>
* <li><code>app:vrpnAnalogY</code> : the id of the Vrpn analog that will
* receive the Y position of the touch event from this widget. The update value
* sent to the Vrpn analog ranges from the <code>app:bottomY</code> value to the
* <code>app:topX</code> value.</li>
* <li><code>app:leftX</code> : the numeric value mapped onto the left border of
* the surface.</li>
* <li><code>app:rightX</code> : the numeric value mapped onto the right border
* of the surface. May be greater or lower than <code>app:leftX</code>.</li>
* <li><code>app:bottomY</code> : the numeric value mapped onto the top border
* of the surface</li>
* <li><code>app:topY</code> : the numeric value mapped onto the top border of
* the surface. May be greater or lower than <code>app:bottomY</code></li>
* </ul>
*
* The <code>app:vrpnAnalogX</code> and <code>app:vrpnAnalogY</code> ids can be
* literals or a resource Ids. These resource ids may be kept in a <a
* href="package-summary.html#vrpnids">separate Vrpn Id list</a>
*
*/
public class VrpnSurface extends android.view.SurfaceView implements
OnTouchListener {
protected String TAG = "VrpnSurface";
int _vrpnAnalogXId = 0;
int _vrpnAnalogYId = 0;
float _leftX, _rightX, _topY, _bottomY;
public VrpnSurface(Context context) {
super(context);
init(null);
}
public VrpnSurface(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public VrpnSurface(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
/**
* Perform initialization that is common to the constructors
*
* @param attrs
*/
private void init(AttributeSet attrs) {
setOnTouchListener(this);
// get The Vrpn button id from the xml app:vrpnButton attribute
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.VrpnSurface);
_vrpnAnalogXId = a.getInt(R.styleable.VrpnSurface_vrpnAnalogX, 0);
_vrpnAnalogYId = a.getInt(R.styleable.VrpnSurface_vrpnAnalogY, 0);
_leftX = a.getFloat(R.styleable.VrpnSurface_leftX, 0.0f);
_rightX = a.getFloat(R.styleable.VrpnSurface_rightX, 1.0f);
_topY = a.getFloat(R.styleable.VrpnSurface_topY, 0.0f);
_bottomY = a.getFloat(R.styleable.VrpnSurface_bottomY, 1.0f);
a.recycle();
// Start at origin
// TODO start at the middle
Log.d(TAG, "ctor init : " + _vrpnAnalogXId + " " + _vrpnAnalogYId);
VrpnClient.getInstance().sendAnalog(_vrpnAnalogXId, 0);
VrpnClient.getInstance().sendAnalog(_vrpnAnalogYId, 0);
}
/**
* Called when a touch event is dispatched to this widget.
*
* <p>
* If the touch event occurs within the surface of the view, two analog Vrpn
* updates are send for the X and y position. Updates are sent when
* ACTION_DOWN, ACTION_MOVE, and ACTION_UP are received. the event history
* is ignored.
* </p>
*
* @see android.view.View.OnTouchListener#onTouch(View, MotionEvent)
* @see android.view.MotionEvent
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
final int action = event.getAction();
if (action == MotionEvent.ACTION_OUTSIDE) {
return false;
}
final float x = event.getX();
final float y = event.getY();
final float w = getWidth();
final float h = getHeight();
if (x < 0 || x > w || y < 0 || y > h) {
// dragging the touch point may trigger values outside the view
// dimensions
return false;
}
/*
* event reports absolute View X/Y coordinates, convert to the
* appropriate bounds then send the updates
*/
final float vrpnX = (x / w) * (_rightX - _leftX) + _leftX;
final float vrpnY = (y / h) * (_bottomY - _topY) + _topY;
Log.d(TAG, "touch action x/y = " + action + " " + vrpnX + "/" + vrpnY);
VrpnClient.getInstance().sendAnalog(_vrpnAnalogXId, vrpnX);
VrpnClient.getInstance().sendAnalog(_vrpnAnalogYId, vrpnY);
return true;
}
}
package eu.ensam.ii.vrpn;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
/**
* A {@link android.view.ToggleButton ToggleButton} connected to a Vrpn Button.
*
* <p>
* This button sends a true update when its status changes to checked and false
* when its status changes to unchecked. Unlike a
* {@link VrpnPressButton}, the status of this widget changes when it is
* pressed, but not when it is released,
* </p>
*
* <p>
* In your layout XML file, include the following into a container widget :
* <pre>
* &lt;eu.ensam.ii.vrpn.VrpnButton
* app:vrpnButton="@id/MyButton"
* /&gt;
* </pre>
* <p>
* The root element of the layout must declare the following line just after the
* <code>xmlns:android ... </code>line :
* <pre>
* xmlns:app="http://schemas.android.com/apk/res/your.package.name.here"
* </pre>
*
* </p>
*
* <p>
* <strong>Custom XML attributes :</strong>
* </p>
* <ul>
* <li><code>app:vrpnButton</code> : the id of the Vrpn button that will receive
* updated from this widget. This value can be a literal or a resource Id that
* can be kept in a <a href="package-summary.html#vrpnids">separate Vrpn Id
* list</a></li>
* </ul>
*/
public class VrpnToggleButton extends android.widget.ToggleButton implements
OnCheckedChangeListener {
private static final String TAG = "VrpnToggleButton";
private int _vrpnButtonId;
public VrpnToggleButton(Context context) {
super(context);
init(null);
}
public VrpnToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public VrpnToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
private void init(AttributeSet attrs) {
setOnCheckedChangeListener(this);
// get The Vrpn button id from the xml app:vrpnButton attribute
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.VrpnRadioButton);
_vrpnButtonId = a.getInt(R.styleable.VrpnToggleButton_vrpnButton, 0);
a.recycle();
// Trigger an initial Vrpn update
Log.d(TAG, "ctor init " + _vrpnButtonId + " : " + isChecked());
VrpnClient.getInstance().sendButton(_vrpnButtonId, isChecked());
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Log.d(TAG, "onCheckedChanged " + _vrpnButtonId + " : " + isChecked);
VrpnClient.getInstance().sendButton(_vrpnButtonId,
buttonView.isChecked());
}
}
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<!--
Edit the documentation here, then copy the contents of the <body> tag into package-info.java (without the tags) -->
<body>
This package include a set of widgets that are bound to a Vrpn server
buttons, analogs and trackers.
<h3>Create a simple application that uses the widgets</h3>
<p>
The following chapters describe how to create a simple application
with a {@link eu.ensam.ii.vrpn.VrpnToggleButton VrpnToggleButton} and
a {@link eu.ensam.ii.vrpn.VrpnSeekBar VrpnSeekBar}. An example
application project that contains these widgets and more can be found
in the <em>example_app</em> directory.
</p>
<h4>Create and configure the project</h4>
<p>
Refer to any Android documentation for instructions about how to
set-up the Android SDK and the Eclipse plugin. Do not forget to setup
the Android SDK in any new workspace (<em>Window</em> &gt; <em>Preferences</em>,
then go to the <em>Android</em> tab and configure the SDK location).
</p>
<p>
Import the <em>Vrpn_library</em> project into your Eclipse workspace.
Open the Android section of the project property page and verify that
<em>Is library</em> checked. If the build complains about "Android
requires .class compatibility set to 5.0", you need first to select
the project, right-click > <em>Properties</em> &gt; <em>Android
tools</em> &gt; <em>Fix project properties</em>, then open the project
properties, go to the <em>Java compiler</em> section, then deselect
<em>enable project specific settings</em>. You may need to clean and
rebuild (<em>Project</em> &gt; <em>Clean</em>) several times before
the project shows no errors.
</p>
<p>
Create a new Android project : <em>File</em> > <em>New</em> > <em>Other
...</em> , <em>Android</em> &gt; <em>Android Project</em>. On the first
page of the wizard, check <em>Create activity</em> and enter any name
you like for the main activity (for instance <em>MainActivity</em>).
</p>
<p>
Add the library to your project : open the properties of the project,
go to the <em>Android</em> page. In the <em>Library</em> pane, click <em>Add</em>
then select <em>vrpn_library</em>, apply the changes and close the
window. At this point, the Eclipse <em>Package Explorer</em> view of
your project should contain a <em>vrpn_library_src</em> folder and the
<em>gen</em> folder should contain a package entry named <em>eu.ensam.ii.vrpn</em>.
</p>
<p>
Open the
<code>AndroidManifest.xml</code>
file. On the <em>Application</em> tab, check <em>Define an
&lt;Application&gt; tag in the AndroidManifest.xml</em>. Then click on the
<em>Browse</em> button that is on the right of <em>Name</em> and
select <em>VrpnApplication</em>.
</p>
<h4>Modify the main activity</h4>
<p>Edit the main activity to define the binding to the Vrpn server
when the activity is created. The method {@link
android.app.Activity#onCreate onCreate} is called when the Activity is
created. The default version of this method contain only the first and
last lines :</p>
<pre>
super.onCreate(savedInstanceState);
....
setContentView(R.layout.main);
</pre>
<p>We need to define the binding with the Vrpn server. The method
becomes :</p>
<pre>
&#64;Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InetAddress vrpnServerAddress = null;
try {
// Replace the IP address
vrpnServerAddress = InetAddress.getByName("192.168.173.1");
} catch (UnknownHostException e) {
}
final int vrpnServerPort = 7777;
VrpnClient.getInstance().setupVrpnServer(vrpnServerAddress, vrpnServerPort);
setContentView(R.layout.main);
}
</pre>
<a name="vrpnids"></a>
<h4>Create a Vrpn Ids list</h4>
<p>
Before laying out your Vrpn widgets in your application, you should
create an XML configuration file that contains the IDs of the Vrpn
buttons and analog that you will be using. This allows you to keep a
unique list of Vrpn IDs, making it easier to avoid duplicate values
and reference this list from your application. Create this file with <em>File</em>
> <em>New</em> > <em>Other</em>, then <em>Android </em> > <em>Android
XML File</em>. In XML file dialog, type any file name you like, for
instance <em>vrpn.xml</em>, then select <em>Values</em> as the type of
resource to create.
</p>
<p>
Edit the resulting file, found at <em>res/values/vrpn.xml</em> to add
the numbers of the Vrpn Buttons and Analogs that you will be using.
</p>
<pre>
&lt;resources&gt;
&lt;item type="id" format="integer" name="VrpnAnalogLightIntensity"&gt;0&lt;/item&gt;
&lt;item type="id" format="integer" name="VrpnButtonLightsOn"&gt;0&lt;/item&gt;
&lt;/resources&gt;
</pre>
<p>
Note that <em>type</em> must be <em>id</em> and <em>format</em> must
be <em>integer</em>, but <em>name</em> can be anything you like.
</p>
<h4>Add your widgets to the main layout</h4>
<p>Say we want an Android application to control the lighting in
our VR application : turn the light on or off and also control the
light intensity. We will create an application with a {@link
eu.ensam.ii.vrpn.VrpnToggleButton VrpnToggleButton} as the light
switch and a {@link eu.ensam.ii.vrpn.VrpnSeekBar VrpnSeekBar} as a
light intensity controller.</p>
<p>
By default, the layout of the main Activity is described in <em>res/layout/main.xml</em>
and just displays <em>Hello</em>. Edit this file. The XML code looks
is something like :
</p>
<pre>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
&gt;
&lt;TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/&gt;
&lt;/LinearLayout&gt;
</pre>
<p>
The root element,
<code>LinearLayout</code>
, is a container for the other widgets, currently contains a
<code>TextView</code>
.
<p>
The basics about XML layouts are presented in <a
href="http://developer.android.com/guide/topics/ui/declaring-layout.html">Declaring
layout</a> SDK page.
</p>
<p>
Before the end tag
<code>&lt;/LinearLayout&gt;</code>
, include a {@link eu.ensam.ii.vrpn.VrpnToggleButton VrpnToggleButton}
to turn the lights on or off :
<pre>
&lt;eu.ensam.ii.vrpn.VrpnToggleButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:vrpnButton="@id/VrpnButtonLightsOn"
/&gt;
</pre>
The Vrpn widgets use so-called
<em>custom XML properties</em> , such as
<em>app:vrpnButton</em> . This custom property allows you to specify
the id of the target Vrpn button in the XML layout file rather than in
Java code. However, the use of this custom property requires the
addition of an XML namespace tag so that the build system can find
then. After the
<em>xmlns:android</em> line, add the following line :
<pre>
xmlns:app="http://schemas.android.com/apk/res/your.application.package.name.here"
</pre>
Different attributes exist for each widgets and the whole list is shown
when you unfold
<em>gen/eu/ensam.ii.vrpn/R.java/R/attr</em> in the Eclipse Package
View. Your layout is now ready for your to include the Vrpn widgets.
Now save the file to build your project. If there are build errors,
check the following :
<ul>
<li>you have added the <code>xmlns::app</code> line in the root
layout</li>
<li>the spelling of the <code>app:</code> attribute</li>
<li>the value of the Vrpn attributes</li>
<li>the spelling of the widget name</li>
</ul>
<p>
In most cases, the <em>Preview</em> tab of the XML layout is not
displayed properly as soon as you include one of the Vrpn widgets.
This is a known problem for which not solution has currently been
found. It prevents you from previewing the layout it wil be displayed
correctly in the application, either on a device or in the emulator.
</p>
<p>Now add a {@link eu.ensam.ii.vrpn.VrpnSeekBar VrpnSeekBar} to
control the light intensity :</p>
<pre>
&lt;eu.ensam.ii.vrpn.VrpnSeekBar
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:minValue="0"
app:maxValue="255"
app:defaultValue="128"
app:vrpnAnalog="@id/VrpnAnalogLightIntensity"
/&gt;
</pre>
Your Android application is now ready to run. Upload on a device and
run. There is not much to seen for the time being. However, if you run
a network sniffer on a machine connected to the same network as the
Android device, you should see UDP packets flowing out of the device.
If you are using the emulator instead of an actual device, see the
<a
href="
http://developer.android.com/guide/developing/devices/emulator.html#emulatornetworking">Emulator
networking</a> SDK page for the correct network setup.
<h4>Test your application</h4>
In order to test your application, you need a VRPN server built with
VRPN_USE_JSONNET. See the
<code>README.jsoncpp</code>
file for instructions to build this server. When you get this server
running, the easiest way to test the Android application is to use the
<code>vrpn_print_devices</code>
application provided with Vrpn. Type the command :
<pre>
vrpn_print_devices Jsonnet@localhost
</pre>
The command should then report the values of the Button and SeekBar as
they are changed.
<h3>Beyond the basics</h3>
<h4>Application with multiple tabs</h4>
If you want to create an application with more widgets that can be
placed on a single screen, using a {@link android.app.TabActivity
TabActivity} instead of a regular Activity may be the way to go. A
{@link android.app.TabActivity TabActivity} can display several
activities of view, each inside a tab. The Android SDK documentation
explains how to create a tabbed application with activities. However,
using activities require careful state management since the activities
may be terminated when the use switches from one tab to another. It is
therefore easier to manage a {@link android.app.TabActivity
TabActivity} that display
<em>views</em> intead of activities.
<p></p>
<p>First of all, your main activity must extend {@link
android.app.TabActivity TabActivity} intead of {@link
android.app.Activity Activity}.</p>
<p>
Then, create a layout for each tab in <em>res/layout</em> with <em>File</em>
> <em>New</em> > <em>Other...</em> , then <em>Android</em> > <em>New
XML file</em>. In the <em>New Android XML file</em> dialog, name the file
<em>tab_one.xml</em>, select <em>Layout</em> as the type of resource,
then select <em>LinearLayout</em> as the root element. Repeat the
process with a second tab layout named <em>tab_two.xml</em>. Update
each tab layout as described in <em>Add your widgets to the main
layout</em> above, paying attention not to forget the <em>xmlns:app</em>
line.
</p>
<p>
The next step is to create a new layout names <em>res/layout/tab_group.xml</em>for
the whole activity. This layout looks like :
</p>
<pre>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;TabHost
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent"&gt;
&lt;TabWidget
android:id="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/&gt;
&lt;FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp"&gt;
&lt;/FrameLayout&gt;
&lt;include layout="@layout/tab_one"&gt;&lt;/include&gt;
&lt;include layout="@layout/tab_two"&gt;&lt;/include&gt;
&lt;/TabHost&gt;
</pre>
<p>
Then edit the <em>onCreate</em> method of your activity. After the
line <em>super.onCreate()</em> add the <em>VrpnClient.getInstance().setUri(....)</em>
line, then add :
<pre>
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.tab_group, getTabHost().getTabContentView(), true);
TabHost.TabSpec spec;
spec = getTabHost().newTabSpec("1").setContent(R.id.tab_one);
getTabHost().addTab(spec)
spec = getTabHost().newTabSpec("2").setContent(R.id.tab_two);
getTabHost().addTab(spec);
getTabHost().setCurrentTab(0);
</pre>
<p>See in the SDK documentation how to use the {@link
android.widget.TabHost.TabSpec#setIndicator setIndicator} method in
order to display an icon in the tab headers.</p>
<h3>Updating the widget library</h3>
<h4>Create a new Widget</h4>
<p>To create a new Widget type you should look at the source code
of VrpnSeekBar, which is the more elaborate. The main steps are as
follows :</p>
<ul>
<li>Decide which Android widget class you want to extend and
which events will trigger Vrpn updates.</li>
<li>Decide whether your widget will send Vrpn button,analog or
tracker updates.</li>
<li>Decide whether your widget needs new custom XML attributes.
If this is the case, look at <code>res/values/attrs.xml</code> for
examples.</li>
<li>Implement a private <em>init</em> method that each
constructor will call. In this method, you must obtain the XML custom
attributes of the widget. You should also send a Vrpn update with the
initial value of the widget</li>
<li>Implement the appropriate change listener, according to which
event you want your widget to respond to. In the change listener,
call the appropriate method : {@link
eu.ensam.ii.vrpn.VrpnClient#sendAnalog(int,double) sendAnalog} or
{@link eu.ensam.ii.vrpn.VrpnClient#sendButton(int,boolean)
sendButton}. Note that a single widget can s end several updates,
like the {@link eu.ensam.ii.vrpn.VrpnSurface VrpnSurface} does or
like a <a
href="http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/ColorPickerDialog.html">color
picker</a>would do. There is currently no <em>sendTracker</em> method
although this may be useful to implement an Arcball for instance.</li>
</ul>
<p>Note that using the full path of the parent class such as in :
<pre>public class VrpnRadioButton extends android.widget.RadioButton</pre>
allows Javadoc to generate a link to the parent class.
</p>
<h4>Generate the documentation</h4>
<p>
Select the <em>vrpn_library/src</em> folder, then select the menu item
<em>Project</em> &gt; <em>Generate Javadoc</em>.
<p>
On the first page of the wizard, select the path of the <em>javadoc</em>
tool. Check that <em>vrpn_library/src</em> is selected, but not <em>vrpn_library/gen</em>,
then select an appropriate destination for the documentation.
</p>
<p>
On the second page of the wizard select <em>android.jar</em> in the <em>Referenced
archives</em> list and take note of the <em>android.jar</em> doc path
(like
<code>file:/E:/android/docs/reference</code>
).
</p>
<p>
On the third page of the wizard add the following line to the <em>Extra
javadoc options</em> text box :
<pre>
-linkoffline http://d.android.com/reference file:/E:/android/docs/reference
</pre>
but replace the
<code>file:/</code>
URL by your local Android SDK URL noted from the page two of the
wizard. This allows Javadoc to include full references to the on-line
Android documentation.
</body>
</html>
\ No newline at end of file
//
//
// Please edit package-info.html, then paste here
//
/**
*
This package include a set of widgets that are bound to a Vrpn server
buttons, analogs and trackers.
<h3>Create a simple application that uses the widgets</h3>
<p>
The following chapters describe how to create a simple application
with a {@link eu.ensam.ii.vrpn.VrpnToggleButton VrpnToggleButton} and
a {@link eu.ensam.ii.vrpn.VrpnSeekBar VrpnSeekBar}. An example
application project that contains these widgets and more can be found
in the <em>example_app</em> directory.
</p>
<h4>Create and configure the project</h4>
<p>
Refer to any Android documentation for instructions about how to
set-up the Android SDK and the Eclipse plugin. Do not forget to setup
the Android SDK in any new workspace (<em>Window</em> &gt; <em>Preferences</em>,
then go to the <em>Android</em> tab and configure the SDK location).
</p>
<p>
Import the <em>Vrpn_library</em> project into your Eclipse workspace.
Open the Android section of the project property page and verify that
<em>Is library</em> checked. If the build complains about "Android
requires .class compatibility set to 5.0", you need first to select
the project, right-click > <em>Properties</em> &gt; <em>Android
tools</em> &gt; <em>Fix project properties</em>, then open the project
properties, go to the <em>Java compiler</em> section, then deselect
<em>enable project specific settings</em>. You may need to clean and
rebuild (<em>Project</em> &gt; <em>Clean</em>) several times before
the project shows no errors.
</p>
<p>
Create a new Android project : <em>File</em> > <em>New</em> > <em>Other
...</em> , <em>Android</em> &gt; <em>Android Project</em>. On the first
page of the wizard, check <em>Create activity</em> and enter any name
you like for the main activity (for instance <em>MainActivity</em>).
</p>
<p>
Add the library to your project : open the properties of the project,
go to the <em>Android</em> page. In the <em>Library</em> pane, click <em>Add</em>
then select <em>vrpn_library</em>, apply the changes and close the
window. At this point, the Eclipse <em>Package Explorer</em> view of
your project should contain a <em>vrpn_library_src</em> folder and the
<em>gen</em> folder should contain a package entry named <em>eu.ensam.ii.vrpn</em>.
</p>
<p>
Open the
<code>AndroidManifest.xml</code>
file. On the <em>Application</em> tab, check <em>Define an
&lt;Application&gt; tag in the AndroidManifest.xml</em>. Then click on the
<em>Browse</em> button that is on the right of <em>Name</em> and
select <em>VrpnApplication</em>.
</p>
<h4>Modify the main activity</h4>
<p>Edit the main activity to define the binding to the Vrpn server
when the activity is created. The method {@link
android.app.Activity#onCreate onCreate} is called when the Activity is
created. The default version of this method contain only the first and
last lines :</p>
<pre>
super.onCreate(savedInstanceState);
....
setContentView(R.layout.main);
</pre>
<p>We need to define the binding with the Vrpn server. The method
becomes :</p>
<pre>
&#64;Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InetAddress vrpnServerAddress = null;
try {
// Replace the IP address
vrpnServerAddress = InetAddress.getByName("192.168.173.1");
} catch (UnknownHostException e) {
}
final int vrpnServerPort = 7777;
VrpnClient.getInstance().setupVrpnServer(vrpnServerAddress, vrpnServerPort);
setContentView(R.layout.main);
}
</pre>
<a name="vrpnids"></a>
<h4>Create a Vrpn Ids list</h4>
<p>
Before laying out your Vrpn widgets in your application, you should
create an XML configuration file that contains the IDs of the Vrpn
buttons and analog that you will be using. This allows you to keep a
unique list of Vrpn IDs, making it easier to avoid duplicate values
and reference this list from your application. Create this file with <em>File</em>
> <em>New</em> > <em>Other</em>, then <em>Android </em> > <em>Android
XML File</em>. In XML file dialog, type any file name you like, for
instance <em>vrpn.xml</em>, then select <em>Values</em> as the type of
resource to create.
</p>
<p>
Edit the resulting file, found at <em>res/values/vrpn.xml</em> to add
the numbers of the Vrpn Buttons and Analogs that you will be using.
</p>
<pre>
&lt;resources&gt;
&lt;item type="id" format="integer" name="VrpnAnalogLightIntensity"&gt;0&lt;/item&gt;
&lt;item type="id" format="integer" name="VrpnButtonLightsOn"&gt;0&lt;/item&gt;
&lt;/resources&gt;
</pre>
<p>
Note that <em>type</em> must be <em>id</em> and <em>format</em> must
be <em>integer</em>, but <em>name</em> can be anything you like.
</p>
<h4>Add your widgets to the main layout</h4>
<p>Say we want an Android application to control the lighting in
our VR application : turn the light on or off and also control the
light intensity. We will create an application with a {@link
eu.ensam.ii.vrpn.VrpnToggleButton VrpnToggleButton} as the light
switch and a {@link eu.ensam.ii.vrpn.VrpnSeekBar VrpnSeekBar} as a
light intensity controller.</p>
<p>
By default, the layout of the main Activity is described in <em>res/layout/main.xml</em>
and just displays <em>Hello</em>. Edit this file. The XML code looks
is something like :
</p>
<pre>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
&gt;
&lt;TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/&gt;
&lt;/LinearLayout&gt;
</pre>
<p>
The root element,
<code>LinearLayout</code>
, is a container for the other widgets, currently contains a
<code>TextView</code>
.
<p>
The basics about XML layouts are presented in <a
href="http://developer.android.com/guide/topics/ui/declaring-layout.html">Declaring
layout</a> SDK page.
</p>
<p>
Before the end tag
<code>&lt;/LinearLayout&gt;</code>
, include a {@link eu.ensam.ii.vrpn.VrpnToggleButton VrpnToggleButton}
to turn the lights on or off :
<pre>
&lt;eu.ensam.ii.vrpn.VrpnToggleButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:vrpnButton="@id/VrpnButtonLightsOn"
/&gt;
</pre>
The Vrpn widgets use so-called
<em>custom XML properties</em> , such as
<em>app:vrpnButton</em> . This custom property allows you to specify
the id of the target Vrpn button in the XML layout file rather than in
Java code. However, the use of this custom property requires the
addition of an XML namespace tag so that the build system can find
then. After the
<em>xmlns:android</em> line, add the following line :
<pre>
xmlns:app="http://schemas.android.com/apk/res/your.application.package.name.here"
</pre>
Different attributes exist for each widgets and the whole list is shown
when you unfold
<em>gen/eu/ensam.ii.vrpn/R.java/R/attr</em> in the Eclipse Package
View. Your layout is now ready for your to include the Vrpn widgets.
Now save the file to build your project. If there are build errors,
check the following :
<ul>
<li>you have added the <code>xmlns::app</code> line in the root
layout</li>
<li>the spelling of the <code>app:</code> attribute</li>
<li>the value of the Vrpn attributes</li>
<li>the spelling of the widget name</li>
</ul>
<p>
In most cases, the <em>Preview</em> tab of the XML layout is not
displayed properly as soon as you include one of the Vrpn widgets.
This is a known problem for which not solution has currently been
found. It prevents you from previewing the layout it wil be displayed
correctly in the application, either on a device or in the emulator.
</p>
<p>Now add a {@link eu.ensam.ii.vrpn.VrpnSeekBar VrpnSeekBar} to
control the light intensity :</p>
<pre>
&lt;eu.ensam.ii.vrpn.VrpnSeekBar
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:minValue="0"
app:maxValue="255"
app:defaultValue="128"
app:vrpnAnalog="@id/VrpnAnalogLightIntensity"
/&gt;
</pre>
Your Android application is now ready to run. Upload on a device and
run. There is not much to seen for the time being. However, if you run
a network sniffer on a machine connected to the same network as the
Android device, you should see UDP packets flowing out of the device.
If you are using the emulator instead of an actual device, see the
<a
href="
http://developer.android.com/guide/developing/devices/emulator.html#emulatornetworking">Emulator
networking</a> SDK page for the correct network setup.
<h4>Test your application</h4>
In order to test your application, you need a VRPN server built with
VRPN_USE_JSONNET. See the
<code>README.jsoncpp</code>
file for instructions to build this server. When you get this server
running, the easiest way to test the Android application is to use the
<code>vrpn_print_devices</code>
application provided with Vrpn. Type the command :
<pre>
vrpn_print_devices Jsonnet@localhost
</pre>
The command should then report the values of the Button and SeekBar as
they are changed.
<h3>Beyond the basics</h3>
<h4>Application with multiple tabs</h4>
If you want to create an application with more widgets that can be
placed on a single screen, using a {@link android.app.TabActivity
TabActivity} instead of a regular Activity may be the way to go. A
{@link android.app.TabActivity TabActivity} can display several
activities of view, each inside a tab. The Android SDK documentation
explains how to create a tabbed application with activities. However,
using activities require careful state management since the activities
may be terminated when the use switches from one tab to another. It is
therefore easier to manage a {@link android.app.TabActivity
TabActivity} that display
<em>views</em> intead of activities.
<p></p>
<p>First of all, your main activity must extend {@link
android.app.TabActivity TabActivity} intead of {@link
android.app.Activity Activity}.</p>
<p>
Then, create a layout for each tab in <em>res/layout</em> with <em>File</em>
> <em>New</em> > <em>Other...</em> , then <em>Android</em> > <em>New
XML file</em>. In the <em>New Android XML file</em> dialog, name the file
<em>tab_one.xml</em>, select <em>Layout</em> as the type of resource,
then select <em>LinearLayout</em> as the root element. Repeat the
process with a second tab layout named <em>tab_two.xml</em>. Update
each tab layout as described in <em>Add your widgets to the main
layout</em> above, paying attention not to forget the <em>xmlns:app</em>
line.
</p>
<p>
The next step is to create a new layout names <em>res/layout/tab_group.xml</em>for
the whole activity. This layout looks like :
</p>
<pre>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;TabHost
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent"&gt;
&lt;TabWidget
android:id="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/&gt;
&lt;FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp"&gt;
&lt;/FrameLayout&gt;
&lt;include layout="@layout/tab_one"&gt;&lt;/include&gt;
&lt;include layout="@layout/tab_two"&gt;&lt;/include&gt;
&lt;/TabHost&gt;
</pre>
<p>
Then edit the <em>onCreate</em> method of your activity. After the
line <em>super.onCreate()</em> add the <em>VrpnClient.getInstance().setUri(....)</em>
line, then add :
<pre>
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.tab_group, getTabHost().getTabContentView(), true);
TabHost.TabSpec spec;
spec = getTabHost().newTabSpec("1").setContent(R.id.tab_one);
getTabHost().addTab(spec)
spec = getTabHost().newTabSpec("2").setContent(R.id.tab_two);
getTabHost().addTab(spec);
getTabHost().setCurrentTab(0);
</pre>
<p>See in the SDK documentation how to use the {@link
android.widget.TabHost.TabSpec#setIndicator setIndicator} method in
order to display an icon in the tab headers.</p>
<h3>Updating the widget library</h3>
<h4>Create a new Widget</h4>
<p>To create a new Widget type you should look at the source code
of VrpnSeekBar, which is the more elaborate. The main steps are as
follows :</p>
<ul>
<li>Decide which Android widget class you want to extend and
which events will trigger Vrpn updates.</li>
<li>Decide whether your widget will send Vrpn button,analog or
tracker updates.</li>
<li>Decide whether your widget needs new custom XML attributes.
If this is the case, look at <code>res/values/attrs.xml</code> for
examples.</li>
<li>Implement a private <em>init</em> method that each
constructor will call. In this method, you must obtain the XML custom
attributes of the widget. You should also send a Vrpn update with the
initial value of the widget</li>
<li>Implement the appropriate change listener, according to which
event you want your widget to respond to. In the change listener,
call the appropriate method : {@link
eu.ensam.ii.vrpn.VrpnClient#sendAnalog(int,double) sendAnalog} or
{@link eu.ensam.ii.vrpn.VrpnClient#sendButton(int,boolean)
sendButton}. Note that a single widget can s end several updates,
like the {@link eu.ensam.ii.vrpn.VrpnSurface VrpnSurface} does or
like a <a
href="http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/ColorPickerDialog.html">color
picker</a>would do. There is currently no <em>sendTracker</em> method
although this may be useful to implement an Arcball for instance.</li>
</ul>
<p>Note that using the full path of the parent class such as in :
<pre>public class VrpnRadioButton extends android.widget.RadioButton</pre>
allows Javadoc to generate a link to the parent class.
</p>
<h4>Generate the documentation</h4>
<p>
Select the <em>vrpn_library/src</em> folder, then select the menu item
<em>Project</em> &gt; <em>Generate Javadoc</em>.
<p>
On the first page of the wizard, select the path of the <em>javadoc</em>
tool. Check that <em>vrpn_library/src</em> is selected, but not <em>vrpn_library/gen</em>,
then select an appropriate destination for the documentation.
</p>
<p>
On the second page of the wizard select <em>android.jar</em> in the <em>Referenced
archives</em> list and take note of the <em>android.jar</em> doc path
(like
<code>file:/E:/android/docs/reference</code>
).
</p>
<p>
On the third page of the wizard add the following line to the <em>Extra
javadoc options</em> text box :
<pre>
-linkoffline http://d.android.com/reference file:/E:/android/docs/reference
</pre>
but replace the
<code>file:/</code>
URL by your local Android SDK URL noted from the page two of the
wizard. This allows Javadoc to include full references to the on-line
Android documentation.
/*
********************************* End of paste Here
*/
package eu.ensam.ii.vrpn;
\ No newline at end of file