Location-Based Service

What makes mobile so different is we can build advanced services that only mobile device with sensing can deliver. We do this all the time on our phone: do a location based search for say pizza store, cafe, cinema -- and the phone uses the location of the phone as an input to the search. This is called location based service.

Android provides a number of building blocks for location based services that we will discuss in this lecture.

What this lecture will teach you

Demo projects

The demo code used in this lecture include:

Resources

Some excellent references.

Location manager

The Android location manager gives location in terms of longitude and latitude for the location of the phone. Depending on the location provider selected (could be based on GPS, WiFi or Cellular) the accuracy of the location will vary.

The key Android plumbing for location is:

A number of services can be built using these simple components:

As part of the initLocationManager() code of the Where_am_I_Kotlin app we see the location manager being set up. The user gets a location manager by specifying the LOCATION_SERVICE as input to the getSystemServicE() of the activity. A location manager is returned.

private fun initLocationManager() {
try {
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
...
...
} catch (e: SecurityException) {
}
}

In the Manifest you will see that it is necessary to get the user's permission to track their location or get a location reading:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

There are fine and coarse permissions that the developer can use. The defaults is: if you ask for ACCESS_FINE_LOCATION then by default you get coarse.

FIne is typically associated with GPS and coarse location with Cellular or network.

Location Provider

Mobile phones can provide location from a set of providers that make a number of trade offs. For example, GPS has good accuracy outdoors but is costly in terms of energy tax for using the GPS chips on the phone. In contrast, Cellular is cheap in terms of energy consumption but could provide very coarse location information (say in the upper valley) because of the lack of cell tower density but could be great in the city. There are a number of trade offs that the user might want to make when selecting a location provider. Basically depending on what location device the user selects there are a number of different trade offs in:

The user can specify the location provider explicitly in the code using a number of constants (e.g., LocationManager.GPS_PROVIDER) but it would be poor programming to rigidly specify the provider -- for example, the user might turn off GPS -- then what. It is better to let the Android systems match the user's needs to what providers are on offer. To do this we use Criteria as shown below. The code states that the user requires fine accuracy but other criterias can be specified (e.g., low power consumption).

The code snippet is taken again from initLocationManager(). The user specifies the level of location information and then asks the system for the best provider given what is currently available and the user's needs.

val criteria = Criteria()
criteria.accuracy = Criteria.ACCURACY_FINE
val provider = locationManager.getBestProvider(criteria, true)

Finding Where Am I

Getting the location of the device (and user) is a cool service offered by phones. There are a number of best practices when apps use location. First, you need to respect the privacy of the user; only updating the location when necessary; letting the user know if you are tracking them and where the information is being stored, communicated, etc; and allowing the user to disable tracking.

To find the last location of the device you need to use getLastKnownLocatioN(), as shown below in the initLocationManager() code snippet. getLastKnownLocation() returns a location indicating the data from the last known location fix obtained from the given provider. This can be done without starting the provider. Note that this location could be out-of-date, for example if the device was turned off and moved to another location. If the provider is currently disabled, null is returned.

val location = locationManager.getLastKnownLocation(provider!!)
if(location != null)
onLocationChanged(location!!)
locationManager.requestLocationUpdates(provider, 0, 0f, this)

Once the user gets the last location we call  onLocationChanged() with the Location. OnLocationChanged() is one of the three callbacks that need to be implemented when we implements LocationListener as a part of our MainActivity (e.g., class MainActivity : AppCompatActivity(), LocationListener{...}). The other two callbacks are:

In our implementation of onLocationChanged(), we prints out the longitude and latitude of the location and then gets a human readable address from the coordinates using Geodecoders. We will discuss Geodecoders in a moment. We leave the other two callbacks empty.

Tracking your location

Within the initLocationManager(), requestLocationUpdate() is the key to tracking location of a device. It registers the LocationListener object (e.g., "this" in the parentheses) with the location manager to tell the system to get GPS data periodically. Aside from the location listener and location provider, the other two parameters are:

When a location change has been detected then the onLocationChanged() callback is fired -- that is, the LocationListener is used for receiving notifications from the LocationManager when the location has changed.

Geocoding

In the onLocationChanged() method the Location object is transformed into two types of output:

Geocoder do the translation from long/lat to address. Geocoding is the process of transforming a street address or other description of a location into a (latitude, longitude) coordinate. Geocoder supports two services:

The first part of the onLocationChanged() method simply prints out the longitude and latitude to the UI.

override fun onLocationChanged(location: Location){
if (location == null) return
try {
val lat = location.latitude
val lng = location.longitude
line1 = "Latitude: $lat \nLongitude: $lng"
line2 = ""

...
...
} catch (e: Exception) {
}
}

The interesting stuff is the next section which does the reverse geocoding.

First the Geocoder is created by setting the Locale.getDefault() locale (used to define your usual location and language -- Hanover and English). The getFromLocation(latitude, longitude, 1) returns a list of Address objects. The list can contain a number of possible results that are known to describe the area immediately surrounding the given latitude and longitude. The parameters are:

In the code below maxResults = 1. We only want the servers best shot. Once the Address is returned, we traverse the address lines to get and format the address for display on the UI, as shown in the snippet below.

val geocoder = Geocoder(this, Locale.getDefault())
val addresses = geocoder.getFromLocation(lat, lng, 1)
val address = addresses[0]
for (i in 0..address.maxAddressLineIndex)
line2 += "${address.getAddressLine(i)}\n"
textView.text = "$line1 \n\n$line2"