Chapter 10. Displaying Web Pages and Maps


In This Chapter

• Displaying Web Pages Through WebView

Handling Page Navigation

Adding Permission for Internet Access

Using WebViewClient

Using Google Maps

• Obtaining Google Keys

Installing the Google API

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.

Displaying Web Pages

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.

Listing 10.1. Code Written into 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.

Listing 10.2. Code Written into the WebViewAppActivity.java Java Activity File


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.

Enabling JavaScript

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.

Handling Page Navigation

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.

Table 10.1. WebView Class Methods

Image

The only step required to complete this application is to add permission to access the Internet from within the application.

Adding Permission for Internet Access

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.

Listing 10.3. The AndroidManifest.xml Configuration File


<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.

Image

Figure 10.1. The application showing the EditText control for entering the web page URL (left), the web page loaded and displayed in the WebView control (middle), and the linked web page opened by the default web browser (right)


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.

Using 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.

Listing 10.4. Code Written into the WebViewAppActivity.java Java Activity File


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.

Image

Figure 10.2. Loading a web page in the WebView control (left), and the linked web page also opens in the WebView control (right).

Using Google Maps

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.

Obtaining a Google Maps API Key

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.

Image

Figure 10.3. Extracting the MD5 fingerprint using the debug certificate

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.

Listing 10.5. Google Maps API Key


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.

Installing 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.

Image

Figure 10.4. The Android SDK Manager window showing different packages and tools installed on our machine

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.

AVDs for Map-Based Applications

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.

Image

Figure 10.5. Creating a new AVD, GoogleAppAVD to run the Google Maps-based application

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.

Creating a Google Maps-Based Application

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.

Image

Figure 10.6. The dialog box used to specify the information for the new Android project


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.

Listing 10.6. The AndroidManifest.xml Configuration File


<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.

Listing 10.7. Code Written into the activity_google_map_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">
    <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.

Listing 10.8. Code Written into the GoogleMapAppActivity.java Java Activity File


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.

Image

Figure 10.7. The application showing the startup Google map

We learn to see a specific map later.

Using Location-Based Services

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.

Listing 10.9. The AndroidManifest.xml Configuration File


<?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.

Listing 10.10. Code Written into the activity_know_location_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: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.

Listing 10.11. The KnowLocationAppActivity.java Java Activity File


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.

Image

Figure 10.8. No output is displayed on an Android emulator

To see whether the application is working correctly, we can set the Android emulator to simulate real hardware and location changes.

Supplying Latitude and Longitude Values Through DDMS

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.

Image

Figure 10.9. Sending GPS locations to the application manually

Sending GPS Locations Manually

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.

Image

Figure 10.10. Latitude and longitude values displayed after manually pushing a GPS location to the emulator

Passing Locations in GPX/KML Format

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).

Image

Figure 10.11. After we load a GPX file, the location information stored in the GPX file is displayed (left), and the locations displayed when clicking Play button (right).


Note

The Speed button is used to send the GPX file location values to the application at the given speed.


Displaying Map Locations

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.

Listing 10.12. Code Written into the activity_know_location_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: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.

Listing 10.13. Code Written into the KnowLocationAppActivity.java Java Activity File


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).

Image

Figure 10.12. Satellite view of the default location (left), satellite view of the San Francisco default location at zoom level 15 (middle), and satellite view of San Francisco at zoom level 16 (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).

Image

Figure 10.13. Traffic view of San Francisco at zoom level 15 (left), and traffic view of San Francisco at zoom level 16 (right)

Printing the GPS Location Address

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.

Reverse Geocoding

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

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.

Listing 10.14. Code Written into the activity_know_location_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: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.

Listing 10.15. Code Written into the KnowLocationoAppActivity.java Java Activity File


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.

Image

Figure 10.14. Map view of the supplied coordinates

Displaying Map Markers

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.

Creating Overlays

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.

Listing 10.16. Code Written into the KnowLocationAppActivity.java Java Activity File


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;
        }
    }
}


Adding Overlays to the Overlay List

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.

Image

Figure 10.15. A marker on the San Francisco map

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.

Using ItemizedOverlay

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.

Listing 10.17. Code Written into the activity_know_location_app.xml Layout File


<?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.

Listing 10.18. Code Written into the KnowLocationAppActivity.java Java Activity File


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).

Image

Figure 10.16. Showing markers on Santa Rosa, San Francisco, and San Jose (left), and an Alert dialog box displayed after selecting a marker (right)

Summary

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

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.199.131