HP5508A replacement v1.7 - Android bluetooth app

Time to make an Android app to take advantage of the bluetooth interface.....

I decided to make a fragment based app. This means that there is a main activity that continues to run while parts of the user interface get replaced as needed. So, for example, if the settings should be visible, we remove the fragment that contains the distance data, etc. show the settings. Then if the settings are done, we only show the interferometer data.

I am going (roughly) though the development process here to make it a little easier for others to modify the app. This is not a tutorial! Some steps will be omitted.

First, we want eclipse. I am using the last available eclipse with the Android Development Toolkit bundle. And since I am running Linux on the development computer, the name of the file that contains all is adt-bundle-linux-x86_64-20140702.zip

Google stopped distributing this, because the want to push their new Android Studio. But I am not yet ready to switch to that one...

To start, we use the Fragment based example from File->New->Android Application Project

This gives us a "Hello World" Fragment based App. I don't like default menu, so I stripped it and then removed the "hello world" Text as well. That gives us a fairly clean looking app:

I don't see the need for the menu bar, so that goes away too, by putting

<style name="AppTheme" parent="AppBaseTheme">

<item name="android:windowNoTitle">true</item>

</style>

into the styles.xml file.

ok, this gives us a blank slate to work from. First a couple of buttons in the main activity:

<LinearLayout

android:id="@+id/LinearLayout1"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:gravity="left"

android:orientation="horizontal" >

<Button

android:id="@+id/info"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center_horizontal"

android:text="Info" />

<Button

android:id="@+id/settings"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center_horizontal"

android:text="Settings" />

</LinearLayout>

We will be using these buttons to switch between the settings fragment and the display fragment

After setting a theme for the app, the next important thing is to implement the fragments for the different screens we want to display. The two buttons we have now will stay all the time, and they will be used to switch between the fragments.

First, we add an element to the main activity that the fragments are going to replace. The activity_main.xml file, then looks like this:

<?xml version="1.0" encoding="UTF-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/container"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context="com.example.umd1bluetoothapp.MainActivity"

tools:ignore="MergeRootFrame" >

<LinearLayout

android:id="@+id/LinearLayout1"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:gravity="left"

android:orientation="horizontal" >

<Button

android:id="@+id/info"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center_horizontal"

android:text="Info" />

<Button

android:id="@+id/settings"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center_horizontal"

android:text="Settings" />

</LinearLayout>

<LinearLayout

android:id="@+id/fragment"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:gravity="left"

android:orientation="horizontal" >

</LinearLayout>

</LinearLayout>

Note the added LinearLayout. This will be replaced by the fragments. So all we have to do is create two xml files representing the fragments:

data_fragment.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/LinearLayout133"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical"

tools:context="com.example.umd1bluetoothapp.DataFragment" >

<Button

android:id="@+id/button1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Button" />

</LinearLayout>

settings_fragment.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/LinearLayout134"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical"

>

<TextView

android:id="@+id/textView1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="TextView" />

</LinearLayout>

These two fragment files are the shell where we will put our data display and interferometer configuration information, respectively. At this point, I don't know yet if I will put the bluetooth configuration into this settings fragment or if I will make a third fragment.

What I have done for demonstration purposes is to add just a button into the data fragment and a textview into the settings fragment. Finally, all we need is a little code in the mainactivity.java file to tie the fragments together.

public class MainActivity extends ActionBarActivity {

final DataFragment dataFragment = new DataFragment();

final SettingsFragment settingsFragment = new SettingsFragment();

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

final Button showData = (Button) findViewById(R.id.info);

final Button showSettings = (Button) findViewById(R.id.settings);

showData.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

android.support.v4.app.FragmentManager fm = getSupportFragmentManager();

android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();

ft.replace(R.id.fragment, dataFragment);

ft.commit();

}

});

showSettings.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

android.support.v4.app.FragmentManager fm = getSupportFragmentManager();

android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();

ft.replace(R.id.fragment, settingsFragment);

