This tutorial assumes you have a working knowledge of how to create an Android application. Setting up the development environment and understanding Android fundamentals is outside the scope of OpenXC, and already Google provides great documentation and tutorials - we won’t repeat them here. The best place to start is Android Studio’s User Guide.

Once you’re comfortable with creating an Android app, continue on with this tutorial to enrich it with data from your vehicle.

We'll mention this again at the end of the tutorial, but you will need to install the Enabler app before your application will work.
  1. Download the complete starter application from GitHub (click the “ZIP” button on the right hand column or use Git), and extract it to your code workspace.
  2. Open the project with Android Studio.
  3. This project is ready to go, so if you want to quickly see something running jump ahead to the testing section. To know more about how this application works or to add the necessary code to a different app, continue reading.

The Starter project is based off of the same “Hello World” application that you should have already created in Google’s tutorial. The first difference is that we’ve specified that the Starter app will use the OpenXC library as a dependency.

This is already done in the Starter project and no changes have to be made. But to make your own app from scratch, go to the app/build.gradle file and add the openxc library to the build dependencies. This is mentioned in the Android Library Setup page:

dependencies {
    compile 'com.openxcplatform:library:7.0.6'
}

You can now proceed to the next steps to start using the library in your project.

Note: If for some reason you are unable to use the remote openxc-android library, you can use a local copy. This requires replacing three files in the starter app, and one file in the imported library. All of the required files are included in each of the respective repositories with a .local extension. Below is a list of the files and locations:

openxc\openxc-starter --> settings.gradle.local
openxc\openxc-starter --> build.gradle.local
openxc\openxc-starter --> app\build.gradle.local
openxc\openxc-android --> library\build.gradle.local

Replace the standard versions of the files with the .local files. For example, in the OpenXC Starter app, replace the app\build.gralde file with the app\build.gradle.local file. Restart your IDE, run the Gradle build process, and your starter app should now be using a local copy of the openxc-android library.

The AndroidManifest.xml is the core of every Android application - it tells the Android OS what views are available, which services are used and what sensors your app needs.

Every OpenXC application, the Starter app included, needs to use the VehicleManager service. The next difference between the “Hello World” app and the Starter app is the addition of this line to the manifest:

<service android:name="com.openxc.VehicleManager"/>

This should go between the <application> tags, like this:

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
        android:name="com.openxc.openxcstarter.StarterActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

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

    <service android:name="com.openxc.VehicleManager"/>
</application>

The next changes are all in Java code - for the Starter app, it’s in StarterActivity.java in the src folder. In order to use the VehicleManager in Java code, we have to initiate the our mVehicleManager variable and then bind with it when the application starts.

