Android Assignmesnts
Transcript of Android Assignmesnts
Assignment 1
Intro
The goal of this lab is to learn the fundamentals of developing Android
Applications, from project creation to installation on a physical device. More
specifically you should gain the knowledge of how to use basic development tools
to support the application development process, as well as the key components of
an Android application itself.
Objectives
• Setup the Development Environment
• Create a "Hello World" Android Application
• Understand the various parts of an Android Project
• Use the Android Emulator
• Install and run the application on a physical device
• Create a simple User Interface
Activities
1. Setting Up the Development Environment
1.1 Download/Install the SDK
For in-depth instructions, visit Android Installation Documentation. Otherwise perform the following
steps.
• Go to http://developer.android.com/sdk/index.html.
• Unpack to a convenient location - Remember the full path to this location,
we will refer to it as <android_sdk_dir> for the rest of the lab.
o For example, on Linux your home directory is a convenient location.
o <android_sdk_dir> would then be /home/<username>/android_dir.
• Add the path to the <android_sdk_dir>/tools directory to your system PATH
o Linux:
1. Using your favorite text editor, open the .mycshrc file in your
home directory.
2. Add the following text to the end of the file:
set path=($path <android_sdk_dir>/tools)
3. Open up a terminal, navigate to your home directory and
execute the following command:
source .mycshrc
o Windows:
1. Right-click My Computer.
2. Click Properties.
3. Click Advanced tab.
4. Click Environment Variables button.
5. Double Click Path under System Variables.
6. Add ;<android_sdk_dir>/tools to the end of the Variable
Values text field.
• Test your installation by running adb from the command line. If you did
everything right, you should get a long list of help instructions.
1.2 Download/Install the Eclipse Plugin
• It is recommended that you use Eclipse 3.4 or later
o UNIX - Fedora Eclipse based on 3.4.2
This version of Eclipse is missing a vital component and requires
adding an additional Eclipse plugin in order to use the Android
plugin:
1. Click the menu Help -> Software Updates.
2. Click the tab Available Software -> Add Site button.
3. Enter http://download.eclipse.org/releases/ganymede into
the Location field.
4. Click OK button.
5. Enter WST Common UI into the search/text box at the top of
the window (give it a second, it tries to search as you type
and its kind of slow).
6. Click the checkbox next to WST Common UI.
7. Click the Install button.
8. Click the Next button.
9. Accept the terms, click Finish.
10. Restart Eclipse.
11. Follow the steps in the next bullet 3.4 Ganymede.
o 3.4 Ganymede:
1. Click the menu Help -> Software Updates.
2. Click Available Software tab -> Add Site button.
3. Enter https://dl-ssl.google.com/android/eclipse/ into the
"Location" field.
4. Click OK button.
5. Click the checkbox next to Developer Tools.
6. Click the Install button.
7. Click the Next button.
8. Accept the terms, click Finish.
9. Restart Eclipse.
o 3.5 Galileo:
1. Click Help -> Install New Software .
2. Click Add... button.
3. Enter a name for the site into the Name field.
4. Enter https://dl-ssl.google.com/android/eclipse/ into the
Location field.
5. Click OK button.
6. Click the checkbox next to Developer Tools.
7. Click the Next button.
8. Accept the terms, click Finish.
9. Restart Eclipse.
• Point Eclipse to <android_sdk_dir>:
1. Click the menu Window -> Preferences.
2. Click Android from the Hierarchy view on the left hand side.
3. Enter <android_sdk_dir> into the SDK Location field.
4. Click the Apply button.
5. Click the OK button.
1.3 Download/Install the SDK Platform Components
At the time of writing this lab there are eight different versions of the Android
Platform available, ranging from 1.1 to 2.2. It is best practice to develop for the
oldest platform available that still provides the functionality you need. This way
you can be assured that your application will be supported by as many devices as
possible. However, you will still want to download newer versions of the platforms
so that you can test your applications against these as well. Due to the size of
each platform component you will only be required to download and develop on
one platform for the whole class. We will target the highest platform that the G1
phones support, Android 1.6 (API 4). Before we can begin developing we must
download and install this platform:
• Select the menu Window -> "Android SDK and AVD Manager", or click
on the black phone shaped icon in the toolbar.
• Select Available Packages on the left hand side.
• Expand the Google Android site in the "Site, Packages, and Archives"
Tree.
• Check the following items:
o SDK Plaform Android 1.6, API 4 Revision 3
o Google APIs by Google Inc., Android API 4, Revision 2
o NOTE: Those of you using linux should follow these
instructions: http://sites.google.com/site/androidhowto/how-to-1/set-up-the-sdk-onlab-
machines-linux.
• Click Install Selected.
• Accept the Terms for all packages and click Install Accepted.
We're now ready to develop our application.
2. Create "Hello World" Application
2.1 Create a new Android Project
• Open Eclipse.
• Click the menu File -> New -> Project.
• Expand the Android folder and select Android Project.
• Name the project lab1<userID>
o For instance, if you userID is jsmith, you would name your
project lab1jsmith .
• You can change the location of where you would like to save the project by
un-selecting the "Default Location" check box, and supplying your own
location.
• Check "Android 1.6" from the Build Target List.
o This identifies that the project is being built to be compatible with
Android versions 1.6 and later.
o Its generally preferred that you choose the lowest build number
possible, so as to be compatible with the largest number of existing
systems in place.
o This build target can be changed any time later on through the
Project Properties menu.
• Fill in the Properties:
o Application Name = Hello World!
This is the friendly name of the application, that shows up on
the device.
o Package Name = edu.itu.android.lab1<userID>
This is the namespace for the project, follows standard Java
conventions.
o Create Activity = HelloWorld
This optional field will automatically create a "Main Activity"
class for the project. You can think of the Main Activity as the
Home Page for your application.
o Min SDK Version = 4
This specifies the minimum API Level on which your
application can run. By default this is set to the API Level of
the Build Target Platform. As new API's are added to newer
Versions, their API levels increase as well. A Program that
uses an API Level of four won't be able to run on a platform
that has a lower API Level.
• Click "Finish".
3. Take a Tour of the Application
The application you've just created is very similar to other java applications you
may have created in Eclipse. Look in the Package Explorer side bar. Notice that
the Android Development Toolkit(ADT) has generated a number of folders and
files for you:
• src: If you expand this out you'll see the package hierarchy you previously
entered. This is where your source code files will go.
o HelloWorld.java: This is the auto-generated stub Activity Class with
the name you entered into the project creation wizard. We'll add
some code to this later.
• Android 1.6: This is the version of the library you had chosen in the project
creation wizard. The application will be built using this version of
'android.jar'
• res: This folder will contain all of the resources (a.k.a. external data files)
that your application may need. There are three main types of resources
that you will be using and the ADT has created a subdirectory for each.
o drawable: This folder will hold image and animation files that you
can use in you application.
It already contains a file called icon.png which represents the
icon that Android will use for your application once it is
installed
o layout: This folder will hold xml layout files that the application can
use to construct user interfaces. You will learn more about this later,
but using a layout resource file is the preferred way to layout your
UI.
It already contains a file called main.xml which defines the
user interface for your 'HelloWorld.java' Activity class. Double
clicking on this file will open up the Android UI Editor that you
can use to help generate the xml layout files.
o values: This folder will hold files that contain value type resources,
such as string and integer constants.
It already contains a file called strings.xml. Double clicking on
this file will open up the Android Resource Editor. Notice that
there are two strings in there already, one of which is named
'app_name'. If you select this value, on the right hand side of
the editor you should see the Application Name you entered
in the project creation wizard. You can use this editor to add
new resources to your application.
• gen: This folder will contain Java files that get auto-generated by ADT.
Notice that it already contains one file called "R.java".
o R.java: This is a special static class that is used for referencing the
data contained in your resource files. If you open this file you will
see a number of static inner classes for each of the resource types,
as well as static constant integers within them. Notice that the
names of the member variables are the same as the names of the
values in your resource files. Each value in a resource file is
associated with an integer ID, and that ID is stored in a member
variable of the same name, within a static class named after its data
type.
The 'app_name' resource value has an ID and is of value type
'string'. The ADT automatically adds an integer constant to
the R.string class and names it 'app_name'.
A debugging hint: Occasionally, an Android project will report
errors in Eclipse that do not show up in any source code
file. Sometimes you can fix this by deleting R.java. When
you rebuild your project, R.java gets generated, and perhaps
your mysterious errors will disappear.
A second hint: I encourage you to turn on build automatically
in Eclipse (Project menu).
• assets: This folder is for asset files, which are quite similar to resources.
The main difference being that anything stored in the 'assets' folder has to
be accessed in the classic 'file' manipulation style. For instance, you would
have to use the AssetManager class to open the file, read in a stream of
bytes, and process the data. You will not be using assets quite as
extensively as you will be using resources.
• AndroidManifest.xml: Every project has a file with this exact name in the
root directory. It contains all the information about the application that
Android will need to run it:
o Package name used to identify the application.
o List of Activities, Services, Broadcast Recievers, and Content
Provider classes and all of their necessary information, including
permissions.
o System Permissions the application must define in order to make
use of various system resources, like GPS.
o Application defined permissions that other applications must have in
order to interact with this application.
o Application profiling information.
o Libraries and API levels that the application will use.
• default.properties: Ths file contains all of the project settings, such as the
build target you chose in the project creation wizard. If you open the file,
you should see 'target=4', which is your build target. You should never edit
this file manually. If you wish to edit the project properties, do so by rightclicking
the project in the 'Package Explorer' panel, and selecting
'Properties'.
The project creation wizard has written the 'Hello World' application for you
already. A string resource containing the display text has been placed into the
res\values\strings.xml file. The value is named 'hello'.
The xml UI layout has been added to res\layout\main.xml. While you can use the
Android Layout Editor to create your xml layout, you can also code them manually
yourself. Lets take a look at this file:
• Right-Click on the file.
• Select Open With -> Text Editor.
Notice the Top Level node, Linear Layout, which defines the style of layout this
file will be using. This 'Linear Layout' is perhaps the most basic, and specifies that
UI elements will be laid out in a continuous line. It has three properties:
orientation, width, and height.
Notice the Second Level node, TextView, which defines a UI element for
displaying text. It has three properties: width, height, and the text to display.
Notice that the text property is not set to "Hello World!". Instead it is set to
reference the resource value which contains the text we want to display. In this
case we are choosing to display the contents of the 'hello' value. We do this by
using the '@' symbol, followed by the value type of the resource (which is a
'string'), followed by the name of the value (which is 'hello').
4. Run "Hello World" on the Emulator
4.1 On the Emulator
Before we can run the application, we need to setup an Android Vitual
Device(AVD), or emulator, to run it on:
• Select the menu Window -> "Android SDK and AVD Manager", or click
on the black phone shaped icon in the toolbar.
• Select Virtual Devices on the left hand side.
• Click the New... button.
• Give your AVD a name.
• Select the target build that we would like to run the application on,
"Android 1.6 - API Level 4".
• Click Create AVD and close out the SDK/AVD Manager.
We're now ready to run our application.
• Select the menu Run -> Run.
o Note: The emulator may take a long time to start up.
o Note: Another way to run your application is to right-click on the
project in the Package Explorer, then select Run As -> Android
Application.
• You can interact with the emulator using the mouse just like you would with
a device. To get started, press the Menu key to see the home screen.
Congratulations! You've just created and an Android Application.
4.2 On a Physical Device
Before we can run the application on a physical device we need to modify the
project, make a configuration change on the phone, and install some drivers for
the phone on our development machine. We begin by making your project declare
itself as debuggable. It's possible to do this through the Android Manifest Editor
that the ADT provides, however doing this manually helps you understand the
manifest better:
• Project Modifications
o From the Package Explorer, double-click the file
AndroidManifest.xml.
o Select the tab labeled AndroidManifest.xml along the bottom.
o Add android:debuggable="true" to the inside of the opening
<application> tag.
o Save the file and close it.
• Phone Modifications
o Turn the phone on.
o Navigate to the Home screen.
o Press MENU (the physical button on the device).
o Select Settings -> Applications -> Development.
o Enable the USB debugging option.
• Installing the Android USB drivers
o Mac OS X: Don't need to install drivers, it should just work.
o Windows and Unix – Follow these instructions:
http://developer.android.com/guide/developing/device.html
Ensure the device is properly connected. Run the application as you would
normally. The "Hello World!" app should start on the phone.
5. Simple Activity Classes
There are four major types of component classes in any Android application:
• Activities: Much like a Form for a web page, activities display a user
interface for the purpose of performing a single task. An example of an
Activity class would be one which displays a Login Screen to the user.
• Services: These differ from Activities in that they have no user interface.
Services run in the background to perform some sort of task. An example
of a Service class would be one which fetches your email from a web
server.
• Broadcast Receivers: The sole purpose of components of this type is to
receive and react to broadcast announcements which are either initiated by
system code or other applications. If you've ever done any work with Java
Swing, you can think of these like Event Handlers. For example, a
broadcast announcement may be made to signal that a WiFi connection
has been established. A Broadcast Receiver for an email
application listening for that broadcast may then trigger a Service to fetch
your email.
• Content Providers: Components of this type function to provide data from
their application to other applications. Components of this type would allow
an email application to use the phone's existing contact list application for
looking up and retrieving an email address.
In this lab, we will be focusing on what Activities are and how they are used. We
will cover the other components in later labs. If you would like more information
on these components, visit the Android overview page for Application
Components.
In case you haven't figured it out by now, you have already created one of these
component classes. That's right, the HelloWorld class is an Activity Class. It's a
simple user interface designed to greet the user. In the section that follows, we'll
make our application more personal by adding a new Activity class to ask for the
user's name. We'll then update the existing HelloWorld greeting Activity to display
that name.
Note: If you ever have problems getting things to compile in Eclipse, you might try
Project -> Clean. You can also try to delete R.java under res, then Project ->
Build Project.
5.1 Getting the User's Name
To get the user's name, you will be creating an Activity class which will allow the
user to enter their name into a text field and press a button when finished to
proceed to the HelloWorld greeting Activity. There are three separate steps to
accomplish here. You must first layout your user interface in XML. Then you must
create the Activity class to parse the input from the user and initiate the
HelloWorld Activity. Finally, you will have to reconfigure the application to use
your new name retrieval Activity on startup.
5.1.1 Create the User Interface
Android allows you to layout your user interfaces using a simple XML
specification. We will go into more depth on this topic in the next lab, so for now
you will be setting up a basic interface using four different GUI elements. Begin by
creating a new Android XML file:
• Select the menu File -> New -> Android XML File.
If Android XML File does not appear in the menu:
o Select Other.
o Expand the Android folder.
o Select Android XML File and click Next.
• Ensure the Project matches the name of your project and that the folder
is /res/layout.
o Layout files should always go in this folder.
• Enter "name_getter.xml" as the file name
o The name of your layout files must only contain lower case letters,
the numbers 0-9, underscores '_', or periods '.'
o [a-z0-9_.]
• Select the Layout radio button.
• Select LinearLayout from the "Select the root element..." drop down and
click Finish.
• By default, the file will be opened to the Layout Editor tab. Select the tab
labeled name_getter.xml to switch to the XML Editor.
o This should be located in the bottom left corner of the Layout Editor.
Each GUI element derives from the View base class. The first element was added
for you when you created the XML layout file and selected LinearLayout from the
dropdown menu. You should be able to see an XML opening and closing tag
labeled LinearLayout in the editor. Each XML layout file must have a single root
view tag, inside which all other view tags are nested. The LinearLayout tag tells
Android to arrange elements contained inside it in a straight line in the order in
which they appear. Let's make a few modifications to the LinearLayout by editing
the attributes contained in the opening LinearLayout tag:
• Set the attributes
labeled android:layout_width and android:layout_height to "fill_parent" (
Include the quotes).
o This tells Android that the LinearLayout should take up all the
available width and height on the screen.
• Add an attribute labeled android:orientation and set it to "vertical" .
o This tells Android that elements nested inside the LinearLayout
should be laid out in a column, as opposed to a single row as
indicated by "horizontal".
Lets add the other three UI elements to our XML layout file:
• Switch back to the Layout Editor tab.
o The three elements we will add all reside under the folder icon
labeled "Views"
o This can be seen along the left hand side of the previous figure,
about halfway down
• Scroll down to the item labeled TextView.
o Click and drag the TextView onto the black canvas.
o The Layout Editor will pre-populate the label with its auto-generated id, which may
look somewhat strange.
• Repeat the previous step for the EditText and Button labels.
Assignment 2
Intro
In this lab we will be learning how to use and extend the Android user
interface library. In a number of ways it is very similar to the Java Swing
library, and in perhaps just as many ways it is different. While being familiar
with Swing may help in some situations, it is not necessary. It is important to
note that this lab is meant to be done in order, from start to finish. Each
activity builds on the previous one, so skipping over earlier activities in the
lab may cause you to miss an important lesson that you should be using in
later activities.
Objectives
At the end of this lab you will be expected to know:
• What Views, View Groups, Layouts, and Widgets are and how they relate to each
other.
• How to declare layouts dynamically at runtime.
• How to reference resources in code and from other resource layout files.
• How to use Events and Event Listeners.
Activities
For this lab we will be creating a "Joke List" application. It is a simple app
that allows a user to view and edit a list of jokes. All tasks for this lab will be
based off of this application. Over the course of the lab you will be iteratively
refining and adding functionality to the Joke List app. With each iteration you
will be either improving upon the previous iteration's functionality, or you will
be implementing the same functionality in a different way.
IMPORTANT:
You will be given a Skeleton Project to work with. This project contains all of
the java and resource files you will need to complete the lab. Some method
stubs, member variables, and resource values and ids have been added as
well. It is important that you not change the names of these methods,
variables, and resource values and ids. These are given to you because there
are unit tests included in this project as well that depend on these items
being declared exactly as they are.
1. Setting Up...
1.1 Creating the Project
To begin, you will need to download and extract the skeleton project for the
JokeList application.
• Download the skeleton project that is included with this lab description.
• Extract the project, making sure to preserve the folder structure.
o Take note of the path to the root folder of the skeleton project.
o You may prefer to extract it to your Eclipse workspace directory.
Next you will need to setup a "Joke List" Android project for this app. Since
the skeleton project was created in Eclipse, the easiest thing is to import this
project into Eclipse.
• Select File -> Import.
• In the Import Wizard, expand General and select Existing Projects
into Workspace. Click Next.
• In the Import Project wizard, click select root directory and click
Browse. Select the root directory of the skeleton project that you
extracted. Click Open and then Finish.
• Click on the project name in the Package Explorer. Select File ->
Rename and change the name of your project to lab2<userid> where
<userid> is your user id (e.g. jsmith).
1.2 Fill in the Joke Class
Throughout the lab you will be working with the Joke object class. This class
will be used to encapsulate two items pertaining to jokes.
1. m_strJoke: This represents the actual text of the joke.
2. m_nRating: This represents a rating that can be assigned to a joke.
There are three possible values that the rating can take:
o UNRATED: indicates no rating has been assigned to this joke by
the user.
o LIKE: indicates the user liked this joke.
o DISLIKE: indicates the user did not like this joke.
Begin by filling in this class:
• Open the Joke.java file
o Under the src/ folder in the edu.calpoly.android.lab2 package.
• Fill in all methods marked //TODO.
o Read the comments if you are confused as to the purpose of any
of the methods.
• Run the JokeTest.java Unit Tests to ensure that you have properly filled
in this class.
o Locate the JokeTest.java file under the test/ folder in the
edu.calpoly.android.lab2 package.
o Right click the file, select Run as -> Android JUnit Test.
o This should open up a JUnit Tab and if you see the Green bar,
you've passed all the tests and you can continue on to the next
section:
o If you see a red bar, this means that you've failed one or more
tests. The tab should show how many tests failed, and which
tests you failed (circled in yellow):
o Feel free to open up the JokeTest.java file to look at what
exactly is being tested. Make the appropriate corrections and
rerun the tests until you've passed all the tests.
o There are additional tests in the SimpleJokeListTest.java file, but
don't run those until later (see section 3.2.3).
1.3 Retrieving the Joke Resources Strings
The skeleton project has been pre-populated with an array of three different
String resources you can use as sample jokes. For a complete background on
Resources and how to properly use them you can see the Android Developer
Guide on Resources. For an overview:
• Open up res/values/strings.xml in the XML editor to view the joke
resources.
o Notice the <string-array name="jokeList"> element.
o This is how you declare an array of strings.
o Notice the name attribute.
When you add an element to a resource file, the Android
Development Toolkit will automatically add a static constant
with this variable name to the R.java file.
You can then use this constant as an identifier for retrieving
the resource element through a Resource Object.
• Open up the R.java file under the gen/ folder in the
edu.calpoly.android.lab2 package.
o Notice that R is a static class with a static subclass for each type
of resource element.
o If you add a string resource element and give it a
name attribute, a static constant with this name gets added to
the R.string class. Arrays get added to the R.array class,
drawables get added to the R.drawable class, etc.
The constant has the same name as the name
attribute. Layouts are slightly different in that you don't
have to specify a name attribute; the name of the constant
will be the name of the XML layout file.
The constant contains the resource id that you can use to
retrieve the resource.
You need to display these jokes when the application starts up. When an
Activity first starts up, its "public void onCreate(Bundle
savedInstance)" method is always called. This method allows you to
initialize the Activity. Right now you will only be initializing local variables to
hold the jokes that you just entered:
• Open SimpleJokeList.java.
• In the public void onCreate(Bundle savedInstance) method:
o Notice the super.onCreate(savedInstance) call. This is crucial,
never forget to make this call. If you don't your Activity won't
work.
o Make a call to this.getResources() , which will return a Resources
object.
The Resources class provides an interface for retrieving
resources by their resourceID.
resourceID's can be found in the static R class, under
their respective resource subclasses (which are named after
their resource type), under the name given to them in the
resource file:
R.array.jokeList
o Create an instance of the Joke ArrayList.
o Retrieve the array of joke strings by
calling getStringArray(R.array.jokeList) on the resource object.
o For each of these strings make a call to addJoke(...), which
will initialize Joke objects and place them in m_arrJokeList.
• Fill in the addJoke(...) method.
When creating a new Activity you will almost always override the onCreate
method. The onCreate method, as you will see later, is the place where you
will be creating your User Interfaces, binding data to different UI controls,
and starting helper threads (this will not be covered in this lab). Also, take
note of the "Bundle savedInstance" parameter that gets passed in. This
variable contains information on the previous state of the UI. As you may
have assumed, this means that you are able to save the state of the UI
before it closes so that it can be initialized to the same state the next time it
is created.
2. Brief Backgound on View Classes
In Android, a user interface is a hierarchy composed of different View objects.
The View class serves as the base class for all graphical elements, of which
there are two main types:
• Widgets: Can either be individual, or groups of UI elements. These are
things like buttons, text fields, and labels. Widgets directly extend the
View class.
• Layouts: Provide a means of arranging UI elements on the screen.
These are things like a table layout or a linear layout. Layouts extend
the ViewGroup class, which in turn extends the View class.
Layouts are all subclasses of the ViewGroup class and their main purpose is
to control the position of all the child views they contain. Layouts can be
nested within other layouts as well, to create complex user interfaces. Some
of the common layout objects you will use are:
• FrameLayout: Takes a single view object and simply pins it to the
upper left hand corner of the screen. Each child view that is added is
drawn on top of the previous one, causing it to be completely or
partially obscured.
• LinearLayout: Has a list of child views and draws them sequentially in
a single direction, either horizontally or vertically. You have the option
of assigning a weight value for each child view which determines how
much it is allowed to grow if there is extra space.
• TableLayout: Positions it's child views in a grid of rows and columns. A
row is a child view specified by the TableRow class. TableRows can have
zero or more cells and can contain empty cells. However, a cell cannot
span multiple columns. Each cell is itself another view, like a Button,
and can be set to shrink or grow.
• RelativeLayout: Positions it's child views relative to other child views
or the parent view. For example, two child views can be left-justified in
the parent, or one child view can be made to be below another.
• AbsoluteLayout: Has an absolute coordinate position for each child
view. This is a rigid layout that pins all child views down exactly where
they are specified. Using this does not allow the user interface to adjust
for different screen sizes and resolutions.
• See Common Layout Objects for more information...
Declaring the layouts for your user interface can be done dynamically (in
code), statically (via an XML resource file), or any combination of the two. In
the following subsection you will build a set of user interfaces in code. In a
future lab, you will build a set of user interfaces in XML.
3. SimpleJokeList
Work done in this section will be limited to the SimpleJokeList.java file.
SimpleJokeList is an Activity class which displays a vertical scrollable list of all
the Jokes in m_arrJokeList. Additionally, it has the ability to add jokes
to m_arrJokeList via a text field and add a button that floats at the top of
the screen.
3.1 Declaring Dynamic Layouts in Code
If you've ever built UIs in Java using Swing, this should be somewhat
familiar. You generally want to try to define the layouts for your interface
using XML when possible, but there are plenty of instances where it is still
necessary to do it at run-time in code. For instance, you may want to
dynamically change the layout based on some type of user input. Never the
less, working with the actual code is good practice for understanding how the
different Layout classes work and what sorts of interfaces all the various
controls offer.
3.1.1 Display a Scrollable Vertical List
Your first Task is to simply display each of the jokes in
the strings.xml resource file in a scrollable vertical list. When finished
your application should look something like this:
• Fill in the initLayout() method in SimpleJokeList.java:
o Initialize the m_vwJokeLayout LinearLayout member
variable.
When constructing View objects, you generally have to
pass a Context object into the constructor. A Context
object provides the functionality for accessing
resources (like our jokeList), databases, and
preferences. The Activity class inherits from Context
which is how we were able to retrieve our jokeList by
calling this.getResources().
o The LinearLayout class displays its child views horizontally by
default. Make sure to change this to vertical using
the setOrientation() method, passing in
the LinearLayout.VERTICAL constant.
LinearLayout: [Docs]
o Create a local ScrollView object.
A ScrollView is merely a FrameLayout that becomes
scrollable when its child views are larger than the
screen area.
It generally has a single child view, which in our case
will be another layout manager
o Add the LinearLayout to the ScrollView by calling
its addView(...) method.
o Make a call setContentView(...), passing in your ScrollView.
setContentView provides the content which the Activity
is supposed to display. Our UI is a hierarchical
structure of nested components. We want to pass in
the top-level, or root, element to this method. In our
case this is the ScrollView.
• Make a call to initLayout() in your onCreate(...) method and
place it above your retrieval and initialization of the jokes in
the strings.xml resource file.
• Update the addJoke(...) method.
o Add the joke text to a new TextView object by calling
its setText(...) method.
A TextView is used for displaying text on the screen.
o Add the TextView to m_vwJokeLayout.
• Your AndroidManifest.xml is currently setup so that
SimpleJokeList Activity will launch on startup. Create a Run
Configuration to specifically launch the SimpleJokeListActivity:
o Select Run -> Run Configurations from the menu.
o Select the Android Application item from the list.
o Click the New Launch Configuration icon
o Give the configuration a name and the name of your android
project.
o Select the Launch radio button and select the
SimpleJokeList activity from the dropdown list. (Click on
the image for a larger view)
• Now run your application to verify that your list of jokes shows up.
• The text is a little small, so use setTextSize(...) to increase the
size of all jokes to 16 font.
• It's somewhat hard to distinguish where one joke ends and
another begins, so let's alternate the background colors of each
joke. We'll store the color values as resources:
o Create a new XML Resource file under /res/values like you
did in Lab 1 and call it colors.xml
Hint: File->New->Android XML File
o You can use either the Resource Editor or you can directly
add the following XML to the file:
<color name="dark">#3D3D3D</color>
<color name="light">#1F1F1F</color>
If adding the XML yourself, make sure to nest the color
tags inside the <resource> tags.
o Modify the background color of the TextViews by
calling setBackgroundColor as you create them in your
addJoke(...) method.
Alternate between setting the background color
to dark and light.
Hint: Retrieve the colors in the same way you retrieved
the joke strings.
3.1.2 Adding Your Own Jokes
In this next task we will give the user the ability to enter their own jokes
by adding a text field and a button to the UI. The text field and button
should float at the top of the screen, above the list of jokes. When you
scroll through the jokes, the text field and button should not scroll, but
remain at the top of the screen.
In order to accomplish this we will walk through a process of nesting
LinearLayouts within each other to achieve the desired effect. However,
feel free to experiment and use whatever type of ViewGroup Layout you
want, so long as the functionality and appearance remains the same. In
the figure shown below, you can see how a root vertically-oriented-
LinearLayout containing a horizontally-oriented-LinearLayout and the
ScrollView of jokes can be combined to achieve the effect we want.
In this step you will only be updating your Layout. The new "Add Joke"
button and text field won't work yet. You will add code to hook-up those
controls in section 3.2 Simple Event Listeners.
• All of the work here, will be done in the initLayout() method.
Additionally, all but a few lines of that code should be written
above the code you wrote in 3.1.1.
• Declare a local vertically-oriented LinearLayout object, this will be
your root ViewGroup (displayed in green in the figure above).
• Declare a local horizontally-oriented LinearLayout object, this will
be the ViewGroup containing the "Add Joke" button and text field.
o Initialize your m_vwJokeButton member variable, setting
its text to "Add Joke".
o Add the Button to the horizontal LinearLayout.
o Initialize your m_vwJokeEditText member variable.
o call m_vwJokeEditText.setLayoutParams(...), passing in
an appropriately initialized LinearLayout.LayoutParams
object so that the EditText will take up all of the extra
available width and height.
o For more information see the Android Developer Guide and
Documentation on:
Layout Parameters
How Android Draws Views
LinearLayout.LayoutParams
o Add the EditText to the horizontal LinearLayout.
• Add your horizontal LinearLayout to your root ViewGroup.
• After your declaration and initialization of your ScrollView
ViewGroup, add the ScrollView to your root ViewGroup.
• Change your call to setContentView(...) to pass in your new root
ViewGroup.
• Run your application to test that you have properly setup your
layout.
3.2 Simple UI Event Listeners
Now that your UI has the components necessary to allow users to enter new
jokes, you need to hook these components up. In this section you will define
and register event listeners to handle adding new jokes.
Read the Android Developers Guide on Handling UI Events to get a detailed
background on event handlers and listeners. In short, you use event listeners
to respond to user interaction with your UI. There are two general steps for
doing this. The first is to define an object that implements an interface for the
type of interaction you want to respond. The second step is to register that
object with the UI control you want to respond to. For example, to react to a
particular Button being clicked, you have to register an object that
implements the OnClickListener interface with the Button you want to listen
to.
3.2.1 Adding a Button Listener
Setup the onClickListener for the "Add Joke" Button. When the user
hits the "Add Joke" Button a new Joke object should be initialized with
the text from the EditText field and added to your joke list. If the
EditText is empty, then the Button should do
nothing. In initAddJokeListeners():
• Call setOnClickListener method for you
m_vwJokeButton member variable. Pass in a reference to what's
called an Anonymous Inner Class that implements the
OnClickListener interface.
o If you are unfamiliar with anonymous inner classes that's
alright. Essentially, its an inline way of creating a one-timeuse
class that implements some interface. You declare the
class and instantiate it in one motion. You can read more
about them from Sun's Java Tutorials on Anonymous Inner
Classes.
o Copy the following code:
m_vwJokeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
//Implement code to add a new joke here...
}
});
o Fill in the onClick method in the anonymous inner class that
you created to add a new joke to the list.
Retrieve the text entered by the user into the EditText
by calling getText().toString() (Check for empty
strings here).
Clear the EditText.
Call your addJoke method
Add the following two lines of code to hide the Soft
Keyboard that appears when the EditText has focus:
o InputMethodManager imm = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(m_vwJokeEditText.getWindowToken(),
0);
o Call initAddJokeListeners from onCreate.
• Run your application to ensure that the "Add Joke" Button
functions properly now. (Click on the image for a larger view)
• In this situation you had two options for where you could
implement the code to handle the "Add Joke" button's onClick
event. Each has its drawbacks:
o Use an Anonymous Inner Class:
This option is a little cleaner and more readable since
the event handling logic for different elements is self
contained and referenced where it is used.
Since this event listener is only being used for one UI
control, you are free to make certain assumptions
when implementing it. For instance, you know exactly
which View generated the event.
However, this will consume more resources because
this instantiates a new object, and class information
for the object will need to be stored as well.
o Make SimpleJokeList implement the OnClickListener
interface:
In this option you would be able to register
SimpleJokeList to respond to Click events from many
different UI Controls. However, you would have to add
conditional logic for determining which view fired the
onClick event.
This has a clear performance benefit in that you don't
have to load extra classes, which reduces your
memory footprint and load time.
This is actually very common in Android applications
due to the need to design for performance imposed by
Assignment 3
Intro
This lab will be a continuation of Lab 2. You will expand on your knowledge of
the Android user interface library. It is important to note that this lab is meant
to be done in order, from start to finish. Each activity builds on the previous one,
so skipping over earlier activities in the lab may cause you to miss an important
lesson that you should be using in later activities.
Objectives
At the end of this lab you will be expected to know:
• How to declare layouts statically as an xml resource.
• How to create custom Views from scratch to suit a specific need.
• How to create Options and Context Menus.
• How to use Adapters and AdapterViews to bind a View class to data.
• How to establish Http connections.
• How to create Toast Notifications.
Activities
For this lab we will be extending the "Joke List" application that you created in
Lab2. This version of the app will be more advanced. It will allow the user to
give ratings to Jokes, delete Jokes, upload Jokes to a server, and download
Jokes from a server. All tasks for this lab will be based off of this
application. Over the course of the lab you will be iteratively refining and adding
functionality to the Joke List app. With each iteration you will be either
improving upon the previous iteration's functionality, or you will be
implementing the same functionality in a different way.
IMPORTANT:
You will be given a Skeleton Project to work with. This project contains all of the
java and resource files you will need to complete the lab. Some method stubs,
member variables, and resource values and ids have been added as well. It is
important that you not change the names of these methods, variables, and
resource values and ids. These are given to you because there are unit tests
included in this project as well that depend on these items being declared
exactly as they are. You have complete access to these test cases during
development, which gives you the ability to run these tests yourself. In fact, you
are encouraged to run these tests to ensure that your application is functioning
properly.
1. Setting Up...
1.1 Creating the Project
To begin, you will need to download and extract the skeleton project for the
JokeList application.
• Download the skeleton project and is included with this lab description.
• Extract the project, making sure to preserve the folder structure.
o Take note of the path to the root folder of the skeleton project.
o You may prefer to extract it to your Eclipse workspace directory.
Next you will need to setup a "Joke List" Android project for this app. Since the
skeleton project was created in Eclipse, the easiest thing is to import this project
into Eclipse.
• Select File -> Import.
• In the Import Wizard, expand General and select Existing Projects into
Workspace. Click Next.
• In the Import Project wizard, click select root directory and
click Browse. Select the root directory of the skeleton project that you
extracted. Click Open and then Finish.
• Click on the project name in the Package Explorer. Select File ->
Rename and change the name of your project to lab3<userid> where
<userid> is your user id (e.g. jsmith).
1.2 Fill in the Joke Class
You may fill in the Joke Class using the functionality that you implemented for
this class in Lab2. However, there is one key difference. A
member variable named m_strAuthorName has been added to the class which
will contain the name of the Joke's Author. In particular:
• You must update the constructors. You are required to pass in an Author
name for all the Constructors except for the default constructor.
• The equals(...) method now requires that the names of the Authors of the
two Jokes being compared must match as well, in addition to their text.
• There is a getter and setter that needs to be filled in.
Run the JokeTest.java Unit Tests to ensure that you have properly filled in this
class.
1.3 Use SimpleJokeList as a Starting Point
You may fill in the AdvancedJokeList class using some of the code that you
implemented for SimpleJokelist in Lab2:
• Fill in the addJoke(...) method using the code
from SimpleJokeList.addJoke(...).
o Note that the signature on the method has changed to accept a Joke
object instead of a string. You will have to modify
the SimpleJokeList.addJoke(...) code to use this new interface.
• Fill in the initAddJokeListeners(...) method using the code
from SimpleJokeList.initAddJokeListeners(...).
o Remember that the signature on the addJoke(...) method has
changed to accept a Joke object instead of a string. You will have to
modify some of the code here to use this new interface.
• Fill in the onCreate(...) method using the code
from SimpleJokeList.onCreate(...).
o Remember that the signature on the addJoke(...) method has
changed to accept a Joke object instead of a string. You will have to
modify some of the code here to use this new interface.
• Fill in the initLayout(...) method using the code from
SimpleJokeList.initLayout(...).
• Run the AdvancedJokeListAcceptanceTest.java Tests to ensure that you
have properly filled in this class. Note that other classes test classes are
also provided, but these tests should fail at this point. They will be used
later in other sections.
• Run your application to ensure that it performs the way it did in Lab2.
2 Declaring Static Layouts in XML
Read the Android Developer Guide on Declaring Layout for complete background
on declaring layouts. Declaring your user interface in XML is the preferred
method of implementation. By declaring your UI in an XML resource file it gives
you better separation between the presentation layer of your application and the
code controlling things underneath. One benefit of this is that modifications to
your UI can be made without having to change any source code or recompile.
This allows you to define different views for different screen sizes, resolutions,
and scenarios while using the same code to control everything.
2.1 Porting Your Dynamic Layout Into Static XML
In order to get some practice with setting up layouts in XML, you will begin by
converting the layout you setup dynamically in SimpleJokeList to an XML layout
file. You will then inflate this layout in AdvancedJokeList and set it as your
ContentView.
• Fill in the /res/layout/advanced.xml layout file:
o Make your advanced.xml layout file produce the exact same UI as
the one you declared dynamically in SimpleJokeList.
o advanced.xml has been stubbed out for you already. It contains a
FrameLayout as the root ViewGroup to prevent compilation errors,
you will need to replace this with an appropriate root ViewGroup
element.
o IMPORTANT: You must use the following resource id's for each of
the UI Components listed. These UI Components are defined as
member variables in AdvancedJokeList.java the same way they were
defined in SimpleJokeList.java:
EditText m_vwJokeEditText: use "newJokeEditText" as the
resource id
Button m_vwJokeButton: use "addJokeButton" as the resource
id
LinearLayout m_vwJokeLayout: use "jokeListViewGroup" as
the resource id
• Edit your initLayout() method to use the advanced.xml layout file:
o Remove all the code from this method. It should be an empty
method when you start.
o You must make your call to setContentView be the first thing that
you do.
o Instead of passing in a view, pass in the Layout Resource Id
for advanced.xml.
Hint: You did this in Lab1, you can find it in the Static R class.
You won't be able to retrieve references to your UI Controls
until the layout has been inflated from the XML file.
o Initialize your view class member variables by retrieving references
to them, instead of constructing new ones:
m_vwJokeLayout
m_vwJokeEditText
m_vwJokeButton
Hint: You did this in Lab1, remember
the findViewById method?
• Try running your AdvancedJokeListTest.java Unit Tests again. They should
still pass.
• Try running your application. The UI should appear and function exactly as
it did for SimpleJokeList.
2.2 Building Custom UI Components
Sometimes the standard View library will not supply the functionality that you
need. In situations like this it is completely acceptable to define your own UI
Components. There are three general approaches to creating custom UI
components:
1. Creating a custom component from scratch.
2. Modifying an existing component to serve your needs.
3. Combining existing components to create a compound component.
In this section you will be using the third approach to develop a custom
component. You will combine a number of different existing View classes to
create a coherent Widget for displaying Jokes. For a complete background on
this approach, as well as the other two approaches, read the Android Developer
Guide on Compound Controls.
The custom component that you are going to implement will have two states, an
expanded and a collapsed state. It will look something like this:
In the collapsed state there is an expand/collapse Button that displays a "+" and
a joke TextView displaying the first two lines of the joke. If the joke is longer
than two lines, it will only display two lines and append to the displayed text an
ellipsis.
In the expanded state there exist the exact same components that existed in the
collapsed state. Additionally, there will exist a RadioGroup containing two
RadioButtons, that will appear horizontally centered underneath the
expand/collapse Button and joke EditText. In the expanded state, the
expand/collapse Button will display a "-" and the joke EditText will no longer
display an ellipsis, but rather display the entire text of the joke (no matter how
long it is). The expand/collapse button should remain anchored to the top left
corner; it should not be centered vertically.
2.2.1 Declare a Custom JokeView XML Layout
The first step is to create the XML layout file that the custom component will use. You have
to implement this custom component using a single XML layout file. Switching between the
collapsed and expanded states is only a matter of hiding the RadioGroup, changing the
Button text, and changing settings on the TextView.
• Fill in the res/layouts/joke_view.xml layout file:
o When writing the layout, write the file as though you were
writing it for the expanded state. Don't worry about the
collapsed state until the next section.
o jokeview.xml has been stubbed out for you already. It
contains a FrameLayout as the root ViewGroup to prevent
compilation errors. You will need to replace this with an
appropriate root ViewGroup element.
o IMPORTANT: You must use the following id's for this list of UI
Components defined in AdvancedJokeList.java
Button m_vwExpandButton: use "expandButton" as the
resource id
RadioButton m_vwLikeButton: use "likeButton" as the
resource id
RadioButton m_vwDislikeButton: use "dislikeButton" as
the resource id
RadioGroup m_vwLikeGroup: use "ratingRadioGroup" as
the resource id
TextView m_vwJokeText: use "jokeTextView" as the
resource id
• Hints:
o You can test your UI without having to run your application by
switching back and forth between the LayoutEditor and the XML
Editor.
Make changes in the XML Editor.
The LayoutEditor will render your UI.
Test your layout by setting the text of your TextView in
the XML Editor to a really long string.
o If TextView keeps cutting the bottom off of really long text, try
adding padding to the top and bottom.
See the Android Documentation on
the paddingTop/paddingBottom XML attributes.
o Experiment with Relative Layout as your root ViewGroup, you
can then nest LinearLayouts as you see fit.
o Empty LinearLayouts can also be used as spacers between
components to help center things or provide adequate spacing.
See the Android Developer Guide on LinearLayout for a
discussion of weight.
Look into the layout_weight XML attribute in the Android
Documentation
2.2.2 Create a Custom JokeView Widget
The next step is to implement your custom component class. This class will be the
JokeView class. It is your task to fill in JokeView.java that has been stubbed out for you. In
general when creating a compound component, after you have established your layout,
you want your component class to extend the class of the root ViewGroup in your layout.
Your component class then becomes a special subclass of that ViewGroup.
Open up JokeView.java:
• Make the JokeView class extend the root ViewGroup of your layout.
o It currently extends the View base class, you will have to
change this to whatever class your root ViewGroup is.
• Fill in the JokeView(Context context, Joke joke) constructor:
o Inflate joke_view.xml:
This will be done differently than the way you've been
doing it. In this particular context, after the layout is
inflated, we want this JokeView object to be the root
ViewGroup of the inflated layout.
Copy the following code:
LayoutInflater inflater
= (LayoutInflater)context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.joke_view, this, true);
Instead of returning an inflated hierarchy of Views, this
JokeView object will become the root of that hierarchy.
o Initialize all the View component member variables by
retrieving references to them as you would normally do for a
layout declared in XML:
m_vwLikeButton
m_vwDislikeButton
m_vwLikeGroup
m_vwJokeText
m_vwExpandButton
• Fill in the setJoke(...) method:
o Update your m_joke reference with the joke was passed.
o Update m_vwJokeText with the text for the new joke.
o Set the checked state to true on the appropriate RadioButton to
reflect the rating for the new joke. If the joke is unrated then
neither RadioButton should be checked.
Hint: You can use the RadioGroup to clear the checked
state of all RadioButtons in the group.
o Make sure to call setJoke(...) from the constructor.
• Fill in the collapseJokeView() method:
o Set the m_vwJokeText to ellipsize the text of the joke. If the
joke is too long to fit in the TextView, the text should be
truncated to fit, and an ellipsis should be appended to the end
of the joke.
Hint: There is a single method call to handle this. See the
Android Documentation on TextView.
o Set the text on m_vwExpandButton to display
the JokeView.EXPAND string constant.
o Set the visibility on m_vwLikeGroup so that it disappears, and
does not take up any space in the layout.
Read the the documention on Using Views, specifically the
part about setting visibility, and the View.setVisisbility(...)
method.
o Make a call to requestLayout().
By ellipsizing the joke TextView and making the rating
RadioGroup disappear we have changed the size of the
JokeView. This has caused the JokeView to
become invalidated. Whenever a view becomes
invalidated it should request to be laid out again.
Failing to make this call will result in the view not being
updated properly.
o Make sure to call collapseJokeView() from the constructor.
• Fill in the expandJokeView() method so that it performs the inverse
functionality of collapseView().
• Setup the m_vwExpandButton to respond to OnClick events:
o Make the JokeView class implement the OnClickListener
interface.
fill in the onClick(...) method so that if the JokeView is in
its Expanded state, it calls collapseJokeView(). If the
JokeView is in its CollapsedState, it calls
expandJokeView().
In the constructor, set the OnClickListener
for m_vwExpandButton to this JokeView object.
• Setup the m_vwLikeGroup to respond to OnCheckedChange events:
o Make the JokeView class implement the
RadioGroup.OnCheckedChangeListener interface.
You can read the details for this interface method in the
Android Documentation
for RadioGroup.OnCheckedChangeListener.
Fill in the onCheckedChanged(...) method so that when
the state of the rating changes in the UI, the internal
state of the joke is updated to properly reflect this change
as well.
o In the constructor, set the OnCheckedChangeListener
for m_vwLikeGroup to this JokeView object.
2.2.3 Make AdvancedJokeList use the JokeView class
The last step is to update AdvancedJokeList to make use of your new JokeView custom
component.
• Edit the AdvancedJokeList.addJoke(...):
o Remove the code that initializes and adds a new TextView
to m_vwJokeLayout.
o Add code to initialize and add a new JokeView to m_vwJokeLayout.
• Run your Application to ensure that your changes to AdvancedJokeList and your
new JokeView custom component are functioning properly.
o Your application should load with all of the jokes in the collapsed state.
o Test that your jokes expand properly by clicking one of the expand Buttons:
3. Adapters & AdapterViews
The purpose of this next section is to introduce you to the concept of
AdapterViews. An AdapterView is a View class that allows us to bind it to a
dataset. This binding then takes care of responding to user selections as well as
populating the AdapterView with data. The binding is performed by a third
intermediate class, called an Adapter. It is the Adapter that is responsible for
keeping track of the selection and supplying the AdapterView with a View object
representation of each item in the dataset. Read the Android Developer Guide
on Binding to Data with AdapterViews for a complete background on the topic.
In the context of this section, the AdapterView is a scrollable vertical ViewGroup
called a ListView. The dataset is then our ArrayList of Joke objects. The Adapter
class is the JokeListAdapter, which follows the standard Object Adapter Design
Pattern; read the wiki on Object Adapter for more information. JokeListAdapter
contains a reference to our list of Joke objects and supplies ListView with a
JokeView for each them.
3.1 Implement JokeListAdapter.java
Begin by filling in the constructor and the stubbed getSelection() method:
• Set m_context and m_jokelist appropriately.
• Selection should be initialized to the Adapter.NO_SELECTION static
constant.
• NOTE: The selection functionality won't be used until later on when you
add the ability to "Remove" a joke.
Make the JokeListAdapter class extend the BaseAdapter class. Check the Android
Documentation on BaseAdapter for details. You will have to add and implement
the following abstract methods:
• public int getCount()
o Returns the number of items in the dataset (m_jokeList).
• public Object getItem(int position)
o Returns the Joke object from the dataset at the specified position.
• public long getItemId(int position)
o If a Joke had a unique Id this would return it. However, you can use
the Joke's position as its unique Id.
• public View getView(int position, View convertView, ViewGroup
parent)
o This method returns a JokeView object for the Joke object at the
position in the dataset specified by position.
o The convertView object allows you to re-use a previously
constructed view for better performance. Since convertView is a
View object that was previously returned
by JokeListAdapter.getView(...), then you can safely assume it is
JokeView.
Check to see if this value is null.
If it is null, then create a new JokeView object for the Joke
at position.
If it is not null, then change this JokeView to use the Joke
at position.
o The parent parameter represents the container the returned
JokeView will get added to. You won't need to use this, but in some
cases it can provide useful information.
3.2 Make AdvancedJokeList Activity Use ListView
You will now make the AdvancedJokeList Activity class use the ListView and the
JokeListAdapter classes to maintain your list of Jokes. You can read the Android
Documentation on ListView for details on the class.
• Update advanced.xml to use a ListView instead of a vertically oriented
LinearLayout nested inside a ScrollView. The ListView will replace both the
ScrollView and the LinearLayout.
o IMPORTANT: You must set the id attribute for the ListView element
to be "jokeListViewGroup".
• Update AdvancedJokeList.java:
o Change the type on m_vwJokeLayout to ListView and update its
initialization in initLayout.
o Initialize your m_jokeAdapter member variable with your ArrayList
of jokes.
Do this in the onCreate method immediately
after m_arrJokeList has been initialized, but before you
populate it with the joke's string resource values.
o Set m_vwJokeLayout's adapter to be m_jokeAdapter.
Do this in
the onCreate method immediately after m_jokeAdapter has
been initialized.
o Update the addJoke(...) method to notify m_jokeAdapter that the
dataset has changed.
Make a call to
JokeListAdapter's notifyDataSetChanged() method after
adding the joke to m_arrJokeList.
If you don't make this call after changing the dataset, the
ListView will not be updated to reflect the new state of your list
of Jokes. You can read the Android Documentation
on BaseAdapter.notifyDataSetChanged() for a complete
description of the method.
Remove the lines of code that explicitly initialize a new
JokeView and add it to m_vwJokeLayout. You don't need
these anymore.
Remove the lines of code that alternate the background colors.
Assignment 4
Intro
For this lab you will be developing a new GPS recording application called
WalkAbout. The purpose of the application is to allow users to record their
GPS location information as they travel. While the application records the
user’s GPS data, it displays it back to the user in the form of a path drawn on
top of a Google Map. While recording data, the user can launch a Camera
activity that will capture and store pictures on an SD-Card. When finished
recording, the application gives the user the option of storing the current GPS
data as a private application file to be loaded and displayed at a later time.
Objectives
At the end of this lab you will be expected to know:
• How to incorporate Google Maps into an application.
• How to register for and receive GPS location information.
• How to draw graphics on the screen using the Canvas class.
• How to create Google Maps Overlays.
• How to use the Camera.
• How to write data to the SD card.
• How to create and delete private application files.
• How to launch and receive results from Activities.
Activities
For this lab you will be working with a brand new application, completely
independent of the previous labs. Over the course of the lab, you will be
iteratively refining and adding functionality to the WalkAbout app. With each
iteration you will be improving upon the previous iteration's functionality.
You'll start by setting up and familiarizing yourself with the Eclipse project.
You will then register for a Google Maps API key and begin incrementally
developing the main map-viewing Activity. These first few exercises will have
you display a map and the user's current position. Next, you will add
functionality to record the user's GPS location by registering for and receiving
data from what is known as the GPS Location Provider. After that, you will
implement a Camera activity for taking pictures and saving them to the SDCard.
In the final section, you will save a user's GPS path to a file that is
private to the application, and allow the application to restore itself from the
file as well.
IMPORTANT:
You will be given a Skeleton Project to work with. This project contains all of
the java and resource files you will need to complete the lab. Some method
stubs, member variables, and resource values and ids have been added as
well. It is important that you not change the names of these methods,
variables, and resource values and ids. These are given to you because there
are unit tests included in this project as well that depend on these items
being declared exactly as they are. You have complete access to these test
cases during development, which gives you the ability to run these tests
yourself. In fact, you are encouraged to run these tests to ensure that your
application is functioning properly.
1. Setting Up...
1.1 Creating the Project
To begin, you will need to download and extract the skeleton project for the
WalkAbout application.
Extract the project, making sure to preserve the directory structure.
Take note of the path to the root folder of the skeleton project.
Next you will need to setup an Android project for this app. Since the
skeleton project was created in Eclipse, the easiest thing is to import this
project into Eclipse
• Select File -> Import.
• In the Import Wizard, expand General and select Existing Projects
into Workspace. Click Next.
• In the Import Project wizard, click select root directory and
click Browse. Select the root directory of the skeleton project that you
extracted. Click Open and then Finish.
• Click on the project name in the Package Explorer. Select File ->
Rename and change the name of your project to lab4<userid> where
<userid> is your user id (e.g. jsmith).
Next you will need to set the correct build target. In the Android SDK and
AVD Manager (available from Windows menu in Eclipse), ensure that you
have Google APIs for API 4. If you don't click on Available Packages (see:
http://code.google.com/android/add-ons/google-apis/installing.html ).
Once you have the Google APIs add-on install, you need to use it as the
target for your project. Right-click on the lab4<userid> project, the select
Properties. Select Android and select Google APIs for 1.6 (API 4) as the
Project Build Target.
1.2 Familiarize Yourself with the Project
The project contains three java class files and a single XML layout file which
you will have to implement. WalkAbout.java will contain the definition for
the main WalkAbout Activity class. This is the class that will display the map
and the user's recorded path. The WalkAbout class makes use of a very
simple XML layout file called map_layout.xml which you will have to fill in.
From the WalkAbout Activity you will be able to launch the CameraPreview
Activity, which will be defined in the CameraPreview.java file. The
CameraPreview Activity class displays a live camera preview on the screen
and will allow the user to capture and save a picture to the SD-Card. This file
was borrowed from the Google API's Demo application and the logic to display
the camera preview is already implemented for you. You will have to fill in the
rest.
The WalkAbout Activity class will rely on the PathOverlay class to draw the
user's traveled path on its map. You will implement the logic needed for
drawing the path in the PathOverlay.java file.
A general class diagram for the project is depicted below. Classes you will be
implementing are colored yellow, the Google Maps API classes that you will be
working with are colored in blue, and the standard Android classes you should
be familiar with are colored in white.
2. Using Google Maps
In this section of the lab you will be working extensively with the Google Maps
package. The Google Maps package allows you to include and manipulate
Maps in your Android Applications. The general strategy for displaying a map
in your application is to have an entire Activity dedicated to viewing a map.
This activity must extend the com.google.android.maps.MapActivity class,
which takes care of all the intricacies involved in setting up and tearing down
the services required to support displaying a map.
The actual map that gets displayed is an instance of
the com.google.android.maps.MapView class, which extends the standard
Android ViewGroup class. This class encapsulates all the gesture logic
necessary for handling panning, zooming, and touching objects on the map.
The map is just a Google map and can be displayed in three different modes:
satellite, street, and traffic. In order to use this class you will need a Google
Maps API Key. We will go over how to do this later.
By making use of the com.google.android.maps.Overlay class you
can overlay interesting data on top of your map. By either extending this base
class or using other overlay classes you can add another interaction layer
onto your map. The com.google.android.maps.MyLocationOverlay class is one
such example; it can draw a beacon on the map to display the device's
current position, and it can draw a compass on the map to display the
device's current heading.
2.1 Displaying a Map
You will begin by implementing the functionality necessary to display a fullscreen
map in the WalkAbout Activity class. The layout for the WalkAbout
Activity should be specified in the res/layout/map_layout.xml file, which you
will have to fill in. This will require you to use the
com.google.android.maps.MapView class. In order to do this, you will have to
register the debug keystore (that Eclipse uses to run your applications) with
Google in order to receive a Maps API Key. When finished, your application
should appear as depicted in the figure below:
For more information and examples on working with the Google Maps
Package see the documentation site and/or visit the Android Developer
Tutorial on the Google Maps Package.
2.1.1 Get a Google Maps API Key.
In order to use the Google Maps API's and classes you will have to
register the keystore you use to sign your application with the Google
Maps Service. Once your keystore is registered, you will be provided with
a Google Maps API key that can be used with any application signed by
your keystore.
Every distribution of the Android Development Toolkit comes with a
debug keystore that is used to sign your application when it is launched
and run from Eclipse. This is how you are able to run your application on
a device or emulator without signing it yourself. For the purposes of this
lab, you need only to register your debug keystore. If you end up
releasing an application to the market you will need to register an actual
keystore.
• Retrieve the MD5 Fingerprint of your debug.keystore file:
o Follow the instructions here:
http://code.google.com/android/add-ons/googleapis/
mapkey.html#getdebugfingerprint
• Register the Fingerprint with Google Maps Service:
o Follow the instructions here:
http://code.google.com/android/add-ons/googleapis/
mapkey.html#registering
• Save the API Key as a resource string in your Application:
o Copy and Paste the API Key into the res/values/strings.xml
file in between the mapApiKey string tags:
<string name="mapApiKey">INSERT_YOUR_API_KEY_HERE</string>
o Storing the API Key as a resource allows you to more easily
change it in the future.
• Your application must declare that it uses the Google Maps Library
in its manifest. Do so by adding the the following <useslibrary
... /> line to your AndroidManifest.xml file. This line
should be nested inside of
the <application></application> tags:
<application ...>
<uses-library android:name="com.google.android.maps" />
</application>
• Your application must use an internet connection to retrieve map
data so it must also declare that it uses the Internet in its
manifest. Do so by adding the following <uses-permission ... />
line to your AndroidManifest.xml file. This line should be nested
inside of the <manifest></manifest> tags:
<manifest ...>
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
2.1.2 Fill in the MapView XML Layout File
The main WalkAbout MapActivity class uses a very simple layout
consisting of a root ViewGroup that contains only a single MapView
element. Implement this layout by filling in the map_layout.xml file:
• Use whatever root ViewGroup you like to contain the MapView
element.
• When adding the MapView element, you need to use the fully
qualified class name:
<com.google.android.maps.MapView ... />
• IMPORTANT: You must give the MapView element an id of
"m_vwMap".
• The MapView should fill the entire contents of the screen.
• When using a MapView element you must give it your API Key. You
do this by inserting a special android:apiKey attribute into the
MapView element declaration.
o Set the attribute equal to the string resource that contains
the API Key.
android:apiKey="@string/mapApiKey"
o This is how you reference resources from within a resource
file.
• Set the MapView to clickable by adding
an android:clickable attribute with a value of true.
2.1.3 Display the Map
The WalkAbout MapActivity will display the MapView. All initialization
relating to the layout should be done in WalkAbout.initLayout(), which
gets called by onCreate():
• Inflate your map_layout.xml file.
• Initialize the MapView m_vwMap member variable by retrieving a
reference to the MapView element in the XML layout file.
• Force the Zoom Controls to be displayed in by making a call to
MapView.setBuiltInZoomControls(...) (hint: you don't actually type
MapView).
You should be able to run your application and see a Google Map. When
you touch the map, the Zoom Controls should be displayed centered
along the bottom of the screen.
2.2 Adding Map Overlays
As stated before, Overlays are used when you want to draw objects on top of
a MapView. Such objects could represent pin points for restaurant locations, a
route for displaying directions, or a simple logo. Each MapView instance is
responsible for drawing the overlays that get displayed on top of it. As such,
each MapView instance has a list of all Overlays that it should draw. When
you want to add an Overlay to a MapView, you simply retrieve the MapView's
list of Overlays and add to it.
The Overlay objects are different from regular View objects in that they are
not requested to be drawn by the system. Instead they are requested to be
drawn by the MapView when the system requests that the MapView draw
itself. As such, when you make changes to an Overlay, you cannot explicitly
request that the overlay be re-drawn by calling a method like
View.invalidate() or View.postInvalidate(). In order to get the Overlay objects
to be redrawn you must call View.invalidate() or View.postInvalidate() on the
MapView that owns the Overlay objects.
You will now add an Overlay to your current MapView which will display the
device's current location and heading on top of the MapView. When finished,
your application should appear as depicted in the figure below:
2.2.1 Display Your Location
In order for your application to display your location, it must access
Positioning data from a Location Provider. In order to do this, it must
declare that it uses the ACCESS_FINE_LOCATION permission in its
manifest.
• Do so by adding the following <uses-permission ... /> line to your
AndroidManifest.xml file. This line should be nested inside of the
<manifest></manifest> tags:
<manifest ...>
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"/>
</manifest>
Enable the map to display your current location and heading by
initializing and adding a com.google.android.maps.MyLocationOverlay to
your MapView. This should be done in the WalkAbout.initLayout()
method:
• Initialize m_locationOverlay with a new MyLocationOverlay object.
• Enable the MyLocationOverlay object's Compass and MyLocation
beacon by using the MyLocationOverlay.enableCompass() and
MyLocationOVerlay.enableMyLocation() methods.
o It is important to note that the MyLocationOverlay depends
on the GPS or Network Location settings being enabled.
These methods will do nothing if you do not have either the
Network or GPS Location setting enabled.
• Retrieve m_vwMap's list of attached Overlay objects by calling its
getOverlays() method.
• Add the m_locationOverlay to the list of Overlay objects.
You should be able to run your application and see a Google Map
containing a beacon pointing out your current location and a compass
displaying your current heading.
2.3 Initialize the WalkAbout Options Menu
The WalkAbout Activity has an Options Menu that will display five different
MenuItems. Create the Options Menu as follows: Do not worry about setting
OnMenuItemClickListeners yet, you will do that later.
• "Start/Stop": This MenuItem acts as a toggle for either starting or
stopping GPS Location recording. This recording is a record of the path
that you have traveled. While recording, the MenuItem should display
"Stop." While stopped, the MenuItem should display "Start."
o Initialize the text for the MenuItem with R.string.startRecording
resource string.
Later on, you will dynamically update the text to reflect the
current recording state.
o Use WalkAbout.STARTSTOP_MENU_ITEM constant as the Id for
the MenuItem.
• "Save": This MenuItem will save the current recorded path.
o Initialize the text for the MenuItem with R.string.save.
o Use the WalkAbout.SAVE_MENU_ITEM constant as the Id for the
MenuItem.
• "Load": This MenuItem loads the last saved path.
o Initialize the text for the MenuItem with R.string.load.
o Use the WalkAbout.LOAD_MENU_ITEM constant as the Id for the
MenuItem.
• "Take Picture": This MenuItem launches a Camera Activity that allows
you to take pictures.
o Initialize the text for the MenuItem with R.string.takePicture.
o Use the WalkAbout.PICTURE_MENU_ITEM constant as the Id for
the MenuItem.
• "Enable GPS": This MenuItem launches the Device Settings Activity that
allows you to enable the GPS Provider.
o Initialize the text for the MenuItem with R.string.enableGPS.
o Use the WalkAbout.ENABLEGPS_MENU_ITEM constant as the Id
for the MenuItem.
3. Using Location Services
The Android System provides services for determining the current location of
the device. The framework for working with these location based services
lives under the android.location package. A number of useful classes live
inside this package:
• LocationManager: Provides an interface for the location based
services. You will be interacting with this class most of the time when
trying to obtain location information.
• LocationProvider: LocationProviders are classes that provide updates
on the current location of the device. There exists a LocationProvider for
each different technology that determines location. There exists a GPS
LocationProvider and a Network LocationProvider. Each LocationProvider
specifies criteria that must be satisfied in order for it to be used. For
example, the GPS LocationProvider requires that the device have GPS
hardware and that it be enabled.
• Criteria: This class allows you to programmatically outline criteria you
would like from a LocationProvider. Such criteria may include accuracy,
power consumption, altitude reporting, speed reporting, monetary cost,
etc. You can then setup and give an instance of this class to the
LocationManager and allow it to choose the LocationProvider that best
matches your criteria.
• LocationListener: This is an interface that defines call backs for
different events that are generated by LocationProviders. These can be
registered with a particular LocationProvider to receive updates when
the location changes or the state of the LocationProvider changes.
• Location:A data object containing location based information.
Generally contains latitude and longitude, as well as a date and
timestamp containing the time at which the location was determined.
Might also contain elevation, speed, and heading information.
• Location based utility classes: A utility class for Geo-Coding which
transforms addresses to GPS Coordinates and back, a data object class
for addresses, and some classes for monitoring the status of the GPS
satellites the device is locked onto.
In general, you usually query the LocationManager for the information you
are seeking. You can check the current status of a LocationProvider, you can
check the last known location reported by a LocationProvider, and you can
register to receive updates from a LocationProvider all through the
LocationManager (just to name a few). To receive updates on location
information directly from a LocationProvider, you need to implement the
LocationListener interface and register yourself with the LocationProvider.
In the subsections that follow, you add functionality to monitor and enable
the GPS LocationProvider in the WalkAbout Activity class. You will record
changes in location as the user's path. You will then implement an Overlay
class that will draw this path onto the WalkAbout Activity's MapView object.
3.1 Testing & Enabling the GPS LocationProvider
It is entirely possible that the user has disabled the GPS hardware. Before
you can monitor and record data from the GPS LocationProvider, the GPS
hardware must be enabled. In this particular subsection, you will begin by
querying the LocationManager for whether the GPS hardware is enabled on
the device. If GPS is not enabled, you will allow the user to launch the
Location Settings Activity to enable it from the "Enable GPS" Options
MenuItem. After the user is done editing the Location Settings, they can
return to the WalkAbout Activity by hitting the back button. When the GPS
hardware is enabled the "Enable GPS" MenuItem will no longer be visible in
the Options Menu.
The following diagram depicts two use cases (Click on the image to view it at
full size). Both use cases have the application starting up without GPS
hardware enabled and without the Network LocationProvider enabled. The
first use case follows the dark green arrows along the top of the figure. The
User clicks the menu button, then clicks the "Enable GPS" MenuItem. The
Location Settings Activity launches, the user then decides not to enable the
GPS. The user clicks the back button and is returned to the Walkabout
Activity. The user clicks the Menu button, and the "Enable GPS" MenuItem is
still visible. Notice that the Network and GPS Location Providers are disabled,
that the MyLocationOverlay's Current Location Beacon is not present, and
Compass heading location is inaccurate. This is because the MyLocation
Overlay relies on the LocationProvider for displaying this information.
The second use case follows the golden arrows along the bottom of the figure.
The User clicks the menu button, then clicks the "Enable GPS" MenuItem. The
Location Settings Activity launches, the user then decides to enable the
GPS. The user checks the "Enable GPS satellites" check box (Note that this
option may be labeled different in different version of Android). The user
clicks the back button and is returned to the Walkabout Activity. The user
clicks the Menu button, and the "Enable GPS" MenuItem is no longer visible.
Notice that when the user returns to the WalkAbout Activity after enabling the
GPS hardware, that the MyLocationOverlay's current Location Beacon is now
present, and Compass heading location is now accurate. It is also important
to note that if the GPS Hardware is enabled when the application starts, then
the "Enable GPS" MenuItem should not be visible.
3.1.1 Initialize LocationManager
In various parts of the WalkAbout class, you will request location
information from the LocationManager attached to this application
context. The WalkAbout class will store a reference to this
LocationManager object in the m_locManager member variable for
convenience. Initialize this member variable in the
WalkAbout.initLocationData() method:
• Retrieve the LocationManager by calling the
Activity.getSystemService(...) method, passing in the
Context.LOCATION_SERVICE constant.
o Remember that this instance of the WalkAbout class is an
Activity and therefore all public and protected Activity
methods are members of this object.
• set m_locManager to the object returned to you. You will have to
cast the return value, because getSystemService(...) returns an
object of type Object.
3.1.2 Dynamically Update Options Menu
The Options menu should display the "Start/Stop" MenuItem as disabled
if the GPS Location Provider is disabled. Additionally, the Options menu
should NOT display the "Enable GPS" MenuItem at all if the GPS Location
Provider is enabled.
Each time the Options menu is displayed, you should check to see if the
GPS Location Provider is enabled and update the "Start/Stop" and
"Enable GPS" MenuItems accordingly.
• You must do this from the Activity.onPrepareOptionsMenu(...). If
this is done from the Activity.onCreateOptionsMenu(...) it will only
be done the first time the menu is shown. See the Android
Documentation on this method for more details.
• You can determine whether a Location Provider is enabled by
calling the LocationManager.isProviderEnabled(...) method and
passing in the name of the Provider. See the Android
Documentation on this method for more details.
• The names of the different Providers are stored as string constants
in the LocationManager class. Check the LocationManager
Constants Documentation to determine which string to pass in.
You should be able to run your application and test that the "Start/Stop"
and "Enable GPS" MenuItems are enabled and invisible respectively
when the GPS Provider is enabled. They should also be disabled and
visible respectively when the GPS Provider is disabled.
3.1.3 Implement "Enable GPS" MenuItem
When the GPS Location Provider is disabled, the user will be able to
launch the settings activity to enable the GPS Location Provider from the
"Enable GPS" Options MenuItem. Set and fill in the
OnMenuItemClickListener for the "Enable GPS" MenuItem:
• Instantiate a new Intent object, passing in the
Settings.ACTION_LOCATION_SOURCE_SETTINGS Action String
constant.
• Call the Activity.startActivityForResult() method passing in the
intent you just created and the
WalkAbout.ENABLE_GPS_REQUEST_CODE static constant.
o We pass in the Request Code so that when we handle the
result, we know which request generated the result. The
result of any Activity that we launch gets processed by the
same method and the Request Code mechanism lets us know
which result is coming from which Activity.
You should be able to run your application and test that you can launch
the settings activity to enable the GPS provider from the "Enable GPS"
MenuItem. When you hit the Back button you should return to the
WalkAbout Activity. However, you should note that the
MyLocationOverlay still won't be working.
3.1.4 Handle the Location Settings Activity Result
After the user is done with the Location Settings Activity and returns to the
WalkAbout Activity, you need to try re-enabling the MyLocationOverlay. Since you
called the Activity.startActivityForResult(...) method to launch the Location Settings
Activity, the WalkAbout Activity.onActivityResult(...) method will be called. The
requestCode parameter in this method will contain the value that you passed into
the startActivityForResult(...) method. You should test this requestCode parameter
to see if it matches the WalkAbout.EBABLE_GPS_REQUEST_CODE constant.
You test the requestCode because this same method will be called when any
Activity that you launch returns a result (You must test this value because you will
be launching another activity later and you want to be able to identify which Activity
is returning a result).
Do this by overriding and filling in the Activity.onActivityResult(...) method:
• Make sure to call super.onActivityResult(...).
• Test the requestCode argument to ensure that it matches the request code
Assignment 5
Intro
For this lab you will be developing a new Application named AppRater that
suggests other Applications for users to download and try. The purpose of
the application is to share fun and interesting applications with other
users. The users can then rate the applications.
Objectives
At the end of this lab you will be expected to know:
• How to create and use ContentProviders.
• How to create and use Services.
• How to broadcast Intents.
• How to post notifications in the Notification Bar.
• How to respond to Intent broadcasts by creating and using BroadcastRecievers.
• How to perform work in a Background thread.
Activities
For this lab you will be working with a brand new application, completely
independent of the previous labs. Once you have a general understanding
of the components of the application you will begin implementing it. You
will be given more general instructions in some sections. We think you
should be comfortable enough with Android by now that you can figure
more out on your own.
IMPORTANT:
You will be given a Skeleton Project to work with. This project contains all
of the java and resource files you will need to complete the lab. Some
method stubs, member variables, and resource values and ids have been
added as well. It is important that you not change the names of these
methods, variables, and resource values and ids. These are given to you
because there are unit tests included in this project as well that depend
on these items being declared exactly as they are. You have complete
access to these test cases during development, which gives you the ability
to run these tests yourself. In fact, you are encouraged to run these tests
to ensure that your application is functioning properly.
1. Setting Up...
1.1 Creating the Project
To begin, you will need to download and extract the skeleton project for
the AppRater application found with this description.
Extract the project, making sure to preserve the directory structure.
Take note of the path to the root folder of the skeleton project.
Next you will need to setup an Android project for this app. Since the
skeleton project was created in Eclipse, the easiest thing is to import this
project into Eclipse
• Select File -> Import.
• In the Import Wizard, expand General and select Existing
Projects into Workspace. Click Next.
• In the Import Project wizard, click select root directory and
click Browse. Select the root directory of the skeleton project that
you extracted. Click Open and then Finish.
• Click on the project name in the Package Explorer. Select File ->
Rename and change the name of your project to lab5<userid>
where <userid> is your user id (e.g. jsmith).
1.2 Familiarize Yourself with the Project
The project contains seven java files and two XML layout
files. AppRater.java will contain the definition for the main AppRater
Activity class. This is the class that will display the list of applications the
user is supposed to test and rate. The AppRater class makes use of a very
simple XML layout file called app_list.xml which has been completed for
you. It is composed of a ListView that displays a list of applications that a
user is supposed to try out.
Applications that users are supposed to try out and rate are represented
by the App model class, defined in App.java. This is a simple class that
encapsulates the name of the application, a rating for it, the Market-URI
from which the app can be downloaded (we'll discuss this later but think
of it as a link to install it from the Market application), a boolean used to
flag the application as installed, and a unique integer ID.
The AppView class is a custom class that is used for visualizing the state
of an App object. The AppView class has an App member variable on
which it bases the state of its display. It's layout is defined in
the app_view.xml XML layout file, which has already been completed for
you. This is a very simple layout file, composed of a LinearLayout
containing a single TextView. The TextView merely displays the name of
the App object that the AppView is visualizing.
The AppView class has three different states:
• If the AppView's App member has not been installed yet, then it's
background color is red.
• If the AppView's App member has been installed but hasn't been
rated yet, then it's background color is yellow.
• If the AppView's App member has been rated, then it's background
color is green.
The AppRater Activity gets the Apps it will display from
the AppContentProvider class as a Cursor of Apps. This is quite similar
to how the JokeList Activity got a Cursor of Jokes from the
JokeDBAdapter. The AppRater Activity's ListView then uses
the AppCursorAdapter class to bind AppViews to the cursor of Apps.
The AppRater Activity starts the AppDownloadService in order to add
new Apps to the underlying database. The new Apps are retrieved from a
web site. This is kind of like launching a new Activity except that there is
no User Interface for the Service. The AppDownloadService then
downloads and inserts new Apps into the database. In order to insert new
Apps into the database the AppDownloadService must use the
AppContentProvider's insert method. This is quite similar to how the
JokeList Activity (in previous labs) downloads and inserts new Jokes by
using JokeDBAdapter.
Once the AppDownloadService has successfully added a new App, it
broadcasts a special Intent. The AppRater Activity will use its
internal NewAppReceiver class to listen for that special Intent. When it
hears the Intent it knows that it has to update its cursor of Apps.
Lastly, once a user has downloaded, installed, and tested an App they can
give it a rating. The user can apply a rating to an App via a ContextMenu
in the AppRater Activity. The AppRater Activity then saves the changes to
the App through the AppContentProvider.
A general class diagram for the project is depicted below. Classes you will
be implementing are colored yellow, the new Android classes that you will
be working with are colored in blue, and the standard Android classes you
should be familiar with are colored in white.
2. AppRater Activity
In this section of the lab you will be learning how to use ContentProviders
to retrieve and update persistant data, how to use Services to perform
work in the background, and how to respond to system-wide notifications
by using BroadcastReceivers. All of the work done in the following
subsections should be performed in the AppRater.java file. The file has
been setup to import the already completed versions of the
AppContentProvider and AppDownloadService classes.
2.1 Querying a ContentProvider
You can interact with a ContentProvider in much the same way that you
interacted with the JokeDBAdapter. You can execute a query which will
return a Cursor, you can delete data, you can update data, and you can
insert data. The main difference is that ContentProviders offer a way to
share data between applications. Where your previous Joke database was
only visible to the JokeList application, the AppContentProvider that you
will implement later on will make your database usable by any
Application.
ContentProviders also abstract away how the underlying data is persisted.
While in this application you will be using an SQLiteDatabase, you could
just as easily store the data in files, on a remote server, or whatever else
you can come up with. For a complete background on ContentProviders
see the Android Developer Guide on ContentProviders.
For this section you must Query the AppContentProvider for a cursor of
Apps that the AppRater Activity will display. When a user clicks on one of
the listed apps, the Market application will be launched to display the
App's info page. The AppContentProvider you are using is currently prepopulated
with a single App so that you can test your implementation.
2.1.1 Fill in onCreate(...)
Start by initializing the AppRater Activity. You should query the
AppContentProvider to retrieve a Cursor containing all of the Apps it
contains. Use this cursor to initialize your AppCursorAdapter and
ListView members.
• The Android Developer Guide offers instructions on how
to Query a ContentProvider.
• The URI for the AppContentProvider is defined in the
AppContentProvider.CONTENT_URI constant String.
You should be able to run your application and view a list of Apps.
2.1.2 Launch the Market Activity
When a user clicks on an AppView, you should start the Market
Activity implicitly, meaning that you should not use an Intent that
explicitly launches the Market Application. Instead, you should use
an Intent object that should cause the Market application to be
launched. You do this by specifying an Intent action-string and the
data to be acted upon. When the Market application starts, it
immediately displays the info page for the App that was
clicked. Note that the Market app is not included in the
emulator. You will need to attach a phone to test this portion of your
app. You should handle the exception that gets thrown if you are
unable to launch the Market for an App - post a Toast message
"Unable to get App from Market" when this happens.
If you are confused on how to implicitly start an Activity or for more
details on Intents, see either the Android Documentation or
the Android Developer Guide on Intents:
• You should use the App.m_strInstallURI as the data which
should be acted upon.
• You should use the Intent.ACTION_VIEW as the action to be
performed.
You should be able to run your application, click on an App, and have
the Market application display the selected App's info page.
2.2 Starting & Stopping Services
Services are application components that run in the background and do
not have User Interfaces. Any time that there is some type of code that
needs to be run regularly but does not need a user interface you could
probably implement it as a Service. Services can be started from your
application's Activities that are currently visible, or they can be awoken by
System Notifications when your Activities are all closed. The AppRater
application will use a Service to update its ContentProvider with new data.
For a more detailed description, read the Android Developer Guide
on Service Application Components.
The AppContentProvider is currently only storing a single App. In this next
section you will have the AppRater Activity start the AppDownloadService
class. The AppDownloadService will download more Apps from a server
and add them to the AppContentProvider. Most importantly, the
AppDownloadService will execute in a background thread to keep the
main AppRater Activity responsive. The AppDownloadService will be
controlled through the AppRater Activity's Options Menu.
2.2.1 Create the Options Menu
The AppDownloadService needs to be started and stopped from the AppRater's Options Menu.
Additionally, you may want to remove all the Apps from the ContentProvider in order to repeatedly test
the AppDownloadService, so you will need to create a MenuItem for this as well. Implement the
AppRater Activity's Options Menu as follows:
• Consider overriding the Activity.onOptionsItemSelected(...) method instead
of implementing and setting OnMenuItemClickListeners.
• The "Start Downloading" MenuItem should use
AppRater.MENU_ITEM_STARTDOWNLOAD as its ID and should
start the AppDownloadService:
o Use the Context.startService(...) method with
an explicit Intent.
• The "Stop Downloading" MenuItem should use
AppRater.MENU_ITEM_STOPDOWNLOAD as its ID and should
stop the AppDownloadService:
o Use the Context.stopService(...) method.
• The "Remove All Apps" MenuItem should use
AppRater.MENU_ITEM_REMOVEALL as its ID and should delete
all Apps from the ContentProvider:
o See Android Developer Guide on Modifying
ContentProviders for information on how to delete rows
from a ContentProvider if you're confused.
Passing in null for
the where and selectionArgs parameters will
cause all rows in the ContentProvider to be deleted.
Run your application and you should see the pre-populated Apps.
Click the "Start Downloading" MenuItem, and wait a few seconds for
the Apps to download and appear in your ListView. You should see a
notification in the Notification Bar. Click the "Remove All" MenuItem
and all the Apps should disappear.
It is important to note that your AppDownloadService will continue to
run. If you wait a few more seconds the ListView should repopulate
itself with the Apps. The AppDownloadService will run even after you
have closed the AppRater Activity. You must explicitly stop it by
using the "Stop Downloading" MenuItem in order to shut it down.
2.3 Responding to Broadcasts
Intents are used as a System-level message passing system. They can be
used to start application components, or they can be used to send
messages between components. In order to listen for a message, you
need to use a Broadcast Receiver.
In the AppRater Application, the AppDownloadService will broadcast an
Intent containing the AppDownloadService.NEW_APP_TO_REVIEW action
string constant after it successfully downloads and saves a new App to the
database. Since the AppDownloadService runs in the background as a
Service, this could happen whether the AppRater Activity is running or
not. If the AppRater Activity is running, it should display a Toast
notification telling the user that a new App was downloaded.
In this section, you will implement the AppReceiver class, which extends
BroadcastReceiver. AppReceiver will listen for
AppDownloadService.NEW_APP_TO_REVIEW Intents and display a Toast
notification when it hears them.
2.3.1 Implement the AppRater.AppReceiver Class
Fill in the AppReceiver.onReceive(...) method so that the AppRater
Activity displays a Toast notification telling the user that a new app
was downloaded:
• You should test the Intent argument to ensure it has
AppDownloadService.NEW_APP_TO_REVIEW as its Action-
String.
• Your Toast notification should use the R.string.newAppToast
String resource.
2.3.1 Register & Un-Register for Broadcasts
In order for your AppReceiver to be notified
when AppDownloadService.NEW_APP_TO_REVIEW action Intents are
broadcast, you must register an instance of it with your Activity.
Likewise, when the Activity moves out of the foreground, you need to
unregister the AppReciever.
• Override and fill in Activity.onResume(...).
o Initialize AppRater.m_receiver.
o Construct a new IntentFilter that only listens for the
AppDownloadService.NEW_APP_TO_REVIEW action string
constant.
Intents can be broadcast from any android
component to any android component. Declaring
an IntentFilter allows you to specify which Intents
you want to listen for by specifying certain Intent
characteristics. In this case we're only interested in
Intents that have an action string
of AppDownloadService.NEW_APP_TO_REVIEW.
o Use the Activity.registerReceiver(BroadcastReceiver
receiver, IntentFilter filter) method to register
m_receiver for Intents containing the
AppDownloadService.NEW_APP_TO_REVIEW action
string.
• Override and fill in Activity.onPause(...).
o Use the Activity.unregisterReceiver(...) method to
unregister m_receiver.
Run your application and click the "Remove All Apps" MenuItem to
clear the AppContentProvider. Then click the "Start Downloading"
MenuItem. After a few seconds the AppRaterActivity should display
the new app Toast notification as new Apps are added to the
ListView.
2.4 Updating Data in ContentProviders
It is sometimes necessary to modify the data contained in a
ContentProvider. In this next section you will add functionality to persist
changes to App objects in the ContentProvider. In particular, you will
enable the user to edit whether they have installed an App, as well as
apply a rating to it. Both of these actions should be performed through a
ContextMenu that gets generated by Long-Clicking on an AppView. The
main use case is pictured below.
2.4.1 Create the ContextMenu
The Context menu should use a CheckBox MenuItem for selecting whether the App has been
installed, and a group of four RadioButtons for selecting the rating to apply to an App.
Implementation Requirements:
• The "Installed" CheckBox should use AppRater.MENU_ITEM_INSTALLED as its
MenuItem ID.
• The "X Stars" Rating RadioButtons should use AppRater.MENU_RATING_GROUP as
their Group ID, and should use the AppRater MENU_ITEM_RATING1,
MENU_ITEM_RATING2, MENU_ITEM_RATING3, MENU_ITEM_RATING4 constants
as their individual MenItemID's.
Functional Requirements:
• If the App has not been installed you should not be able to give it a rating and all the
RadioButtons should be disabled (but still visible).
• Once an App has installed, the RadioButtons should be enabled to allow the user to
edit the rating.
• If the user has installed and rated an app, then the ContextMenu should have the
proper CheckBox and proper RadioButton selected respectively.
• If the App has not been rated, then no RadioButton should be selected.
2.4.2 Persist App Changes
The tricky part here is figuring out which App to make the changes
to. You need to identify the AppView that was clicked when the
ContextMenu was generated. From the AppView you can retrieve the
App. Follow these instructions to retrieve the AppView and its App:
• When the onCreateContextMenu(...) was called, your ListView
that generated the ContextMenu provided the method with
an AdapterView.AdapterContextMenuInfo parameter.
o When you add a MenuItem to a ContextMenu the
ContextMenuInfo gets added to the MenuItem.
o The AdapterContextMenuInfo has a public member
variable that references the AppView that was clicked.
• Retrieve the ContextMenuInfo from the MenuItem.
o You'll need to cast it to an
AdapterView.AdapterContextMenuInfo.
• Retrieve the targetView member from the
AdapterContextMenuInfo object.
o You'll need to Cast it to an AppView.
• Retrieve the App from the AppView.
o This is the App that was clicked. So simple right :)
Here are the Functional/Implementation Requirements you should
statisfy:
• When the user clicks the "Installed" MenuItem, you should
toggle the App's m_bInstalled member variable and reset its
m_nRating to App.UNRATED.
• When the user clicks a "X Stars" Radio Button, you should set
the App's m_nRating to the ID of the clicked MenuItem.
• When the user clicks any MenuItem you should update the
ContentProvider with the new data for the App.
• When the user clicks any MenuItem this should cause the
background color of the AppView to change.
o Should change to red if the app has been uninstalled.
o Should change to yellow if the app has been installed.
o Should change to green if that app has been rated.
An additional use case for uninstalling a previously installed and
rated App is pictured in the screenshots below. Notice that the rating
should be reset to App.UNRATED if it is re-installed.
Hints:
• You've changed your underlying data, don't forget to update
your Cursor & Adapter.
• The Android Developer Guide offers instructions on how
to Update a ContentProvider.
• The URI for the AppContentProvider is defined in the
AppContentProvider.CONTENT_URI constant String.
• IMPORTANT: You must restrict your update to a single App
row in the ContentProvider or you will end up persisting your
change to all the Apps. The section on Querying a
ContentProvider offers advice on how to restrict your URI to
affect only a single record.
o If you create your URI correctly you can pass in null for
the where and selectionArgs parameters.
3. Implementing Background Services
3.1 AppDownloadService
For this section of the Lab you will implement your own version of the
AppDownloadService that you used in the AppRater Activity. For a
complete Background on the Service class you should probably read the
Android Developer Guide on the Service Lifecycle and the Android
Documentation on the Service Class.
3.1.1 Handle Initialization and Destruction of the Service
When the Service is first started, the onCreate() method is called.
You should initialize your Timer and TimerTask.
• You should read the Documentation on the java.util.TimerTask
Class. A TimerTask represents a java.util.Runnable object that
your Timer will execute.
o Your TimerTask should only call the
AppDownloadService.getAppsFromServer() method.
• You should read the Documentation on the java.util.Timer
Class. Timer's are essentially threads which can be scheduled
to execute TimerTasks at specified rates.
o Your Timer should schedule your TimerTask for "fixeddelay"
execution.
o Your TimerTask should be scheduled at a period of
AppDownloadService.UPDATE_FREQUENCY milliseconds.
Feel free to adjust this number for testing
purposes.
o You shouldn't create a daemon Timer.
When the Service is stopped, its onDestroy() method is called. You
should free up any resources that you have allocated in this method,
including threads.
• If you don't explicitly cancel your Timer and TimerTask, they
will continue to run indefinitely.
3.1.2 Fill In the getAppsFromServer() Method
This method should download a list of Application Name and
InstallURI pairs from a server. For each Application Name and
InstallURI pair, the method should construct an App object and hand
it over to the AppDownloadService.addNewApp(...) method.
• You should use the two argument App constructor.
• The method should download from the URL that is stored in the
AppDownloadService.GET_APPS_URL String constant.
• If you visit the URL, you will see the format the data comes in:
o The individual Application Name and InstallURI's data
pieces, which correspond to the same App, are separated
by a comma ',' character .
o Application Name and InstallURI pairs, which represent
Assignment 6
Intro
For this lab you will be filling in a new Application that simply demonstrates a few
key features of the Android framework. In particular, the application demonstrates
how to send SMS text messages and how to monitor motion of the device through
the Accelerometer. The purpose of the application is simply to demonstrate
functionality. When the application starts up, the user is presented with an Activity
that allows them to choose which feature they would like to demo by pressing one of
two buttons, either SMS or Accelerometer monitoring. When the user hits one of the
buttons, it launches the Activity for the selected demo.
Objectives
At the end of this lab you will be expected to know:
• How to send SMS text messages.
• How to listen for changes in the status of a sent SMS text message.
• How to register to receive information from a device's available sensors.
• How to monitor the motion of a physical device.
Activities
For this lab you will be working with a brand new application, completely independent
of the previous labs. Once you have a general understanding of the components of
the application, you will begin implementing it.
IMPORTANT:
You will be given a Skeleton Project to work with. This project contains all of the java
and resource files you will need to complete the lab. Some method stubs, member
variables, and resource values and ids have been added as well. It is important that
you not change the names of these methods, variables, and resource values and ids.
These are given to you because there are unit tests included in this project as well
that depend on these items being declared exactly as they are. These units test will
be used to evaluate the correctness of your lab. You have complete access to these
test cases during development, which gives you the ability to run these tests
yourself. In fact, you are encouraged to run these tests to ensure that your
application is functioning properly.
1. Setting Up...
1.1 Creating the Project
To begin, you will need to download and extract the skeleton project for the Lab
6 application. Extract the project, making sure to preserve the directory
structure. Take note of the path to the root folder of the skeleton project.
Next you will need to setup an Android project for this app. Use the settings listed
below for the remaining fields:
• Project Name: lab6<userid>
• Build Target: Android 1.6
• Application Name: Lab6
1.2 Familiarize Yourself with the Project
The project contains three java files and three XML layout files. Please note:
Although for the purposes of this class this is assignment 6, the lab is actually
referred to as Lab 7 in terms of how the files are labeled in the downloaded project
stub. Lab7.java defines a simple Activity that allows you to choose which demo you
want to start. The Lab7 class makes use of a very simple XML layout file
called main.xml. Both Lab7.java and main.xml have been completed for you.
SMSActivity.java defines a simple SMS text messaging Activity. It allows the user
to enter a phone number, a textual message, and send the an SMS text message. It
also listens to the status of the sent SMS text message and updates the UI to display
changes to the status. The SMSActivity class makes use of an XML layout file
called sms.xml which has been completed for you. SMSActivity.java has been
partially completed. The UI components have been hooked up and initialized. It will
be your job to implement functionality for sending the text message and responding
to status changes.
When the device moves, the Accelerometer sensor in the device measures that
movement. AccelActivity.java defines a simple Accelerometer monitoring Activity.
It only displays measurements from the device's Accelerometer to the user. It makes
use of a simple XML layout file called accel.xml which has been completed for you.
AccelActivity.java has been partially completed for you. It is your job to fill in the
code necessary to listen for measurements from the Accelerometer and update the
display.
2. SMS Activity
In this section of the lab, you will be learning how to send an SMS text message and
listen for changes in its delivery status. All the work done in this section will be
performed in the SMSActivity.java file. Access to SMS is done through the
android.telephony.SmsManager class. It provides an interface for sending both text
and data messages, as well as listening for status changes of sent messages and
delivery of incoming messages. For this lab, we will only be concerned with sending
text messages and monitoring the status of those messages. These exercises should
give you enough information to start working with SMS on your own. Data messages
are sent in almost the same way as text messages, and monitoring of incoming
messages is done through BroadcastReceivers. Both should be fairly straightforward
to figure out on your own.
2.1 Sending an SMS Text Message
For this subsection, you must simply send an SMS text message.
2.1.1 Fill in sendSMS()
When the send button is pressed, the sendSMS() method is called. This method
should send an SMS text message to the phone number contained in the
EditText with the Id of R.id.messageNumber. The text of the message should be
retrieved from the EditText with the Id of R.id.messageText.
• You need to retrieve an instance of SMSManager by calling its static
getDefault() method.
• You can then use one of its send methods to send a text message.
o Don't worry about the PendingIntent arguments just yet, you can
pass in null. In the next section you will set these up so that you can
respond to status changes.
• Read the Android Documentation on SMSManager for more details on
these methods.
2.1.2 Testing SMS with the Emulator
If you don't have a SIM card for your device, or if you don't have a device you
can test sending a text message on the Emulator. You will need to start two
separate emulator instances. From there, you can specify the port number of
the recipient Emulator as the phone number. The ADB will then deliver the text
message to the other Emulator:
• For more details on this, see the Android Developer Guide on Sending
SMS to Another Emulator and Emulating SMS
You should be able to run your application, and verify that it can send a text
message.
2.2 Listening for SMS Status Changes
For this subsection, you will implement the functionality necessary to monitor and
respond to changes to the status of your sent text message.
2.2.1 Fill in BroadcastReceivers
Status changes of a sent SMS message are reported through Intent broadcasts. Therefore, in order to respond to
these changes you will need a BroadcastReceiver, two in fact. There are two separate status changes that get
broadcasted. The first status change gets broadcast once the message has been sent. The second is broadcast once
the message has been delivered.
• Fill in the MessageSentReceiver.onReceive(...) method so that it updates the text of the
TextView with the Id of R.id.statusText:
o Check the result code.
If it represents success then set the text to R.string.sent.
If it represents failure then set the text to R.string.error.
o Read the documentation on SMSManager.sendTextMessage(...) for
details on how to interpret the result code.
• Fill in the MessageDeliveredReceiver.onReceive(...) method so that it sets the text of the
R.id.statusText TextView to R.string.delivered.
2.2.2 Fill in initStatusReceivers()
The initStatusReceivers() method is called from onCreate(...). You need to
register your new receivers to listen for status change broadcasts in this
method.
• You will have to create IntentFilters to listen for SMSActivity.SMS_SENT
and SMSActivity.SMS_DELIVERED action strings.
• Read the Android Documenation on Context.registerReceiver(...) if you
forgot how to do this from the last lab.
2.2.3 Setup PendingIntents
The following is to be performed in the sendSMS() method. You will need to
create two PendingIntent objects and pass them into your
SMSManager.sendTextMessage(...) method call. PendingIntents are a way of
creating an Intent to execute at a later time. They can either start a service,
start an activity, or send a broadcast.
• Read the static PendingIntent.getBroadcast(...) method for details on how
to obtain a PendingIntent.
• You will need to initialize both SMSActivity.pendingSent and
SMSActivity.pendingDelivered.
o pendingSent should be initialized with an Intent containing the
SMSActivity.SMS_SENT action string.
o pendingDelivered should be initialized with an Intent containing the
SMSActivity.SMS_DELIVERED action string.
2.2.4 Cancel PendingIntents
When the user hits the reset button, the UI will be reset and the SMSActivity
should no longer receive udpates from the message it has previously sent.
When the reset button is pressed the cancelPendingIntents() method is called.
Fill in this method so that it cancels the SMSActivity's PendingIntents.
You should now be able to run your application, send a text message and verify
that the TextView displaying the status gets updated properly.
Note: You may not get notified when the message is delivered if you're sending
texts through the emulator, don't worry about this.
3. Accelerometer
In this section of the lab, you will be learning how to monitor changes to the
Accelerometer sensor. All work in this section will be performed in the
AccelActivity.java file. As is usually the case, you will not be interacting with the
Accelerometer sensor directly, but will instead be using the
android.hardware.SensorManager class. This class provides you with a set of API's
for retrieving Sensor objects, querying Sensors for measurements, and registering
listeners with Sensors. The Sensor objects themselves do not provide you with
measurements. Sensor objects only provide hardware information like vendor,
resolution, and range.
There are a number of different types of Sensors from which data can be obtained.
Examples include Magnetic, Light, and Temperature. The ones that you can access
depend entirely on which Sensors the device actually has. For this lab you will only
be interacting with the Accelerometer Sensor. Interaction with other sensors is
performed in the same manner and this will give you a good starting point to learn
the rest on your own.
3.1 Monitoring Device Motion
In the subsections that follow, you register the AccelActivity to be notified of any
changes to the Accelerometer. When notified of changes you will simply display the
raw data to the screen. There Android SDK does not currently provide a method for
simulating the Accelerometer on the Emulator, so the following section must be
tested on a physical device to ensure that it is working properly.
3.1.1 Fill in registerWithSensor()
Retrieve an instance of the SensorManager and use it to register this instance of
AccelActivity as a SensorEventListener for the Accelerometer Sensor.
• The SensorManager can be obtained by
calling getSystemService(Context.SENSOR_SERVICE) and casting.
• Read the Android Documentation
on SensorManager.registerListener(...) for information on how to register
a SensorEventListener.
o You will need to obtain an instance of the Sensor you want to
register with. You will need to use one of the SensorManger's get
methods to do this as you cannot instantiate one directly.
3.1.2 Fill In onSensorChanged(...)
When a sensor changes, and thus has a new measurement, the SensorManager
will call SensorChangeListener.onSensorChanged(...) on all listeners that have
registered with Sensor. It will pass in a SensorEvent object which contains data
about the change. You need to fill in the onSensorChanged(...) method so that
the new data is displayed on the screen:
• Read the AndroidDocumentation on the SensorEvent.values field to learn
how to get the new data.
• Update the R.id.accelerationX, R.id.accelerationY, and R.id.accelerationZ
TextFields to display the new data.
Run your application to ensure that the Accelerometer values are properly
updated on the screen. Remember, you have to do this on a physical device.
4. Deliverables
To complete this lab, you will be required to:
1. Put your entire project directory into a .zip or .tar file, similar to the stub you
were given. Submit the archive to the ITU Database (EMS). The name of your
archive should be lab6<username>.zip|tar. So if your username
is jsmith and you created a zip file, then your file would be
named lab6jsmith.zip.