Android Logo MathCS.org - Android

Fun with MP3 and GPS

java -> android -> mp3 and gps ...

One of the strengths of Android is that one application can fairly easily make use of another app's services. There are a number of different ways to do that, but some services are provided as an integral part of Android and are particularly easy to use.

Playing MP3

Android provides a simple mechanism for playing MP3 music files. You need two ingredients: the content (e.g. an MP3 music/sound file) and a player (e.g. a MediaPlayer object). The content could be a file stored locally on your phone or somewhere on a web server. To access the content from a web server you need to construct a Uniform Resource Identifier (URI) that points to it:

A URI is either a locator (uniform resource locator, or URL) or a name (uniform resource name, or URN), or both. An easy way to create a URI is by using the static

   Uri.parse(URI-String)

method of the android.net package.

The ISBN number of a book, for example, uniquely identifies a specific book, edition and all, and is thus a URN. A web address, e.g. http://www.mathcs.org/index.html, on the other hand, specifies a unique location of a particular document and would therefore be a URL. In our case, we can think of a URI as a locator similar to what's commonly known as a "web address". A concrete URI to a valid MP3 document is, for example:

http://www.mathcs.org/java/android/music/crs-bigband.mp3

Make sure that this URI is valid by typing it into the address field of a web browser to verify (the song should play if your browser is configured appropriately. Here are the steps to add a music-playing capability to our simple application:

  1. Create a new method playMusic()in our FirstApp program
  2. Inside that method construct a URI pointing to a valid MP3 file
  3. Construct a reference to a MediaPlayer
  4. Ask the MediaPlayer to load the resource (assuming it can go online)
  5. Ask the MediaPlayer to play the file
  6. Add a call to the new method to the onCreate method of your program

Note that the MediaPlayer includes the following methods (and many more):

Here is the corresponding code for the playMusic method; you still need to modify the onCreate method to call it. Note that we have added log calls to track information in case things go wrong in a simple error-handling block (possible errors include invalid MP3 location, invalid MP3 file format, no network access for your device, dropped network access, etc).

public void playMusic()
{
	try
	{
		Uri musicSource = Uri.parse("http://www.mathcs.org/java/android/music/crs-bigband.mp3");
		MediaPlayer player = MediaPlayer.create(this, musicSource);
		player.start();
		Log.i(DEBUG_TAG, "Music started");
	}
	catch(Exception ex)
	{
		Log.e(DEBUG_TAG, "Music failed ", ex);
	}
}				

Of course you need to call this new method from onCreate, or else it will never execute. You also need to import various classes from a number of Android classes before your code compiles, which is easy to do in Eclipse:

If you now execute your program in the emulator or on a real device it should not only show you the text message as before, but also play our music file. However, you may notice that:

There is not much to do about the startup delay right now (later we will learn how to pre-load data in separate threads) but we can fix the second problem easily enough: just as the onCreate method executes when your program first executes, there is an onStop method that executes when an application is stopped. We will discuss this in detail soon, but for now you need to override the default onStop method to stop playing music and recover any resources used. To make this work you also need to move the player from a local variable to a field since more that one method needs to access it now.

Important: if you override onStop you must include a call to the superclass' onStop method, usually as the last line of your overriding method. If you do not, your app will generate an error when you switch away from it.

Tip: In Eclipse you can easily override a method by selecting "Source | Override/Implement ..." from the menu bar. You will see all methods available for overriding and you can simply check the ones you need.

Here is the entire code for our new and improved first Android app, including the music-playing capability. We have also introduced a log method to quickly and application-wide turn logging on or off via a boolean variable log_enabled):

import android.app.Activity;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;

public class FirstApp extends Activity
{
	private static final String DEBUG_TAG = "FirstAppLogging";
	private static boolean log_enabled = true;
	
	private MediaPlayer player = null;
	
	public void onCreate(Bundle savedInstanceState)
	{
		logInfo("FirstApp starting");
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		playMusic();
	}
	
	public void playMusic()
	{
		try
		{
			Uri musicSource = Uri.parse("http://www.mathcs.org/java/android/music/crs-bigband.mp3");
			player = MediaPlayer.create(this, musicSource);
			player.start();
			logInfo("Music started");
		}
		catch(Exception ex)
		{
			logInfo("Music failed: " + ex.getMessage());
		}
	}
	
	public void onStop()
	{
		if (player != null)
		{
			player.stop();
			player.release();
			player = null;
			logInfo("Music stopped");
		}
		super.onStop();
	}
	
