During this session, you will be developing an Android application, that displays real time data regarding the vehicle's actual state (RPM, speed). A second screen will hold a map, and show the route the car has taken, and a third one presents all the data in tabular format.
Introduction
The Android platform is the easiest mobile platform to start development. Android applications can be built on any operating system, IDEs are freely accessible, and the language is strongly rooted in the Java language, with previous knowledge of Java, only the platform specific concepts need to be learned, most of the core libraries can be used on Android devices, and many third party libraries were ported or are usable as is. The full Android documentation is accessible on the Android Developers site.
There are several mapping frameworks available on Android, the most popular being OSM and Google's own Maps. We will be using the latter. While proprietary, it's freely usable on the Android platform. There are also several charting frameworks, But specific components used in car dashboard are missingfrom most of them. Hence we'll be using a less popular framework, that has an adequate implementation: sule.io. As there was a bug in both the Maven repository, and the downloadable source code, we included a patched version below.
Exercises
You will be running two different images for the lab. One is named OBD, this will be the development machine. The other is the OBD Android image, and will act as an Android tablet.
Setting up the virtual Android device
Before starting the vrtual machine running your Android device, check which network interface is connected to the lab's network. After the host operating system booted up, launch a terminal, and list the interfaces:
sudo su ifconfig -a
After this, make sure that the network is set to Bridge mode, and that the active interface is forwarded to the virtual machine. This is the best way to guaranty that your device will be accessible from the development machine.
Setting up the developer machine
Check networking for the developer machine too, and start up the development machine. After starting it, set the resolution and the pixel density, so that the windows will display in full screen, and correctly.
xrandr -s 1920x1080 --dpi 96
Creating and Android project
We'll be using Android Studio for development. The 2.1 version is installed on the virtual machines along with version 23 of the Android SDK, with the proper additions.
- Download and add the debug.keystore, so that your Google Maps key can be accessed!
- Download the attached .zip file, containing the project.
- Start Android Studio (/usr/local/android-studio/bin/studio.sh)
- Import the project! (File > New > Import Project)
- Compile the project!
- Review the project's components!
Setting up the virtual Android device as debug target
After you set up your devices:
- On the virtual Android, check your IP address(Settings > About Tablet > Status).
- On the development machine, restart adb in the /home/obd/Android/Sdk/platform-tools folder, with the command ./adb kill-server), and connect to your virtual Android./adb connect x.x.x.x.
- Check your connection by listing your connected devices(./adb devices)!
In case of an error, try restarting adb on the virtual Android:
su
setprop service.adb.tcp.port 5555
stop adbd
start adbd
And reconnect afterwards.
If everything works fine, deploy your application from Android Studio to your virtual Android. It won't do much, but that's a start.
If you're not able to start the application from Android Studio, you can use adb to install the application. To make this easy, add the /home/obd/Android/Sdk/platform-tools directory to the path.
You can regenerate your APK file from the Build-menu.
To reinstall the APK, navigate to app/build/outputs/apk folder, and execute the adb install -r app-debug.apk command.
Dashboard
The first view that we'll be creating is the dashboard. There's already a gauge component in the linked library.
- Add the attached .aar library to your project!
- File > New > New Module... > Import .AAR package
- Add the gradle dependency to the project's build.gradle file: compile project(':Name-Of-Your-Module')
- Create a new layout resource for your dashboard. (File > New > Android Resource File) by choosing the proper type (Layout) let's name it framgent_dashboard.
- Add the gauge namespace to the root element of the layout (LinearLayout):
xmlns:gauge="http://schemas.android.com/apk/res-auto"
- Add a gauge to the layout (io.sule.gaugelibrary.GaugeView). Set it's id to "speedMeter".
<io.sule.gaugelibrary.GaugeView android:id="@+id/speedMeter" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:background="#ffffff" gauge:showOuterShadow="false" gauge:showOuterRim="false" gauge:showInnerRim="false" gauge:needleWidth="0.010" gauge:needleHeight="0.40" gauge:scaleStartValue="0" gauge:scaleEndValue="100" gauge:showNeedle="true" />
- Add a new class (right click on hu.bme.tmit.obddashboard csomagra, then New > Java Class), let's call it DashboardFragment.
- Make it extend the android.support.v4.app.Fragment class. Make sure you're including the class from the support package!
- Override the onCreateView method!
- Use the Inflater received as a parameter, and inflate a new instance of the previously created layout, with the inflate method.
- Get a reference to the speedMeter throught the findViewById method.
- Make your newly created DashboardFragment inherit from the OBDListener interface!
- After adding the valueChanged method, filter the received events so that they only respond to "vehicle_speed" data. When such an entry is received, parse a float from the received data, and set the gauge to that value with the setTargetValue method.
To check out your fancy new layout, you'll have to implement the first part of the "Application logic" exercise. Precisely create a DashboardFragment in the Activity's onCreate method.
Value list
The value list view is a debugger view that displays the last received OBD information in a list.
- Let's create a layout that holds a ListView!
- Create another view that will act as a template for the list items. It should have two TextView components in a LinearLayout!
- Create a new class, and call it ValuesFragment.
- Make it extend the android.support.v4.app.Fragment class!
- Just as earlier, override the onCreateView method!
- Yet again, take advantage of the received Inflater, and inflate a new intance of your view with the inflate method!
- Get a reference of the listview!
- Create a SortedMap typed variable that will hold the received data in a String -> String map! Initiate it with an empty map in the onCreateView method!
- Make your class implement the OBDListener interface!
- In the valueChanged method, put the received enrty in your sorted map.
- Now it get's tricky. After putting the new item in the map, create a SimpleAdapter-t. This adapter will present the list to the list view. Check the API documentation for the proper initialization, choose the constructor that fits your Map! To get a reference to the Context, just call getContext. Your second paramter will be a list of maps. The inner map will have two items, in our case, one's key should be "KEY" and the other's "VALUE". The resource should be the layout's id that you created for the list item. The from array should hold the strings {"KEY", "VALUE"}, and the to array the ids of the associated text views.
- Once you're done with that, you just have to set the created adapter as the list's adapter.
Map
The map view will display the path travelled.
- Start by extending the SupportMapFragment class! This version of GoogleMaps uses async calls to set up the interface. You will specifically be using the getMapAsync method.
- In the callback, make the settings below:
UiSettings settings = googleMap.getUiSettings();
settings.setAllGesturesEnabled(false);
settings.setMyLocationButtonEnabled(false);
To show the traveller's path, use the addPolyline method. When receiving new location data in the valueChanged method, draw a new segment from the last location data to the current one! At last, set a 10 zoom level for the camera, and move it to the received location with themoveCamera method!
Application logic
With all the components ready, add your Fragments to the Activity, and connect to the OBD data service. The needed steps are indicated in the Activity's source code behind TODO comments. Just follow the instructions!
Managing Fragments
The Activity and Fragment lifecycle are managed by the Android framework. The major difference between the two is that Activitys are always instantiated by the framework, you never have to call the constructor of an Activity. While when handled dynamically, it's the developers job to create Fragment instances. This is done in the onCreate method for the initial layout, and in response to navigation later on. It's important however that only a single instance exists of a fragment. To avoid creating multiple instances of the same fragment after the device is put to sleep and woken back up, or the user switches back and forth between applications, you always have to check for an existing instance before creating one.
Connecting to the OBD service
Service connections should be established when the Activity resumes. You'll get notified of this event in the onResume method. In order to avoid exceptions, you have to disconnect in the onPause method. Connecting to a service is an asynchronous process, once you initiate the connection, you'll get notified of the result through the onServiceConnected method. It will also provide a Binder that you'll be able to use to access your service's exposed methods.
Procesing events
After subscribing to event on the Binder, the service will play back timed events from a captured data set. You'll have to forward this event to the currently visible Fragment. To update the user interface in Android, the platform uses a separate Thread, this makes the interface fluid and responsive - as long as not abused. You can schedule tasks to run on this thread with the runOnUIThread method. As your event handling code in the Fragments directly manipulates UI components, you'll have to call the Fragment's valueChanged from a Runnable that runs on the UI thread.
Extending the dashboard
- Add two other gauges to the dashboard, and display "torque_at_transmission" and "engine_speed". This last one should be scaled down by 100.
- Create a new layout to handle landscape orientation.