ft.commit();

}

});

}

The program starts with no fragment showing and depending on which button is pressed, the proper fragment appears:

Next, we have to flesh out the fragments a little.

In the data fragment, we want to display the distance as measured by the interferometer. Also, probably a zero button.

For the data fragment,

<TextView

android:id="@+id/distanceText"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Distance (nm):"

android:textAppearance="?android:attr/textAppearanceLarge" />

<TextView

android:id="@+id/distanceNumber"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="0.00"

android:textAppearance="?android:attr/textAppearanceLarge" />

<Button

android:id="@+id/zero"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Zero" />

Results in the following view (depending on rotation)

Next, for the settings pane we need at a minimum buttons for the type of interferometer:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:gravity="center"

android:orientation="vertical" >

<RadioGroup

android:id="@+id/radioGroupInterferometerType"

android:layout_width="wrap_content"

android:layout_height="wrap_content" >

<RadioButton

android:id="@+id/x1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:checked="true"

android:text="1X" />

<RadioButton

android:id="@+id/x2"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="2X" />

<RadioButton

android:id="@+id/x4"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="4X" />

</RadioGroup>

</LinearLayout>

This results in the following view

One of the fragments should show by default on launch, so we add that:

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

if (savedInstanceState == null) {

getSupportFragmentManager().beginTransaction().replace(R.id.fragment, dataFragment).commit();

}

final Button showData = (Button) findViewById(R.id.info);

The persistence of the fragments (and the main activity) is not infinite. Android is free to dispose/reload them as needed. When that happens, it calls functions to let us know that this happened. For example, at this point, when we go to the settings screen and select '2x', then go to the info screen and back to the settings screen, usually the 2x is still selected. But if we go to the settings screen and select '2x', then go to the info screen, rotate the screen and then go back to the settings screen, usually we see '1x' selected because the fragment has been destroyed and reloaded.

Here are the different ways this can happen:

The moral of the story is that we have to fill in the functions that destroy/reload the fragments to maintain consistency.

Here, we will handle it by storing the settings immediately when the buttons are pressed and loading them back whenever onResume is called...

public static class SettingsFragment extends Fragment {

private static final String TAG = "SettingsFragment";

public SettingsFragment() {

}

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {

View rootView = inflater.inflate(R.layout.settings_fragment,container, false);

RadioGroup radioGroup = (RadioGroup) rootView.findViewById(R.id.radioGroupInterferometerType);

radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener()

{

@Override

public void onCheckedChanged(RadioGroup group, int checkedId) {

SharedPreferences preferences = getActivity().getPreferences(MODE_PRIVATE);

SharedPreferences.Editor editor = preferences.edit();

// checkedId is the RadioButton selected

RadioButton rb=(RadioButton) getActivity().findViewById(checkedId);

if (0 == rb.getText().toString().compareTo("1X"))

editor.putInt("InterferometerType", 1); // 1x selected

else if (0 == rb.getText().toString().compareTo("2X"))

editor.putInt("InterferometerType", 2); // 2x selected

else

editor.putInt("InterferometerType", 4); // 4x selected

editor.commit(); // write settings

Log.i(TAG, "interferometer type change" +rb.getText());

}

});

Log.i(TAG, "onCreateView");

return rootView;

}

@Override

public void onResume() {

// load settings and populate controls

RadioGroup radioGroup = (RadioGroup) getActivity().findViewById(R.id.radioGroupInterferometerType);

SharedPreferences preferences = getActivity().getPreferences(MODE_PRIVATE);

int settingInterferometerType = preferences.getInt("InterferometerType", 0);

if (4==settingInterferometerType)

radioGroup.check(R.id.x4); // 4x interferometer

else if(2==settingInterferometerType)

radioGroup.check(R.id.x2); // 2x interferometer

else

radioGroup.check(R.id.x1);; // 1x interferometer or no program settings yet

Log.i(TAG, "onResume "+settingInterferometerType);

super.onResume();

}

