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.
To use Location (needed for this lecture), Google Maps, Activity Recognition, Cloud services have to set up your environment to use Google Play Services -- if you don't then you can't do anything. You have to:
Below we shows a list of the separate APIs that you can include when compiling your app; specifically we add dependencies for location/ activity recognition and maps. You will need to add these to implement MyRuns4.
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.0'
implementation 'com.google.android.gms:play-services-location:11.8.0'
implementation 'com.google.android.gms:play-services-maps:11.8.0'
testImplementation 'junit:junit:4.12'
}
You should also make sure for this lecture that you update your manifest with permission associated with the level of location updates; as shown below:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
and to specify that the project is using the play services and its version
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
So to run the code snippets you'll need to do the first item above. If you use new components that reply on play then you need to do the second item as well.
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 onCreate() code of the WhereAmI 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.
public class WhereAmI extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LocationManager locationManager;
String svcName = Context.LOCATION_SERVICE;
locationManager = (LocationManager)getSystemService(svcName);
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:
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:
The code snippet is taken again from onCreate(). 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.
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setPowerRequirement(Criteria.POWER_LOW);
criteria.setAltitudeRequired(false);
criteria.setBearingRequired(false);
criteria.setSpeedRequired(false);
criteria.setCostAllowed(true);
String 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 onCreate() 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.
Location l = locationManager.getLastKnownLocation(provider);
updateWithNewLocation(l);
locationManager.requestLocationUpdates(provider, 2000, 10,
locationListener);
}
Once the user gets the last location it class the updateWithNewLocation() function with the Location. updateWithNewLocation() 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.
But the last line of code in the onCreate() refreshes the current location is the key to tracking location of a device. The locationListener() method sets up a call back locationListener() which is called periodically. As shown above, onCreate() calls requestLocationUpdates() passing a LocationListener object called locationListener. The other parameters are:
When a location change has been detected then locationListener callback is fired -- that is, the LocationListener is used for receiving notifications from the LocationManager when the location has changed. Assuming that the LocationListener has been registered with the location manager uses the requestLocationUpdates(String, long, float, LocationListener) method as the call back for handling the new Location object. There are four callbacks:
In our code snippet we simply call updateWithNewLocation() if the onLocationChanged() is called.
private final LocationListener locationListener = new LocationListener() {
public void onLocationChanged(Location location) {
updateWithNewLocation(location);
}
public void onProviderDisabled(String provider) {}
public void onProviderEnabled(String provider) {}
public void onStatusChanged(String provider, int status,
Bundle extras) {}
};
In the updateWithNewLocation() 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 Geocoder class comes with the Google Maps library (see next section). To use the library you have to import it into the application. In addition, the Geocoder class uses a server to translate over the Internet so you need to add the following permission to the Manifest:
<uses-permission android:name="android.permission.INTERNET" />
The first part of the updateWithNewLocation() method simply prints out the longitude and latitude to the UI. 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, sorry, I mean American). The getFromLocation(latitude, longitude, 1) returns an Address object. The list can contain a number of possible results; that is, getFromLocation returns an array of Addresses 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 the method uses a StringBuilder to get and format the address for display to the UI, as shown in the snippet below.
private void updateWithNewLocation(Location location) {
TextView myLocationText;
myLocationText = (TextView)findViewById(R.id.myLocationText);
String latLongString = "No location found";
String addressString = "No address found";
if (location != null) {
double lat = location.getLatitude();
double lng = location.getLongitude();
latLongString = "Lat:" + lat + "\nLong:" + lng;
double latitude = location.getLatitude();
double longitude = location.getLongitude();
Geocoder gc = new Geocoder(this, Locale.getDefault());
try {
List<Address> addresses =gc.getFromLocation(latitude, longitude, 1);
StringBuilder sb = new StringBuilder();
if (addresses.size() > 0) {
Address address = addresses.get(0);
for (int i = 0; i < address.getMaxAddressLineIndex(); i++)
sb.append(address.getAddressLine(i)).append("\n");
sb.append(address.getLocality()).append("\n");
sb.append(address.getPostalCode()).append("\n");
sb.append(address.getCountryName());
}
addressString = sb.toString();
} catch (IOException e) {}
}
myLocationText.setText("Your Current Position is:\n" +
latLongString + "\n\n" + addressString);
}