In This Chapter
• Displaying Web Pages Through WebView
• Adding Permission for Internet Access
• Obtaining Google Keys
• Creating an AVD for Map-Based Applications
• Using Location-Based Services
• Supplying Latitude and Longitude Values Through DDMS
• Adding Zoom
• Displaying Markers on the Map
It is interesting to view web pages and Google Maps through Android applications. The Internet and the web pages found there are a huge source of information today. It also is often beneficial to embed a Google Map in an Android application. Maps provide an easy way to search destinations. They are heavily used in the fields of education, hotel business, travel, and tourism. Besides normal map view, Google Maps also display satellite views of the desired locations.
WebView
is a widget commonly used for viewing web applications or pages. It displays web pages as a part of our activity layout.
Let’s make a small browser application that prompts the user to enter the URL of a website, and then loads and displays it through the WebView
control. Launch the Eclipse IDE and create a new Android application. Name the application WebViewApp
.
In this application, we use TextView
, EditText
, Button
, and WebView
controls. The TextView
control displays the text Address:
to tell the user that the URL of the website must be entered in the adjacent EditText
control. The EditText
control displays an empty text box where the user can enter the URL of the site to open. When the user clicks the Button
control, the website whose URL was entered in the EditText
control is loaded and displayed via the WebView
control. To define these four controls, the code shown in Listing 10.1 is written in the activity_web_view_app.xml
layout file.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Address:"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/web_url"
android:hint="http://"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/go_btn"
android:text="Go"
android:paddingTop="10dip"
android:paddingBottom="10dip"
android:paddingLeft="35dip"
android:paddingRight="35dip"
android:layout_gravity="center"/>
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
We can see that the TextView
control is set to display the text Address:
. The ID assigned to the EditText
control is web_url
, and its hint
text is set to http://
. The hint
text appears in a light color inside the EditText
control to tell the user about the type of data that should be entered. The ID assigned to the Button
control is go_btn
; the button caption set is Go
; and the spacing of the button text from the left, right, top, and bottom border of the button is set to 35dip
, 35dip
, 10dip
, and 10dip
, respectively. The Button
control itself is set to appear at the center of the View
. The last control, WebView
, is assigned the ID web_view
.
Modify the Java activity file WebViewAppActivity.java
to appear as shown in Listing 10.2.
package com.androidunleashed.webviewapp;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.webkit.WebView;
import android.view.KeyEvent;
public class WebViewAppActivity extends Activity implements OnClickListener {
EditText url;
WebView webView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_view_app);
url = (EditText)this.findViewById(R.id.web_url);
webView = (WebView) findViewById(R.id.web_view);
webView.setInitialScale(50);
webView.getSettings().setJavaScriptEnabled(true);
url.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if ((event.getAction() == KeyEvent.ACTION_UP) && (keyCode ==
KeyEvent.KEYCODE_ENTER)) {
webView.loadUrl(url.getText().toString());
return true;
}
return false;
}
});
Button b = (Button)this.findViewById(R.id.go_btn);
b.setOnClickListener(this);
}
@Override
public void onClick(View v) {
webView.loadUrl(url.getText().toString());
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
webView.goBack();
return true;
}
return super.onKeyUp(keyCode, event);
}
}
The EditText
and WebView
controls from the layout file are captured and mapped to the EditText
and WebView
objects, url
and webView
, respectively. The size of the web page that is loaded and viewed through the WebView
control is resized to 50%
. We want the user-entered website URL loaded into webView
when either of the following events occurs:
• When the user presses the Enter
key in the EditText
control
• When the user clicks the Go
button after entering the URL in the EditText
control
To accomplish this, we need to associate a KeyListener
with the EditText
control and a ClickListener
with the Button
control so that the onKey()
callback method is called when any key is pressed in the EditText
control and the onClick()
callback method is called when a Click
event occurs on the Button
control. In the onKey()
method, we check for two things:
• Whether the event that has occurred is connected to a keypress
• Whether the code of the pressed key matches that of the Enter
key
In short, the onKey()
method checks for the occurrence of the Enter
key in the EditText
control. If the Enter
key is pressed, the URL of the site entered in the url EditText
control is loaded and displayed in the WebView
control using the loadUrl()
method.
The onKey()
callback method is set to return the Boolean value true
if we don’t want to listen for more keypresses and want to terminate the event handler and exit from the method to do something else. The method is set to return the Boolean value false
if we do not want to terminate the event handler and want to continue to listen for more keypresses. We can see in the onKey()
method that when the user presses the Enter
key, we terminate the KeyListener
event handler by returning the Boolean value true
in the callback method. We return the Boolean value false
from the onKey()
callback method to have the Key Listener listen for more keys until the Enter
key is pressed in the EditText
control.
Note
Every keypress consists of several key events. Each key event has an attached key code that helps to identify which key is pressed.
Similarly, in the onClick()
callback method that is called when a click event occurs on the Button
control, we simply load the website whose URL was entered in the url EditText
control.
Our web page may not work properly if it contains JavaScript code because the JavaScript is disabled by default in a WebView
. Let’s learn more.
It may happen that the web page we load through WebView
contains JavaScript. JavaScript is disabled in a WebView
by default. To view the web page correctly, we need to enable JavaScript for our WebView
. The JavaScript is enabled through the WebSettings attached to our WebView
. We can retrieve the WebSettings with the getSettings()
method, and then enable JavaScript with setJavaScriptEnabled()
method.
When the WebView
widget is used for loading web pages, it maintains a history of visited web pages. We can use this web page history to navigate backward and forward. To view the previous or next page, the goBack()
and goForward()
methods are used.
The function
public boolean onKeyUp(int keyCode, KeyEvent event)
checks to see whether a key is released. The function calls goBack()
to navigate to the previous page in the history. The goBack()
method is called when both conditions are true, that is, if the Back
key is pressed and the canGoBack()
function returns true
. The canGoBack()
method returns true
only if there exists a web page history for us to visit. Similarly, we can use canGoForward()
to check whether there is a forward history.
Note
After reaching the end of the history, the goBack()
or goForward()
methods do nothing.
Table 10.1 shows an outline of different methods provided by the WebView
class.
The only step required to complete this application is to add permission to access the Internet from within the application.
Our application must have access to the Internet to load a web page, so we need to request Internet permission in the AndroidManifest.xml
file by adding the following statements:
<manifest ... >
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>
Our manifest file now appears as shown in Listing 10.3. Only the code in bold is newly added; the rest is the default code automatically created for us by the ADT after creating a new Android project.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidunleashed.webviewapp"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8"
android:targetSdkVersion="15"/>
<uses-permission android:name="android.permission.INTERNET" />
<application android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity android:name=".WebViewAppActivity"
android:label="@string/title_activity_web_view_app">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Now our application is complete, and we can run it. The first screen that we see on startup is shown in Figure 10.1 (left). The white space below the Go
button represents the WebView
control, which is initially blank. We can type the URL of the website we want to view into the EditText
control and either press the Enter
key or click the Go
button to load and view it via the WebView
control. After we load my website, http://bmharwani.com
, the website is displayed as shown in Figure 10.1 (middle). After we select a link, the WebView
control is updated to display information about the linked web page, as shown in Figure 10.1 (right). If we click the Back
key, we move back one step in the browsing history, and the web page shown in Figure 10.1 (middle) reappears in the WebView
control.
Note
After we enter the URL in the EditText
control and press Enter
, the application doesn’t insert a blank line in the EditText
box. Instead, it navigates to the entered URL because we have associated a KeyListener
with the EditText
control.
One thing that you will observe when executing the application is that when any link is selected, the web page loads, but it covers the entire view, making the TextView
, EditText
, and Button
controls invisible. Android invokes the default web browser to open and load the linked web page instead of using the application’s WebView
control. To override this problem, we use the WebViewClient
class.
Android invokes the default web browser to open and load the linked web page. To open links within our WebView
, the WebViewClient
class and its shouldOverrideUrlLoading()
method are used.
So, open the WebViewApp
Android application and modify its WebViewAppActivity.java
Java activity file to perform the following tasks:
• Use the WebViewClient
class.
• Use the shouldOverrideUrlLoading()
method to load the clicked links in the WebView
control.
The code in the file WebViewAppActivity.java
is modified to appear as shown in Listing 10.4. Only the code in bold is modified.
package com.androidunleashed.webviewapp;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.webkit.WebView;
import android.view.KeyEvent;
import android.webkit.WebViewClient;
public class WebViewAppActivity extends Activity implements OnClickListener {
EditText url;
WebView webView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_view_app);
url = (EditText)this.findViewById(R.id.web_url);
webView = (WebView) findViewById(R.id.web_view);
webView.setInitialScale(50);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient(){
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
url.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if ((event.getAction() == KeyEvent.ACTION_UP) && (keyCode ==
KeyEvent.KEYCODE_ENTER)) {
webView.loadUrl(url.getText().toString());
return true;
}
return false;
}
});
Button b = (Button)this.findViewById(R.id.go_btn);
b.setOnClickListener(this);
}
@Override
public void onClick(View v) {
webView.loadUrl(url.getText().toString());
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
webView.goBack();
return true;
}
return super.onKeyUp(keyCode, event);
}
}
We use two functions explained here:
• setWebViewClient()
The setWebViewClient()
function replaces the current handler with the WebViewClient
, enabling us to utilize its methods to make all the clicked links open in our WebView
control.
• public boolean shouldOverrideUrlLoading (WebView view, String url)
The view
and url
parameters used in the shouldOverrideUrlLoading()
method refer to the WebView
that is initiating the callback and the URL of the clicked link, respectively.
By default, the WebView
asks the Activity Manager
to choose the proper handler to open a clicked link in a web page. The Activity Manager
in turn invokes the user’s default browser to load the url
of the linked page. Through this function, we can decide whether we want the host application or the WebView
to load the linked url
. If the function returns true
, it means that we want the host application to handle the url
. If the function returns false
, it means we don’t want the host application to interrupt and prefer the WebView
to handle the url
; that is, we want the url
to be loaded in our WebView
.
After making these changes in the activity file, we see a screen prompting for a URL. The web page whose URL was entered in the EditText
control is then loaded and opened in the WebView
control, as shown in Figure 10.2 (left). After we select any link, the linked page also opens in the WebView
control (see Figure 10.2—right). The default browser no longer handles the linked URL.
We can create map-based applications in Android to display maps in different views. For example, we can have satellite views, traffic views, and street views of different places. We can locate or pinpoint places on the map and zoom in and out. We can also use Location-Based Services (LBS), which use latitude and longitude values to locate the device or emulator.
To access Google Maps in our Android application, we need to have a Google Maps API key first.
You need to apply for a free Google Maps API key before you can integrate Google Maps into your Android application. The steps for obtaining a Google key are as follows:
1. To get a Google key, the application needs to be signed with a certificate and you need to notify Google about the Hash (MD5) fingerprint of the certificate. To test the application on the Android emulator, search for the SDK debug certificate located in the default folder: C:Users<user_name>.android
. The filename of the debug certificate is debug.keystore
. For deploying to a real Android device, substitute the debug.keystore
file with your own keystore file.
2. Copy the debug.keystore
to any drive.
3. Using the debug keystore certificate, extract its MD5 fingerprint using the keytool.exe
application provided with the JDK installation. This fingerprint is needed to apply for the free Google Maps key. The keytool.exe
file can be found in the C:Program FilesJavajdk_version_numberin
folder.
4. Open the command prompt and go to the C:Program FilesJavajdk_version_numberin
folder using the CD
command.
5. Issue the following command to extract the MD5 fingerprint:
keytool.exe -list -alias androiddebugkey -keystore "E:debug.keystore" -store-
pass android -keypass android
You get the MD5 fingerprint, as shown in Figure 10.3.
Now you need to sign up for the Google Maps API. Open the browser and go to http://code.google.com/android/maps-api-signup.html
. Follow the instructions on the page and supply the extracted MD5 fingerprint to complete the signup process and obtain the Google Maps key. After successful completion of the signup process, the Google Maps API key is displayed as shown in Listing 10.5.
Your key is:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
This key is good for all apps signed with your certificate whose fingerprint is:
XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
Here is an example xml layout to get you started on your way to mapping glory:
<com.google.android.maps.MapView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/>
<com.google.android.maps.MapView
android:id="@+id/mapvw"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:enabled="true"
android:clickable="true"
android:apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/>
Replace xxxxx...
with your own map key in the android:apiKey
attribute. This output shows the Google Maps API key for the supplied MD5 fingerprint. After we obtain the Google Maps API key, the next step is to install the Google API.
For building Google Map-based applications, we need to install the Google API. Recall that in Chapter 1, “Introduction to Android,” we installed the Google API. To confirm that it is actually installed, select the Window
, Android SDK Manager
option. A dialog box opens showing the status of packages and tools on your machine (see Figure 10.4). If the Google API is not yet installed, you can select it in the Android SDK Manager window and then click the Install package
button.
To run the Google Maps-based Android application, we need an AVD that targets the Google API platform, so the final step before creating and running the application is to create an AVD that targets it.
We need to create a separate AVD to try out Google Maps-based applications. To create a new AVD, select Window
, AVD Manager
to open the AVD Manager
dialog box. The dialog displays a list of existing AVDs. Click the New
button to define a new AVD. A Create new Android Virtual Device (AVD)
dialog box opens where we can specify information about the new AVD. Set the Name
as GoogleAppAVD
to indicate that it is a virtual device to run Google Map-based applications. Choose Google APIs (Google Inc.)—API Level 16
for the Target
, set SD Card
to 64 MiB
, and leave the Default (WVGA800)
for Skin
. Also keep the default in the Hardware
section. Using the New
button, we can set the GPS support
property. Besides the GPS support
, we can also add several other properties that we want our AVD to emulate, such as Abstracted LCD density
, DPad support
, Accelerometer
, and Maximum horizontal camera pixels
. Finally, click the Create AVD
button (see Figure 10.5) to create the virtual device called GoogleAppAVD
.
The GoogleAppAVD
is created and displayed in the list of existing AVDs in the Android SDK and AVD dialog box. Click the Refresh
button if the newly created AVD doesn’t show up in the list. Close the Android SDK and AVD dialog.
Launch the Eclipse IDE and select the File
, New
, Android Project
option to create a new Android application. Name the project GoogleMapApp
and select Google APIs (Google Inc.) (API 16)
from the Build SDK
drop-down list. Select API 16: Android 4.1
from the Minimum Required SDK
and click Finish
after supplying all the pertinent information for the new project, as shown in Figure 10.6.
Note
After we select the Google APIs in the SDK Target list, two files, maps.jar
and usb.jar
, are automatically included in our project and thus enable our project to access maps.
To access Google Maps in our application, we have to add the following two statements to the AndroidManifest.xml
file:
• The first statement asks for permission to access the Internet:
<uses-permission android:name="android.permission.INTERNET" />
• The second statement includes the Android map library:
<uses-library android:name="com.google.android.maps" />
After we add these two code lines, the AndroidManifest.xml
file appears as shown in Listing 10.6. Only the code in bold is newly added; the rest is the default code.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidunleashed.googlemapapp"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:icon="@drawable/ic_launcher" android:label="@string/app_
name"
android:theme="@style/AppTheme" >
<uses-library android:name="com.google.android.maps" />
<activity android:name=".GoogleMapAppActivity" android:label="@string/title_
activity_google_map_app">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
In the activity_google_map_app.xml
layout file, we add a MapView
control to display a Google Maps interface element. We need to include the Google Maps API key that we just obtained to use a MapView
in our application. The Google Maps API key enables our Android application to interact with Google Maps services to obtain map-related information. The MapView
control is enabled and is also made sensitive to mouse clicks by setting its android:enabled
and android:clickable
attributes to true.
The code shown in Listing 10.7 is written into the activity_google_map_app.xml
to add the MapView
control to our application.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.maps.MapView
android:id="@+id/map_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:enabled="true"
android:clickable="true"
android:apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" />
</LinearLayout>
To use maps, the application Activity needs to extend the MapActivity
class. We also need to override the onCreate()
method to lay out the activity_google_map_app.xml
screen where we have defined our MapView
control. The code to display Google Maps that we add to GoogleMapAppActivity.java
is shown in Listing 10.8.
package com.androidunleashed.googlemapapp;
import android.os.Bundle;
import com.google.android.maps.MapActivity;
public class GoogleMapAppActivity extends MapActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_google_map_app);
}
protected boolean isRouteDisplayed() {
return true;
}
}
In the preceding code, we override the isRouteDisplayed()
method to return the Boolean value true
, to enable our Activity to display routing information such as traffic directions. After running the application, we see the output shown in Figure 10.7.
We learn to see a specific map later.
Location-Based Services (LBS) help find the device’s current location. A location is represented with longitude and latitude values. For using Location-Based Services, we use the following:
• Location Manager—Provides an interface to the Location-Based Services, enabling applications to obtain periodic updates of the device’s geographical location.
• Location Provider—Used to determine the device’s current location. It provides feedback on the geographical location of the device. There may be several Location Providers, and each provider uses a specific technology to determine a device’s location. Some use satellites; others use cellular radio, a specific carrier, or the Internet. They differ in battery consumption, monetary cost, and accuracy. The two popular Location Providers are GPS (Global Positioning System)
and Android's Network Location Provider
. The former uses satellites, and the latter uses cellular technology to determine device location.
In short, to determine the current device location, Android uses the services of Location Providers through a Location Manager.
To understand the procedure of accessing Location-Based Services, let’s create a new Android project called KnowLocationApp
. In this application, we display the location of the device in terms of longitude and latitude.
Depending on the Location Provider being used, we need to add certain permissions to our application. A GPS provider requires fine
permission, while the Network provider requires only coarse
. If we use fine
permission, then coarse
permission is implicitly added. The permission tags that need to be added to AndroidManifest.xml
for fine
and coarse
permissions are as follows:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
We are accessing Google Maps in the application; consequently, we also need to add the following two statements in the manifest file:
<uses-permission android:name="android.permission.INTERNET" />
<uses-library android:name="com.google.android.maps" />
After we add these two statements and the statement for the fine
permission, AndroidManifest.xml
appears as shown in Listing 10.9. Only the statements in bold are added; the rest of the code is the default code provided by the ADT after creating the new Android project.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidunleashed.knowlocationapp"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="16"
android:targetSdkVersion="15"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<application android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<uses-library android:name="com.google.android.maps" />
<activity android:name=".KnowLocationAppActivity"
android:label="@string/title_activity_know_location_app">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
In this application, we want to display the location of the device in terms of longitude and latitude, so we have to define two TextView
controls in the application layout file to display them. After we define two TextView
controls, activity_know_location_app.xml
appears as shown in Listing 10.10.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="Latitude: "
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/latitude_view" />
<TextView android:text="Longitude: "
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/longitude_view" />
</LinearLayout>
Then, modify KnowLocationAppActivity.java
to appear as shown in Listing 10.11.
package com.androidunleashed.knowlocationapp;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import android.location.LocationManager;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
public class KnowLocationAppActivity extends Activity {
private TextView latitudeView;
private TextView longitudeView;
private LocationManager locationManager;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_know_location_app);
latitudeView = (TextView) findViewById(R.id.latitude_view);
longitudeView = (TextView) findViewById(R.id.longitude_view);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); #1
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
new LocationListener(){
public void onProviderDisabled(String provider) { }
public void onProviderEnabled(String provider) {}
public void onStatusChanged(String provider, int status, Bundle extras)
{}
public void onLocationChanged(Location loc) {
if (loc != null) {
int lt = (int) (loc.getLatitude());
int lg = (int) (loc.getLongitude());
latitudeView.setText("Latitude is: "+String.valueOf(lt));
longitudeView.setText("Longitude is: "+String.valueOf(lg));
}
}
});
}
}
The two TextView
controls defined with the IDs latitude_view
and longitude_view
in the layout file are accessed and mapped to the latitudeView
and longitudeView TextView
objects, respectively.
As mentioned earlier, we need to use the Location Manager to access Location-Based Services. To use the Location Manager, we request an instance of the LOCATION_SERVICE
using the getSystemService()
method. So, statement #1
provides us with a Location Manager instance called locationManager
.
When the location of the device changes, the notifications related to the change in the device’s location are received via the Location Manager. To know when the location of the device is changed, we need to register a listener to receive location updates. To register a listener to get updates whenever the location of the device changes, we call the requestLocationUpdates()
method. The syntax is
requestLocationUpdates(Provider, minTime, minDistance, LocationListener )
• The Provider
parameter refers to the location provider that we are using in the application. The value for this parameter can be LocationManager.GPS_PROVIDER
or LocationManager.NETWORK_PROVIDER
or any other location provider being used in our application.
• The minTime
and minDistance
parameters are used to specify the minimum time and the minimum distance between location change updates. The minTime
and minDistance
values are provided in milliseconds
and meters
. I recommend not to use 0,0 to conserve battery life.
• LocationListener
is the listener that checks for any change in the device location and other events and executes the respective methods in response.
Example:
LocationManager.GPS_PROVIDER, 36000, 1000, new LocationListener(){ }
This example invokes the onLocationChanged()
method when either of the two events occurs:
• After 36000 milliseconds
• When the distance traveled by the device is 1000 meters
Four methods may be invoked by the LocationListener
, depending on the occurrence of different events. The methods are
• public void onLocationChanged(Location location)—Called when the location of the device is changed or when the minimum time and distance values are exceeded
• public void onProviderDisabled(String provider)—Called when the location provider is disabled by the user
• public void onProviderEnabled(String provider)—Called when the location provider is enabled by the user
• public void onStatusChanged(String provider, int status, Bundle extras)—Called when the provider status changes from available to unavailable or vice versa
When the location of the device changes, the onLocationChanged()
method is invoked by the LocationListener
. Through the getLatitude()
and getLongitude()
methods, we access the latitude and longitude values of the current location of the device. The latitude and longitude values are then displayed through the latitudeView
and longitudeView TextView
controls.
After running the application, we get the output shown in Figure 10.8. We can see that no latitude and longitude values are displayed because we are running our application on an Android emulator instead of an actual device.
To see whether the application is working correctly, we can set the Android emulator to simulate real hardware and location changes.
To pass latitude and longitude values to the provider through an emulator, we can use the Dalvik Debug Monitor Service (DDMS) interface. In Eclipse, perform the following steps to push the GPS location into the emulator:
1. Ensure the emulator is running. Select the Window
, AVD Manager
option. Select the GoogleAppAVD
virtual device, the one we created in this chapter, and click Start
. We get a dialog box showing the emulator Launch Options
. Click Launch
to run the emulator.
2. Open the DDMS perspective by selecting the Window
, Open Perspective
, DDMS
option.
3. In the DDMS perspective, open the File Explorer
by selecting Window, Show View, File Explorer
.
4. If you can’t see the running emulator anywhere, open the Devices
view by selecting Window
, Show View
, Device
. You can see all the running emulators and select the one that you want to use for pushing the GPS location.
5. Locate the Location Controls
section in the Emulator Control
tab. There are three separate tabs in the Location Controls
section (see Figure 10.9):
• Manual—Use this tab to manually send in the coordinates by specifying the latitude and longitude values.
• GPX—Use the .GPX
file to send geographical locations to our application. The GPX (GPS Exchange Format) is a lightweight XML data format used for sending GPS data. If we have a .GPX
file, we can load it by clicking the Load GPX...
button. After loading the .GPX
file, we can click the Play
button to send a series of coordinates to the Android emulator at regular time intervals. Remember, only GPX 1.1 files are supported.
• KML—Use KML (Keyhole Markup Language) files to send graphical locations to our application. As with .GPX
files, we can send a series of coordinates to the Android emulator by clicking the Play
button.
In the Manual
tab, we can enter the Longitude
and Latitude
values and just click the Send
button. After receiving the GPS locations, the LocationListener
in our application fires the onLocationChanged()
method and displays the new location of the user in the Longitude
and Latitude
coordinates. If we send the Longitude
and Latitude
values as -122.084095
and 37.422006
, then on running the application, we see the output shown in Figure 10.10.
As mentioned earlier, we can upload a GPX or KML file to the emulator and set the playback speed. The emulator then sends our GPS location to our application at the specified speed.
To load a GPX file, select the GPX
tab. A Load GPX
button is displayed. Click the Load GPX
button to upload the GPX file. The location information contained therein is displayed, as shown in Figure 10.11 (left). After we select a location from the list, it is sent to the emulator. If the Play
button is clicked, all the locations are sent to the application at the specified speed, as shown in Figure 10.11 (right).
Note
The Speed
button is used to send the GPX file location values to the application at the given speed.
To display that location on the map whose latitude and longitude values are supplied, we need to add the MapView
control to KnowLocationApp
. After we add the MapView
control, the activity_know_location_app.xml
application layout file appears as shown in Listing 10.12. Only the code in bold is newly added; the rest is the same as in Listing 10.10.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="Latitude: "
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/latitude_view" />
<TextView android:text="Longitude: "
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/longitude_view" />
<com.google.android.maps.MapView
android:id="@+id/mapvw"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:enabled="true"
android:clickable="true"
android:apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" />
</LinearLayout>
We can see that the MapView
control is defined with a mapvw
ID. It is set to enable mode to listen for the mouse clicks by setting the android:enabled
and android:clickable
attributes to true
. The Google Map API key is supplied to the control, enabling it to access Google Maps.
Next, we need to write Java code into the activity file to perform the following tasks:
• Access the MapView
control from the layout file and map it to the MapView
object.
• Display a default MapView
zoom control to use the zoom in/out feature.
• Select the type of view to display—satellite
, street
, or traffic
.
• Convert the GPS location (longitude and latitude values) pushed into the emulator into micro degrees and animate the Google Map to display that location.
• Set the zoom level of the Google Map.
To perform these tasks, the code shown in Listing 10.13 is written into the KnowLocationAppActivity.java
activity file. Only the code in bold is new; the rest is the same as we saw in Listing 10.11.
package com.androidunleashed.knowlocationapp;
import android.os.Bundle;
import android.widget.TextView;
import android.location.LocationManager;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.GeoPoint;
public class KnowLocationAppActivity extends MapActivity {
private TextView latitudeView;
private TextView longitudeView;
private LocationManager locationManager;
private MapController mapController;
private MapView mapView;
private GeoPoint point;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_know_location_app);
latitudeView = (TextView) findViewById(R.id.latitude_view);
longitudeView = (TextView) findViewById(R.id.longitude_view);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
mapView = (MapView) findViewById(R.id.mapvw);
mapView.setBuiltInZoomControls(true);
mapView.setSatellite (true);
mapController = mapView.getController();
locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, 0, 0,
new LocationListener(){
public void onProviderDisabled(String provider) { }
public void onProviderEnabled(String provider) {}
public void onStatusChanged(String provider, int status, Bundle extras)
{}
public void onLocationChanged(Location loc) {
if (loc != null) {
int lt = (int) (loc.getLatitude());
int lg = (int) (loc.getLongitude());
latitudeView.setText("Latitude is: "+String.valueOf(lt));
longitudeView.setText("Longitude is: "+ String.valueOf(lg));
int latit=(int)(loc.getLatitude() * 1E6);
int longit=(int)(loc.getLongitude()* 1E6);
point = new GeoPoint(latit, longit);
mapController.animateTo(point);
mapController.setCenter(point);
mapController.setZoom(15);
}
}
});
}
@Override
protected boolean isRouteDisplayed() {
return true;
}
@Override
protected boolean isLocationDisplayed() {
return false;
}
}
The MapView
control is accessed from the layout file and is mapped to the mapView MapView
object. The MapView
already has controls that enable us to zoom in and out. By passing the Boolean value true
to the setBuiltInZoomControls()
method, we get the MapView
’s default zoom controls. The Google Map displays the satellite
view by passing the Boolean value true
to the setSatellite()
method.
By default, Google Maps are displayed in a map view that displays streets and important places. We can set the Google Map to appear in satellite
and traffic
view, as well. To switch among different views, we need to call the respective setter method and pass the Boolean value true
to it. The different setter methods are
• setSatellite(boolean)—Displays the satellite
view of the Google Map when passed a true
boolean value.
• setTraffic (boolean)—Displays the traffic
view of the Google Map when passed a true
Boolean value. We can zoom in the traffic
view to see the streets. Different colors are used to show traffic conditions. Green lines in the map (see Figure 10.13) represent the smooth traffic, yellow lines represent moderate traffic, and red lines represent slow traffic. In the print book, you’ll find the yellow lines in white, green lines in gray, and red lines in dark gray.
Tip
To turn off a particular map view, pass a false
Boolean value to it.
After setting the Google Map view, we obtain a controller from the MapView
instance and assign it to a mapController MapController
object. The latitude and longitude values passed by us through DDMS are retrieved and assigned to the integers lt
and lg
, respectively. Recall that after pushing a GPS location into the emulator through DDMS, the LocationListener
fires the onLocationChanged()
method. In this application, we are using a GeoPoint
object to represent a geographical location. The latitude and longitude of a location in the GeoPoint
class are represented in micro degrees. Hence, we need to multiply the longitude and latitude entered by us through DDMS
by 1,000,000
to convert from degree to micro degree and store these values as GeoPoint
points.
We used the animateTo()
method of the MapController
class to navigate the map to a particular location.
We set the center of the location being displayed through Google Map by passing the point (containing the GPS location in micro degree format) to the setCenter()
method of the map view’s controller.
Finally, we set the zoom level of the map to the value 15 by using the setZoom()
method.
Note
Android defines 22 zoom levels for maps.
The method isLocationDisplayed()
is set to return the Boolean value true
to display the current device location.
The method isRouteDisplayed()
is set to return the Boolean value true
to display route information such as driving directions.
After we run the application, the Google Map appears, displaying the default location, as shown in Figure 10.12 (left). To see the satellite view of San Francisco, we push its latitude and longitude values 37.775
and -122.4183333
to the Android emulator through DDMS
. The satellite view of San Francisco appears, as shown in Figure 10.12 (middle). The figure also shows the MapView’s default zoom controls, and a zoomed satellite view of San Francisco is displayed in Figure 10.12 (right).
Note
Android maps support zooming in and out. The “i” key zooms in on the map, and the “o” key zooms out.
The following statement superimposes a traffic
view on top of the San Francisco satellite view when added to the KnowLocationAppActivity.java
activity file:
mapView.setSatellite(true);
It can be replaced by the following lines:
mapView.setTraffic(true);
mapView.setSatellite(true);
After we make these changes, the application shows the San Francisco traffic view zoomed to level 15, as shown in Figure 10.13 (left). After we zoom one more level, the traffic view appears, as shown in Figure 10.13 (right).
Android provides a class known as Geocoder
that helps in getting the street address of supplied GPS coordinates and vice versa. The process of translating between street addresses and longitude/latitude GPS locations is known as geocoding. There are two types of geocoding:
• Forward Geocoding—Translates street addresses into latitude and longitude values
• Reverse Geocoding—Translates latitude and longitude values into the street addresses
In both the methods, Locale
is used to define the location and language. Both geocoding functions return a list of Address
objects, where each Address
object contains the details that include the latitude, longitude, phone number, country, street, and house number information.
For reverse geocoding, we pass the latitude and longitude to a Geocoder
’s getFromLocation()
method, and it returns a list of possible matching addresses. If the Geocoder
could not resolve any addresses for the specified coordinate, it returns null
:
List<Address> result = geocoder.getFromLocation(lat,long, max_no_ofResults);
where the lat
and long
parameters refer to the latitude
and longitude
values whose street address we are looking for, and the max_no_ofResults
parameter refers to the number of possible addresses we want to have returned. The method returns null
if no match is found.
Forward geocoding returns latitude and longitude values for the supplied location, where location includes street address, postal codes, train stations, landmarks, and hospitals.
To perform forward geocoding lookup, the getFromLocationName()
method on a Geocoder
instance is called, passing the location whose coordinates we are looking for:
List<Address> result = geocoder.getFromLocationName(street_address, max_no_ofResults);
where max_no_ofResults
refers to the maximum number of coordinates to return. All possible matches for the supplied address are returned, and each result includes latitude and longitude values, along with additional address information available for the coordinates. If no matches are found, the method returns null
.
The Locale
plays an important role in geocoding. It provides the geographical context for interpreting our search requests, as there can be many places with the same location name. To avoid any ambiguity, provide as much detail as possible in the supplied address, as shown in the following example:
Geocoder geoGeocoder = new Geocoder(this, Locale.US);
String streetAddress = "2-98 11th St, San Francisco, CA 94103";
List<Address> co_ords = null;
try {
co_ords = geoGeocoder.getFromLocationName(streetAddress, 10);
} catch (IOException e) {}
Let’s modify KnowLocationApp
to print the address of the supplied latitude and longitude values, along with showing that location on the Google Map. To print the address of the supplied coordinates, we have to add one more TextView
control to our application layout file. After we add the TextView
control, the activity_know_location_app.xml
layout file appears as shown in Listing 10.14. Only the code in bold is newly added; the rest of the code is the same as we saw in Listing 10.12.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:text="Latitude: "
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/latitude_view" />
<TextView android:text="Longitude: "
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/longitude_view" />
<TextView android:text="Address is: "
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/address_view" />
<com.google.android.maps.MapView
android:id="@+id/mapvw"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:enabled="true"
android:clickable="true"
android:apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" />
</LinearLayout>
To fetch the address of the supplied longitude and latitude coordinates, we implement reverse geocoding in the KnowLocationAppActivity.java
Java activity file (see Listing 10.15). The code added to the file is shown in bold.
package com.androidunleashed.knowlocationapp;
import android.os.Bundle;
import android.widget.TextView;
import android.location.LocationManager;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.GeoPoint;
import android.location.Geocoder;
import java.util.List;
import android.location.Address;
import java.util.Locale;
import java.io.IOException;
public class KnowLocationAppActivity extends MapActivity {
private TextView latitudeView;
private TextView longitudeView;
private LocationManager locationManager;
private MapController mapController;
private MapView mapView;
private GeoPoint point;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_know_location_app);
latitudeView = (TextView) findViewById(R.id.latitude_view);
longitudeView = (TextView) findViewById(R.id.longitude_view);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
mapView = (MapView) findViewById(R.id.mapvw);
mapView.setBuiltInZoomControls(true);
mapController = mapView.getController();
locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, 0, 0,
new LocationListener(){
public void onProviderDisabled(String provider) { }
public void onProviderEnabled(String provider) {}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onLocationChanged(Location loc) {
if (loc != null) {
int lt = (int) (loc.getLatitude());
int lg = (int) (loc.getLongitude());
latitudeView.setText("Latitude is: "+String.valueOf(lt));
longitudeView.setText("Longitude is: "+ String.valueOf(lg));
int latit=(int)(loc.getLatitude() * 1E6);
int longit=(int)(loc.getLongitude()* 1E6);
point = new GeoPoint(latit, longit);
mapController.animateTo(point);
mapController.setCenter(point);
mapController.setZoom(15);
String addr=getAddress(point);
TextView addressView = (TextView) findViewById(R.id.address_view);
addressView.setText(addr);
}
}
});
}
@Override
protected boolean isRouteDisplayed() {
return false;
}
@Override
protected boolean isLocationDisplayed() {
return false;
}
public String getAddress(GeoPoint point) {
String locationAdd = "";
Geocoder geoCoder = new Geocoder(getBaseContext(), Locale.getDefault());
try {
List<Address> addresses = geoCoder.getFromLocation(point.getLatitudeE6()
/ 1E6, point.getLongitudeE6() / 1E6, 1);
if (addresses.size() > 0) {
Address address=addresses.get(0);
for (int index = 0; index < address.getMaxAddressLineIndex(); index++)
locationAdd += address.getAddressLine(index);
}
}
catch (IOException e) {
e.printStackTrace();
}
return locationAdd;
}
}
The latitude and longitude supplied by us to the emulator through DDMS
is converted into micro degrees and collected into a point GeoPoint
. The GeoPoint
containing the latitude and longitude values is passed to the getAddress
function to perform reverse geocoding. In the getAddress()
function, an instance of the Geocoder
class called geoCoder
is created by supplying a Locale
context to it. Thereafter, the geoCoder
instance of the getFromLocation()
method is called, passing the latitude and longitude values contained in point
to it. The latitude
and longitude
values are converted into degrees while passing to the method. Following the latitude and longitude values, a numerical value 1
is passed to the getFromLocation()
method to fetch a single address of the supplied coordinates. The street address returned by the getFromLocation()
method is in the form of List<Address>
. All the elements in the list are extracted and displayed through the newly added TextView
control addressView
.
Let’s run the application to see how reverse geocoding takes place. After we pass the San Francisco latitude and longitude values 37.775
and -122.4183333
to the Android emulator through DDMS
, the location and the address (2-98 11th StSan Francisco, CA) 94103
are displayed on the Google Map, coordinates as shown in Figure 10.14.
We cannot display markers or pin points on the Google Map directly. Instead, we add a transparent layer onto the map and the display markers or text on it. We can add as many layers as we want. The transparent layer we want to create is known as an Overlay
.
To display markers that pinpoint locations on the map, we need to create transparent overlays on top of a MapView
.
To add a new Overlay
, a new class is created that extends Overlay
. In the class, we may
• Override the draw
method to show the markers on the map.
• Override the onTap
method to take the desired action when the user clicks (taps) the marker(s) added through the overlay.
Drag and drop an image called spot.png
into the four drawable
folders (drawable-xhdpi
, drawable-hdpi
, drawable-ldpi
, and drawable-mdpi)
of the res
folder. Modify the KnowLocationAppActivity.java
activity file to appear as shown in Listing 10.16. Only the code in bold is newly added; the rest is the same as we saw in Listing 10.15.
package com.androidunleashed.knowlocationapp;
import android.os.Bundle;
import android.widget.TextView;
import android.location.LocationManager;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.GeoPoint;
import android.location.Geocoder;
import java.util.List;
import android.location.Address;
import java.util.Locale;
import java.io.IOException;
import com.google.android.maps.Overlay;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
public class KnowLocationAppActivity extends MapActivity {
private TextView latitudeView;
private TextView longitudeView;
private LocationManager locationManager;
private MapController mapController;
private MapView mapView;
private GeoPoint point;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_know_location_app);
latitudeView = (TextView) findViewById(R.id.latitude_view);
longitudeView = (TextView) findViewById(R.id.longitude_view);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
mapView = (MapView) findViewById(R.id.mapvw);
mapView.setBuiltInZoomControls(true);
mapController = mapView.getController();
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
new LocationListener(){
public void onProviderDisabled(String provider) { }
public void onProviderEnabled(String provider) {}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onLocationChanged(Location loc) {
if (loc != null) {
int lt = (int) (loc.getLatitude());
int lg = (int) (loc.getLongitude());
latitudeView.setText("Latitude is: "+String.valueOf(lt));
longitudeView.setText("Longitude is: "+ String.valueOf(lg));
int latit=(int)(loc.getLatitude() * 1E6);
int longit=(int)(loc.getLongitude()* 1E6);
point = new GeoPoint(latit, longit);
mapController.animateTo(point);
mapController.setCenter(point);
mapController.setZoom(15);
String addr=getAddress(point);
TextView addressView = (TextView) findViewById(R.id.address_view);
addressView.setText(addr);
MyOverlay mapOverlay = new MyOverlay();
List<Overlay> overlayList=mapView.getOverlays();
overlayList.clear();
overlayList.add(mapOverlay);
mapView.postInvalidate();
}
}
});
mapView.invalidate();
}
@Override
protected boolean isRouteDisplayed() {
return false;
}
@Override
protected boolean isLocationDisplayed() {
return false;
}
public String getAddress(GeoPoint point) {
String locationAdd = "";
Geocoder geoCoder = new Geocoder(getBaseContext(), Locale.getDefault());
try {
List<Address> addresses = geoCoder.getFromLocation(point.getLatitudeE6()
/ 1E6, point.getLongitudeE6() / 1E6, 1);
if (addresses.size() > 0) {
Address address=addresses.get(0);
for (int index = 0; index < address.getMaxAddressLineIndex(); index++)
locationAdd += address.getAddressLine(index);
}
}
catch (IOException e) {
e.printStackTrace();
}
return locationAdd;
}
class MyOverlay extends Overlay
{
@Override
public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when)
{
super.draw(canvas, mapView, shadow);
Point screenPoints = new Point();
mapView.getProjection().toPixels(point, screenPoints);
Bitmap spotPic = BitmapFactory.decodeResource(getResources(),
R.drawable.spot);
canvas.drawBitmap(spotPic, screenPoints.x, screenPoints.y-50, null);
return true;
}
}
}
Here, MyOverlay
is the class that extends Overlay
. We create an instance called mapOverlay
of the MyOverlay
class to create an overlay. Each MapView
contains a list of overlays currently being displayed, and we can get a reference to that list by calling the getOverlays()
method, as shown here:
List<Overlay> overlayList=mapView.getOverlays();
This statement gets a reference to the list of overlays, called overlayList
. We clear all the items from the list and add a mapOverlay
instance of the MyOverlay
class to the list. Finally, the postInvalidate()
method is called on the mapView
instance to update the changes on the map and display the added overlay.
In the draw()
method, we use a Canvas
object to draw markers. A Canvas
object represents a visible display surface, and it includes the methods for drawing lines, text, shapes, and images. To display markers, we need to convert the GeoPoint
representing the latitude and longitude values where we want to display markers into the screen coordinates by using the Projection
class. We create an instance of the Projection
class by calling the getProjection()
method. We can access the fromPixel()
and toPixel()
methods of the projection instance to translate GeoPoints
coordinates to screen coordinates and vice versa. We call the toPixels()
method on the Projection
instance to convert the GeoPoint
coordinates to the screenPoints
screen coordinates. Then, we generate a bitmap graphic by using the BitmapFactory
class. Thus, we use the drawable resource spot.png
image to generate a bitmap called spotPic
. The bitmap graphic representing the marker (spot.png
) is displayed on the canvas using the screen coordinates stored in screenPoints
.
The output appears as shown in Figure 10.15.
If we want to display several markers on the map, we use ItemizedOverlay
, which helps in creating a list of locations to be marked on the map.
Android provides a nice class, called ItemizedOverlay
, to manage drawing OverlayItems
on a map. It greatly simplifies the process of displaying markers on the map. Let’s use the ItemizedOverlay
class in our application to display several markers.
We do not pass the longitude and latitude information to the Android emulator through DDMS. In addition, we don’t want to display the street address of the longitude and latitude values, so let us remove the three TextView
controls from KnowLocationApp
. After we remove the three TextView
controls, our activity_know_location_app.xml
layout file appears as shown in Listing 10.17.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.maps.MapView
android:id="@+id/mapvw"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:enabled="true"
android:clickable="true"
android:apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/>
</LinearLayout>
To apply ItemizedOverlay
, modify the KnowLocationAppActivity.java
file to appear as shown in Listing 10.18. Only the code in bold is modified; the rest of the code is the same as we saw in Listing 10.16.
package com.androidunleashed.knowlocationapp;
import android.os.Bundle;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.GeoPoint;
import android.app.AlertDialog;
import android.graphics.Canvas;
import com.google.android.maps.OverlayItem;
import android.graphics.drawable.Drawable;
import com.google.android.maps.ItemizedOverlay;
import java.util.ArrayList;
import android.content.DialogInterface;
public class KnowLocationAppActivity extends MapActivity {
private MapController mapController;
private MapView mapView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_know_location_app);
mapView = (MapView) findViewById(R.id.mapvw);
mapView.setBuiltInZoomControls(true);
mapController = mapView.getController();
Drawable marker=getResources().getDrawable(R.drawable.spot);
marker.setBounds(0, 0, marker.getIntrinsicWidth(),marker.getIntrinsicHeight());
MyOverlay mapOverlay = new MyOverlay(marker);
mapView.getOverlays().add(mapOverlay);
GeoPoint SantaRosa = new GeoPoint((int)(38.4405556*1000000),(int)
(-122.7133333*1000000));
GeoPoint SanFrancisco = new GeoPoint((int)(37.775*1000000),(int)
(-122.4183333*1000000));
GeoPoint SanJose = new GeoPoint((int)(37.3394444*1000000),(int)
(-121.8938889*1000000));
mapOverlay.addPoint(SantaRosa,"Santa Rosa", "Santa Rosa");
mapOverlay.addPoint(SanFrancisco ,"San Francisco", "San Francisco");
mapOverlay.addPoint(SanJose ,"San Jose", "San Jose");
GeoPoint point = mapOverlay.getCenter();
mapView.getController().setCenter(point);
mapController.setZoom(8);
}
@Override
protected boolean isRouteDisplayed() {
return false;
}
class MyOverlay extends ItemizedOverlay<OverlayItem> {
private ArrayList<OverlayItem> overlayItemList = new
ArrayList<OverlayItem>();
public MyOverlay(Drawable marker) {
super(boundCenterBottom(marker));
populate();
}
@Override
protected boolean onTap(int index) {
AlertDialog.Builder alertDialog = new AlertDialog.
Builder(KnowLocationAppActivity.this);
alertDialog.setTitle("Alert window");
alertDialog.setMessage("The selected place is "+ overlayItemList.
get(index).getTitle());
alertDialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int buttonId) {
return;
}
});
alertDialog.setIcon(R.drawable.ic_launcher);
alertDialog.show();
return true;
}
public void addPoint(GeoPoint p, String title, String snippet){
OverlayItem newItem = new OverlayItem(p, title, snippet);
overlayItemList.add(newItem);
populate();
}
@Override
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
super.draw(canvas, mapView, shadow);
}
@Override
protected OverlayItem createItem(int i) {
return overlayItemList.get(i);
}
@Override
public int size() {
return overlayItemList.size();
}
}
}
The MyOverlay
class extends ItemizedOverlay
, which in turn extends Overlay. The ItemizedOverlay
helps in creating a list of locations that can be used to place markers on a map. The location where we want to place a display marker is supplied to the class constructor, which is then passed to the super class.
The boundCenterBottom()
method is called on the marker to define the anchor point as the middle of the bottom edge of our marker. That is, the bottom center of the marker is aligned with the geographical coordinates of the Item. Then we call the populate()
method of ItemizedOverlay
to cache the OverlayItem(s)
. The class calls the size()
method to determine the number of overlay items and executes a loop, calling createItem(i)
for each item. The createItem()
method returns the overlay
item of the given index value. An overlay
item is composed of the GeoPoint
coordinate of the place to be marked, along with a title and a snippet.
In the onCreate()
method, we create an instance of a Drawable
called marker
that represents the spot.png
image file in the drawable resource. We need to define the bounds of our Drawable
object marker
before we use it on a map. The setBounds(Rect)
method is called to decide the location and size of the Drawable
object. The preferred size of Drawable
is determined by using the getIntrinsicHeight()
and getIntrinsicWidth()
methods. We create an overlay by creating an instance of the MyOverlay
class called mapOverlay
and passing the marker to it. We add our overlay to the mapView
.
We then create three GeoPoint
instances, SantaRosa
, SanFrancisco
, and SanJose
, through their respective longitude and latitude values. We convert the latitude and longitude values of the locations to micro degrees by multiplying them by 1,000,000 and then converting the result to integers. We call the addPoint()
method of MyOverlay
to add the three GeoPoint
instances as overlay items to the overlayItemList.
To make the markers visible on the map, we need to set the center of the displayed map to a point such that all the markers are visible. We choose the first point from the overlay to use as the center point for our map. The getCenter()
method of the overlay is called to return the first point, and the setCenter()
method of the MapView
’s controller
is called that sets the center of the map. The setZoom()
method sets the zoom level to 8.
For each item in the overlay, the marker is drawn twice, once for the marker and once to represent its skewed and darkened shadow.
The onTap()
method defines an AlertDialog
, sets its title to Alert
window, and displays a message, The selected place is
. It then accesses the overlay
item by using the index value of the tapped marker. The overlay
item is composed of the GeoPoint
coordinates, a title
, and a snippet of the location being marked. The title
of the marked locations is displayed in the AlertDialog
dialog.
After running the application, we see three markers displayed on the map on Santa Rosa
, San Francisco
, and San Jose
, as shown in Figure 10.16 (left). After we select a marker, an Alert
window is displayed showing the location whose marker is selected, as shown in Figure 10.16 (right).
In this chapter, we learned to display web pages through WebView
controls, handle page navigation, and add Internet access permission to the Android application. We saw how to use WebViewClient
, use Google Maps, obtain a Google Key, and install the Google API. We learned to create an AVD for a map-based application, use Location-Based Services
, supply latitude and longitude values through DDMS
, add a Zoom facility, and display map markers.
In the next chapter, we learn about Broadcast Receivers
. We see how to broadcast and receive the broadcasted intent. We also see how the Notification
system is used, created, configured, and displayed in the status bar. We learn the procedure of sending and receiving SMS programmatically, and, finally, we learn how to send email and use the TelephonyManager
in making phone calls. Using the WebViewClient
Class
18.222.199.131