Lab 7 - Android Sensors and Location Based Services
In this lab session, we will:
- Read data from the accelerometer
- Use Google maps API and GPS to locate the phone
1. Orientation indicator that displays the device orientation as Left, Middle and Right
- Open this base project
- The Main Activity has 3 TextViews in a row, with texts “Left, Middle, Right”. The plan is to dynamically set the background color based on the Accelerometer sensor value (as shown in the above figure).
In MainActivity.kt:
- Create an instance of SensorManager to access the sensor service (see code below)
- Create an instance of Sensor class, access the Accelerometer sensor through the SensorManager, and then register to the sensor listener.
- To register to sensor events, you have to implement the SensorEventListener interface and implement its two methods:
- onAccuracyChanged ( .. )
- onSensorChanged( .. )
- You can either:
- make MainActivity implement the interface
- create a new class for it
- or create an anonymous object inside MainActivity which implements it.
- To register to sensor events, you have to implement the SensorEventListener interface and implement its two methods:
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
sensorManager.registerListener(<<sensorListener>>, accelerometer, SensorManager.SENSOR_DELAY_NORMAL)
- Now read the Accelerometer data with the interfaces function onSensorChanged(event: SensorEvent)
override fun onSensorChanged(event: SensorEvent) { val x = event.values[0] val y = event.values[1] val z = event.values[2] . . . . . . }
- Observe (e.g. by logging the values) which of the axis values changes in which situation and how large the values are. Tracking one axis is enough to get the result as shown in the above screenshot.
- Add code which sets the background color of the TextView according to the device position.
- Tip: Kotlin when statement is handy here
Note: Similar to using BroadcastReceiver, we should be be attentive with when we are registering the listener and also appropriately unregister them, to avoid draining resources unnecessarily.
2. Google Maps
2.0 Add the Google Maps SDK to your project
- Let's add a Google Maps Activity to our project
In Android Studio, select New -> Activity -> Gallery...
and choose the Maps Activity. This will set up most things for us.
- To make the app open MapsActivity instead of MainActivity by default, move the Intent filters in Manifest from MainActivity to MapsActivity.
- You also need to set "exported" to true for MapsActivity, to allow the system to launch it
To use Google Map in your application you need an Google API key. Use THIS KEY (get password from lab instructor / check Slack #lab7
) or or refer to https://developers.google.com/maps/gmp-get-started to create your own API key
- In AndroidManifest.xml - insert your API key inside the android:name="com.google.android.geo.API_KEY" metadata XML tag
Run the project. You can see the default pin is added to Sydney.
If you are not seeing a map, and instead see a grey screen
- Double-check that you didn't include whitespace by mistake when copying the API key
- Check the version of the maps library in build.gradle, you may need to update it to a newer version (e.g. 'com.google.android.gms:play-services-maps:18.1.0' )
- Let us modify the onMapReady()function of MapsActivity
- Change the coordinates and title of the Sydney Marker so that it shows Delta building instead
- Coordinates of Delta:
58.385254, 26.725064
- Change the "moveCamera" line, update the Zoom level of the camera as well with:
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(delta,12F))
2.1 Reading the current location from the GPS and show it on the map
For this, we can use the Android Location API that can be used to track your mobile device’s current location.
Permission Handling
For the next parts, our app needs the android.permission.ACCESS_COARSE_LOCATION
and android.permission.ACCESS_FINE_LOCATION
permissions
- Add them both to the Manifest file
In this lab, we will manually grant the permissions via command-line, using Android Debug Bridge (ADB). Open up a terminal/commandline, and depending on your OS, locate the adb binary. It is bundled with the Android SDK installation, inside the platform-tools folder, so the exact location may depend upon where you installed Android Studio / Android SDK (how to find your SDK location)
- Windows : For default Android Studio installation:
\AppData\Local\Android\Sdk\platform-tools
- Windows : For default Android Studio installation:
Open the folder containing the adb binary (adb.exe on Windows) in command-line. Issue this command (change the package, permission package accordingly!) to grant both permissions to the app:
adb shell pm grant com.example.myapppackage android.permission.SOME_PERMISSION_HERE
If you have issues with adb, troubleshoot if adb can see your device with the command adb devices
Adding the code
- Create a new Kotlin class file called LocationHelper.kt
- (Add this code to your newly created file)
- This code uses FusedLocationProviderClient to get location updates
- NB! This code also relies on the dependency (add to build.gradle):
implementation 'com.google.android.gms:play-services-location:21.0.0' // Location Requests
- In the MapsActivity class, write declare a class field for the LocationHelper, instantiate it in onCreate().
- Now use functions of the LocationHelper (requestLoactionUpdates, stopLocationUpdates) to register for location update results.
- requestLoactionUpdates( callback: LocationCallback) need you to implement the LocationCallback (which is an abstract class).
- You can create an anonymous object which extends that class, and override the onLocationResult( result: LocationResult) method of the LocationCallback interface
val myCallBackImplementation = object: LocationCallback(){ override fun onLocationResult(result: LocationResult) { // .. do something with result } }
- Inside onLocationResult, update the Google Map by adding a 2nd marker (located at the acquired user location), and moving the camera to it
- requestLoactionUpdates( callback: LocationCallback) need you to implement the LocationCallback (which is an abstract class).
inside onMapReady() to obtain the current location and then pin it on the map: mMap.addMarker(MarkerOptions().position(coords).title("You are here ")) mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(coords,15F))
Note: right now, we are adding a *new marker* on every location update. Eventually, this will clutter the map and waste device memory. A better solution would be declare a single marker as a class field, and update its location on every update!
2.2 My Location Button
Alternatively, we can use the "My Location" Button, which also displays the current location.
- Add the following line to onMapReady() function ( you can comment out the previous GPS location code)
mMap.setMyLocationEnabled(true)
Once you click on the button, it will show your current location. Let’s make this app more interactive. Once you touch on the current location it will pin the location with the address. For this, we need our MapsActivity class implements OnMyLocationClickListener interface.
class MapsActivity : AppCompatActivity(), OnMapReadyCallback ,GoogleMap.OnMyLocationClickListener { . . . . }
To get the address of the current location, we use google Reverse Geocoding API. https://developers.google.com/maps/documentation/geocoding/intro#ReverseGeocoding
- Add the following codes to the override fun onMyLocationClick(location: Location) function
override fun onMyLocationClick(location: Location) { val latitude = location.latitude val longitude = location.longitude var latlng= LatLng(latitude,longitude) val geocoder = Geocoder(this) val addresses = geocoder.getFromLocation(latitude, longitude, 1) val loc = addresses[0].getAddressLine(0) mMap.addMarker(MarkerOptions().position(latlng).title("You are here $loc")) mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latlng,15F)) }
- After that add mMap.setOnMyLocationClickListener(this) to the onMapReady() function
3. Draw a line on the map
Here we just draw a straight line between two locations without considering the real routes between them. We draw the line from the Delta building to the current location of the device. However, google maps API provides a facility to draw a route on the map with waypoints.
- Create a function which use Polyline
fun drawPolyLine(){ var start = LatLng(58.385254, 26.725064 ) var end=LatLng(latitude,longitude) mMap.addPolyline(PolylineOptions().add(start,end)) }
- Now call this function in the onMyLocationClick() to draw the line