This update describer the Android application created to manage the acquisition of points for the competition. At this moment, the competition only includes running or walking the maximum number of km at the end of each month. Also, since we can not trust the participants good will (or, since we know how witty they can be) we will prevent two basic cheating:
- Driving - it will be an easy, fast and effortless way of increase the status
- Shaking the phone -the resident does not move but still increases the total distance
So, the distance tracking will involve both GPS location and the phone's accelerations data
Furthermore, the application will have to retain the information after being close. Consequently, we will be using a local database in the phone, an SQLite.
This update will developing in the User's Node, a Nexus 5 phone.
Main application
Initial setup:Nexus 5 - Android 6.0.1
Full code in github - SmartApp
MainActivity
When launching, the user will be able to select one of our two activities:
- SmartHome - currently disabled till previous version is updated
- CompetitionActivity - to be used when the person wants to improve their status in the competition. It will start recording and update the central node
Competition System Activity - Version 1 (ONLY INDIVIDUAL TRACKING)
The GUI is constantly updated to show:
- Current distance
- Today's distance
- Monthly distance
Additionally, this information is stored in a local database in the phone.
PODIUM (Not enabled yet) > request other residents information from the server and see current state of the competititon
How to track the phone distance
GPS Location
To access GPS Location - we use Android Location library (android.location)
More in detail, the app uses of its classes:
- Location - hold the GPS information (such as latitude and longitude) and offers some methods to operate them.
- LocationListener - offers callbacks when the location, status or accuracy has changed
- LocationManager - manager of the Location Service.
The app needs to obtain current location, compare it with the previous one and extract the distance.
This distance will then be updated in the main GUI and included in monthly and daily calculations.
Permissions
For certain sensible operations, an Android app will have to request permission (so that the user can decide whether to grant it or not). It can be done in the apps Manifest (programmatically) or directly requested at run time .
We include the COARSE and FINE LOCATION permission in our Android Manifest:
<!-- GPS --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
NOTE - For API 23 or more: permission request needs to be done during run time. So, I include the following lines in our CompetitionActivity (inside the onCreate() method)
** It also includes request for the external storage, needed for the local database
// ****** REQUESTS ***** //Location and Write/Read external storage requestPermissions(INITIAL_PERMS, INITIAL_REQUEST); //Check permissions if (!canAccessLocation() || !canAccessMemory()) { requestPermissions(INITIAL_PERMS, INITIAL_REQUEST); Toast.makeText(ctx, "Request Permissions", Toast.LENGTH_LONG).show(); }else { //Start tracking service if ( ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED ) { System.out.println("Start tracking "); mLocationManager = (LocationManager) getSystemService(LOCATION_SERVICE); mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, LOCATION_REFRESH_TIME, LOCATION_REFRESH_DISTANCE, mLocationListener); }else{ //Do nothing } }
The code will first request the permission. Afterwards, checks whether it was granted (if not, requests again) and if so, starts the GPS Tracking activity
The code: CompetitionTrackActivity.java
We will have to create an instance of LocationManager and attach a self defined LocationListener(we did so when checking and requesting the permissions):
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, LOCATION_REFRESH_TIME, LOCATION_REFRESH_DISTANCE, mLocationListener);
The mLocationListener itself implements the methods:
- onLocationChanged - this is the one used to obtained the distance
- onStatusChanged - not used for now
- onProviderEnabled - not used for now
- onProviderDisabled - not used for now
Withe onLocationChanged, we will compare the new obtain location with the previous one and obtained the distance between them:
@Override public void onLocationChanged(final Location location) { //Check if it is the first updated location if (previousLocation ==null) { previousLocation = location; }else{ float distance = previousLocation.distanceTo(location); CompetitionTrackActivity.updateSessionDistance(distance); previousLocation = location; } } }
(*) CompetitionTrackActivity.(distance) is a method to update the GUI with the new data
Problem with this approach: cars
It is very easy to gain a lot of kms while driving or commuting in the train if we just implement this approach.
A direct solution, using the same library is calculating the speed too (we have the distance, we can timestamp each location ---> V = distance/ time_variation ). If the speed is above a threshold (say.... 20km/h as the do in Pokemon Go), we discard the obtained distance
This app, however, will combine GPS solution with a step counter (to make sure the person is moving).
!!! GPS Location will probably drain a lot of battery, so I will keep an eye on it
Step counter from acceleration data
To count the steps we make use of the hardware Android library(we will use accelerometer data)
With this approach, we will count the number of steps. Since the system will always give us the total step number, the StepCounter will store the initial step_number and obtain the steps walked by subtracting to the absolute total.
Again, we implement a listener that will tell the app when the step number has bee updated.
The code: CountSteps.java
CountSteps implements SensorEventListener, with the meethods:
- onSensorChanged() - when new step is recorded
- onAccuracyChanged() - not used
To obtain the number of steps walked, the app has the following code:
@Override public void onSensorChanged(SensorEvent event) { if (activityRunning) { if (!started) { initial_count = event.values[0]; started = true; } System.out.println(event.values[0] + "vs "+initial_count); count = event.values[0] - initial_count; } }
Problem with this approach:shaking the phone
As with any other step counter, if you shake the device the number of steps increases. We will need other methods to determine whether the person is really moving or not (aka GPS location).
This solution - Combination of both
In the final app, we use the GPS LocationListener to determined that the person is moving. Also, we include a min_distance parameter to make sure it is, in fact, moving and not some GPS variation.
However, the distance is not obtain with this new location, but with the step counter ! (again, this may not be a good enough solution: in the future, we can try to correlate the GPS distance and the distance obtained from the steps themselves to see any kind of trick).
The code is in this case:
@Override public void onLocationChanged(final Location location) { //Check if it is the first updated location if (previousLocation ==null) { previousLocation = location; }else{ float distance = previousLocation.distanceTo(location); previousLocation = location; //Obtain distance from step counter, not GPS float steps = stepCounter.getSteps(); if (distance > MIN_DISTANCE_THRESHOLD) { Toast.makeText(ctx, "Location steps: " + steps, Toast.LENGTH_LONG).show(); if (steps >0) CompetitionTrackActivity.updateSessionDistance(stepsToKm(steps)); }else { //Restart counter // Steps but no movement??? Cheating.... initial_steps = initial_steps +steps; stepCounter = new CountSteps(ctx, initial_steps); } }
How to retain the information
Local database - SQLite
SQLite library
Permissions
We include the WRITE and READ EXTERAL STORAGE permission in our Android Manifest:
<!-- Write and read --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
NOTE - For API 23 or more: permission request needs to be done during run time. So, I include the following lines in our CompetitionActivity (inside the onCreate() method)
** This code is shown in the previous section
The code: DatabaseManager.java
First, I developed the functions to constantly store the distance values in a table. This table has the user as name, and columns for the time_stamp, current distance, day distance,month distance and updated(to be used when updating to the central node server).
Thanks to this database the app will retrieve today's and the month accumulated distance when restarting !!
The final result
Please see the following video showing the first running of the application
Conclusion
In this post I explained how to create an Android application to track the traveled distance. This is the main app of the competition system. It will:
- Record the resident's distance:
- current distance
- update the values of day total and month total (since it is a monthly competition!)
- Store it in a local database
Top Comments