Initiate our Starter App variables as shown here:

    public class StarterActivity extends Activity {
        private static final String TAG = "StarterActivity";

        private VehicleManager mVehicleManager;
        private TextView mEngineSpeedView;

        @Override
        protected void onCreate(Bundle savedInstanceState) {

Then add the ServiceConnection in the StarterActivity to bind with the VehicleManager:

    private ServiceConnection mConnection = new ServiceConnection() {
        // Called when the connection with the VehicleManager service is established
        public void onServiceConnected(ComponentName className, IBinder service) {
            Log.i(TAG, "Bound to VehicleManager");
            // When the VehicleManager starts up, we store a reference to it
            // here in "mVehicleManager" so we can call functions on it
            // elsewhere in our code.
            mVehicleManager = ((VehicleManager.VehicleBinder) service)
                    .getService();

            // We want to receive updates whenever the EngineSpeed changes. We
            // have an EngineSpeed.Listener (see above, mSpeedListener) and here
            // we request that the VehicleManager call its receive() method
            // whenever the EngineSpeed changes
            mVehicleManager.addListener(EngineSpeed.class, mSpeedListener);
        }

        // Called when the connection with the service disconnects unexpectedly
        public void onServiceDisconnected(ComponentName className) {
            Log.w(TAG, "VehicleManager Service  disconnected unexpectedly");
            mVehicleManager = null;
        }
    };

In the onResume() method of the activity, we request to bind with the service using the new ServiceConnection instance:

@Override
public void onResume() {
    super.onResume();
    // When the activity starts up or returns from the background,
    // re-connect to the VehicleManager so we can receive updates.
    if(mVehicleManager == null) {
        Intent intent = new Intent(this, VehicleManager.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }
}

Now, when your app starts it will also start the OpenXC VehicleManager and if the Enabler is running, it will be ready to receive data from the vehicle.

The activity now has a connection to the vehicle service, and we want it to be notified whenever the speed of the vehicle changes. Look for the EnginerSpeed.Listener object in the StarterActivity:

EngineSpeed.Listener mSpeedListener = new EngineSpeed.Listener() {
    public void receive(Measurement measurement) {
        // When we receive a new EngineSpeed value from the car, we want to
        // update the UI to display the new value. First we cast the generic
        // Measurement back to the type we know it to be, an EngineSpeed.
        final EngineSpeed speed = (EngineSpeed) measurement;
        // In order to modify the UI, we have to make sure the code is
        // running on the "UI thread" - Google around for this, it's an
        // important concept in Android.
        StarterActivity.this.runOnUiThread(new Runnable() {
            public void run() {
                // Finally, we've got a new value and we're running on the
                // UI thread - we set the text of the EngineSpeed view to
                // the latest value
                mEngineSpeedView.setText("Engine speed (RPM): "
                        + speed.getValue().doubleValue());
            }
        });
    }
};

This mSpeedListener is referred to from ServiceConnection.onServiceConnected() method we previously, where it’s handed to the VehicleManager for future updates. Every time a new value for EngineSpeed is received by the VehicleManager, the receive(Measurement) method of the new Listener will be called with the data.

Custom Message Listener

In order to listen to a custom message from the vehicle, follow these steps:

  1. Generate a firmware that can send custom message.
  2. Create a custom message listener and add it to the VehicleManager
    private ServiceConnection mConnection = new ServiceConnection() {
        // Called when the connection with the VehicleManager service is
        // established, i.e. bound.
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            Log.i(TAG, "Bound to VehicleManager");
            // When the VehicleManager starts up, we store a reference to it
            // here in "mVehicleManager" so we can call functions on it
            // elsewhere in our code.
            mVehicleManager = ((VehicleManager.VehicleBinder) service)
                    .getService();

            // We want to receive updates whenever our Message changes.
            // We have customVehicleMessageListener and here we request that the VehicleManager
            // call its receive() method whenever the custom simpleVehicleMsg changes
            mVehicleManager.addListener(SimpleVehicleMessage.class, customVehicleMessageListener);
 
        }
    private VehicleMessage.Listener customVehicleMessageListener = new VehicleMessage.Listener() {
        // When we receive a new SimpleVehicleMessage value from the car, we want to update the
        // UI to display the new value. First we create a new SimpleVehicleMessage from the
        // received VehicleMessage and if the name of message is as specified (custom), we set
        // the text of the customMessageView view to the latest value
        @Override
        public void receive(final VehicleMessage message) {
                StarterActivity.this.runOnUiThread(new Runnable() {
                    public void run() {
                        SimpleVehicleMessage simpleVehicleMsg = new SimpleVehicleMessage(message.getTimestamp(),
                                ((SimpleVehicleMessage) message).getName(),
                                ((SimpleVehicleMessage) message).getValue());
                        if (simpleVehicleMsg.getName().equalsIgnoreCase("custom_message")) {
                            customMessageView.setText(simpleVehicleMsg.getName() +": "+ simpleVehicleMsg.getValue());
                        }
                    }
                });
        }
    };

Lastly, the Starter app adds one more element to the user interface so there’s a place to display the current speed. In the main layout file for the activity, res/layout/activity_starter.xml, the existing “hello world” TextView is replaced with these two:

<TextView
    android:id="@+id/textView1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hello_world" />
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@+id/textView1"
    android:id="@+id/engine_speed" />

These widgets have IDs and are using the RelativeLayout to make sure they don’t print on top of each other.

In the app’s onCreate method, we grab a reference to that text object in Java as mEngineSpeedView:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_starter);
    // grab a reference to the engine speed text object in the UI, so we can
    // manipulate its value later from Java code
    mEngineSpeedView = (TextView) findViewById(R.id.engine_speed);
}

Finally, look back at the EngineSpeed.Listener we created before - it’s updated the UI every time a new measurement arrives:

EngineSpeed.Listener mSpeedListener = new EngineSpeed.Listener() {
    public void receive(Measurement measurement) {
        // When we receive a new EngineSpeed value from the car, we want to
        // update the UI to display the new value. First we cast the generic
        // Measurement back to the type we know it to be, an EngineSpeed.
        final EngineSpeed speed = (EngineSpeed) measurement;
        // In order to modify the UI, we have to make sure the code is
        // running on the "UI thread" - Google around for this, it's an
        // important concept in Android.
        StarterActivity.this.runOnUiThread(new Runnable() {
            public void run() {
                // Finally, we've got a new value and we're running on the
                // UI thread - we set the text of the EngineSpeed view to
                // the latest value
                mEngineSpeedView.setText("Engine speed (RPM): "
                        + speed.getValue().doubleValue());
            }
        });
    }
};

That’s all you need to do to get measurements from OpenXC. You can see the full list of Measurement Java classes that you can use in the library documentation.

Your Android device likely doesn’t have any vehicle data flowing through it yet. The next step is to use a pre-recorded vehicle trace file to simulate a real vehicle interface on your desk.

Install the Enabler app if you haven’t already. That application helps control the source of vehicle data, e.g. a vehicle interface or a trace file.

Download the driving trace and copy it to the SD card of your Android device. In the case of using an emulator, follow these steps:

  1. Open Android Device Monitor
  2. Drag and drop the trace file into the sd card folder

With an Android device, you can do this in a few ways:

  • Mount the Android device as USB disk.
  • Use the File Manager from the Android Device Monitor (included with the Android SDK).
  • Copy the file with abd on the command line: $ adb push driving.json /sdcard/openxc-driving.json
  • Download the file directly onto the device using the built-in browser

Finally, the last steps:

  1. Run the OpenXC Enabler app on the device
  2. In the Enabler, Go to Settings -> Data Sources and change the vehicle interface to a Pre-recorded Trace File.
  3. At the bottom of the screen under Trace File Playback, select a trace file for playback. You need a file manager app on your device to browse for a file. Later Android devices come with a manager pre-installed. Older devices may need to download another app, such as the OI File Manager. In the file manager, browse to the trace file you downloaded.
  4. Return to the front view of the Enabler - the message count should be increasing, indicating the trace is playing.

Run the Starter app app and you should see the engine speed changing in the UI!

You’ve now completed the OpenXC Android tutorial, but there’s more to learn about supported Android devices and vehicle interfaces. You can also check out the Android API Guide for more information on how to use the API. If you are having trouble, check out the troubleshooting steps.