@Override

public void onDestroy() {

// TODO Auto-generated method stub

Log.i(TAG, "onDestroy");

super.onDestroy();

}

}

We are now ready to deal with the Bluetooth aspect of the program. It seems right to add another fragment to handle the bluetooth settings, so we add a new file, bluetooth_settings_fragment.xml with just a button for now:

<?xml version="1.0" encoding="UTF-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical"

tools:context="com.example.umd1bluetoothapp.MainActivity"

tools:ignore="MergeRootFrame" >

<LinearLayout

android:id="@+id/LinearLayout1"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:gravity="center"

android:orientation="horizontal" >

<Button

android:id="@+id/button1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Button" />

</LinearLayout>

</LinearLayout>

Then we add the support for the fragment to the code in the activity_main.xml

<?xml version="1.0" encoding="UTF-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/container"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context="com.example.umd1bluetoothapp.MainActivity"

tools:ignore="MergeRootFrame" >

<LinearLayout

android:id="@+id/LinearLayout1"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:gravity="center"

android:orientation="horizontal" >

<Button

android:id="@+id/info"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center_horizontal"

android:text="Info" />

<Button

android:id="@+id/settings"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center_horizontal"

android:text="Settings" />

<Button

android:id="@+id/bluetoothSettings"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center_horizontal"

android:text="Bluetooth Settings" />

</LinearLayout>

<LinearLayout

android:id="@+id/fragment"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:gravity="left"

android:orientation="horizontal" >

</LinearLayout>

</LinearLayout>

Finally code support in the MainActivity:

public class MainActivity extends ActionBarActivity {

private static final String TAG = "MainActivity";

final DataFragment dataFragment = new DataFragment();

final SettingsFragment settingsFragment = new SettingsFragment();

final BluetoothSettingsFragment bluetoothSettingsFragment = new BluetoothSettingsFragment();

@Override

protected void onCreate(Bundle savedInstanceState) {

[...]

final Button showData = (Button) findViewById(R.id.info);

final Button showSettings = (Button) findViewById(R.id.settings);

final Button showBluetoothSettings = (Button) findViewById(R.id.bluetoothSettings);

showData.setOnClickListener(new OnClickListener() {

[...]

});

showBluetoothSettings.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

android.support.v4.app.FragmentManager fm = getSupportFragmentManager();

android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();

ft.replace(R.id.fragment, bluetoothSettingsFragment);

ft.commit();

}

});

}

public static class DataFragment extends Fragment {

[...]

}

public static class SettingsFragment extends Fragment {

[...]

}

public static class BluetoothSettingsFragment extends Fragment {

private static final String TAG = "BluetoothSettingsFragment";

public BluetoothSettingsFragment() {

}

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {

View rootView = inflater.inflate(R.layout.bluetooth_settings_fragment, container,false);

Log.i(TAG, "onCreateView");

return rootView;

}

@Override

public void onResume() {

Log.i(TAG, "onResume");

super.onResume();

}

@Override

public void onDestroy() {

// TODO Auto-generated method stub

Log.i(TAG, "onDestroy");

super.onDestroy();

}

}

}

This results in a new fragment for the bluetooth settings:

Next, we want the bluetooth connection to persist and not be killed every time we rotate the screen or pause the app. One way of doing this is by extending the Application class of our project like described here:

So we create a new file, bluetoothBaseApplication.java that does that:

public class bluetoothBaseApplication extends Application {

private static final String TAG = "bluetoothBaseApplication";

@Override

public void onCreate()

{

super.onCreate();

Log.i(TAG, "onCreate");;

}

}

And we change the manifest to load that first:

<application

android:allowBackup="true"

android:name="com.umd1bluetoothapp.bluetoothBaseApplication"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

<activity

android:name="com.umd1bluetoothapp.MainActivity"

android:label="@string/app_name" >

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

Now we can start putting in the bluetooth infrastructure. First we rename the button in the bluetooth fragment and add a list view, so that it looks like this when its functional:

