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.
The demo code used in this lecture include:
Some excellent references.
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.
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)
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.
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.
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:
Geocoder
class requires a backend
service that is not included in the core android framework.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"