	public static void logInfo(String msg)
	{
		if (log_enabled)
			Log.i(DEBUG_TAG, msg);
	}
}

GPS

Another service offered by Android is to utilize the Global Positioning System (GPS sensor) to identify the coordinates of your, or more precisely your phone's location. Actually, Android can either use the very exact GPS sensor to triangulate your position from three or more geo-stationary satellites in space or it can, less exact, triangulate your location using the closest cell phone towers. Using space-based satellites is much more precise, but it only works if your device has an unobstructed view of the sky; it won't work indoors, for example. Either way, you can determine the latitude and longitude of your location and use applications such as Google Maps to determine your location by name and/or on a map. You can use that information to locate places of interest near you, or to get directions from your location to other places, or much more. In fact, location-based apps are more and more popular and useful so it might be interesting to introduce early on how to use it.

Our first problem is that the Android emulator does not contain any location sensors so we need to (a)ensure that the (simulated) GPS sensor is turned on, and (b) provide it with the proper location coordinates manually.

Seeding the Emulator with GPS Coordinates

  1. Run our FirstApp program so that the emulator starts up if it is not already up
  2. Start the "Google Maps" application (for that to work you need to have started the emulator including the Google API's)
  3. In the maps application, bring up the menu and select "My Location". You will see a message that the application is waiting for a response from the GPS sensor
  4. In Eclipse, switch to the DDMS perspective. If you do not see that perspective listed in the upper right corner, select "Window | Open Perspective | Other ..." and select DDMS from the list
  5. In the "Devices" window of the DDMS perspective, highlight the currently running emulator
  6. In the "Emulator Control" window, scroll down to "Location Control"
  7. Enter the desired longitude, or for now use the default ones, and latitude and click "Send"
  8. Your map application should now show the location you entered (the default one is in Palo Alto, CA)

The map's "My Location" will time out if you do not send coordinates via DDMS quickly enough. You might need two tries to get it going.

Tip: To get the coordinates of any location, point your web browser to "maps.google.com". Search for the location you want and center it on the screen (via right-clicking). Then paste the following in the address bar of your browser:
     javascript:void(prompt('',gApplication.getMap().getCenter()));
A Javascript dialog will pop up, giving you the coordinates of the center of the map in the form (latitude, longitude). You can paste those numbers into the text fields of the Location Control window of the DDMS perspective in Eclipse (make sure to paste the right number in the right field)

Now the emulator has been provided with the coordinates of your desired location so that we can write a method to retrieve them. Of course we want to write the method so that if it runs on a real device with a true location sensor, we will read the true location of the device. Add the following method to our first application:

public void getLocation()
{
	try
	{
		LocationManager locMan = (LocationManager)getSystemService(LOCATION_SERVICE);
		Log.i(DEBUG_TAG, "Location manager loaded: " + locMan);
		Location loc = locMan.getLastKnownLocation(LocationManager.GPS_PROVIDER);
		Log.i(DEBUG_TAG, "Location = " + loc.toString());
	}
	catch(Exception ex)
	{
		Log.e(DEBUG_TAG, "Location failed", ex);
	}
}

In the first line of the try block we ask the system to initialize a LocationManager for us (and log the result as a check). Then we use the location manager to retrieve the last known location using the GPS sensor and log that to see the answer.

Note: This will only work if you previously turned the GPS sensor on and it was able to get a lock on the location (either real or simulated). If not, the location object loc will be null and your method will throw an exception.

And of course you need to put a call to that method into the onCreate method. However, that's not all! Access to special services (such as the GPS sensor) require special permissions. Those permissions must be specified in the manifest of your program, named AndroidManifest.xml. If you later publish your app on the Android marketplace, a user will be notified that the app about to be downloaded needs specific permissions and the user could cancel the download request if he/she is unwilling to grant the permissions.

In our case, double-click on the file AndroidManifest.xml at the top level of your project and switch to the "permissions" tab. Click the "Add ..." button and select the "Uses Permission" entry, then click "OK". You should see a drop-down list on the right - pick android.permission.ACCESS_FINE_LOCATION. To verify, click the last tab of the manifest editor, labeled AndroidManifest.xml. Your XML file should look similar to the following:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="org.mathcs.firstapp"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="true">
        <activity android:name=".FirstApp"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-sdk android:minSdkVersion="3" />

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

The permissions are specified in the uses-permission tag(s) at the end of the file. Now all should be in place to run the program:

Now run your program in debug mode and watch the LogCat output. With any luck you'll see the location of your device show up in the log.

Once we have figured out how to use graphical user interface elements, we will revisit interaction with the GPS sensor.