<?xml version="1.0" encoding="UTF-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical"

tools:context="com.umd1bluetoothapp.MainActivity"

tools:ignore="MergeRootFrame" >

<LinearLayout

android:id="@+id/LinearLayout1"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:gravity="center"

android:orientation="vertical" >

<Button

android:id="@+id/showDevices"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Show/Refresh Devices" />

<ListView

android:id="@+id/bluetoothDevicesList"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_weight="1" />

</LinearLayout>

</LinearLayout>

Next, the bluetooth adapter object that is persistent for the lifetime of the app:

public class bluetoothBaseApplication extends Application {

private static final String TAG = "bluetoothBaseApplication";

BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

@Override

public void onCreate()

{

super.onCreate();

Log.i(TAG, "onCreate");

}

}

Then the fragment code

public static class BluetoothSettingsFragment extends Fragment {

private static final String TAG = "BluetoothSettingsFragment";

ArrayList<String> bluetoothDeviceList = new ArrayList<String>(); // list of bluetooth devices

public BluetoothSettingsFragment() {

}

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

View rootView = inflater.inflate(R.layout.bluetooth_settings_fragment, container, false);

IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);

getActivity().registerReceiver(bluetoothReceiver, filter);

final Button showDevices = (Button) rootView.findViewById(R.id.showDevices);

final ListView bluetoothDevicesList = (ListView) rootView.findViewById(R.id.bluetoothDevicesList);

showDevices.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

bluetoothDevicesList.setAdapter(null); // clear list

BluetoothAdapter bluetoothAdapter = ((bluetoothBaseApplication) getActivity().getApplicationContext()).bluetoothAdapter;

bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

bluetoothAdapter.startDiscovery();

}

});

}

Log.i(TAG, "onCreateView");

return rootView;

}

[...]

@Override

public void onDestroy() {

// TODO Auto-generated method stub

Log.i(TAG, "onDestroy");

getActivity().unregisterReceiver(bluetoothReceiver);

super.onDestroy();

}

private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {

@Override

public void onReceive(Context context, Intent intent) {

String action = intent.getAction();

if (BluetoothDevice.ACTION_FOUND.equals(action)) {

BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

bluetoothDeviceList.add(device.getName() + "\n"+ device.getAddress());

Log.i(TAG, "adding device " + device.getName() + "\n"+ device.getAddress() + "\n");

ListView listView = (ListView) getActivity().findViewById(R.id.bluetoothDevicesList);

listView.setAdapter(new ArrayAdapter<String>(context,android.R.layout.simple_list_item_1,bluetoothDeviceList));

}

}

};

}

The BroadcastReceiver runs independent of the rest of the fragment code so that the user interface does not lock up while the bluetooth device list is updated. The mechanism is to send a message "BluetoothDevice.ACTION_FOUND" to the bluetooth subsystem. As this system performs this action it sends the results with "BluetoothDevice.ACTION_FOUND". The Broadcast receiver then pushes the information into the fragments' ListView.

The contents of the ListView get dumped on screen rotation, so we need to store it. I decided to store it at the fragment level instead of the application level. All that is needed is to add a function onSaveInstanceState to handle the storing of the list contents and add an if statement to the onCreateView function to restore this data if available .

public static class BluetoothSettingsFragment extends Fragment {

private static final String TAG = "BluetoothSettingsFragment";

ArrayList<String> bluetoothDeviceList = new ArrayList<String>(); // list of bluetooth devices

public BluetoothSettingsFragment() {

}

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

[...]

if (savedInstanceState != null) {

if (savedInstanceState.containsKey("bluetoothList")) {

bluetoothDeviceList = savedInstanceState.getStringArrayList("bluetoothList");

ListView listView = (ListView) rootView.findViewById(R.id.bluetoothDevicesList);

listView.setAdapter(new ArrayAdapter<String>(getActivity(),android.R.layout.simple_list_item_1,bluetoothDeviceList));

}

}

Log.i(TAG, "onCreateView");

return rootView;

}

@Override

public void onSaveInstanceState(Bundle outState) {

super.onSaveInstanceState(outState);

outState.putStringArrayList("bluetoothList", bluetoothDeviceList);

}

This application is almost ready. The final part is to add code to perform the actual connection to the bluetooth device, and parsing the data into the data screen.

To connect we write some functions in the bluetoothBaseApplication.java file

Most instructions on the net regarding Android bluetooth serial connections seem to use code very similar to this:

https://web.archive.org/web/20160108144134/http://project-greengiant.googlecode.com/svn/trunk/Blog/Android%20Arduino%20Bluetooth/Android/AndroidArduinoBluetooth/src/Android/Arduino/Bluetooth/BluetoothTest.java

I was only able to get that to work unreliably. This document describes a way that worked far better for me:

http://www.egr.msu.edu/classes/ece480/capstone/spring14/group01/docs/appnote/Wirsing-SendingAndReceivingDataViaBluetoothWithAnAndroidDevice.pdf

I am mirroring it below, in case the site goes down. Anyway, for our purposes we have do the following:

In the fragment we call the function connectToBluetoothDevice(String macAddress) with the mac address of the entry we pressed:

public void onItemClick(AdapterView<?> parent, View view,int position, long id) {

String data=(String)parent.getItemAtPosition(position);

Log.i(TAG, "Selected BT device : "+ data);

// extract MAC address

String[] lines = data.split("\\n");

String macAddress = lines[1];

BluetoothAdapter bluetoothAdapter = ((bluetoothBaseApplication) getActivity().getApplicationContext()).bluetoothAdapter;

if (!bluetoothAdapter.isEnabled()) {

//make sure the device's bluetooth is enabled

Intent enableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);

startActivityForResult(enableBluetooth, 1); //REQUEST_ENABLE_BT = 1

}

((bluetoothBaseApplication) getActivity().getApplicationContext()).connectToBluetoothDevice(macAddress);

}

The function (in bluetoothBaseApplication.java) then starts one thread for the connect() function

private class ConnectThread extends Thread

{

private final BluetoothSocket bluetoothSocket;

private final BluetoothDevice bluetoothDevice;

ConnectedThread mConnectedThread ;

UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");

public ConnectThread(BluetoothDevice device) {

BluetoothSocket tmp = null;

bluetoothDevice = device;

try {

tmp = device.createRfcommSocketToServiceRecord(MY_UUID);

} catch (IOException e) {

Log.e(TAG, "exception : "+ e);

}

bluetoothSocket = tmp;

Log.i(TAG, "ConnectThread : ");

}

public void run() {

bluetoothAdapter.cancelDiscovery();

try {

bluetoothSocket.connect();

} catch (IOException connectException) {

Log.e(TAG, "exception : "+ connectException);

try {

bluetoothSocket.close();

} catch (IOException closeException) {

Log.e(TAG, "exception : "+ closeException);

}

return;

}

Log.i(TAG, "run : ");

mConnectedThread = new ConnectedThread(bluetoothSocket);

mConnectedThread.start();

}

public void cancel() {

try {

bluetoothSocket.close();

} catch (IOException e) {

Log.e(TAG, "exception : "+ e);

}

}

}

and that function starts a second thread that actually handles the data input stream:

private class ConnectedThread extends Thread {

private final InputStream serialInputStream;

public ConnectedThread(BluetoothSocket socket) {

InputStream tmpIn = null;

Log.i(TAG, "connectedthread : ");

try {

tmpIn = socket.getInputStream();

} catch (IOException e) {

Log.e(TAG, "exception1 : "+ e);

}

serialInputStream = tmpIn;

}

public void run() {

Log.i(TAG, "connectedthread run : ");

BufferedReader r = new BufferedReader(new InputStreamReader(serialInputStream));

String line;

while (true) {

try {

while ((line = r.readLine()) != null) {

String[] dataFields = line.trim().split("\\s+"); // split string on whitespace

// string should have either 8 or 16 fields

int numberOfFields = dataFields.length;

if ((8==numberOfFields)||(16==numberOfFields))

{

// more checks could be done on the fields

Log.i(TAG, "data : "+dataFields[2]); // x axis displacement

}

}

} catch (IOException e) {

Log.e(TAG, "exception 2: "+ e);

break;

}

}

}

}

This results in a fully functional data transfer, but it only prints out the x axis count. So as a last step we need to update the info fragment, if it is loaded. I implemented that by sending a message in ConnectedThread to the Info fragment using a broadcast:

while ((line = r.readLine()) != null) {

String[] dataFields = line.trim().split("\\s+"); // split string on whitespace

// string should have either 8 or 16 fields

int numberOfFields = dataFields.length;

if ((8==numberOfFields)||(16==numberOfFields))

{

// more checks could be done on the fields

Intent theIntent = new Intent().setAction("DATA");

theIntent.putExtra("distanceX",Long.parseLong(dataFields[2]));

getApplicationContext().sendBroadcast(theIntent);

//Log.i(TAG, "data : "+dataFields[2]); // x axis displacement

}

}

And then in the data fragment, we implement a BroadcastReceiver:

public static class DataFragment extends Fragment {

private static final String TAG = "DataFragment";

public DataFragment() {

}

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {

View rootView = inflater.inflate(R.layout.data_fragment, container,false);

IntentFilter filter = new IntentFilter();

filter.addAction("DATA");

getActivity().registerReceiver(dataReceiver, filter);

Log.i(TAG, "onCreateView");

return rootView;

}

@Override

public void onDestroy() {

Log.i(TAG, "onDestroy");

getActivity().unregisterReceiver(dataReceiver);

super.onDestroy();

}

private final BroadcastReceiver dataReceiver = new BroadcastReceiver() {

@Override

public void onReceive(Context context, Intent intent) {

Long x=0L;

// Get extra data included in the Intent

String action = intent.getAction();

if (action.equals("DATA")) {

x = (Long) intent.getLongExtra("distanceX",0);

TextView distanceText = (TextView) getActivity().findViewById(R.id.distanceNumber);

distanceText.setText(x.toString());

}

//Log.i(TAG, "data : "+x);

}

};

}

So now we are displaying the raw data in real time:

Now this is not nm yet, so lets add that calculation. The value is dependent on the type of interferometer, so we pull that from the program settings.

public static class DataFragment extends Fragment {

private static final String TAG = "DataFragment";

int multiplier = 1;

public DataFragment() {

}

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {

View rootView = inflater.inflate(R.layout.data_fragment, container,false);

SharedPreferences preferences = getActivity().getPreferences(MODE_PRIVATE);

int settingInterferometerType = preferences.getInt("InterferometerType", 0);

if (4 == settingInterferometerType)

multiplier = 4; // 4x interferometer

else if (2 == settingInterferometerType)

multiplier = 2; // 2x interferometer

else

multiplier = 1; // 1x interferometer or no program settings yet

IntentFilter filter = new IntentFilter();

filter.addAction("DATA");

[...]

public void onReceive(Context context, Intent intent) {

Long x=0L;

// Get extra data included in the Intent

String action = intent.getAction();

if (action.equals("DATA")) {

x = (Long) intent.getLongExtra("distanceX",0);

TextView distanceText = (TextView) getActivity().findViewById(R.id.distanceNumber);

Double x_nm = 632.99/2.0/multiplier * (double)x;

distanceText.setText(String.format ("%.2f", x_nm));

}

//Log.i(TAG, "data : "+x);

}

};

}

Finally, we need to implement the zero button somehow. There are many ways to do that, but one way is to simply save the current displayed value to a double and have the BroadcastReceiver take that into account:

Now we have a complete working app!

Tested to work on a GalaxyNote i717 with CyanogenMod 10.1 as well as a Galaxy A9 A9000 with stock 5.1.1 Android.