Android Development 2

Size: px
Start display at page:

Download "Android Development 2"

Transcription

1 Android Development 2 Lesson 1: Fragment s The Sandbo x Enviro nment Abo ut Eclipse Perspectives and the Red Leaf Icon Wo rking Sets Andro id Fragments Using Fragments Pro gramatically Wrapping Up Quiz 1 Project 1 Lesson 2: Lo aders Why Use a Loader? Performing Tasks in a Loader Wrapping Up Quiz 1 Project 1 Lesson 3: Advanced Layo ut s Suppo rting Orientatio n Changes Persisting Data on Rotation Suppo rting Multiple Screen Sizes Wrapping Up Quiz 1 Project 1 Lesson 4: Cust o m View Co mpo nent s Defining a Custom Component Implementing View Attributes in a Custom Component Wrapping Up Quiz 1 Project 1 Lesson 5: Basic Services Creating, Declaring, and Starting a Service Wrapping Up Quiz 1 Project 1 Lesson 6: No t if icat io ns Creat and Update a Notification Responding To User Taps On A Notification Updating A No tificatio n Wrapping Up Quiz 1 Project 1 Project 2 Lesson 7: Co nt ent Pro viders Creating and Using a Content Provider Examining the Co de Wrapping Up Quiz 1 Project 1 Lesson 8: Camera Basics: Using t he Built -in Camera Applicat io n Starting the Built-in Camera Using an Intent Saving Image to External Sto rage

2 Wrapping Up Quiz 1 Project 1 Lesson 9: Camera Advanced: Building a Cust o m Camera Applicat io n Using the Camera API Camera Parameters Checking fo r a Camera and Handling Multiple Cameras Camera Features and the Andro id Manifest Wrapping Up Quiz 1 Project 1 Lesson 10: Bro adcast Receivers Creating a Bro adcastreceiver fo r System Events Creating a Bro adcastreceiver fo r Service Events Using the Lo calbro adcastmanager Wrapping Up Quiz 1 Project 1 Lesson 11: Media: Audio Creating a MediaPlayer and Playing an Audio File Handling MediaPlayer State and the Activity Lifecycle Handling MediaPlayer Events and UI Updates Wrapping Up Audio Quiz 1 Project 1 Lesson 12: Media: Video Video Playback with a Video View Adding a MediaController to a VideoView VideoView Events and Methods Wrapping UP Quiz 1 Project 1 Lesson 13: WebView WebView Basics Using WebSettings Using a WebChro meclient Using WebViewClient Using WebView Metho ds Enabling JavaScript WebView Wrap-up Quiz 1 Quiz 2 Project 1 Lesson 14: Andro id 2 Final Pro ject Final Pro ject Project 1 Copyright O'Reilly Media, Inc. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. See for more information.

3 Fragments Welcome to the O'Reilly School of Technology Andro id 2 course! Course Objectives When you complete this course, you will be able to: create applications optimized for both phones and tablets. support old and new devices using the Android support library. utilize various Android systems for sharing with and receiving data from other Android applications. create media rich applications with audio and video. Note If you're new to Android, we highly recommend that you contact us to complete the first Android course before taking this one. The lessons in this Android 2 course will all assume you have a firm grasp on Object Oriented Programming, the Java programming language, and the basics of Android application development with the Andro id SDK. If yo u've already co mpleted the first Andro id co urse in this series o r are already familiar with using Eclipse under the remote development process for O'Reilly School of Technology, you can skip ahead to the Android Fragments sectio n. Lesson Objectives When you complete this lesson, you will be able to: learn about the UserActive method of learning. read Abo ut the Learning Sandbo x Enviro nment. set Up Eclipse for Working with Android Applications. create A Simple Website. add Web Controls to Your Website. Learning with O'Reilly School of T echnology Courses As with every O'Reilly School of Technology course, we'll take a user-active approach to learning. This means that you (the user) will be active! Yo u'll learn by do ing, building live pro grams, testing them and experimenting with them hands-o n! To learn a new skill or technology, you have to experiment. The more you experiment, the more you learn. Our system is designed to maximize experimentation and help you learn to learn a new skill. We'll program as much as possible to be sure that the principles sink in and stay with you. Each time we discuss a new concept, you'll put it into code and see what YOU can do with it. On occasion we'll even give you code that doesn't work, so you can see common mistakes and how to recover from them. Making mistakes is actually ano ther go o d way to learn. Above all, we want to help you to learn to learn. We give you the tools to take control of your own learning experience. When you complete an OST course, you know the subject matter, and you know how to expand your knowledge, so you can handle changes like software and operating system updates. Here are some tips for using O'Reilly School of Technology courses effectively: T ype t he co de. Resist the temptation to cut and paste the example code we give you. Typing the code actually gives you a feel for the programming task. Then play around with the examples to find out what else yo u can make them do, and to check yo ur understanding. It's highly unlikely yo u'll break anything by experimentation. If you do break something, that's an indication to us that we need to improve our system! T ake yo ur t ime. Learning takes time. Rushing can have negative effects on your progress. Slow down and let your brain absorb the new information thoroughly. Taking your time helps to maintain a relaxed, positive

4 let your brain absorb the new information thoroughly. Taking your time helps to maintain a relaxed, positive approach. It also gives you the chance to try new things and learn more than you otherwise would if you blew through all of the coursework too quickly. Experiment. Wander from the path often and explore the possibilities. We can't anticipate all of your questions and ideas, so it's up to you to experiment and create on your own. Your instructor will help if you go completely off the rails. Accept guidance, but do n't depend o n it. Try to solve problems on your own. Going from misunderstanding to understanding is the best way to acquire a new skill. Part of what you're learning is problem solving. Of course, you can always contact your instructor for hints when you need them. Use all available reso urces! In real-life problem-solving, you aren't bound by false limitations; in OST courses, you are free to use any resources at your disposal to solve problems you encounter: the Internet, reference books, and online help are all fair game. Have f un! Relax, keep practicing, and don't be afraid to make mistakes! Your instructor will keep you at it until you've mastered the skill. We want you to get that satisfied, "I'm so cool! I did it!" feeling. And you'll have some projects to show off when you're done. Lesson Format We'll try out lots of examples in each lesson. We'll have you write code, look at code, and edit existing code. The code will be presented in boxes that will indicate what needs to be done to the code inside. Whenever you see white boxes like the one below, you'll type the contents into the editor window to try the example yourself. The CODE TO TYPE bar on top of the white box contains directions for you to follow: CODE TO TYPE: White boxes like this contain code for you to try out (type into a file to run). If you have already written some of the code, new code for you to add looks like this. If we want you to remove existing code, the code to remove will look like this. We may also include instructive comments that you don't need to type. We may run programs and do some other activities in a terminal session in the operating system or other commandline environment. These will be shown like this: INTERACTIVE SESSION: The plain black text that we present in these INTERACTIVE boxes is provided by the system (not for you to type). The commands we want you to type look lik e this. Code and information presented in a gray OBSERVE box is for you to inspect and absorb. This information is often color-coded, and followed by text explaining the code in detail: OBSERVE: Gray "Observe" boxes like this contain information (usually code specifics) for you to observe. The paragraph(s) that follow may provide addition details on inf o rmat io n that was highlighted in the Observe box. We'll also set especially pertinent info rmatio n apart in "No te" bo xes: Note Notes provide information that is useful, but not absolutely necessary for performing the tasks at hand. T ip Tips provide information that might help make the tools easier for you to use, such as shortcut keys.

5 WARNING Warnings provide information that can help prevent program crashes and data loss. T he Sandbox Environment About Eclipse We're using an Integrated Develo pment Enviro nment (IDE) called Eclipse. It's the pro gram filling up yo ur screen right now. IDEs assist programmers by performing many of the tasks that need to be done repetitively. IDEs can also help to edit and debug code, and organize projects. Note You'll make some changes to your working environment during this lesson, so when you complete the lesson, you'll need to exit Eclipse to save those changes. The Eclipse window displays lesson content, and provides space for you to create, manage, and run pro grams: Perspectives and the Red Leaf Icon The Ellipse Plug-in for Eclipse, developed by the O'Reilly School of Technology, adds an icon to the tool bar in Eclipse. This icon is your "panic button." Since Eclipse is so versatile, you are allowed to move things around, like views, toolbars, and such. If you become confused and want to return to the default perspective (window layout), clicking on the Red Leaf icon allows you to do that right away. The icon has these functions: To reset the current perspective, click the ico n. To change perspectives, click the drop-down arrow beside the icon and select a series name (Android, Java, Python, C++, etc.). Most of the perspectives look similar, but subtle changes may be present "behind the scenes," so it's best to use the correct perspective for the course. For this co urse, select Andro id.

6 Working Sets All projects created in Eclipse exist in the workspace directory of your account on our server. As you create multiple projects for each lesson in each course, it's possible that your workspace directory can become pretty cluttered. To help alleviate the potential clutter, in this course, we use working sets. A working set is a logical view of the workspace; it behaves like a folder, but it's really just an association of files. Working sets allow you to limit the detail that you see at any given time. The difference between a working set and a folder is that a working set doesn't actually exist in the file system. A working set is a convenient way to group related items together. You can assign a project to one or more working sets. In some cases, like with the Android ADT plugin to Eclipse, new projects are created without regard for working sets and will be placed in the workspace, but not assigned to a working set (appearing in the "Other Projects" working set). To assign one of these projects to a working set, right-click on the project name and select Assign Wo rking Set s from the co ntext menu. We've created some working sets for you already. To turn the working set display on and off in Eclipse, see these instructio ns. Setting Up Your Android Emulator The Android team has made an excellent Eclipse plugin for Android called ADT (Android Developer Toolkit). ADT helps with Android development in Eclipse in many different ways, so it's important that we get the Eclipse environment and ADT set up correctly from the start, so we can build and test our Android applications. Note The Andro id Develo per To o lkit plugin fo r Eclipse changes extremely frequently. The develo pers behind the toolkit are doing amazing work and constantly updating and improving the plugin. However, this means the most recent version may differ from what you see here and what the instructions detail. Don't worry if what you see slightly differs from the instructions. While the look, feel, and features may have changed (likely for the better), the core decisions and options such as application and package names will generaly still be recognizable. We periodically update the toolkit on our systems. Point ADT to the Android SDK The ADT plugin is installed on the instance of Eclipse that you are using right now. To open ADT, you can either click the Android Virtual Device Manager icon in the button bar at the top, or select Windo w AVD Manage r:

7 Go ahead and try that now. You'll probably get an error message informing you that the Android SDK could not be found: To fix this error, open the Eclipse preferences from the toolbar menu by clicking Windo w Pref erences. The Eclipse preferences window will appear. Then click the Andro id section on the left. (You may be asked if you want to send usage data to Google. Click "No.") Then, in the SDK Location field, type C:\Pro gram Files (x86)\andro id\andro id-sdk and click OK. Note Sometimes when reopening a remote Eclipse session, ADT will forget that it already has the location of the SDK, and will pop-up the error again. If that happens, just open the Eclipse Preferences window again (Windo w Pref erences) and it should show that the path is in there already. Click OK and everything sho uld wo rk fine again. Your Preferences for Android will look like this:

8 Now ADT is ready to go! To test to make sure it's working, open the ADT window by clicking the button or selecting Windo w AVD Manager. The ADT dialog window will open. Feel free to look around in the window to get an idea of what goes on there before you continue on to the next section, where we'll create an emulato r using the AVD Manager. Note Your AVD Manager probably won't be empty like the screenshot above. Due to the nature of the remo te develo pment enviro nment we're using and the way the AVD Manager handles emulato rs, yo u'll pro bably see many o ther users' emulato rs. Co nversely, any changes yo u make in the AVD Manager will be visible to other users as well. Please be respectful of the other users and do not modify or delete any emulators other than those you've created for yourself. Create an Emulator If you closed it, open your ADT window again. This is the window that allows you to create and configure as many Android emulators as you like so you can test your application on various different hardware and software configurations. For now, we'll create a single emulator. On the right side of the ADT window, click New... The "Create new Android Virtual Device (AVD)" wizard appears. Fo r the Name, enter your-ost-username-andro id2.2.3 (fo r example, if yo ur username is jjamiso n, your emulator name would be jjamiso n-andro id2.2.3). In the Device dropdown, select the Nexus S.

9 in the Target dropdown, select Andro id API Level 10. For the SD card, select the Size radio button and enter 20 MiB. When you're ready, click Creat e AVD at the bottom. Then, select your new emulator in the Virtual Devices list, and click St art... on the right: A Launch Options window appears. The emulator is actually a little too big for our remote Eclipse session, so we'll scale it down a little. Check the Scale display t o real size box, enter 8.0 in the Screen Size (in.) field, and then click Launch: The emulator will take a while to load. Now might be a good time to pour yourself another cup of coffee or let the dog out. When the emulator is finally loaded, you'll see it in another window on top of Eclipse. At this point, you can close the Virtual Device Manager window, but try not to close the emulator when developing your application. You'll save a lot of time if you don't have to sit through the boot-up process of the emulator. Alternatively, you might use the Snapshot feature in the Launch Options window (above). In Snapshot mode, whenever the emulator is closed, AVD saves a snapshot of the current state of the emulator, which allows it to boot up faster. However, if your emulator ends up in a weird or broken state, you'll need to check the Wipe user dat a box in the Launch Options window when you restart it, in order to reset the snapshot state of the emulator. To switch between this lesson content and the emulator, use the tabs at the bottom of the screen: Note You can set up other emulators to match different devices, if you like. Always begin the emulator name with your OST user name, so you can differentiate them from emulators created by other users. In the next section, we'll finally dig into some code and run our first Android application! Android Fragments What are Android Fragments? The Android developer documentation describes a fragment as "a piece of an application's user interface or behavior that can be placed in an Activity." I like to think of fragments as an extension of Andro id's Activity pattern to better encapsulate yo ur view lo gic away fro m each specific Activity. This makes it easier to reuse your view logic and better support multiple screen sizes from phones to tablets. If you took the first course, you'll remember using Fragments briefly in the Dialogs lesson. In this course, we'll use Fragments much more; in fact, we'll use them in every single applicatio n we build. Note Don't confuse Android Fragments with the fragmentation of the Android platform. Android fragmentation that you may hear about in various news sources refers to the "fragmentation" of the various different platform versions of the Android OS installed on each Android phone. Let's get going and start a project using Fragments. Create a new Android Project. Select File New Ot her, and select Andro id Applicat io n Pro ject. Name the project Fragment s, enter the package name co m.o st.andro id.f ragment s, select the options for the other values as shown, and click Next :

10 Uncheck the Creat e cust o m launcher ico n box, check the Add pro ject t o wo rking set s box and click Select to choose the Andro id2_lesso ns working set:

11 Click Next. In the next two windows, keep the default choices:

12

13 Click Finish to create the project. Next, we need to add the support library to the project. ADT makes this process pretty straightforward. Right-click the Fragment s root project folder, choose Andro id T o o ls Add Suppo rt Library. Android automatically downloads the latest version of the support library and includes it in your project. When it's finished, verify that the process worked by expanding the Fragment s/libs folder to find the andro id-suppo rt -v4.jar file. Now, in your new project, in the /src folder, co m.o st.andro id.f ragment s package, open the MainAct ivit y.java file and make these changes:

14 CODE TO TYPE: MainActivity.java package com.ost.android.fragments; import android.os.bundle; import android.app.activity; import android.view.menu; import android.support.v4.app.fragmentactivity; public class MainActivity extends FragmentActivity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); public boolean oncreateoptionsmenu(menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getmenuinflater().inflate(r.menu.main, menu); return true; This code will compile as is; if you see any red squiggles in the text, go back and make sure you've included the Suppo rt Library pro perly. No w that o ur Activity suppo rts fragments, let's create a fragment. Create a new class named Ho mefragment, change the package name to co m.o st.andro id.f ragment s and make sure it extends from andro id.suppo rt.v4.app.fragment. Your New Java Class wizard looks like this:

15 We'll come back to this file in a bit, but first we'll hook this Fragment up to our Activity. Open the act ivit y_main.xml layout file in the /res/layo ut folder and make these changes:

16 /res/layo ut/activity_main.xml <RelativeLinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".mainactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <fragment android:layout_width="match_parent" android:layout_height="match_parent" class="com.ost.android.fragments.homefragment"/> </RelativeLinearLayout> Now the Activity will load the new fragment automatically as soon as the view is loaded. Let's run it. Right-click the Fragment s root project name and select Run As Andro id Applicat io n. The application will crash. Check LogCat for the error (you may have to double-click on the LogCat window tab to expand the window and see the error message clearly): There's a lot of red text here so it could be tough to find the exact information we need. We'll look for references to files we've actually created in the applicatio n, which in this case is Ho mefragment. The erro r tells us "Fragment com.ost.android.fragments.homefragment did not create a view." We are on the right track. Our Fragment is being loaded, but hasn't created a view yet so it's crashing the application right away. Let's fix that. Create a new Android XML Layout file named ho me_f ragment.xml and then modify it as shown:

17 CODE TO TYPE: /res/layo ut/ho me_fragment.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="home Fragment Button"/> </LinearLayout> No w go back to Ho mefragment.java and make these changes: CODE TO TYPE: Ho mefragment.java package com.ost.android.fragments; import android.os.bundle; import android.support.v4.app.fragment; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; public class HomeFragment extends Fragment { public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle savedin stancestate) { return inflater.inflate(r.layout.home_fragment, container, false); Now our application works and shows the fragment loading successfully. Save the modified files and run the application once more. Once it loads, your emulator looks like this:

18 Great! So what's going on here? Well, first we changed our usual starting Activity to extend from Fragment Act ivit y. We used the Android Support library to get access to the FragmentActivity class. If we were writing an application for the Android 3 (Honeycomb) version or later, we wouldn't need the support library. As you might have guessed, a FragmentActivity class is required to lo ad a Fragment. OBSERVE: activity_main.xml <LinearLayout xmlns:android=" <fragment android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/homefragment" class="com.ost.android.fragments.homefragment" /> </LinearLayout>

19 We used the MainActivity's view to load the fragment. In activity_main.xml, we added the f ragment xml node to our layout. The fragment node tells the Activity to load a Fragment and place the Fragment's view into the layout in its place. The layout widt h and height properties are applied to the Fragment's view: OBSERVE: Ho mefragment.java package com.ost.android.fragments; import com.ost.android.fragments.r; import android.os.bundle; import android.support.v4.app.fragment; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; import com.ost.android.fragments.r; public class HomeFragment extends Fragment { public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle savedin stancestate) { return inflater.inflate(r.layout.home_fragment, container, false); In our newly created HomeFragment class (that extends the Fragment class from the Support Library) we implemented the o ncreat eview method in order to load a view for the Fragment properly. The method receives a reference to a Layo ut Inf lat er object, so we use that to inflate a view we defined in XML and return that inflated view. The second parameter sent to the inflate method is the ViewGro up that will eventually contain this view. By sending the ViewGroup co nt ainer, our new view will inherit the layout parameters from this ViewGroup. The last paramet er defines whether we want to attach this view auto matically to the co ntainer ViewGro up fro m the seco nd parameter. We don't want to do that though because it's already going to be handled automatically in the framework classes, so we pass in f alse here. So, Fragments are loaded into Activities and have their own views. You can learn more in the Android documentation for the Fragment class. If you take a look at the lifecycle, you see it has a similar lifecycle to that of the Activity class. Just like Activity, Fragment has o ncreat e, o nst art, and o nresume methods, as well as their corresponding deconstruction methods o npause, o nst o p, and o ndest ro y. There are also some other lifecycle methods that distinguish Fragment fro m the Activity class. Perhaps the most important difference between a Fragment and an Activity is that the Fragment class is not an extension of Co nt ext. Fragments get their context from the Activity that creates them, so they cannot exist without an Activity. A Fragment can always get a reference to its parent Activity, and thus a Co ntext reference, by calling the get Act ivit y() method. However, when implementing a Fragment you must make sure that the parent Activity hasn't been destroyed. This is where the new Fragment lifecycle method o nact ivit ycreat ed comes in handy. If a Fragment must perform logic requiring a context when it is loaded, then you place that logic in the o nact ivit ycreat ed method where you can guarantee that the parent Activity has already finished its creation lifecycle and is ready to be used as a Co ntext. Using Fragments Programatically In addition to loading fragments through XML, we can load them dynamically in our Activity. Often you don't even need a Layout XML for your activity at all when loading Fragments programmatically, but we're going to continue using our previous view here. Let's start by creating a new Fragment; name the class Seco ndfragment and of course have it extend the andro id.suppo rt.v4.app.fragment class. Also, make sure the file is in the co m.o st.andro id.f ragment s package. Now make these changes:

20 CODE TO TYPE: Seco ndfragment.java package com.ost.android.fragments; import android.os.bundle; import android.support.v4.app.fragment; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; import com.ost.android.fragments.r; public class SecondFragment extends Fragment { public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle savedinstancestate) { return inflater.inflate(r.layout.second_fragment, container, false); Now let's create the XML layout view file for this fragment. As you might have guessed, we'll name this file seco nd_f ragment.xml. Make sure that the file is in the /res/layo ut / folder and then make these changes: CODE TO TYPE: /res/layo ut/seco nd_fragment.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="second Fragment Loaded!" /> </LinearLayout> Now we can close these new files, go back to our previous code, and update it to load the new Fragment. Open the act ivit y_main.xml layout file and make these changes:

21 CODE TO TYPE: /res/layo ut/activity_main.xml <LinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".mainactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <fragment android:layout_height="match_parentwrap_content" android:layout_width="match_parent" class="com.ost.android.fragments.homefragment" /> <LinearLayout android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent" /> </LinearLayout> Next, o pen MainAct ivit y.java and make these changes: CODE TO TYPE: MainActivity.java package com.ost.android.fragments; import android.os.bundle; import android.support.v4.app.fragmentactivity; import android.support.v4.app.fragmentmanager; import android.support.v4.app.fragmenttransaction; public class MainActivity extends FragmentActivity { /** Called when the activity is first created. */ public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); public void loadsecondfragment() { FragmentManager fm = getsupportfragmentmanager(); FragmentTransaction ft = fm.begintransaction(); SecondFragment sf = new SecondFragment(); ft.add(r.id.fragment_container, sf); ft.commit(); Open Ho mefragment.java and make one last set of changes:

22 CODE TO TYPE: Ho mefragment.java package com.ost.android.fragments; import android.os.bundle; import android.support.v4.app.fragment; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; public class HomeFragment extends Fragment { public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle savedinstancestate) { return inflater.inflate(r.layout.home_fragment, container, false); View view = inflater.inflate(r.layout.home_fragment, container, false); view.findviewbyid(r.id.home_fragment_button).setonclicklistener(buttonclickl istener); return view; public View.OnClickListener buttonclicklistener = new View.OnClickListener() { public void onclick(view v) { MainActivity activity = (MainActivity) getactivity(); activity.loadsecondfragment(); ; Save the changed files and run the application. Your emulator loads and looks the same as before, but now when you click the Ho me Fragment But t o n, it loads the Seco ndfragment :

23 Let's go back over some key areas now and discuss our code in detail. First, let's look at the changes we made to MainAct ivit yfragment.java: OBSERVE: public void loadsecondfragment() { FragmentManager fm = getsupportfragmentmanager(); FragmentTransaction ft = fm.begintransaction(); SecondFragment sf = new SecondFragment(); ft.add(r.id.fragment_container, sf); ft.commit(); We start by getting a reference to the Fragment Manager class. This is accessed by using the get Suppo rt Fragment Manager class inherited from FragmentActivity. Just like before, this is the Support Library version of the FragmentManager. If this application was targeting Honeycomb or later, we'd just call getfragmentmanager to get o ur reference. This is o ne o f the few instances using Fragments where the API name differs in the Support Library from the latest SDK.

24 We initiate a Fragment T ransact io n that will define the Fragment changes that are about to occur. Every time a change is made to an Activity's Fragments, a Fragment T ransact io n must be used. Then we create an instance of our Seco ndfragment, and update the Fragment T ransact io n, telling it to add our fragment to the ViewGroup in this Activity's view with the corresponding id R.id.f ragment _co nt ainer. Finally, we call co mmit on the transaction to finalize our changes. OBSERVE: Ho mefragment.java public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle savedinstancestate) { View view = inflater.inflate(r.layout.home_fragment, container, false); view.findviewbyid(r.id.home_fragment_button).setonclicklistener(buttonclickl istener); return view; public View.OnClickListener buttonclicklistener = new View.OnClickListener() { public void onclick(view v) { MainActivity activity = (MainActivity) getactivity(); activity.loadsecondfragment(); ; We also made some interesting changes to our Ho mefragment.java. First, we modified the o ncreat eview method in order to set a click listener on our Button. You might be used to implementing click handlers for Buttons in XML Layouts using the o nclick attribute convention. Unfortunately, a Fragment cannot use that convention, so click listeners must be set using the set OnClickList ener method on the Button directly. If an o nclick attribute method is defined in the View, Android will still attempt to call a method with that name on the owning Activity (that is, the activity class that's created when you create the project, MainActivity.java), even if the View was defined in a Fragment; however, we are writing our code so that our Activities don't need to manage the contents of the Fragment's views, so we keep this logic contained in the Fragment itself. In our click listener, we used the get Act ivit y method (inherited from the Fragment class) to get a reference to our FragmentActivity. We know that this Fragment will belong to a MainAct ivit y class, so we can safely cast our reference to that class. Finally, we call the lo adseco ndfragment method on our activity to start the loading process. Wrapping Up We've covered the basics of Fragments in Android in this lesson, but there's still more functionality to explore. Check the Android Developer Documentation Site for more detailed information regarding the entire Fragment process. We'll be using Fragments, or at the very least FragmentActivity, in every lesson for this course, so make sure you feel comfortable with the basics we've learned here before you go on. Practice what you've learned in the homework. See you in the next lesson! Copyright O'Reilly Media, Inc. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. See for more information.

25 Loaders Lesson Objectives In this lesson, you will: write and implement a Loader. replace an AsyncTask with a Lo ader. implement Lo adercallbacks to handle Lo ader results. register a Lo ader and Lo adercallbacks with the Lo adermanager. Welcome back! In this lesson we'll cover Loaders, a great new feature in Android that helps to load data asynchronously. Loaders are managed outside of the scope of an activity, which allows us to retrieve data from a Loader even if the activity has been destro yed and recreated (like when the user ro tates the screen). Like Fragments, Lo aders first became available in API 11 (Ho neyco mb), and are available to applicatio ns targeting earlier APIs thro ugh the suppo rt library. Why Use a Loader? At first glance, Lo aders might no t seem vital. After all, we already have AsyncTasks to perfo rm lo ng-running pro cesses. Ho wever, AsyncTasks do n't exactly co o perate with Andro id's life-cycle fo r Views and Fragments. The example will help illustrate the need fo r Lo aders. Let's get started. Create a new Android project using these criteria: Name the project Lo aders. Use the package name co m.o st.andro id.lo aders. Uncheck the Creat e cust o m launcher ico n box. Assign the Andro id2_lesso ns working set to the project. We'll begin by demo nstrating the sho rtco mings o f AsyncTask. In MainAct ivit y.java, make these changes:

26 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.loaders; import android.app.activity; import android.os.asynctask; import android.os.bundle; import android.text.format.dateutils; import android.widget.textview; import android.view.menu; public class MainActivity extends Activity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); AsyncTask<Void, Void, String> mytask = new AsyncTask<Void, Void, String>() { protected String doinbackground(void... params) { try { Thread.sleep(DateUtils.SECOND_IN_MILLIS * 5); catch (InterruptedException e) { return "AsyncTask Complete!"; protected void onpostexecute(string result) { super.onpostexecute(result); TextView tv = (TextView) findviewbyid(r.id.text); tv.settext(result); ; mytask.execute(); public boolean oncreateoptionsmenu(menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getmenuinflater().inflate(r.menu.main, menu); return true; We also need to make some minor edits to act ivit y_main.xml:

27 CODE TO TYPE: /res/layo ut/activity_main.xml <RelativeLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".mainactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerhorizontal="true" android:layout_centervertical="true" /> </RelativeLayout> Save your changes and run the project. You see this:

28 Then, after about five seconds (depending on how fast the emulator is running through the Virtual Desktop), the screen updates:

29 OBSERVE: MainActivity.java... public class MainActivity extends Activity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); AsyncTask<Void, Void, String> mytask = new AsyncTask<Void, Void, String>() { protected String doinbackground(void... params) { try { Thread.sleep(DateUtils.SECOND_IN_MILLIS * 5); catch (InterruptedException e) { return "AsyncTask Complete!"; protected void onpostexecute(string result) { super.onpostexecute(result); ; TextView tv = (TextView) findviewbyid(r.id.text); tv.settext(result); mytask.execute(); This little application demonstrates running an AsyncT ask process that takes about five seconds to finish. When the task is started, it calls T hread.sleep() in its do InBackgro und() method. This causes the execution in that Thread to pause on this line for five seconds. After waiting five seconds, the thread continues and finishes the do InBackgro und() method, returning the St ring value. In the o npo st Execut e() method, the resulting St ring value is finally applied to the T ext View. Note You should never actually use T hread.sleep() in your Android applications. We're just using it here to simulate something that takes five seconds to complete. If you need to schedule something to occur after a short period of time in your Applications, consider using the Timer and TimerTask classes instead. You might also consider using a Service, which we'll cover in a bit. Now, rotate the emulator. Focus on the emulator window and press [Ct rl+f12] on your keyboard. The emulator rotates to landscape mode. Also, the TextView in the middle of the screen goes back to displaying the previous message, "Hello Wo rld, MainActivity!" Then, after ano ther five seco nds, the message "AsyncTask Co mplete!" displays once more. This will happen each time you rotate the emulator. Go ahead and try it a couple of times. Note [Ct rl+f11] and [Ct rl+f12] will both rotate the emulator. To find out about more emulator keyboard shortcuts, see the documentation site. So, let's see what's really going on here. Every time an Android device rotates, the Android system destroys the current Activity (and any active Fragments) and then recreates them in the new o rientatio n. Applicatio ns can explicitly prevent this from happening, but at the cost of its ability to use a separate layout per orientation automatically. Some applicatio ns will prevent this by disabling ro tatio n, thereby fo rcing the user to use the applicatio n in their specified orientation. Generally, neither of these strategies are recommended. It is better to learn how to use the Application lifecycle to preserve the state of the application during a rotation and restore the state when the Activity/Fragment is resto red. Here, we are requesting our data from a process that takes some time to return. We don't want to re-request the data

30 each time the device rotates. We could pass the necessary data to the next Activity using the Android life-cycle methods (specifically o nsaveinst ancest at e()), but the user could also rotate before the task actually finishes. We shouldn't have to start our request over just because the user rotated before the task returned. This is where Loaders come in handy. Performing Tasks in a Loader Let's update the Application to use a Loader instead of an AsyncTask. Edit MainAct ivit y.java as shown:

31 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.loaders; import android.app.activity; import android.os.asynctask; import android.content.context; import android.os.bundle; import android.support.v4.app.fragmentactivity; import android.support.v4.app.loadermanager; import android.support.v4.content.asynctaskloader; import android.support.v4.content.loader; import android.text.format.dateutils; import android.widget.textview; public class MainActivity extends Activity { public class MainActivity extends FragmentActivity implements LoaderManager.LoaderCallb acks<string> { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); AsyncTask<Void, Void, String> mytask = new AsyncTask<Void, Void, String>() { protected String doinbackground(void... params) { try { Thread.sleep(DateUtils.SECOND_IN_MILLIS * 5); catch (InterruptedException e) { return "AsyncTask Complete!"; protected void onpostexecute(string result) { super.onpostexecute(result); TextView tv = (TextView) findviewbyid(r.id.text); tv.settext(result); ; mytask.execute(); LoaderManager lm = getsupportloadermanager(); Loader<String> loader = lm.initloader(0, null, this); if (!loader.isstarted()) loader.forceload(); public Loader<String> oncreateloader(int loaderid, Bundle args) { return new MyLoader(this); public void onloadfinished(loader<string> loader, String data) { TextView tv = (TextView) findviewbyid(r.id.text); tv.settext(data); public void onloaderreset(loader<string> loader) { private static class MyLoader extends AsyncTaskLoader<String> { public MyLoader(Context context) {

32 super(context); public String loadinbackground() { try { Thread.sleep(DateUtils.SECOND_IN_MILLIS * 5); catch (InterruptedException e) { return "AsyncTaskLoader Complete!"; Make sure yo u're impo rting the Suppo rt Library versio n o f the FragmentActivity, Lo adermanager, AsyncTaskLo ader, and Loader classes. Now, save your changes and run the Application again. Test the rotation. Test rotating before the first five seconds are even up. Notice anything different? The Application now takes only five seconds total to switch the TextView to say "AsyncTaskLoader Complete!" Even if the five seconds runs out during a rotation, the View reflects the result immediately when recreated. Alright, so this is pretty co o l, but what's happening? Why is this different fro m the AsyncTask? Let's walk thro ugh this code step by step, starting with our additions to the o ncreat e() method. OBSERVE: MainActivity.java - o ncreate() public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main);... LoaderManager lm = getsupportloadermanager(); Loader<String> loader = lm.initloader(0, null, this); We start by getting an instance of the Lo adermanager from the Activity by calling get Suppo rt Lo adermanager(). Then we get an instance of the Lo ader we want, using the init Lo ader() method on the Lo adermanager. init Lo ader() takes three parameters. The f irst, an Integer, is an ID that can be used to help the callbacks identify which type of Loader it should create. We have only one type of Loader in this routine, so this value doesn't really matter. The next paramet er is a Bundle object that can be used to send some additional data to the routine that creates the Loader, such as data that might be needed in the constructor of the Loader. We don't have any data like this, so we simply send null. The last paramet er is the most important for our code. It requires an instance of the Lo ade rmanage r.lo ade rcallbacks<t > interface. The interface has a generic defined, which must match the Generic used in the definition of the Loader class. OBSERVE:... if (!loader.isstarted()) loader.forceload(); Next, we check t o see whet her o r no t t he Lo ader has act ually been st art ed yet, and if not, f o rce it t o st art right away. There's another method on the Loader class called st art Lo ader() which might seem like the method to call when you want to start the Loader, but in this case it isn't. Our Loader is an implementation of the AsyncT asklo ader class, which requires you to call the f o rcelo ad() method on the Lo ader to kick off its request process. Next, we'll look at the changes to the class definition.

33 OBSERVE: MainActivity class public class MainActivity extends FragmentActivity implements LoaderManager.LoaderCallb acks<string> Our regular "pre-ho neyco mb" Activity class do esn't actually suppo rt getting an instance o f the Lo ade rmanage r class, so we need to change our class to extend the support library version of Fragment Act ivit y instead. Also, in order to send "this" to the Lo adermanager.init Lo ader() method, we have to implement t he Lo adercallbacks interface as well. We want our Lo ader to return a St ring value, so we define the generic parameter here as the St ring class. Next, we'll look at the methods required to implement Lo adercallbacks: OBSERVE: MainActivity.java - Lo adercallbacks metho ds public Loader<String> oncreateloader(int loaderid, Bundle args) { return new MyLoader(this); public void onloadfinished(loader<string> loader, String data) { TextView tv = (TextView) findviewbyid(r.id.text); tv.settext(data); public void onloaderreset(loader<string> data) { Lo adermanager.lo adercallbacks<t> requires three metho ds. The first metho d, o ncre at e Lo ade r(), has two parameters. The f irst paramet er, an Int eger, is the same Integer that was passed to the LoaderManager.initLoader earlier. We don't need to use this parameter in our implementation. The seco nd paramet er is a Bundle o bject and, of course, corresponds to the second parameter sent to Lo adermanager.init Lo ader earlier. Again, we're not using this parameter, so we just ignore it. The most important requirement of this method is that it returns a Lo ader object that implements the Generic parameter defined fo r this implementatio n. We just create a new instance o f o ur MyLo ader class. All Lo aders require a Co nt ext in the constructor, and since our Fragment Act ivit y is a Context, we just pass this to the MyLoader constructor. This method is actually called internally by the Lo adermanager class the first time init Lo ad() is called on the Lo adermanager. Lo adermanager will then cache the Lo ader and return the cache value on subsequent calls to init Lo ader(). The second method, o nlo adfinished(), is called after the Lo ader finishes loading its data. The callback method receives two parameters. T he f irst is a ref erence t o t he Lo ader that performed the work. T he seco nd paramet er is t he dat a result ; in our case, it will contain the St ring value "AsyncTaskLoader Complete!" This callback method will always be called on the UI thread, so it is perfectly safe to modify UI components here. We apply the text data to our T ext View here so we can see the results on the screen. The final callback method is o nlo aderreset (). It takes just one parameter, the Loader that was created in init Lo ader(). This method is called automatically by the Lo adermanager when the Lo ader's data is about to be released and will no longer be available. This gives you the opportunity to update your view to respond accordingly. For example, if the loader is being reset and given new parameters, this method would be called before the new load, allowing you to update your view and invalidate the old data. Next we'll take a close look at the Loader we created.

34 OBSERVE: MyLo ader class private static class MyLoader extends AsyncTaskLoader<String> { public MyLoader(Context context) { super(context); public String loadinbackground() { try { Thread.sleep(DateUtils.SECOND_IN_MILLIS * 5); catch (InterruptedException e) { return "AsyncTaskLoader Complete!"; When you create a custom loader to perform background data, it's usually better (and easier) to ext end t he AsyncT asklo ade r class than the base Lo ader class. AsyncTaskLo ader, as the name implies, actually uses an AsyncTask object internally so you don't have to worry about managing any of the threading logic. With AsyncTaskLoader, you only need to implement one method: lo adinbackgro und(). This is where you perform the work required to load the data. Thanks to the underlying AsyncT ask, lo adinbackgro und() is already called on a separate thread. As you can see, we just copied our code from the AsyncT ask into the method here. Note Time periods in Android/Java are often expressed in milliseconds. The Dat eut ils class has some excellent features to help manage time units. We used Dat eut ils in this lesson to explicitly define a period of 5 seconds. You could just as easily hard-code 5000 here (5000 milliseconds = 5 seconds after all), but using Dat eut ils constants makes it easier to read so you (or anyone else reading your co de) will kno w immediately what interval is intended. There are also o ther co nstants such as HOUR_IN_MILLIS and DAY_IN_MILLIS which help with quantities where the math is co nsiderably mo re difficult to read in an instant. So, let's recap the flow here step-by-step. In the o ncreat e() method we ask the Lo adermanager for an instance of our Lo ader. Lo adermanager then calls o ncreat elo ader() on the Lo adercallbacks implementation where we create our instance of our MyLo ader class. Lo adermanager caches this, and returns the reference in init Lo ader(). Then we check to determine whether the Lo ader is already started, and if it isn't we force it to start by calling f o rcelo ad(). This causes our MyLo ader instance to create a new thread and start its work in the lo adinbackgro und() method. When the work is complete, the Loader returns its data, which is cached in the Lo adermanager. Lo adermanager then calls o nlo aderfinished() on the Lo adercallbacks, where we present the data result. Next, whenever the device is rotated, our Fragment Act ivit y gets destroyed and created. o ncreat e() gets called once more, where we once again ask the Lo adermanager for an instance of our Lo ader. It already has a reference cached, so it returns the reference immediately. Then we check to determine whether the Lo ader is started already; it is, so we do nothing. Lo adermanager will also check to find out if the Lo ader instance has completed or not, and if it has, it will immediately call o nlo aderfinished() on the Lo adercallbacks, passing the cached data. Wrapping Up As you can see, Loaders can help considerably when you need to perform long-running tasks. In fact, you handle most background tasks in your applications with either a Lo ader or a Service. There is one unfortunate downside to Loaders though a lack of support for reporting progress. AsyncT ask made that task relatively easy, but reporting progress with a Loader takes considerably more effort and code to implement cleanly. One alternate solution is to show an indeterminate progress bar when you start a load. If your background loads are long enough that you need to present accurate progress to the user, consider using a Service and Binder messages to report progress to your Views. We will cover Services and Binders later in the course. The last type of Lo ader class left for us to explore is the Curso rlo ader. It's a subclass of AsyncT asklo ader. This type of Loader is used to manage Curso r objects that encapsulate data results from a Co nt ent Pro vider. Co nt ent Pro viders and Curso rs will also be covered soon, in the upcoming Content Providers lesson. Practice what you've learned in this lesson in your homework. See you next lesson! Copyright O'Reilly Media, Inc.

35 Copyright O'Reilly Media, Inc. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. See for more information.

36 Advanced Layouts Lesson Objectives In this lesson you will: create alternate layo uts using reso urce qualifiers. reuse a fragment in both tablet and phone layouts. use a single activity to handle both phone and tablet layouts using fragments. Welcome back! In previous lessons we've created layouts for our applications using XML, but we've still only scratched the surface. In this lesson we'll go over some of the more advanced tools and features available for making layouts in Android, including alternate layouts for orientation and screen size (that is, for phones and tablets) as well as some layout optimization tools. Let's get started. Create a new Android project using these criteria: Name the project AdvancedLayo ut s. Use the package name co m.o st.andro id.advancedlayo ut s. Uncheck the Creat e cust o m launcher ico n box. Assign the Andro id2_lesso ns working set to the project. We'll need a tablet-sized emulator for this lesson. Click the Andro id Virt ual Device Manager button ( ) at the top of the Eclipse window. In the Device Manager window, click New. In the Create New Android Virtual Device (AVD) window, give the device an appropriate name, like username-t ab-wxga For Device, select 10.1" WXGA (T ablet ) (1280 x 800: mdpi). Set the Target to Andro id API Level 10. For CPU/ABI, select the ARM (armeabi-v7a) option. For SD card, select a Size of 20 MiB. Click OK. Back in the Android Virtual Device Manager window, make sure the new AVD is selected and click St art... to start the emulator. In the Launch Options dialog, select Scale display t o real size, enter 8 for the screen size, and click Launch.

37 Supporting Orientation Changes The way Android handles rotation can be a bit problematic at times. The Android OS will completely destroy and recreate the fro nt Activity (and its Fragments) during ro tatio n. If the applicatio n develo per isn't prepared to handle this, it can lead to a very frustrating user experience, and po ssibly even crash the who le applicatio n. Fo rtunately, the Andro id life-cycle provides some hooks that allow us to persist the state of our Activity through a rotation. This process also makes it convenient to change the layout of our View depending on the orientation of the device. Let's practice doing that. First, we'll update our primary View to make it a little more interesting. Open the layout file act ivit y_main.xml and make these changes:

38 CODE TO TYPE: /res/layo ut/activity_main.xml <RelativeLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".mainactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="40dp" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerhorizontal="true" android:layout_margintop="20dp" android:text="reverse" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="20dp" /> </RelativeLayout> No w, update MainAct ivit y.java:

39 CODE TO TYPE: MainActivity.java package com.ost.android.advancedlayouts; import android.app.activity; import android.os.bundle; import android.support.v4.app.fragmentactivity; import android.view.view; import android.view.view.onclicklistener; import android.widget.button; import android.widget.edittext; import android.widget.textview; import android.view.menu; public class MainActivity extends FragmentActivity { EditText inputedittext; TextView reversetextview; Button reversebutton; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); inputedittext = (EditText) findviewbyid(r.id.inputedittext); reversetextview = (TextView) findviewbyid(r.id.reversetextview); reversebutton = (Button) findviewbyid(r.id.reversebutton); reversebutton.setonclicklistener(new OnClickListener() { public void onclick(view v) { String inputtext = inputedittext.gettext().tostring(); String reversedtext = new StringBuffer(inputText).reverse().toString(); ); reversetextview.settext(reversedtext); public boolean oncreateoptionsmenu(menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getmenuinflater().inflate(r.menu.main, menu); return true; Run the application, type something into the EditText text area, then click the Reverse button. You see your text reversed in the TextView area. Now that we have a basic functioning application, let's implement a unique layout for landscape orientation. Click File New Ot her, choose Andro id XML Layo ut File from the list, and click Next. Select the AdvancedLayo ut s project, name the file act ivit y_main.xml (yes, the same name as our existing layout file), select LinearLayo ut as the root element, and click Ne xt :

40 On this screen you'll select the resource qualifiers for this view. Select Orient at io n from the list, click the right arrow in the middle of the window and, in the "Screen Orientation" drop-down, select Landscape. For the Folder field, enter /re s/layo ut -land:

41 Click Finish. ADT creates a new act ivit y_main.xml file in a new folder named layo ut -land. Modify this new layout file as shown:

42 CODE TO TYPE: /res/layo ut-land/activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="20dp" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <EditText android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="reverse" /> </LinearLayout> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margintop="20dp" /> </LinearLayout> Before going any further, take a moment to compare this file and the other activity_main.xml file from the /res/layo ut folder. What's different? What stayed the same? Notice that the android:id attributes we've used for each of our views are exactly the same. We'll explain this soon, but first, test the application.

43 Rotate the emulator (by pressing [Ct rl+f12]) and notice the changes in the view layout:

44 So what happened here, and what's with the layo ut -land folder? OBSERVE: MainActivity.java... public class MainActivity extends FragmentActivity { EditText inputedittext; TextView reversetextview; Button reversebutton; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); inputedittext = (EditText) findviewbyid(r.id.inputedittext); reversetextview = (TextView) findviewbyid(r.id.reversetextview); reversebutton = (Button) findviewbyid(r.id.reversebutton); reversebutton.setonclicklistener(new OnClickListener() { public void onclick(view v) { String inputtext = inputedittext.gettext().tostring(); String reversedtext = new StringBuffer(inputText).reverse().toString(); ); reversetextview.settext(reversedtext); Just like multiple drawable folders with different resolution qualifiers (which we covered in the first course), the layo ut - land fo lder has a qualifier, but instead o f that qualifier defining an alternate device resolution, it defines a device orientation, in this case the landscape orientation. When we set the View resource id for our Activity, we pass the value R.layo ut.act ivit y_main. Android tries to load the most specific type of file matching this id first; if it doesn't find a layout resource in any folder with qualifiers that match the device's current configuration, it falls back on the default file in the fo lder with no qualifiers.

45 You could also create a new act ivit y_main.xml layout file in a /res/layo ut -po rt folder. Layout files in this folder would then be used for portrait orientation. If you did this, you technically wouldn't need a act ivit y_main.xml in the default /res/layo ut folder. However, we don't recommend removing that default activity_main.xml. Always keep a default layo ut file fo r each view and o nly put specialized layo ut files fo r different device co nfiguratio ns in their respective resource qualifier folders as needed. If an application attempts to load a layout file and it can't find a file in any folder with matching qualifiers, the applicatio n will crash. Persisting Data on Rotation You might have noticed a problem in the previous application while rotating the emulator. While the text you typed in the EditText is still present after a rotation, the TextView no longer contains the reversed text. The EditText component has an "auto-save" feature in Android that allows it to automatically persist its data on rotation, but the TextView component does not have this feature. Many applications never have to worry about this limitation of the TextView component. Often the TextView text will already be defined by a String resource constant, and thus will be loaded each time the layout is loaded, even on rotation. Or perhaps the o ncreat eview() method is automatically defining the text for each TextView. But, obviously, there are some situations (like ours) where the data should probably be persisted through rotation. We could just "recompute" the reversed String after a rotation, but there's a better and more reliable way to make the fix. Modify MainAct ivit y.java once again as shown:

46 CODE TO TYPE: MainActivity.java package com.ost.android.advancedlayouts; import android.os.bundle; import android.support.v4.app.fragmentactivity; import android.view.view; import android.view.view.onclicklistener; import android.widget.button; import android.widget.edittext; import android.widget.textview; public class MainActivity extends FragmentActivity { public static final String KEY_REVERSED_TEXT = "keyreversedtext"; EditText inputedittext; TextView reversetextview; Button reversebutton; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); inputedittext = (EditText) findviewbyid(r.id.inputedittext); reversetextview = (TextView) findviewbyid(r.id.reversetextview); reversebutton = (Button) findviewbyid(r.id.reversebutton); reversebutton.setonclicklistener(new OnClickListener() { public void onclick(view v) { String inputtext = inputedittext.gettext().tostring(); String reversedtext = new StringBuffer(inputText).reverse().toString(); ); reversetextview.settext(reversedtext); if (savedinstancestate!= null) { String reversedtext = savedinstancestate.getstring(key_reversed_text); reversetextview.settext(reversedtext); protected void onsaveinstancestate(bundle outstate) { super.onsaveinstancestate(outstate); outstate.putstring(key_reversed_text, reversetextview.gettext().tostring()); Run the application and test the changes. The reversed text remains on the screen after rotation. Let's walk through these changes to make sure you understand them. First, look at the o nsaveinst ancest at e(bundle o ut St at e) method: OBSERVE: MainActivity.java - o nsaveinstancestate protected void onsaveinstancestate(bundle outstate) { super.onsaveinstancestate(outstate); outstate.putstring(key_reversed_text, reversetextview.gettext().tostring());

47 Here we override the o nsaveinst ancest at e(bundle o ut St at e) method, a method on Activity (also on Fragments) that is called when the Activity is going away for any reason and the state of the Activity needs to be saved. The method receives a Bundle object, which is intended to be used to store the current state of the Activity. A Bundle is a unique Android class that has many methods for storing data of various types. It also has matching methods for reading previously stored data of each type. The data types supported are mostly just the standard primitive types, like int and lo ng; Bundles can also sto re String, Array, ArrayList, Parcelable, and Serializable o bject types, which typically are used for more complex state models. We only need to store a simple String on Bundle. The values are stored in a Key-Value pattern similar to a HashMap. However, unlike a HashMap, the Key in a Bundle must be a String. We use a st at ic f inal defined String key constant KEY_REVERSED_T EXT to make sure we're using the same key to store and retrieve the data. OBSERVE: MainActivity.java - o ncreate(bundle savedinstancestate) if (savedinstancestate!= null) { String reversedtext = savedinstancestate.getstring(key_reversed_text); reversetextview.settext(reversedtext); We've seen the parameter "Bundle savedinstancestate" that's passed to the o ncreate metho d many times, but never used it. As you might have guessed, this savedinst ancest at e object will have all the state data you saved previously to the o ut St at e object in the o nsaveinst ancest at e method. We must do a null-check first, because this object will always be null the first time the Activity is created. After that, we retrieve our saved state data using the same key we used to save the data in this case, o ur reversed St ring. Take note that you are not limited to just one variable at a time, so feel free to store all the state data you need between rotations on the o ut St at e object. Supporting Multiple Screen Sizes Now that you've seen how to support alternate layouts, we'll go over how to support alternate screen sizes. The process is similar to that used to create alternate layouts for portrait and/or landscape. However, keep in mind that alternate layo uts fo r screen sizes can make wo rking with yo ur yo ur Activities and Fragments mo re difficult. We'll begin by making a layout for a tablet device. We'll use the Android XML Layout wizard again to create an alternate activity_main.xml file. Click File New Ot her and then choose Andro id XML Layo ut File from the list. Select the AdvancedLayo ut s project. Name the file act ivit y_main.xml, and click Next. Now, from the list on the left side, choose Smallest Screen Widt h and click the right-pointing arrow. In the Smallest Screen Width field that appears on the right, type 600. In the Folder field on the bottom of the wizard, you can see a preview of the folder qualifier name that will be generated for this new layout file: /res/layout-sw600dp. Click Finish.

48 In the newly created /res/layo ut -sw600dp/act ivit y_main.xml layout file, add this code:

49 CODE TO TYPE: /res/layo ut-sw6 0 0 dp/activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <RelativeLayout android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" > <EditText android:id="@+id/inputedittext" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="40dp" /> <Button android:id="@+id/reversebutton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/inputedittext" android:layout_centerhorizontal="true" android:layout_margintop="20dp" android:text="reverse" /> </RelativeLayout> <fragment android:id="@+id/fragresult" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" class="com.ost.android.advancedlayouts.resultfragment" /> </LinearLayout> Now, make a new layout for the new fragment. Select File New Ot her, and in the popup menu, choose Andro id XML Layo ut File. Name the file f ragment _result, select Relat ivelayo ut, and click Finish. Add the same result TextView from the original layout into f ragment _result.xml as shown: CODE TO TYPE: /res/layo ut/fragment_result.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/reversetextview" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:layout_centerinparent="true" /> </RelativeLayout> Next, create the new Fragment class. Right-click the co m.o st.andro id.advancedlayo ut s package and select New Class. Name the class Result Fragment, set the Superclass to andro id.suppo rt.v4.app.fragment, and click Finish. Modify the new class as shown:

50 CODE TO TYPE: ResultFragment.java package com.ost.android.advancedlayouts; import android.os.bundle; import android.support.v4.app.fragment; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; import android.widget.textview; public class ResultFragment extends Fragment { private TextView reversetextview; public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle savedin stancestate) { View view = inflater.inflate(r.layout.fragment_result, container, false); reversetextview = (TextView) view.findviewbyid(r.id.reversetextview); return view; public void setreversetext(string reversetext) { reversetextview.settext(reversetext); Create another Android XML Layout file named result _act ivit y.xml, selecting FrameLayo ut as the root element: CODE TO TYPE: result_activity.xml <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" > <fragment android:id="@+id/fragresult" android:layout_width="match_parent" android:layout_height="match_parent" class="com.ost.android.advancedlayouts.resultfragment" /> </FrameLayout> You also need to make a new Activity that will be used to load the ResultFragment on smaller screens. Right-click the co m.o st.andro id.advancedlayo ut s package and select New Class. Name the class Result Act ivit y, set the Superclass to andro id.suppo rt.v4.app.fragment Act ivit y, and click Finish. Modify the new class as shown:

51 CODE TO TYPE: ResultActivity.java package com.ost.android.advancedlayouts; import android.os.bundle; import android.support.v4.app.fragmentactivity; public class ResultActivity extends FragmentActivity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.result_activity); Bundle extras = getintent().getextras(); String reversedtext = extras.getstring(mainactivity.key_reversed_text); ResultFragment f = (ResultFragment) getsupportfragmentmanager().findfragmentbyid(r. id.fragresult); if (f!= null) { f.setreversetext(reversedtext); In order to use the new Activity that you created, you need to update the Andro idmanif est. Open it and modify the code as shown: CODE TO TYPE: /Andro idmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.ost.android.advancedlayouts" android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="8" android:targetsdkversion="17" /> <application android:allowbackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/apptheme" > <activity android:name="com.ost.android.advancedlayouts.mainactivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <activity android:name="com.ost.android.advancedlayouts.resultactivity" android:label="result Activity" /> </application> </manifest> Make some changes to the original Activity and views to utilize these new classes. Modify the MainAct ivit y class as sho wn:

52 CODE TO TYPE: MainActivity.java package com.ost.android.advancedlayouts; import android.content.intent; import android.os.bundle; import android.support.v4.app.fragmentactivity; import android.support.v4.app.fragmentmanager; import android.view.view; import android.view.view.onclicklistener; import android.widget.button; import android.widget.edittext; import android.widget.textview; public class MainActivity extends FragmentActivity { public static final String KEY_REVERSED_TEXT = "keyreversedtext"; EditText inputedittext; TextView reversetextview; Button reversebutton; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); inputedittext = (EditText) findviewbyid(r.id.inputedittext); reversetextview = (TextView) findviewbyid(r.id.reversetextview); reversebutton = (Button) findviewbyid(r.id.reversebutton); reversebutton.setonclicklistener(new OnClickListener() { public void onclick(view v) { String inputtext = inputedittext.gettext().tostring(); String reversedtext = new StringBuffer(inputText).reverse().toString(); ); reversetextview.settext(reversedtext); FragmentManager fm = getsupportfragmentmanager(); ResultFragment frag = (ResultFragment) fm.findfragmentbyid(r.id.fragresult); if (frag!= null) { frag.setreversetext(reversedtext); else { final Intent i = new Intent(MainActivity.this, ResultActivity.class); Bundle extras = new Bundle(); extras.putstring(key_reversed_text, reversedtext); i.putextras(extras); startactivity(i); if (savedinstancestate!= null) { String reversedtext = savedinstancestate.getstring(key_reversed_text); reversetextview.settext(reversedtext); protected void onsaveinstancestate(bundle outstate) { super.onsaveinstancestate(outstate); outstate.putstring(key_reversed_text, reversetextview.gettext().tostring());

53 Now, remove the result T ext View from the original layout files, because that's going to be handled by our fragment now. Modify /res/layo ut /act ivit y_main.xml as shown: CODE TO TYPE: /res/layo ut/activity_main.xml <RelativeLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingbottom="@dimen/activity_vertical_margin" android:paddingleft="@dimen/activity_horizontal_margin" android:paddingright="@dimen/activity_horizontal_margin" android:paddingtop="@dimen/activity_vertical_margin" tools:context=".mainactivity" > <EditText android:id="@+id/inputedittext" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="40dp" /> <Button android:id="@+id/reversebutton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/inputedittext" android:layout_centerhorizontal="true" android:layout_margintop="20dp" android:text="reverse" /> <TextView android:id="@+id/reversetextview" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/reversebutton" android:layout_margin="20dp" /> </RelativeLayout> Remove that TextView from the landscape layout. Modify /res/layo ut -land/act ivit y_main.xml as shown:

54 CODE TO TYPE: /res/layo ut-land/activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="20dp" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <EditText android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="reverse" /> </LinearLayout> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="20dp" /> </LinearLayout> With that last change, we should be good to go! Run the application and test it. Compare the standard emulator to the larger tablet-sized emulato r to see ho w the app behaves. On a phone-sized emulator, after typing some text into the first edit field and clicking the Reverse button, the application launches a second activity showing just the reversed version of the original text.

55

56 --> On a tablet-sized emulator, instead of a new Activity, you see the reversed text immediately on the right side of the screen. Until no w, we've been using Fragments in almo st exactly the same way as Activities. Here, finally, we've demo nstrated one of the primary reasons Fragments were introduced into the Android SDK. Alternate layouts based on device screen size are perhaps the best use case for Fragments. We've made a number of changes; let's look at them in detail now:

57 OBSERVE: MainActivity.java protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); inputedittext = (EditText) findviewbyid(r.id.inputedittext); reversebutton = (Button) findviewbyid(r.id.reversebutton); reversebutton.setonclicklistener(new OnClickListener() { public void onclick(view v) { String inputtext = inputedittext.gettext().tostring(); String reversedtext = new StringBuffer(inputText).reverse().toString(); ); FragmentManager fm = getsupportfragmentmanager(); ResultFragment frag = (ResultFragment) fm.findfragmentbyid(r.id.fragresult); if (frag!= null) { frag.setreversetext(reversedtext); else { final Intent i = new Intent(MainActivity.this, ResultActivity.class); Bundle extras = new Bundle(); extras.putstring(key_reversed_text, reversedtext); i.putextras(extras); startactivity(i); Here in MainAct ivit y.java, we update the OnClickList ener with some conditional logic. We check t o det ermine if t he Result Fragment already exist s. If it does exist, we set t he reversed St ring using t he Result Fragment 's set ReverseT ext () met ho d. If the Fragment can't be found in the FragmentManager, then we st art a new Result Act ivit y, and pass t he reversed St ring int o it using an ext ras Bundle. We reuse the key co nstant KEY_REVERSED_T EXT that we defined earlier to handle persisting data o n ro tatio n. Next let's consider the alternate activity_main.xml layout we defined in the /res/layo ut -sw600dp folder: OBSERVE: /res/layo ut-sw6 0 0 dp/activity_main.xml... <fragment android:id="@+id/fragresult" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" class="com.ost.android.advancedlayouts.resultfragment" /> </LinearLayout> This layout folder has a qualifier of sw600dp, which stands for "smallest width of at least 600dp." The smallest width refers to the smallest dimensio n fo r a device in either landscape o r po rtrait. Pho nes have a relatively small "smallest width" compared to tablets. 600dp is a fairly standard cut-off line for the "smallest width" for a device to be considered a tablet. Since we have at least 600dp width of screen space, we choose to include t he Result Fragment in o ur layo ut in addition to the other view component we're using for our main view. This means phones will only load the default act ivit y_main.xml from the layo ut or layo ut -land folder, which don't include the ResultFragment. So, on phones, MainAct ivit y won't be able to find the ResultFragment in its view, so it will launch the Result Act ivit y instead. Fro m here we'll jump into the ResultActivity class:

58 OBSERVE: ReverseActivity.java public class ResultActivity extends FragmentActivity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.result_activity); Bundle extras = getintent().getextras(); String reversedtext = extras.getstring(mainactivity.key_reversed_text); ResultFragment f = (ResultFragment) getsupportfragmentmanager().findfragmentbyid(r. id.fragresult); if (f!= null) { f.setreversetext(reversedtext); In the ResultActivity class, we need to ret rieve t he reversed St ring f ro m t he Int ent ext ras and then pass it t o t he Result Fragment. The ResultFragment was defined in the result _act ivit y.xml layout so we only need to f ind t he f ragment using t he Fragment Manager and then call the same set ReverseT ext () method again. That's how we're able to reuse the ResultFragment to accomplish our goals for both Tablets and Phones. Wrapping Up We covered a lot of ground in this lesson. We started by learning how resource qualifiers can be used to load alternate layouts based on device orientation. Then we learned how to make sure our app persists its data during rotation. Finally, we learned how to combine alternate layouts with Fragments to conditionally include an additional Fragment in our view when the screen is large enough, or load the view in an Activity if there isn't enough space. With what you've learned in this lesson, you can create truly powerful applications that support both Tablet and Phone sized devices in whatever orientation the user prefers. Nice work. See you in the next lesson! Copyright O'Reilly Media, Inc. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. See for more information.

59 Custom View Components Lesson Objectives In this lesson you will: create a custom view component from scratch. include the custom component in a layout via XML. define custo m attributes. handle custom attributes in a custom component. use custom attributes in XML layouts. We've done lots of UI building using various components provided by Android, but there will come a time when you want to create your own reusable UI component. This may be because you want to: completely customize the look, layout, and behavior of a graphical component. change the behavior or appearance of an existing component. co mbine several existing co mpo nents into a single reusable widget. Defining a Custom Component Create a new Android project using this criteria: Name the project Cust o mco mpo nent s. Use the package name co m.o reillyscho o l.andro id2.cust o mco mpo nent s. Uncheck the Creat e cust o m launcher ico n box. Assign the Andro id2_lesso ns working set to the project. In the new project, create a new class named MyCust o mco mpo nent, set the package to co m.o reillyscho o l.andro id2.cust o mco mpo nent s, have it extend from andro id.widget.framelayo ut, and check the Co nst ruct o rs f ro m superclass box. The completed class will have three constructors: MyCusto mco mpo nent(co ntext co ntext) MyCusto mco mpo nent(co ntext co ntext, AttributeSet attrs) MyCusto mco mpo nent(co ntext co ntext, AttributeSet attrs, int defstyle) Modify MyCust o mco mpo nent.java as shown:

60 CODE TO TYPE: MyCusto mco mpo nent.java package com.oreillyschool.android2.customcomponents; import android.content.context; import android.util.attributeset; import android.widget.framelayout; public class MyCustomComponent extends FrameLayout { public MyCustomComponent(Context context) { super(context); // TODO Auto-generated constructor stub this(context, null); public MyCustomComponent(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub this(context, attrs, 0); public MyCustomComponent(Context context, AttributeSet attrs, int defstyle) { super(context, attrs, defstyle); // TODO Auto-generated constructor Now create a new Android XML file named my_cust o m_co mpo nent _view.xml with a LinearLayo ut as the root element and make sure that the file is saved in the /res/layout folder. Then modify my_cust o m_co mpo nent _view.xml as shown: CODE TO TYPE: /res/layo ut/my_custo m_co mpo nent_view.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="verticalhorizontal" > <ToggleButton android:id="@+id/button01" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:texton="one" android:textoff="one" /> <ToggleButton android:id="@+id/button02" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:texton="two" android:textoff="two" /> <ToggleButton android:id="@+id/button03" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:texton="three" android:textoff="three" /> </LinearLayout> Now, return to MyCust o mco mpo nent.java and make this change:

61 CODE TO TYPE: MyCusto mco mpo nent.java package com.oreillyschool.android2.customcomponents; import android.content.context; import android.util.attributeset; import android.widget.framelayout; public class MyCustomComponent extends FrameLayout { public MyCustomComponent(Context context) { this(context, null); public MyCustomComponent(Context context, AttributeSet attrs) { this(context, attrs, 0); public MyCustomComponent(Context context, AttributeSet attrs, int defstyle) { super(context, attrs, defstyle); inflate(context, R.layout.my_custom_component_view, this); We've defined the custom component's class and layout. Now we'll add it to our application. Open act ivit y_main.xml and make these changes: CODE TO TYPE: /res/layo ut/activity_main.xml <RelativeLinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingbottom="@dimen/activity_vertical_margin" android:paddingleft="@dimen/activity_horizontal_margin" android:paddingright="@dimen/activity_horizontal_margin" android:paddingtop="@dimen/activity_vertical_margin" android:orientation="vertical" tools:context=".mainactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> <com.oreillyschool.android2.customcomponents.mycustomcomponent android:id="@+id/button_bar" android:layout_width="match_parent" android:layout_height="wrap_content" /> </RelativeLinearLayout> Save the changes and run it in the emulator:

62 This is a great start. Now add some functionality to make it more interesting. Go back to MyCust o mco mpo nent.java and make these changes:

63 CODE TO TYPE: MyCusto mco mpo nent.java package com.oreillyschool.android2.customcomponents; import android.content.context; import android.util.attributeset; import android.view.view; import android.view.view.onclicklistener; import android.widget.togglebutton; import android.widget.framelayout; public class MyCustomComponent extends FrameLayout implements OnClickListener { private ToggleButton button01; private ToggleButton button02; private ToggleButton button03; private ToggleButton selectedtogglebutton; public MyCustomComponent(Context context) { this(context, null); public MyCustomComponent(Context context, AttributeSet attrs) { this(context, attrs, 0); public MyCustomComponent(Context context, AttributeSet attrs, int defstyle) { super(context, attrs, defstyle); inflate(context, R.layout.my_custom_component_view, this); button01 = (ToggleButton)findViewById(R.id.button01); button02 = (ToggleButton)findViewById(R.id.button02); button03 = (ToggleButton)findViewById(R.id.button03); button01.setchecked(true); selectedtogglebutton = button01; button01.setonclicklistener(this); button02.setonclicklistener(this); button03.setonclicklistener(this); public void onclick(view v) { selectedtogglebutton.setchecked(false); selectedtogglebutton = (ToggleButton)v; selectedtogglebutton.setchecked(true); Save the changes and run the application. The components behave like radio buttons: only one button can be checked at a time and each time a new button is clicked, the previous one becomes unchecked. Great. Now that we have all that working, let's go back and take a look at what we've done.

64 OBSERVE: MyCusto mco mpo nent.java... public class MyCustomComponent extends FrameLayout implements OnClickListener { private ToggleButton button01; private ToggleButton button02; private ToggleButton button03; private ToggleButton selectedtogglebutton; public MyCustomComponent(Context context) { this(context, null); public MyCustomComponent(Context context, AttributeSet attrs) { this(context, attrs, 0); public MyCustomComponent(Context context, AttributeSet attrs, int defstyle) { super(context, attrs, defstyle); inflate(context, R.layout.my_custom_component_view, this); button01 = (ToggleButton)findViewById(R.id.button01); button02 = (ToggleButton)findViewById(R.id.button02); button03 = (ToggleButton)findViewById(R.id.button03); button01.setchecked(true); selectedtogglebutton = button01; button01.setonclicklistener(this); button02.setonclicklistener(this); button03.setonclicklistener(this); public void onclick(view v) { selectedtogglebutton.setchecked(false); selectedtogglebutton = (ToggleButton)v; selectedtogglebutton.setchecked(true); We create a new component by sub-classing an existing UI component (andro id.widget.framelayo ut ) to create a button bar. Instead of creating a completely custom component, we group existing components together (in this case three T o gglebut t o n instances: but t o n01, but t o n02 and but t o n03), and add custom behavior to ensure that only one button is checked at a time in the button bar:

65 OBSERVE: my_custo m_co mpo nent_view.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <ToggleButton android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:texton="one" android:textoff="one" /> <ToggleButton android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:texton="two" android:textoff=two" /> <ToggleButton android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:texton="three" android:textoff="three" /> </LinearLayout> We create a corresponding layout for our custom component in my_cust o m_co mpo nent _view.xml, that consists of three T o gglebut t o n nodes inside of a LinearLayo ut, which presents the buttons horizontally and comprises our button bar: OBSERVE: activity_main.xml <LinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingbottom="@dimen/activity_vertical_margin" android:paddingleft="@dimen/activity_horizontal_margin" android:paddingright="@dimen/activity_horizontal_margin" android:paddingtop="@dimen/activity_vertical_margin" android:orientation="vertical" tools:context=".mainactivity" > <com.oreillyschool.android2.customcomponents.mycustomcomponent android:id="@+id/button_bar" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout> Finally, we place our custom component inside a view by adding a co m.o reillyscho o l.andro id2.cust o mco mpo nent s.mycust o mco mpo nent node in act ivit y_main.xml, which creates an instance of our component inside the main view of our application, and sets the width and height of that instance. Implementing View Attributes in a Custom Component To make our custom component feel more like an official Android component, we can add custom attributes that allow us to specify certain pro perties within XML layo ut files rather than pro grammatically. This streamlines the co mpo nent and makes it easier to use. Create a new Android XML file. Specify Values as the Resource Type and name the file at t rs.xml. Make sure that the

66 file is saved to res/values. Then add this code to at t rs.xml: CODE TO TYPE: attrs.xml <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="mycustomcomponent"> <attr format="integer" name="defaultbuttonindex" /> </declare-styleable> </resources> Next, modify MyCust o mco mpo nent.java as shown:

67 CODE TO TYPE: MyCusto mco mpo nent package com.oreillyschool.android2.customcomponents; import android.content.context; import android.content.res.typedarray; import android.util.attributeset; import android.view.view; import android.view.view.onclicklistener; import android.widget.togglebutton; import android.widget.framelayout; public class MyCustomComponent extends FrameLayout implements OnClickListener { private ToggleButton button01; private ToggleButton button02; private ToggleButton button03; private ToggleButton selectedtogglebutton; public MyCustomComponent(Context context) { this(context, null); public MyCustomComponent(Context context, AttributeSet attrs) { this(context, attrs, 0); public MyCustomComponent(Context context, AttributeSet attrs, int defstyle) { super(context, attrs, defstyle); inflate(context, R.layout.my_custom_component_view, this); button01 = (ToggleButton)findViewById(R.id.button01); button02 = (ToggleButton)findViewById(R.id.button02); button03 = (ToggleButton)findViewById(R.id.button03); button01.setchecked(true); selectedtogglebutton = button01; button01.setonclicklistener(this); button02.setonclicklistener(this); button03.setonclicklistener(this); TypedArray a = getcontext().obtainstyledattributes(attrs, R.styleable.MyCustomC omponent, defstyle, 0); final int N = a.getindexcount(); for (int i=0; i<n; i++) { int attr = a.getindex(i); switch (attr) { case R.styleable.MyCustomComponent_defaultButtonIndex: int index = a.getint(attr, 0); switch(index) { case 1: selectedtogglebutton = button02; break; case 2: selectedtogglebutton = button03; break; default: selectedtogglebutton = button01; break; selectedtogglebutton.setchecked(true); break; a.recycle();

68 public void onclick(view v) { selectedtogglebutton.setchecked(false); selectedtogglebutton = (ToggleButton)v; selectedtogglebutton.setchecked(true); Finally, modify act ivit y_main.xml as shown: CODE TO TYPE: activity_main.xml <LinearLayout xmlns:android=" xmlns:oreilly=" mponents" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingbottom="@dimen/activity_vertical_margin" android:paddingleft="@dimen/activity_horizontal_margin" android:paddingright="@dimen/activity_horizontal_margin" android:paddingtop="@dimen/activity_vertical_margin" android:orientation="vertical" tools:context=".mainactivity" > <com.oreillyschool.android2.customcomponents.mycustomcomponent android:id="@+id/button_bar" android:layout_width="match_parent" android:layout_height="wrap_content" oreilly:defaultbuttonindex="2" /> </LinearLayout> Save the changes and run the application; when the application loads, instead of the first button, the third button is checked.

69 Let's review what we've done. We add a custom attribute to our custom component. This custom attribute allows us to specify the button within the button bar that will be checked by default when the component first loads. Using a custom attribute allows us to configure the component within a layout (in this case within the main view of our application), as well as make the component cleaner and even more reusable. We can also distinguish between the properties our component inherits from its parent class, and its own properties: OBSERVE: attrs.xml <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="mycustomcomponent"> <attr format="integer" name="defaultbuttonindex" /> </declare-styleable> </resources> The values file at t rs.xml holds the declarations for custom attributes. In our case, we specify that our custom component, MyCust o mco mpo nent, is st yleable, then we specify an integer attribute, def ault But t o nindex. This attribute is the zero-based index of the button we want checked, by default:

70 OBSERVE: MyCusto mco mpo nent.java... public MyCustomComponent(Context context, AttributeSet attrs, int defstyle) { super(context, attrs, defstyle); inflate(context, R.layout.my_custom_component_view, this); button01 = (ToggleButton)findViewById(R.id.button01); button02 = (ToggleButton)findViewById(R.id.button02); button03 = (ToggleButton)findViewById(R.id.button03); button01.setonclicklistener(this); button02.setonclicklistener(this); button03.setonclicklistener(this); TypedArray a = getcontext().obtainstyledattributes(attrs, R.styleable.MyCustomC omponent, defstyle, 0); final int N = a.getindexcount(); for (int i=0; i<n; i++) { int attr = a.getindex(i); switch (attr) { case R.styleable.MyCustomComponent_defaultButtonIndex: int index = a.getint(attr, 0); switch(index) { case 1: selectedtogglebutton = button02; break; case 2: selectedtogglebutton = button03; break; default: selectedtogglebutton = button01; break; selectedtogglebutton.setchecked(true); break; a.recycle(); public void onclick(view v) { selectedtogglebutton.setchecked(false); selectedtogglebutton = (ToggleButton)v; selectedtogglebutton.setchecked(true); Next, we add logic to MyCust o mco mpo nent to determine whether the default button index is specified and, if it is, to set the default button. In order to do that, we retrieve an array of all of the styled attributes specified by the layout XML, look through the array to determne whether def ault But t o nindex is set, and then set the default checked button. We set a def ault value, just in case def ault But t o nindex wasn't used in the layout. We also make a call to recycle() o n t he T ypedarray o bject after we finish reading the attributes. Android reuses the resource array for multiple components, so it's important to call this method whenever you finish reading the values required by yo ur custo m co mpo nent:

71 OBSERVE: activity_main.xml <LinearLayout xmlns:android=" xmlns:oreilly=" mponents" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".mainactivity" > <com.oreillyschool.android2.customcomponents.mycustomcomponent android:layout_width="match_parent" android:layout_height="wrap_content" oreilly:defaultbuttonindex="2" /> </LinearLayout> Finally, we go to our application's layout and add a namespace f o r o ur cust o m at t ribut es (the full package name of our application). Then, using that namespace, we set t he cust o m at t ribut e o n o ur cust o m co mpo nent. Wrapping Up Whew! We covered a lot of fairly advanced topics. When you know how to create custom components properly in Android, it opens up a lot of options in your application development. Now you aren't limited to basic Android components. Now when Android doesn't provide the component you're looking for, you can just create your own! Good luck with the homework and see you next lesson! Copyright O'Reilly Media, Inc. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. See for more information.

72 Basic Services Lesson Objectives In this lesson you will: create an Andro id Service implementatio n fro m scratch. perform a long running task in a Service. define/register a Service in an applicatio n manifest. start and sto p a service pro gramatically. There are two main uses for an Android Service: They allow you to specify long-running parts of your application to run in the background, leaving the rest of your application available for user interaction. (For example, if you had a media player application that could download new content, you would want the media downloading to occur in a Service.) They allow you to specify functionality in your application that you can make available to other applications as well. (Fo r example, yo u might have a Twitter client that expo ses its uplo ading/tweeting service to o ther applicatio ns so that you can post tweets or images from them.) The Andro id Develo per do cumentatio n clarifies that Services are not threads themselves, o r separate pro cesses, but rather a means of letting the system know what functionality you want to run in the background or share with other applications, and that the system itself takes charge o f the Service functio nality and its callbacks. Creating, Declaring, and Starting a Service Let's get started. Create a new Android project with this criteria: Name the project BasicServices. Use the package name co m.o reillyscho o l.andro id2.basicservices. Uncheck the Creat e cust o m launcher ico n box. Assign the Andro id2_lesso ns working set to the project. Let's start with our Service class. Create a new class in the co m.o reillyscho o l.andro id2.basicservices package that extends from andro id.app.service. Name it SimpleService. You see a stub for a single method: o nbind(int ent arg0) (we'll change that to o nbind(int ent int ent ).) Right-click the SimpleService filename and select So urce Override/Implement Met ho ds. A window appears showing methods in the Service parent class that you can override. Select o ncreat e(), o ndest ro y(), and o nst art Co mmand(int ent, int, int ) and click OK. Now you see stubs for those three methods as well. Okay, make these changes:

73 CODE TO TYPE: SimpleService.java package com.oreillyschool.android2.basicservices; import android.app.service; import android.content.intent; import android.os.ibinder; import android.util.log; public class SimpleService extends Service { public IBinder onbind(intent intent) { return null; public void oncreate() { // TODO Auto-generated method stub super.oncreate(); Log.d("SimpleService", "Service created."); public void ondestroy() { // TODO Auto-generated method stub super.ondestroy(); Log.d("SimpleService", "Service destroyed."); public int onstartcommand(intent intent, int flags, int startid) { // TODO Auto-generated method stub Log.d("SimpleService", "Service started."); return START_STICKY; return super.onstartcommand(intent, flags, startid); Add the code below to Andro idmanif est.xml:

74 CODE TO TYPE: Andro idmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.oreillyschool.android2.basicservices" android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="10" /> <application android:allowbackup="true" > <service android:name=".simpleservice" /> <activity android:name=".mainactivity" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> Finally, make these changes in MainAct ivit y: CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.basicservices; import android.app.activity; import android.content.intent; import android.os.bundle; import android.os.handler; import android.view.menu; public class MainActivity extends Activity { /** Called when the activity is first created. */ public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); startservice(new Intent(MainActivity.this, SimpleService.class)); Handler handler = new Handler(); handler.postdelayed(new Runnable() { public void run() { stopservice(new Intent(MainActivity.this, SimpleService.class));, 10000); public boolean oncreateoptionsmenu(menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getmenuinflater().inflate(r.menu.main, menu); return true;

75 Run the application to see our service start and stop. Once it's loaded into your emulator, take a look at the LogCat windo w in Eclipse: The service was created, started, and eventually destroyed. Our service didn't do much, but we can look at our code and see the general pro cedure fo r creating and starting services: OBSERVE: SimpleService.java package com.oreillyschool.android2.basicservices; import android.app.service; import android.content.intent; import android.os.ibinder; import android.util.log; public class SimpleService extends Service { public void oncreate() { super.oncreate(); Log.d("SimpleService", "Service created."); public void ondestroy() { super.ondestroy(); Log.d("SimpleService", "Service destroyed."); public int onstartcommand(intent intent, int flags, int startid) { Log.d("SimpleService", "Service started."); return START_STICKY; public IBinder onbind(intent intent) { return null; The Andro id o perating system manages the actual Service instance. We implement callbacks fo r the creatio n, destruction, and start of a service. In those callbacks, we add lo g st at ement s so we can see when each method is executed in Lo gcat.

76 OBSERVE: Andro idmanifest.xml <?xml version="1.0" encoding="1.0"?> <manifest xmlns:android=" package="com.oreillyschool.android2.basicservices" android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="10" /> <application > <service android:name=".simpleservice" /> <activity android:name=".mainactivity" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> Just like Activities, Services must be declared inside the manifest. This registers the Service with the Android OS so that it can create an instance when it receives an Int ent for the Service. OBSERVE: MainActivity.java package com.oreillyschool.android2.basicservices; import android.app.activity; import android.content.intent; import android.os.bundle; import android.os.handler; public class MainActivity extends Activity { /** Called when the activity is first created. */ public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); startservice(new Intent(MainActivity.this, SimpleService.class)); Handler handler = new Handler(); handler.postdelayed(new Runnable() { public void run() { stopservice(new Intent(MainActivity.this, SimpleService.class));, 10000); In the main activity, we start the service, and then schedule it to stop 10 seconds later, using a Handler object's po st Delayed(Runnable, lo ng) method. The Handler allows us to execute some code after a delay. The Runnable object's run() method is executed after the delay. The delay is defined by the second parameter, a lo ng, which is interpreted as milliseconds. The method's st art Service and st o pservice are both defined on the Co nt ext class, which is a parent class of Act ivit y. These methods take an Intent, which is simliar to the startactivity method that we use to start a new Activity, but this Intent gives a reference to our SimpleService.class type instead. In this example, we see the basic procedure to create, declare, start, and stop a service. Before we move on, let's specify a task for

77 service to execute. Right-click the res folder in your BasicServices root project folder and select New Fo lder. In the New Folder window, create a folder named raw. Now we'll add an audio file to our project to integrate into our application. To do wnlo ad the audio file, right-click o n the link belo w and save the file to yo ur "Co mputer (\\beam\winusers) (V:)" wo rkspace/basicse rvice s/re s/raw fo lder. Do wnlo ad the audio file here: art_no w_by_alex_bero za.mp3 ("Art No w" by Alex (feat. Sno wflake) is licensed under a Creative Co mmo ns license.) Note The /raw fo lder is used primarily fo r sto ring files within yo ur applicatio n in their raw unco mpressed fo rm. Files in the /raw folder are given a resource id in the form of R.raw.filename. Typically, they are accessed in co de via the Reso urces.o penrawreso urce() metho d. Now make these changes to SimpleService: CODE TO TYPE: SimpleService.java package com.oreillyschool.android2.basicservices; import android.app.service; import android.content.intent; import android.media.mediaplayer; import android.os.ibinder; import android.util.log; public class SimpleService extends Service { private MediaPlayer mplayer; public IBinder onbind(intent intent) { return null; public void oncreate() { super.oncreate(); mplayer = MediaPlayer.create(this, R.raw.art_now_by_alex_beroza); Log.d("SimpleService", "Service created."); public void ondestroy() { super.ondestroy(); Log.d("SimpleService", "Service destroyed."); mplayer.stop(); mplayer = null; public int onstartcommand(intent intent, int flags, int startid) { Log.d("SimpleService", "Service started."); mplayer.start(); return START_STICKY; Let's make a few changes to the interfaces. First, make these changes to /res/values/st rings.xml:

78 CODE TO TYPE: /res/values/strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">hello World, MainActivity!</string><string> <string name="app_name">basicservices</string> <string name="start_button_label">start</string> <string name="stop_button_label">stop</string> <string name="attribution_text">"art Now" by Alex (feat. Snowflake)\nhttp://ccmixte r.org/files/alexberoza/30344\nis licensed under a Creative Commons license:\nhttp://cre ativecommons.org/licenses/by/3.0/</string> </resources> Next, make these changes to act ivit y_main.xml in res/layo ut : CODE TO TYPE: activity_main.xml <LinearLayout xmlns:android=" android:layout_width="fill_parent" android:layout_width="match_parent" android:layout_height="fill_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" > <Button android:id="@+id/startbutton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/start_button_label" /> <Button android:id="@+id/stopbutton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/stop_button_label" /> </LinearLayout> <TextView android:layout_width="fill_parent" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hello" /> android:text="@string/attribution_text" /> </LinearLayout> Finally, make these changes to MainAct ivit y:

79 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.basicservices; import android.app.activity; import android.content.intent; import android.os.bundle; import android.os.handler; import android.view.view; import android.view.view.onclicklistener; public class MainActivity extends Activity { private OnClickListener startonclicklistener = new OnClickListener() { public void onclick(view v) { startservice(new Intent(MainActivity.this, SimpleService.class)); ; private OnClickListener stoponclicklistener = new OnClickListener() { public void onclick(view v) { stopservice(new Intent(MainActivity.this, SimpleService.class)); ; /** Called when the activity is first created. */ public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); startservice(new Intent(MainActivity.this, SimpleService.class)); Handler handler = new Handler(); handler.postdelayed(new Runnable() { public void run() { stopservice(new Intent(MainActivity.this, SimpleService.class));, 10000); findviewbyid(r.id.startbutton).setonclicklistener(startonclicklistener); findviewbyid(r.id.stopbutton).setonclicklistener(stoponclicklistener); Save the modified files and run the application. When you click the St art button, the audio file starts playing. The audio is playing in the background, so if you click the home button on your emulator, the audio will continue to play even though the application is not in the foreground. If you go back to the application and click St o p, the audio will stop. Let's take a look at our changes:

80 OBSERVE: SimpleService.java package com.oreillyschool.android2.basicservices; import android.app.service; import android.content.intent; import android.media.mediaplayer; import android.os.ibinder; public class SimpleService extends Service { private MediaPlayer mplayer; public IBinder onbind(intent intent) { return null; public void oncreate() { super.oncreate(); mplayer = MediaPlayer.create(this, R.raw.art_now_by_alex_beroza); public void ondestroy() { super.ondestroy(); mplayer.stop(); mplayer = null; public int onstartcommand(intent intent, int flags, int startid) { mplayer.start(); return START_STICKY; In SimpleService, we add a MediaPlayer and call st art () when the service starts and st o p() when the Service stops (and is destroyed). This is a basic implementation of the MediaPlayer object. It could certainly be improved, but that's outside of the scope of this lesson. We'll go over audio and video playback in a future lesson.

81 OBSERVE: activity_main.xml <LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" > <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> </LinearLayout> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> In the activity_main.xml layout, we add two buttons for st art ing and st o pping the Service:

82 OBSERVE: MainActivity.java package com.oreillyschool.android2.basicservices; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; import android.view.view.onclicklistener; public class MainActivity extends Activity { private OnClickListener startonclicklistener = newonclicklistener() { public void onclick(view v) { startservice(new Intent(MainActivity.this, SimpleService.class)); ; private OnClickListener stoponclicklistener = new OnClickListener() { public void onclick(view v) { stopservice(new Intent(MainActivity.this, SimpleService.class)); ; /** Called when the activity is first created. */ public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); findviewbyid(r.id.startbutton).setonclicklistener(startonclicklistener); findviewbyid(r.id.stopbutton).setonclicklistener(stoponclicklistener); Finally, in the MainActivity.java class, we wire the buttons in the interface to start and stop the Service and thereby start and stop the music. Let's consider our Service class once again, specifically the return value of o nst art Co mmand. There are a few different modes of operation for an Android Service. By specifying a particular value for o nst art Co mmand to return, you specify the mode in which the service will run. In our example, we specify the constant value ST ART _ST ICKY. In doing so, we let the system know that if our SimpleService is killed (usually due to the system needing to free up memory when memory is low) after o nst art Co mmand(), then the system should restart it. This value allows a service to be started and run indefinitely until yo u explicitly sto p it. In o ur example, the music file will play in the backgro und indefinitely (o r until the entire file finishes playing). Services that behave in this way are generally referred to as started services. The supported constants for the o nst art Co mmand result, defined on the Service class, are START_STICKY, START_NOT_STICKY, START_REDELIVER_INTENT, and START_STICKY_COMPATIBILITY. Fo r a detailed description about how Service will behave based on each constant, read the Android Developer Documentation site linked to each constant we mentioned. If we want our service to run only when we have specific tasks (Intents) for it to handle, then we specify o nst art Co mmand to return ST ART _NOT _ST ICKY. In this case, if the service is killed after o nst art Co mmand(), it is not restarted unless there are Intents waiting to be handled. This behavior is useful for processing work independently, but there is another tool we can use to do the same work more efficiently: an extension of the Service class called Int ent Service. The Int ent Service class is an extremely useful subclass of Service. Because you only need to implement one abstract method (o nhandleint ent ), it's easier to use than a regular Service. By default, the Int ent Service class uses a ST ART _NOT _ST ICKY command mode type, so this class is a great option if you need to perform work in a Service, but don't want to implement a full Service class. Another benefit of Int ent Service is that the o nhandleint ent method is executed in a separate worker thread, so you don't have to worry about accidentally perfo rming wo rk o n yo ur applicatio n's primary thread. Wrapping Up

83 Services are an essential tool of the Android SDK. In this lesson, we learned about creating a basic Service class implementation. We covered how to stop and start a service from our Application, and learned a bit about how the life cycle of a Service can be affected by the o nst art Co mmand return value. We also learned about a convenient subclass alternative to Service called Int ent Service. Get cozy and comfortable creating and using Services in your own applications. Practice in the homework and see you next lesson! Copyright O'Reilly Media, Inc. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. See for more information.

84 Notifications Lesson Objectives In this lesson you will: create a Notification. start a Notification using an Intent. implement actions to be performed when an Intent is clicked. pro gramatically update a No tificatio n. pro gramatically remo ve a No tificatio n. In the previous lesson, we used Android Services to run tasks in the background. Running tasks in the background allows our application to do other work while waiting for the task to finish. When we want a running service to let us know when certain events occur, we can use Notifications. Notifications inform the user of events, as well as provide a means by which to launch Activities fro m applicatio ns. Creat and Update a Notification Let's get started. Create a new Android project using this criteria: Name the project No t if icat io ns. Use the package name co m.o reillyscho o l.andro id2.no t if icat io ns. Uncheck the Creat e cust o m launcher ico n box. Assign the Andro id2_lesso ns working set to the project. Open /res/layo ut /act ivit y_main.xml and make these changes: CODE TO TYPE: /res/layo ut/activity_main.xml <RelativeLinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingbottom="@dimen/activity_vertical_margin" android:paddingleft="@dimen/activity_horizontal_margin" android:paddingright="@dimen/activity_horizontal_margin" android:paddingtop="@dimen/activity_vertical_margin" android:gravity="center" android:orientation="vertical" tools:context=".mainactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> <Button android:id="@+id/button_notify_now" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="notify Now" /> </RelativeLinearLayout> Open the MainAct ivit y.java class in the /src folder and make these changes:

85 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.notifications; import android.app.activity; import android.app.notification; import android.app.notificationmanager; import android.app.pendingintent; import android.content.intent; import android.os.bundle; import android.support.v4.app.notificationcompat; import android.support.v4.app.taskstackbuilder; import android.view.menu; import android.view.view; public class MainActivity extends Activity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); { findviewbyid(r.id.button_notify_now).setonclicklistener(new View.OnClickListener() public void onclick(view v) { notifynow(); ); public void notifynow() { PendingIntent pi = TaskStackBuilder.create(this).addParentStack(MainActivity.class).addNextIntent(new Intent()).getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); Notification notification = new NotificationCompat.Builder(this).setSmallIcon(R.drawable.ic_launcher).setContentTitle("Notify now").setcontenttext("you've been notified!").setcontentintent(pi).build(); E); NotificationManager nm = (NotificationManager) getsystemservice(notification_servic nm.notify(0, notification); public boolean oncreateoptionsmenu(menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getmenuinflater().inflate(r.menu.main, menu); return true; Note Make sure you import the andro id.suppo rt.v4.app.t askst ackbuilder version of the T askst ackbuilder here, otherwise the application will crash on pre-ice Cream Sandwich devices. That's it! Now we're ready to test our first notification. Run the application and you'll see a simple layout with a single button. Click the button and you see a new notification at the top of the screen.

86 If you click and drag down from anywhere on that bar at the top of the screen you'll open up the notification drawer. Here you'll be able to see the actual notification we just created (instead of just the icon).

87 You're off to a great start. Before you go any further, let's look over this code and analyze it: OBSERVE: MainActivity.java public void notifynow() { PendingIntent pi = TaskStackBuilder.create(this).addParentStack(MainActivity.class).addNextIntent(new Intent()).getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); The no t if yno w() method is executed whenever we press the button in our view. The first object we create is a PendingInt ent. The PendingInt ent is used to instruct the Android System how to react when the user taps the Notification in the notification drawer. We construct PendingInt ent using the T askst ackbuilder class, which uses the builder pattern to help generate our PendingIntent. In order to use it in the notification, we must define a "parent stack" by calling addparent St ack(mainact ivit y.class) and the "next Intent" using addnext Int ent (new Int ent ()) with the builder, before generating the final PendingInt ent with get PendingInt ent (0, Pe ndingint e nt.flag_updat E_CURRENT ).

88 The addparent St ack method requires a parameter reference to an Act ivit y class or object, so here we can just use the keyword t his. If we were making a notification from within a Service, we would use a class reference instead, such as MainAct ivit y.class. The addnext Int ent method requires an Intent as its only parameter. This Int ent will start when the user taps the Notification. We send a new, plain Int ent object that doesn't launch anything when it's executed. The final method, get PendingInt ent, requires at least two parameters: an Integer request code parameter that can be attached to the intent, and an Integer flag that defines how the system should handle other PendingIntents with the same notification id that it receives from our app. The PendingInt ent.flag_updat E_CURRENT flag, retains any PendingIntent that matches o ur no tificatio n id and replaces its extra data with the data attached to this new PendingIntent. This notification id is referenced later, and is not related to the requestcode we just defined. OBSERVE: Notification notification = new NotificationCompat.Builder(this).setSmallIcon(R.drawable.ic_launcher).setContentTitle("Notify now").setcontenttext("you've been notified!").setcontentintent(pi).build(); After constructing PendingInt ent, we have to construct an actual No t if icat io n object. We use a builder to do this as well. We use the No t if icat io nco mpat.builder class reference so our notification works on pre-ice Cream Sandwich devices. We have four methods we must call here before building the final notification. First, set SmallIco n defines the icon that is used in the notification drawer. set Co nt ent T it le and set Co nt ent T ext define the title and content of the notification, respectively. Then we call set Co nt ent Int ent with the PendingInt ent we generated earlier to register the intent with o ur no tificatio n. The final metho d, build(), takes no parameters and simply finalizes the builder, returning the generated no tificatio n o bject. OBSERVE: E); NotificationManager nm = (NotificationManager) getsystemservice(notification_servic nm.notify(0, notification); Finally, we find a reference to the device's No t if icat io nmanager using the get Syst emservice() method, passing the NOT IFICAT ION_SERVICE constant, and casting the result to the pro per class t ype. We call the no t if y method, passing a no t if icat io n id which acts as the identifier for the notification unique within our app, and the generat ed no t if icat io n o bject as well. Responding To User Taps On A Notification Let's update our code so it actually does something when we tap our notification. As you might have guessed, we need to define a different "next Intent" for our PendingInt ent. Before we do that though, we'll make a new Activity that we want to have launched when a user taps the notification. Create a new class named Next Act ivit y, and make sure the package name is the same as the others (co m.o reillyscho o l.andro id2.no t if icat io ns). Also make sure to set the superclass as andro id.app.act ivit y. Now, make these changes to the class:

89 CODE TO TYPE: NextActivity.java package com.oreillyschool.android2.notifications; import android.app.activity; import android.app.notificationmanager; import android.os.bundle; public class NextActivity extends Activity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_next); E); NotificationManager nm = (NotificationManager) getsystemservice(notification_servic nm.cancel(0); We referenced a layout in this code that doesn't exist yet. Let's create it now. In the /res/layo ut folder, make a new XML layout file named act ivit y_next.xml and then make these changes: CODE TO TYPE: /res/layo utactivity_next.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="next Activity" /> </LinearLayout> Don't forget to add a reference to our new Activity to the Andro idmanif est.xml:

90 CODE TO TYPE: Andro idmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.oreillyschool.android2.notifications" android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="10" android:targetsdkversion="10" /> <application android:allowbackup="true" > <activity android:name="com.oreillyschool.android2.notifications.mainactivity" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <activity android:name=".nextactivity" android:label="nextactivity"/> </application> </manifest> Finally, make these changes to MainAct ivit y.java:

91 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.notifications; import android.app.activity; import android.app.notification; import android.app.notificationmanager; import android.app.pendingintent; import android.content.intent; import android.os.bundle; import android.support.v4.app.notificationcompat; import android.support.v4.app.taskstackbuilder; import android.view.view; public class MainActivity extends Activity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); { findviewbyid(r.id.button_notify_now).setonclicklistener(new View.OnClickListener() public void onclick(view v) { notifynow(); ); public void notifynow() { Intent resultintent = new Intent(this, NextActivity.class); PendingIntent pi = TaskStackBuilder.create(this).addParentStack(this).addNextIntent(new Intent()).addNextIntent(resultIntent).getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); Notification notification = new NotificationCompat.Builder(this).setSmallIcon(R.drawable.ic_launcher).setContentTitle("Notify now").setcontenttext("you've been notified!").setcontentintent(pi).build(); E); NotificationManager nm = (NotificationManager) getsystemservice(notification_servic nm.notify(0, notification); Run the program. The original button works creates a notification that looks exactly the same as before. However, clicking the notification now causes our new Activity to appear, and removes the notification from the notification drawer:

92 Let's go o ver o ur changes, starting with MainAct ivit y.java:

93 OBSERVE: MainActivity.java public void notifynow() { Intent resultintent = new Intent(this, NextActivity.class); PendingIntent pi = TaskStackBuilder.create(this).addParentStack(this).addNextIntent(resultIntent).getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); Notification notification = new NotificationCompat.Builder(this).setSmallIcon(R.drawable.ic_launcher).setContentTitle("Notify now").setcontenttext("you've been notified!").setcontentintent(pi).build(); ; NotificationManager nm = (NotificationManager) getsystemservice(notification_service) nm.notify(0, notification); Not much changed here. We replace the Intent that we passed to addnext Int ent () with the new result Int ent created at the beginning of this method. This new result Int ent references our newly created Next Act ivit y class. Now when the notification is clicked, the Next Act ivit y will be launched, just as if we had called startactivity(resultintent). OBSERVE: NextActivity.java package com.oreillyschool.android2.notifications; import android.app.activity; import android.app.notificationmanager; import android.os.bundle; public class NextActivity extends Activity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_next); E); NotificationManager nm = (NotificationManager) getsystemservice(notification_servic nm.cancel(0); Our new Activity isn't particulary unique, however we did add some code to make sure the notification gets removed from the notification drawer. We got a reference to the No t if icat io nmanager just like we did in MainActivity.java, but this time we call the cancel method with a value of 0. This removes any notification from the notification drawer created by our application with a notification id of 0. We hard-code our id in MainActivity.java so we can safely assume the same id is in this class, however, the id could also be passed through Intent extras if the precise id wasn't a hardco ded value. Updating A Notification Let's make one last change to our application to demonstrate how to update a Notification. Make these changes in Next Act ivit y.java:

94 CODE TO TYPE: NextActivity.java package com.oreillyschool.android2.notifications; import android.app.activity; import android.app.notification; import android.app.notificationmanager; import android.app.pendingintent; import android.content.intent; import android.os.bundle; import android.support.v4.app.notificationcompat; import android.support.v4.app.taskstackbuilder; public class NextActivity extends Activity { E); protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_next); Intent resultintent = new Intent(this, MainActivity.class); PendingIntent pi = TaskStackBuilder.create(this).addParentStack(this).addNextIntent(resultIntent).getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); Notification notification = new NotificationCompat.Builder(this).setSmallIcon(R.drawable.ic_launcher).setContentTitle("Next Notify").setContentText("You've been re-notified!").setcontentintent(pi).build(); NotificationManager nm = (NotificationManager) getsystemservice(notification_servic nm.notify(0, notification); nm.cancel(0); That's it! Save and run the application once more to see the results. After tapping the notification once you see that the the Next Act ivit y is created. Drag the notification drawer back down and tap the notification once more, and you see the MainAct ivit y again. This Activity is a new instance of MainAct ivit y, not the same one as before. You can test this by hitting the back button; you'll be taken back to the previous Next Act ivit y. Tap it once more to return back to the original Next Act ivit y.

95 Wrapping Up We've demonstrated lots of ways to interact with Notifications in Android. We learned how to create notifications using the T askst ackbuilder and No t if icat io nco mpat.builder builder classes, as well as display, update, and remove notifications using the No t if icat io nmanager. Notifications have undergone big changes in recent versions of the Android SDK and many more features are available to devices running those versions of Android. While we couldn't cover everything in one lesson, the skills you learned here will allow you to create consistent notifications on old and new Android devices. You'll get a chance to use your new skills in the homework. See you next lesson! Copyright O'Reilly Media, Inc. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. See for more information.

96 Content Providers Lesson Objectives In the lesson you will: create a Co ntentpro vider fro m scratch. create a database using SQLiteOpenHelper. access Co ntentpro vider data thro ugh the Co ntentreso lver. insert new data into a ContentProvider. display Co ntentpro vider results in a list using Curso r and Curso radapter. In this lesson, we'll cover an Android feature called Content Providers. Content Providers encapsulate an application's interaction with structured data stored on the device. They provide simple Create, Read, Update, and Delete (often called CRUD) metho ds, access to external Applicatio n sharing, and security. A Co ntent Pro vider is typically backed by a SQLite database instance, but the actual implementatio n is co ntro lled by the develo per. Creating and Using a Content Provider We have a lot of code to write before we'll be able to test this application, so let's get started. Create a new Android pro ject with these criteria: Name the project Co nt ent Pro viders. Use the package name co m.o reillyscho o l.andro id2.co nt ent pro viders. Assign the Andro id2_lesso ns working set to the project. Uncheck the Creat e cust o m launcher ico n box. Now let's work with our views. Open the act ivit y_main.xml layout file and make these changes:

97 CODE TO TYPE: /res/layo ut/activity_main.xml <RelativeLinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".mainactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="data:" /> <EditText android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:hint="label" android:imeoptions="actionnext" android:inputtype="text" /> <EditText android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="value" android:imeoptions="actiondone" android:inputtype="number" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="add" /> </LinearLayout> <ListView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:choicemode="singlechoice" /> </RelativeLinearLayout> Next, create a new layout xml that we'll use for our list items. Create a new Android Layout XML file in the /res/layo ut folder named it em_dat a.xml, then make these changes.

98 CODE TO TYPE: item_data.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parentwrap_content" android:orientation="verticalhorizontal" > <CheckedTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:checkmark="?android:attr/listchoiceindicatorsingle" /> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_margin="5dp" android:layout_weight="1" /> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_margin="5dp" android:layout_weight="2" /> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginright="5dp" android:layout_weight="1" /> </LinearLayout> Next, we'll define a small data model for our Content Provider. In the co m.o reillyscho o l.andro id2.co nt ent pro viders package, create a new Java class named MyDat aco nt ract s, and make the following changes to the class:

99 CODE TO TYPE: MyDataCo ntracts.java package com.oreillyschool.android2.contentproviders; import android.content.contentresolver; import android.net.uri; import android.provider.basecolumns; public final class MyDataContracts { public static final String AUTHORITY = "com.oreillyschool.android2.contentproviders.provider"; public static final Uri BASE_CONTENT_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); public static final class DataContract implements BaseColumns { /** * The DataContract table name and MIME type vendor name */ public static final String NAME = "data"; /** * The row name for the label field */ public static final String LABEL = "label"; /** * The row name for the value field */ public static final String VALUE = "value"; public static final Uri DATA_CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(NAME).build(); Now create the actual Content Provider, and a database helper. In the same package, create another Java class named MyDat aco nt ent Pro vider and make the following changes:

100 CODE TO TYPE: MyDataCo ntentpro vider.java package com.oreillyschool.android2.contentproviders; import android.content.contentprovider; import android.content.contentresolver; import android.content.contenturis; import android.content.contentvalues; import android.content.context; import android.content.urimatcher; import android.database.cursor; import android.database.sqlite.sqlitedatabase; import android.database.sqlite.sqliteopenhelper; import android.net.uri; import android.provider.basecolumns; import android.text.textutils; import com.oreillyschool.android2.contentproviders.mydatacontracts.datacontract; public class MyDataContentProvider extends ContentProvider { private static final int B_DATA = 100; private static final int B_DATA_ID = 101; private static final String TYPE_DIR = "vnd.android.cursor.dir/vnd.com.oreillyschoo l.android2.contentproviders.provider.%s"; private static final String TYPE_ITEM = "vnd.android.cursor.item/vnd.com.oreillysch ool.android2.contentproviders.provider.%s"; ); private static final UriMatcher surimatcher; static { surimatcher = new UriMatcher(UriMatcher.NO_MATCH); surimatcher.adduri(mydatacontracts.authority, DataContract.NAME, B_DATA); surimatcher.adduri(mydatacontracts.authority, DataContract.NAME+"/#", B_DATA_ID private MySQLHelper mdbhelper; public boolean oncreate() { mdbhelper = new MySQLHelper(getContext()); return true; public String gettype(uri uri) { int uricode = surimatcher.match(uri); switch (uricode) { case B_DATA: return String.format(TYPE_DIR, DataContract.NAME); case B_DATA_ID: return String.format(TYPE_ITEM, DataContract.NAME); default: throw new UnsupportedOperationException("Uri Not Supported"); public Uri insert(uri uri, ContentValues values) { SQLiteDatabase db = mdbhelper.getwritabledatabase(); String tablename = mdbhelper.gettablenameforcode(surimatcher.match(uri)); if (!TextUtils.isEmpty(tableName)) { long id = db.insert(tablename, null, values); if (id > -1) { ContentResolver resolver = getcontext().getcontentresolver();

101 resolver.notifychange(uri, null); return ContentUris.withAppendedId(uri, id); else { return null; public Cursor query(uri uri, String[] projection, String selection, String[] select ionargs, String sortorder) { SQLiteDatabase db = mdbhelper.getreadabledatabase(); String tablename = mdbhelper.gettablenameforcode(surimatcher.match(uri)); if (!TextUtils.isEmpty(tableName)) { Cursor cursor = db.query(tablename, projection, selection, selectionargs, n ull, null, sortorder); cursor.setnotificationuri(getcontext().getcontentresolver(), uri); return cursor; else { return null; public int update(uri uri, ContentValues values, String selection, String[] selecti onargs) { SQLiteDatabase db = mdbhelper.getwritabledatabase(); String tablename = mdbhelper.gettablenameforcode(surimatcher.match(uri)); if (!TextUtils.isEmpty(tableName)) return db.update(tablename, values, selection, selectionargs); else return 0; public int delete(uri uri, String selection, String[] selectionargs) { SQLiteDatabase db = mdbhelper.getwritabledatabase(); String tablename = mdbhelper.gettablenameforcode(surimatcher.match(uri)); if (!TextUtils.isEmpty(tableName)) return db.delete(tablename, selection, selectionargs); else return 0; private class MySQLHelper extends SQLiteOpenHelper { private static final String DB_NAME = "MySqliteDB"; private static final int DB_VERSION = 1; private static final String CREATE_TABLE_DATA = "CREATE TABLE " + DataContract. NAME + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + DataContract.LABEL + " STRING, " + DataContract.VALUE + " INTEGER)"; public MySQLHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); public void oncreate(sqlitedatabase db) { db.execsql(create_table_data);

102 public void onupgrade(sqlitedatabase db, int oldversion, int newversion) { db.execsql("drop TABLE IF EXISTS " + DataContract.NAME); oncreate(db); public String gettablenameforcode(int uricode) { switch (uricode) { case B_DATA: case B_DATA_ID: return DataContract.NAME; default: return null; Now, modify MainAct ivit y.java to tie everything together. Make these changes in the class:

103 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.contentproviders; import android.app.activity; import android.content.contentresolver; import android.content.contentvalues; import android.database.cursor; import android.os.bundle; import android.support.v4.app.fragmentactivity; import android.support.v4.app.loadermanager; import android.support.v4.content.cursorloader; import android.support.v4.content.loader; import android.support.v4.widget.simplecursoradapter; import android.text.textutils; import android.view.keyevent; import android.view.menu; import android.view.view; import android.view.inputmethod.editorinfo; import android.widget.button; import android.widget.edittext; import android.widget.listview; import android.widget.textview; import android.widget.textview.oneditoractionlistener; import com.oreillyschool.android2.contentproviders.mydatacontracts.datacontract; public class MainActivity extends FragmentActivity { private EditText mdatalabeledit; private EditText mdatavalueedit; private Button maddbutton; private ListView mlistview; private SimpleCursorAdapter madapter; private String[] mprojection; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mdatalabeledit = (EditText) findviewbyid(r.id.data_label_edit); mdatavalueedit = (EditText) findviewbyid(r.id.data_value_edit); maddbutton = (Button) findviewbyid(r.id.add_data_button); mlistview = (ListView) findviewbyid(android.r.id.list); mprojection = new String[]{DataContract._ID, DataContract.LABEL, DataContract.V ALUE; int[] viewids = {R.id.id_label_text, R.id.data_label_text, R.id.value_label_tex t; madapter = new SimpleCursorAdapter(this, R.layout.item_data, null, mprojection, viewids, 0); mlistview.setadapter(madapter); LoaderManager lm = getsupportloadermanager(); lm.initloader(0, null, mloadercallbacks); maddbutton.setonclicklistener(new View.OnClickListener() { public void onclick(view v) { addnewdata(); ); mdatavalueedit.setoneditoractionlistener(new OnEditorActionListener() { public boolean oneditoraction(textview v, int actionid, KeyEvent event) { if (actionid == EditorInfo.IME_ACTION_DONE) {

104 ); addnewdata(); return true; return false; private void addnewdata() { ContentValues values = new ContentValues(); String label = mdatalabeledit.gettext().tostring(); String value = mdatavalueedit.gettext().tostring(); if (!TextUtils.isEmpty(label) &&!TextUtils.isEmpty(value)) { values.put(datacontract.label, label); values.put(datacontract.value, value); ContentResolver resolver = getcontentresolver(); resolver.insert(datacontract.data_content_uri, values); // Clear the old data mdatalabeledit.gettext().clear(); mdatavalueedit.gettext().clear(); mdatalabeledit.requestfocus(); private LoaderManager.LoaderCallbacks<Cursor> mloadercallbacks = new LoaderManager. LoaderCallbacks<Cursor>() { public Loader<Cursor> oncreateloader(int id, Bundle args) { return new CursorLoader(MainActivity.this, DataContract.DATA_CONTENT_URI, mprojection, null, null, DataContract.VALUE + " ASC"); public void onloaderreset(loader<cursor> loader) { madapter.swapcursor(null); ; public void onloadfinished(loader<cursor> loader, Cursor cursor) { madapter.swapcursor(cursor); public boolean oncreateoptionsmenu(menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getmenuinflater().inflate(r.menu.main, menu); return true; Before we can test the App we have to make one last change. Just like Activities, ContentProviders need to be defined in the manifest. Open the project's Andro idmanif est.xml file and make these changes:

105 CODE TO TYPE: Andro idmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.oreillyschool.android2.contentproviders" android:versioncode="1" android:versionname="1.0" > ider" <uses-sdk android:minsdkversion="10" android:targetsdkversion="10" /> <application android:allowbackup="true" > <activity android:name="com.oreillyschool.android2.contentproviders.mainactivity" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <provider android:name="com.oreillyschool.android2.contentproviders.mydatacontentprov android:authorities="com.oreillyschool.android2.contentproviders.provider" android:exported="false" /> </application> </manifest> Now we are able to test the application. Launch the application in the emulator:

106 Type a text label and a numeric value in the fields at the top and click Add to add your entries to the list. They appear immediately in the list. Add a few more entries with different values to see how the list is sorted on the Values column:

107 Examining the Code We've written lots of new code to go over. Let's start with the "Contracts" class, MyDat aco nt ract s:

108 OBSERVE: MyDataCo ntracts.java package com.oreillyschool.android2.contentproviders; import android.content.contentresolver; import android.net.uri; import android.provider.basecolumns; public final class MyDataContracts { public static final String AUTHORITY = "com.oreillyschool.android2.contentpr oviders.provider"; public static final Uri BASE_CONTENT_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); public static final class DataContract implements BaseColumns { /** * The DataContract table name and MIME type vendor name */ public static final String NAME = "data"; /** * The row name for the label field */ public static final String LABEL = "label"; /** * The row name for the value field */ public static final String VALUE = "value"; public static final Uri DATA_CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(NAME).build(); When implementing a Content Provider, it can be useful to create a "Contracts" type class that can group together relevant metadata constants that interact with your Content Provider. Google uses Contract classes for its shared Content Providers, such as the Contacts Content Provider. The AUT HORIT Y variable is the symbolic name of the provider. It's used to register the provider with the Android OS. It is also used as the base authority parameter of all Query URIs that the provider supports. BASE_CONT ENT _URI is used (as the name implies) as the base part of all URIs that the provider supports. We use a builder Uri.Builder to help us construct all URIs in the application. The.scheme(Co nt ent Reso lver.scheme_cont ENT ) is required for Content Providers. The variable passed to the.aut ho rit y(aut HORIT Y) part must match the authority value defined in the manifest. These are all used by the OS to match Content requests with your Content Provider. We define an inner-class, Dat aco nt ract, for our single "Data" table. It's common practice to have an innerclass defined in the Contract class for each table supported by the provider. Here we have a constant defined for the table name, NAME, and for each row of our Data table: LABEL and VALUE. Our contract class also implements the BaseCo lumns interface, which has no methods, but does have a constant _ID that maps to the "id" primary key column of our table required for each table in SQLite. We also define the constant DAT A_CONT ENT _URI URI object that will map to our "data" table. It builds upon the BASE_CONT ENT _URI constant and appends its unique table name as a URI path. Now let's go over the actual Content Provider itself, MyDat aco nt ent Pro vider. We'll focus on a small piece at a time, starting with the static variables defined at the top of the class:

109 OBSERVE: MyDataCo ntentpro vider.java - Part 1 public class MyDataContentProvider extends ContentProvider { private static final int B_DATA = 100; private static final int B_DATA_ID = 101; private static final String TYPE_DIR = "vnd.android.cursor.dir/vnd.com.oreil lyschool.android2.contentproviders.provider.%s"; private static final String TYPE_ITEM = "vnd.android.cursor.item/vnd.com.ore illyschool.android2.contentproviders.provider.%s"; private static final UriMatcher surimatcher; static { surimatcher = new UriMatcher(UriMatcher.NO_MATCH); surimatcher.adduri(mydatacontracts.authority, DataContract.NAME, B_DATA) ; surimatcher.adduri(mydatacontracts.authority, DataContract.NAME+"/#", B_ DATA_ID);... Here we define a few more metadata constants to help us match URIs to the appropriate Contract properties. The first two constants, B_DAT A and B_DAT A_ID, are used for queries to our "Data" table; the first is used to query a list of results, while the second is used to query a single result. The T YPE_DIR and T YPE_IT EM constants are used to help define the MIME type of the data returned for a specific URI. Like the first two co nstants, these are used to distinguish between queries fo r multiple ro ws and queries for a single row, respectively. In particular, the first half of both of these strings, vnd.andro id.curso r.dir and vnd.andro id.curso r.it e m, are the Andro id-specific MIME types required in Android in order to define a multi-row query and single row query. The last half of the string (everything after the "/" character) is called the subtype o f the MIME type. Usually subtypes are defined using the vendo r prefix "vnd," the authority of the content provider, and the table name. Note While it is common practice to define Subtypes as we have in this application, it isn't required. Fo r example, many Andro id built-in applicatio n Co ntent Pro viders use simplified subtypes. The Contacts Content Provider uses a subtype of just phone_v2, which creates the entire MIME type for a single row query vnd.andro id.curso r.it em/pho ne_v2. Typically, MIME types are defined as part of the Contract class, but this isn't required. We define these here to relate them to the get T ype() method in a more straightforward way. The last constant we define is the UriMat cher. It's the utility that will tie most of the other constants together. To use a UriMatcher, you need to register each of your application's supported URIs with the id constants. Because the matcher is a static variable, we use a static blo ck to register each suppo rted URI. The matcher doesn't take the full URI object directly; instead you register the URI authority and table name parts. So in the static blo ck first line, surimat che r = ne w UriMat che r(urimat che r.no_mat CH); we create the matcher that gives a default match value of NO_MAT CH. Next we call the adduri() method on the matcher to register the third parameter, our first id constant B_DAT A, with our contract's authority, MyDat aco nt ract s.aut HORIT Y, and the table name, Dat aco nt ract.name. Then we call adduri() again to register the single row query id B_DAT A_ID. We use the same authority as we would for all URIs we register with the matcher. Fo r the seco nd parameter, we pass the table name again because it's the same table. We also append the String "/#" to the table, which is used as a regular expression mask for all URIs with this table name and an appended numeric id. Typically, this id is matched with a specific row id.

110 OBSERVE: MyDataCo ntentpro vider.java - Part 2... private MySQLHelper mdbhelper; public boolean oncreate() { mdbhelper = new MySQLHelper(getContext()); return true; public String gettype(uri uri) { int uricode = surimatcher.match(uri); switch (uricode) { case B_DATA: return String.format(TYPE_DIR, DataContract.); case B_DATA_ID: return String.format(TYPE_ITEM, DataContract.NAME); default: throw new UnsupportedOperationException();... Next, we have the only member variable, a MySQLHelper class object which we define below as an inner class that extends the Andro id class SQLiteOpenHelper. The SQLiteOpenHelper class is used to interface with a SQLite database. You might remember this class from the first Android course. The o ncreat e() method is used only to instantiate our SQLite helper. The get T ype() method uses the matcher co nstant to match URIs to the MIME type co nstants we defined earlier. Since o ur MIME types will all look nearly the same, we use our St ring.f o rmat () routine to replace the %s part of the appropriate MIME type constant with the actual table name. This allows us to reuse the MIME type constants instead of having to type the full MIME type String fo r each table query type. The matcher returns our URI constant int values, which allows us to use a swit ch/case block to format and return the proper MIME type for the URI id:

111 OBSERVE: MyDataCo ntentpro vider.java - Part 3 public Uri insert(uri uri, ContentValues values) { SQLiteDatabase db = mdbhelper.getwritabledatabase(); String tablename = mdbhelper.gettablenameforcode(surimatcher.match(uri)); if (!TextUtils.isEmpty(tableName)) { long id = db.insert(tablename, null, values); if (id > -1) { ContentResolver resolver = getcontext().getcontentresolver(); resolver.notifychange(uri, null); return ContentUris.withAppendedId(uri, id); else { return null; public Cursor query(uri uri, String[] projection, String selection, String[] sel ectionargs, String sortorder) { SQLiteDatabase db = mdbhelper.getreadabledatabase(); String tablename = mdbhelper.gettablenameforcode(surimatcher.match(uri)); if (!TextUtils.isEmpty(tableName)) { Cursor cursor = db.query(tablename, projection, selection, selectionargs, null, null, sortorder); cursor.setnotificationuri(getcontext().getcontentresolver(), uri); return cursor; else { return null; public int update(uri uri, ContentValues values, String selection, String[] sele ctionargs) { SQLiteDatabase db = mdbhelper.getwritabledatabase(); String tablename = mdbhelper.gettablenameforcode(surimatcher.match(uri)); if (!TextUtils.isEmpty(tableName)) return db.update(tablename, values, selection, selectionargs); else return 0; public int delete(uri uri, String selection, String[] selectionargs) { SQLiteDatabase db = mdbhelper.getwritabledatabase(); String tablename = mdbhelper.gettablenameforcode(surimatcher.match(uri)); if (!TextUtils.isEmpty(tableName)) return db.delete(tablename, selection, selectionargs); else return 0; Continuing with the member methods, we have the four "CRUD" methods: insert (), query(), updat e() and delet e(). All four of these use the SQLit eopenhelper to perform its action on the database. The query() method gets a read-only version of the database, while the other three methods use a read-write version of the database because they are actually making changes to the data. All fo ur use the helper metho d get T ablenamefo rco de(), that we added on the DB helper class. get T ablenamefo rco de() takes the URI id found by the matcher and gives the appropriate table name. In addition, each method performs some error checking to make sure that we have a valid table name, then performs its respective action.

112 After the insert method performs the insert, a Co nt ent Reso lver object dispatches a notification that we've changed the data, calling reso lver.no t if ychange(uri, null);. The first parameter is the URI associated with the data we just inserted. The second parameter is an object of type Co nt ent Observer. We pass null for the Co nt ent Observer to notify all observers listening for changes on this URI. If you only wanted to notify a single observer (and you have a reference to that observer) you would pass the method that observer here. The insert () method has a return type of URI as well, which expects to have the lo ng id from the insert appended to the original URI. The Co nt ent Uris helper class has a convenience method that allows you to append our id to the original URI. In the query() method, after get t ing t he Curso r o bject f ro m o ur query, we register a notification URI with the cursor with the line curso r.set No t if icat io nuri(get Co nt ext ().get Co nt ent Reso lver(), uri);. This allows any future Curso radapt ers (like the adapter in MainAct ivit y) to register a Co nt ent Observer for the Cursor's URI and update the list with the new data automatically. The updat e() and delet e() methods are nearly identical. They perform the update or the delete on the table and immediately return the result: OBSERVE: MyDataCo ntentpro vider.java - Part 4... private class MySQLHelper extends SQLiteOpenHelper { private static final String DB_NAME = "MySqliteDB"; private static final int DB_VERSION = 1; private static final String CREATE_TABLE_DATA = "CREATE TABLE " + DataContra ct.name + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + DataContract.LABEL + " STRING, " + DataContract.VALUE + " INTEGER)"; public MySQLHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); public void oncreate(sqlitedatabase db) { db.execsql(create_table_data); public void onupgrade(sqlitedatabase db, int oldversion, int newversion) { db.execsql("drop TABLE IF EXISTS " + DataContract.NAME); oncreate(db); public String gettablenameforcode(int uricode) { switch (uricode) { case B_DATA: case B_DATA_ID: return DataContract.NAME; default: return null; At the end of our Content Provider, we have our inner class MySQLHelper definition. We def ine t he creat e st at ement s for each table in the database (just one, in our case). We take advantage of our constants here in the Contract to avoid typos. We use the NAME constant with the "drop table" call that we use in the o nupgrade() method. We have also added the helper method to find the appropriate table for a URI id value. Again, we use a swit ch/case block here to determine which table name to return for the urico de value. Now that we've finished with the Content Provider, let's look at the MainActivity class, starting with the o ncreat e() method:

113 OBSERVE: MainActivity.java o ncreate() protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mdatalabeledit = (EditText) findviewbyid(r.id.data_label_edit); mdatavalueedit = (EditText) findviewbyid(r.id.data_value_edit); maddbutton = (Button) findviewbyid(r.id.add_data_button); mlistview = (ListView) findviewbyid(android.r.id.list); mprojection = new String[]{DataContract._ID, DataContract.LABEL, DataContrac t.value; int[] viewids = {R.id.id_label_text, R.id.data_label_text, R.id.value_label_ text; madapter = new SimpleCursorAdapter(this, R.layout.item_data, null, mprojecti on, viewids, 0); mlistview.setadapter(madapter); LoaderManager lm = getsupportloadermanager(); lm.initloader(0, null, mloadercallbacks); maddbutton.setonclicklistener(new View.OnClickListener() { public void onclick(view v) { addnewdata(); ); { mdatavalueedit.setoneditoractionlistener(new OnEditorActionListener() { public boolean oneditoraction(textview v, int actionid, KeyEvent event) ); if (actionid == EditorInfo.IME_ACTION_DONE) { addnewdata(); return true; return false; The first part of this method is fairly standard. We set the layout and find all the views based on their ids. Then we do some work to prepare our Curso radapt er. We're actually using an extension of Curso radapt er called SimpleCurso radapt er, which will map data column values to views automatically, based on the data column name and view id, respectively. First, we define our two arrays: an array of each co lumn in o ur t able t hat we are displaying (all o f t hem), and an array of the ids f o r t he views in o ur it em_dat a.xml layo ut where we want these values displayed. Finally, we creat e o ur adapt er, passing the Co nt ext t his, the list item layout R.layo ut.it em_dat a, null for our Cursor since we don't have one yet, our two arrays mpro ject io n and viewids, and 0 for the flag parameter (since we don't need to use the adapter flags). Then, as with all list adapters, we call mlist View.set Adapt er(madapt er); to set this adapter o n the list view. We are using a Curso rlo ader to request our data, so we use the Lo adermanager to initialize our loader, giving the init Lo ader() met ho d o ur mlo adercallbacks variable for the Lo ade rmanage r.lo ade rcallbacks parameter. Finally, we register a callback metho d fo r the "Add" butto n:

114 OBSERVE: MainActivity.java addnewdata() private void addnewdata() { ContentValues values = new ContentValues(); String label = mdatalabeledit.gettext().tostring(); String value = mdatavalueedit.gettext().tostring(); if (!TextUtils.isEmpty(label) &&!TextUtils.isEmpty(value)) { values.put(datacontract.label, label); values.put(datacontract.value, value); ContentResolver resolver = getcontentresolver(); resolver.insert(datacontract.data_content_uri, values); // Clear the old data mdatalabeledit.gettext().clear(); mdatavalueedit.gettext().clear(); mdatalabeledit.requestfocus(); In the "Add" button callback method addnewdat a(), we generat e a Co nt ent Values o bject that will be supplied to the Content Provider. A Co nt ent Values object maps table column names with data values, using a key/value pattern similar to a HashMap. We register the values from the two Edit T ext views with our Co nt ent Values object by calling values.put (Dat aco nt ract.label, label) and values.put (Dat aco nt ract.value, value). Then, instead of getting a reference to our Content Provider directly, we use the Co nt ent Reso lver class, calling the insert () method with our table's URI Dat aco nt ract.dat A_CONT ENT _URI, and our newly built Co nt ent Values. The ContentResolver will find a Content Provider that is registered with the Android system to support this URI (which happens to be our Content Provider) and in turn, call the insert () method on the Content Provider with these same parameters. After performing the insert, we do a lit t le bit o f clean up. We remove the text from the Edit T ext views calling get T ext ().clear() on each. Then we request focus back on the first Edit T ext view, mdat alabeledit.request Fo cus(), to make it easier for the user to add new data to the list: OBSERVE: MainActivity.java mlo adercallbacks private LoaderManager.LoaderCallbacks<Cursor> mloadercallbacks = new LoaderManag er.loadercallbacks<cursor>() { public Loader<Cursor> oncreateloader(int id, Bundle args) { return new CursorLoader(MainActivity.this, DataContract.DATA_CONTENT_URI, mprojection, null, null, DataContract.VALUE + " ASC"); public void onloaderreset(loader<cursor> loader) { madapter.swapcursor(null); ; public void onloadfinished(loader<cursor> loader, Cursor cursor) { madapter.swapcursor(cursor); In our Lo adercallbacks o ncreat elo ader method, we creat e a Curso rlo ader t o request o ur dat a f ro m t he Co nt ent Pro vider. The Cursor Loader will use the Co nt ent Reso lver just like we did in the addnewdat a() method; however, using a Curso rlo ader allows us to perform this query asynchronously, and also ensures that we can re-register o ur Curso r data with the Curso radapt e r efficiently after a configuration change such as a rotation of the device. A Curso rlo ader constructor takes parameters similar to those that a call to Co nt ent Pro vider.query() method would require: a co nt ext (which is used by the loader to find the Co nt ent Reso lver), the URI f o r t he query, a St ring array "pro ject io n" o f which co lumns in t he t able we want t o receive result s, a St ring select io n, a St ring array o f select io n args, and a St ring def ining t he so rt o rder f o r t he dat a. For the projection, we reuse the mpro ject io n variable we defined when we built our adapter. We want all rows of our table, so we pass null for the select io n and select io n args. For our sort, we pass Dat aco nt ract.value + " ASC" to have the data sorted on the VALUE column in ascending order.

115 If the o nlo aderreset method has been called, we have no more data for the list, so we just pass null to the adapter. Finally, in the o nlo adfinished() method, we supply the Curso radapt er with the Curso r result from the query: OBSERVE: Andro idmanifest.xml Pro vider tag... <application android:allowbackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/apptheme" >... <provider android:name="com.oreillyschool.android2.contentproviders.mydatacont entprovider" android:authorities="com.oreillyschool.android2.contentproviders.pro vider" android:exported="false" android:label="@string/app_name" / > </application> Finally, in order for the Android system to know about our Content Provider, we have to define it in the Android Manifest. Just like Activities and Services, Pro vider def init io ns are nested inside of the applicat io n tag. The name paramet er must be the full package and class name of the provider. The aut ho rit y value must match the AUTHORITY constant that we use in our Contract class to generate each content URI used to access the provider. The expo rt ed pro pert y is used to allow other applications to access your Content Provider. The label pro pert y should be a user-readable string naming your provider, so we re-use the applicatio n name string reso urce. Wrapping Up Content Providers can seem daunting at first. They tend to require a lot of setup code just to get them working. However, after all the configuration is in place, it's fairly straightforward to add support for new tables and queries. In the end, they are a convenient way to abstract the data storage (and access logic) away from your view logic. Now you know how to define and register a new Content Provider with the Android OS. You've learned how to back a Content Provider with a SQLite database, and implement each of the "CRUD" methods with the database. You've also learned how to access the Content Provider using a Content Resolver and tie the resulting data to your views with Cursors. These skills will help you create powerful Android applications with structured data. Nice work! See you after you get done with the homework! Copyright O'Reilly Media, Inc. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. See for more information.

116 Camera Basics: Using the Built-in Camera Application Lesson Objectives In this lesson you will: create an Intent to launch the built-in camera applicatio n. load a Bitmap returned as a result from the built-in camera application. save images from the built-in camera to external storage. Starting the Built-in Camera Using an Intent Like many Android's features, there are a couple of different ways to access an Android device's camera. The easiest way is to hand off the camera utilization to the device's default camera application. This doesn't allow for much customization of the photo-taking process, but it's relatively simple to implement. We'll cover the specifics of thi process in this lesson. Create a new Android project with this criteria: Name the project CameraBasics. Use the package name co m.o reillyscho o l.andro id2.camerabasics. Uncheck the Creat e cust o m launcher ico n box. Assign the Andro id2_lesso ns working set to the project. Now update some strings in st rings.xml. Open it up and make these changes: CODE TO TYPE: /res/values/strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">camera Basics</string> <string name="action_settings">settings</string> <string name="hello_world">hello World, MainActivity!</string> <string name="capture_image">snap it!</string> </resources> Open act ivit y_main.xml and make these changes:

117 CODE TO TYPE: activity_main.xml <RelativeLinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical" tools:context=".mainactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <ImageView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <Button android:layout_width="150dp" android:layout_height="wrap_content" /> </RelativeLinearLayout> Next, open Andro idmanif est.xml and make these changes: CODE TO TYPE: Andro idmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.oreillyschool.android2.camerabasics" android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="10" android:targetsdkversion="10" /> <application android:allowbackup="true" > <activity android:name="com.oreillyschool.android2.camerabasics.mainactivity" android:screenorientation="landscape" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> Finally, o pen MainAct ivit y.java and make these changes:

118

119 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.camerabasics; import android.app.activity; import android.content.intent; import android.graphics.bitmap; import android.os.bundle; import android.provider.mediastore; import android.view.menu; import android.view.view; import android.view.view.onclicklistener; import android.widget.imageview; public class MainActivity extends Activity { private static final int TAKE_PICTURE_REQUEST_B = 100; private ImageView mcameraimageview; private Bitmap mcamerabitmap; private OnClickListener mcaptureimagebuttonclicklistener = new OnClickListener() { public void onclick(view v) { startimagecapture(); ; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mcameraimageview = (ImageView) findviewbyid(r.id.camera_image_view); findviewbyid(r.id.capture_image_button).setonclicklistener(mcaptureimagebuttonc licklistener); protected void onactivityresult(int requestcode, int resultcode, Intent data) { if (requestcode == TAKE_PICTURE_REQUEST_B) { if (resultcode == RESULT_OK) { // Recycle the previous bitmap. if (mcamerabitmap!= null) { mcamerabitmap.recycle(); mcamerabitmap = null; Bundle extras = data.getextras(); mcamerabitmap = (Bitmap) extras.get("data"); mcameraimageview.setimagebitmap(mcamerabitmap); else { mcamerabitmap = null; private void startimagecapture() { startactivityforresult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE), TAKE_PICTUR E_REQUEST_B); public boolean oncreateoptionsmenu(menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getmenuinflater().inflate(r.menu.main, menu); return true;

120 Save all the modified files and run the application. When the application starts up, you see a mostly blank screen with a button at the bottom: Note Remember to use [Ct rl+f11] or [Ct rl+f12] to rotate the emulator. Click the Snap it! butto n. The built-in camera applicatio n will start up: There's no actual physical camera when you use the emulator; the preview area of the camera application is completely white. On an actual device, you'd see a live preview. Go ahead and click the camera shutter butto n to take a picture. Yo u hear a shutter click so und, and the interface changes to display Cancel, Ret ake, and OK buttons. Click OK. Now you see the main screen again with a placeho lder picture. This emulato r returns the placeho lder as the taken "picture" (even tho ugh the built-in applicatio n sho wed o nly white space).

121 It may be a difficult to identify because the emulato r uses white space and a placeho lder, but the applicatio n successfully called the built-in camera applicatio n and received the picture taken. Let's take a lo o k at ho w that happened: OBSERVE: activity_main.xml <LinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingbottom="@dimen/activity_vertical_margin" android:paddingleft="@dimen/activity_horizontal_margin" android:paddingright="@dimen/activity_horizontal_margin" android:paddingtop="@dimen/activity_vertical_margin" android:gravity="center_horizontal" android:orientation="vertical" tools:context=".mainactivity" > <ImageView android:id="@+id/camera_image_view" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <Button android:id="@+id/capture_image_button" android:layout_width="150dp" android:layout_height="wrap_content" android:text="@string/capture_image" /> </LinearLayout> In our layout, we add a <But t o n> to launch the built-in camera application and an ImageView to hold the image returned by that applicatio n:

122 OBSERVE: Andro idmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.oreillyschool.android2.camerabasics" android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="10" android:targetsdkversion="10" /> <application android:allowbackup="true" > <activity android:name=".mainactivity" android:screenorientation="landscape" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> In the manifest, we add andro id:screenorient at io n="landscape" to the <application> node's attributes to force our application to be landscape-oriented in order to match the default orientation of typical camera applications: OBSERVE: MainActivity.java... protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mcameraimageview = (ImageView) findviewbyid(r.id.camera_image_view); findviewbyid(r.id.capture_image_button).setonclicklistener(mcaptureimagebuttonclick Listener); The bulk of the work is done in MainActivity.java. In the o ncreat e method, we set up t he UI and grab a ref erence t o t he ImageView t o ho ld t he camera image. We also wire up a View.OnClickList ener instance to our Snap it! button. When the user clicks this button, the listener calls the st art ImageCapt ure method. The st art ImageCapt ure method starts the built-in camera application by calling Act ivit y.st art Act ivit yfo rresult and passing it a new Int ent. The Int ent 's action is MediaSt o re.act ION_IMAGE_CAPT URE, which specifically instructs the Int ent to use the device's default application to capture images (that is, the default Camera app). We also pass a custom request code that we defined earlier to st art Act ivit yfo rresult so that we can handle the image returned by the camera application in o nact ivit yresult.

123 OBSERVE:... protected void onactivityresult(int requestcode, int resultcode, Intent data) { if (requestcode == TAKE_PICTURE_REQUEST_B) { if (resultcode == RESULT_OK) { // Recycle the previous bitmap. if (mcamerabitmap!= null) { mcamerabitmap.recycle(); mcamerabitmap = null; Bundle extras = data.getextras(); mcamerabitmap = (Bitmap) extras.get("data"); mcameraimageview.setimagebitmap(mcamerabitmap); else { mcamerabitmap = null;... In o nact ivit yresult, first we check t o see if we have an exist ing image f ro m t he camera, and if so, we recycle t hat image, calling mcamerabit map.recycle(). It is important to recycle bitmaps in Android correctly when you're finished using them. This frees up the application's reserved heap space for images, which can be limited. Next we pull o ut t he dat a sent back f ro m t he camera applicat io n and place t his Bit map int o t he ImageView o f o ur applicat io n. The Bitmap returned by the built-in camera application is stored inside the Int ent passed to o nact ivit yresult, in its ext ras bundle, under the key "dat a". The Android Developer Documentation on image capture intents lists an extra Uri that may be sent with the Int ent under the key MediaSt o re.ext RA_OUT PUT. The Uri is an optional parameter that allows you to specify a path and filename for the captured image. In general, you can do this to save the the image data to a file. We didn't take advantage of that capability here though because the emulator doesn't actually send the extra. In fact, if we were to use it when the camera application doesn't support it, the Int ent returned in o nact ivit yresult would be null. The documention strongly suggests that you use this extra. While it does not work well in the emulator, when you use an Int ent to open the built-in camera application, it's good practice to utilize the MediaSt o re.ext RA_OUT PUT and test on an actual device. Saving Image to External Storage While saving an image to external storage is not tied specifically into using a camera application (built-in or custom), it's a common task when working with cameras and images, so let's add this feature to our application. First, make these changes to st rings.xm l: CODE TO TYPE: strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">camera Basics</string> <string name="action_settings">settings</string> <string name="capture_image">snap it!</string> <string name="save_image">save Picture</string> </resources> Make these changes to act ivit y_main.xml:

124 CODE TO TYPE: activity_main.xml <LinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical" tools:context=".mainactivity" > <ImageView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="10dp" android:orientation="horizontal" android:gravity="center" > <Button android:layout_width="150dp" android:layout_height="wrap_content" /> <Button android:layout_width="150dp" android:layout_height="wrap_content" </LinearLayout> </LinearLayout> Now make these changes to Andro idmanif est.xml:

125 CODE TO TYPE: Andro idmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.oreillyschool.android2.camerabasics" android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="10" android:targetsdkversion="10" /> <uses-permission android:name="android.permission.write_external_storage" /> <application android:allowbackup="true" > <activity android:name=".mainactivity" android:screenorientation="landscape" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> Finally, make these changes to MainAct ivit y.java:

126 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.camerabasics; import java.io.file; import java.io.fileoutputstream; import java.text.simpledateformat; import java.util.date; import java.util.locale; import android.app.activity; import android.content.intent; import android.graphics.bitmap; import android.os.bundle; import android.os.environment; import android.provider.mediastore; import android.view.view; import android.view.view.onclicklistener; import android.widget.button; import android.widget.imageview; import android.widget.toast; public class MainActivity extends Activity { private static final int TAKE_PICTURE_REQUEST_B = 100; private ImageView mcameraimageview; private Bitmap mcamerabitmap; private Button msaveimagebutton; private OnClickListener mcaptureimagebuttonclicklistener = new OnClickListener() { public void onclick(view v) { startimagecapture(); ; private OnClickListener msaveimagebuttonclicklistener = new OnClickListener() { public void onclick(view v) { File savefile = openfileforimage(); if (savefile!= null) { saveimagetofile(savefile); else { Toast.makeText(MainActivity.this, "Unable to open file for saving image.", Toast.LENGTH_LONG).show(); ; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mcameraimageview = (ImageView) findviewbyid(r.id.camera_image_view); findviewbyid(r.id.capture_image_button);.setonclicklistener(mcaptureimagebuttonclic klistener); msaveimagebutton = (Button) findviewbyid(r.id.save_image_button); msaveimagebutton.setonclicklistener(msaveimagebuttonclicklistener); msaveimagebutton.setenabled(false); protected void onactivityresult(int requestcode, int resultcode, Intent data) { if (requestcode == TAKE_PICTURE_REQUEST_B) {

127 if (resultcode == RESULT_OK) { // Recycle the previous bitmap. if (mcamerabitmap!= null) { mcamerabitmap.recycle(); mcamerabitmap = null; Bundle extras = data.getextras(); mcamerabitmap = (Bitmap) extras.get("data"); mcameraimageview.setimagebitmap(mcamerabitmap); msaveimagebutton.setenabled(true); else { mcamerabitmap = null; msaveimagebutton.setenabled(false); private void startimagecapture() { startactivityforresult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE), TAKE_PICTURE_RE QUEST_B); private File openfileforimage() { File imagedirectory = null; String storagestate = Environment.getExternalStorageState(); if (storagestate.equals(environment.media_mounted)) { imagedirectory = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "com.oreillyschool.android2.camerabasics"); if (!imagedirectory.exists() &&!imagedirectory.mkdirs()) { imagedirectory = null; else { SimpleDateFormat dateformat = new SimpleDateFormat("yyyy_mm_dd_hh_mm", Locale.getDefault()); return new File(imageDirectory.getPath() + File.separator + "image_" + dateformat.format(new Date()) + ".png"); return null; private void saveimagetofile(file file) { if (mcamerabitmap!= null) { FileOutputStream outstream = null; try { outstream = new FileOutputStream(file); if (!mcamerabitmap.compress(bitmap.compressformat.png, 100, outstream)) { Toast.makeText(MainActivity.this, "Unable to save image to file.", Toast.LENGTH_LONG).show(); else { Toast.makeText(MainActivity.this, "Saved image to: " + file.getpath(), Toast. LENGTH_LONG).show(); outstream.close(); catch (Exception e) { Toast.makeText(MainActivity.this, "Unable to save image to file.", Toast.LENGTH_LONG).show(); Save the modified files and run the application. Now we have two buttons instead of one: Snap it! and Save Image:

128 The Save Image button is disabled because we haven't taken a picture yet. Click Snap it!, use the built-in camera application, take a picture, and click OK to return to the application. You see the same Android placeholder image as before, and the Save Image button is now enabled. Click on it, and you'll see a toast message that indicates that the file has been saved. The message also includes the name of the file saved: Note The next step changes the perspective in the sandbox. To return to the sandbox and this lesson content later, select Windo w Clo se Perspect ive. The images are saved by going to the DDMS perspective: select Windo w Open Perspect ive Ot her... DDMS, or clickin the double arrow at the top right and selecting DDMS Perspect ive:

129 Select the emulator in the Devices tab, go to the File Explo rer, and then go to mnt /sdcard/pict ures/co m.o reillyscho o l.andro id2.camerabasics; all of the files we saved: Let's review how we were able to save the images to the emulator's SD card: OBSERVE: Andro idmanifest.xml... <uses-sdk android:minsdkversion="10" android:targetsdkversion="10" />... <uses-permission android:name="android.permission.write_external_storage" /> First, we added the <uses-permissio n /> tag to our manifest. This declares to the Android OS that our application

130 requires permission to write data to the device SD card. If this application is published to the Google Play market, users will see this permissio n listed as a requirement. By do wnlo ading the applicatio n, they presumably "grant" that permissio n to the applicatio n: OBSERVE: activity_main.xml... <ImageView android:id="@+id/camera_image_view" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="10dp" android:orientation="horizontal" android:gravity="center" > <Button android:id="@+id/capture_image_button" android:layout_width="150dp" android:layout_height="wrap_content" android:text="@string/capture_image" /> <Button android:id="@+id/save_image_button" android:layout_width="150dp" android:layout_height="wrap_content" android:text="@string/save_image"/> </LinearLayout> </LinearLayout> Next, we added a but t o n to the UI so that the user can elect to save the current displayed image to a file:

131 OBSERVE: MainActivity.java... public class MainActivity extends Activity { private static final int TAKE_PICTURE_REQUEST_B = 100; private ImageView mcameraimageview; private Bitmap mcamerabitmap; private Button msaveimagebutton; private OnClickListener mcaptureimagebuttonclicklistener = new OnClickListener() { public void onclick(view v) { startimagecapture(); ; private OnClickListener msaveimagebuttonclicklistener = new OnClickListener() { public void onclick(view v) { File savefile = openfileforimage(); if (savefile!= null) { saveimagetofile(savefile); else { Toast.makeText(MainActivity.this, "Unable to open file for saving image.", Toast.LENGTH_LONG).show(); ;... The bulk of our changes were to the MainActivity.java file. At the top, we added a View.OnClickList ener f o r t he Save Image but t o n. This listener first at t empt s t o o pen a f ile t o save t he image, and if t he f ile is o pened successf ully, the listener writ es t he image dat a t o t he f ile. If it co uld no t o pen a f ile, it displays a t o ast message indicat ing t hat. To open a file and save the image to it, the listener calls two new methods: o penfilefo rimage and saveimaget o File: OBSERVE: MainActivity.java... private File openfileforimage() { File imagedirectory = null; String storagestate = Environment.getExternalStorageState(); if (storagestate.equals(environment.media_mounted)) { imagedirectory = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "com.oreillyschool.android2.camerabasics"); if (!imagedirectory.exists() &&!imagedirectory.mkdirs()) { imagedirectory = null; else { SimpleDateFormat dateformat = new SimpleDateFormat("yyyy_mm_dd_hh_mm", Locale.getDefault());... return new File(imageDirectory.getPath() + File.separator + "image_" + dateformat.format(new Date()) + ".png"); return null; The first method, o penfilefo rimage, tries to open a file to save the image on the SD card. Specifically, it t ries t o creat e t he f ile in a subf o lder o f t he "Pict ures" f o lder o f t he SD card. To do this, it checks to determine whether the storage is actually mounted by calling Enviro nment.get Ext ernalst o ragest at e(). If the media is

132 mounted, then it tries to open the "Pict ures" direct o ry o f t he SD card. If "Pict ures" do es no t exist, then it tries to create the directory. Finally, once a reference to the directory is obtained, o penfilefo rimage writ es t he image dat a t o a PNG f ile. If any of these steps fails in opening the directory or the file, o penfilefo rimage ret urns null. Note Enviro nment contains several constants that represent the various potential states of external storage. Enviro nment also contains several constants for standard directory names for Android, such as the "Pictures" folder that we used in our application. There are also static methods on Enviro nment that allow you to request information on an Android device's file system. For more information, see the Andro id Develo per do cumentatio n: OBSERVE: MainActivity.java... private void saveimagetofile(file file) { if (mcamerabitmap!= null) { FileOutputStream outstream = null; try { outstream = new FileOutputStream(file); if (!mcamerabitmap.compress(bitmap.compressformat.png, 100, outstream)) { Toast.makeText(MainActivity.this, "Unable to save image to file.", Toast.LENGTH_LONG).show(); else { Toast.makeText(MainActivity.this, "Saved image to: " + file.getpath(), Toast. LENGTH_LONG).show(); outstream.close(); catch (Exception e) { Toast.makeText(MainActivity.this, "Unable to save image to file.", Toast.LENGTH_LONG).show();... The second method, saveimaget o File writ es t he image dat a t o t he o pened f ile. If there are any errors in the file, then a t o ast message is displayed describing t he issue. If the file is written succesfully, a t o ast message wit h t he f ile's name is displayed. Wrapping Up In this lesson, we made an application that allows users to take photos and then save those photos to the device's SD Card. Our applicatio n also used the device's default Camera applicatio n to take the pho to s. Accessing the built-in camera application with an Int ent is the most straightforward way to work with a device's camera. This method will pro bably meet mo st o f yo ur image-capturing needs. Usually the built-in camera applicatio n is equipped with a full range of features including autofocus, flash, and scenes (action, portrait, macro,and so on). It's convenient to retrieve the image data once the camera application is done. If you want to create a truly custom camera application, you can dive into the Android Camera API that we'll cover in the next lesson. See you there! Copyright O'Reilly Media, Inc. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. See for more information.

133 Camera Advanced: Building a Custom Camera Application Lesson Objectives In this lesson you will: o pen the hardware camera. create a live preview of the camera image. take images with the camera. release the camera reso urces. tag camera features that your application uses for Google Play. find advanced camera functio nality. In the previous lesson, we saw how to use the built-in camera application on most devices: we started the application via an Int ent, received the picture taken in Act ivit y.o nact ivit yresult, and saved the picture to external storage. If your application only needs camera functionality once in a while, you'll probably be able to get by with the built-in camera application. However, if your application revolves around photography, it may need custom functionality that the stock camera doesn't support. When you need to create a custom camera, you can use Android's Camera object. The Camera object has many options and features, but this makes it much more complex to use than the built-in camera application. Also, since we test our applications only in the emulator, the features we can use will be limited. In this lesson we'll review where you can find these features if you need to create a custom camera in the future. The code for this project will be similar to the project from the Camera Basics lesson. We'll create a new project to keep the functionality separate, but we'll reuse much of the code from Andro idmanif est.xml, MainAct ivit y.java, and act ivit y_main.xml. Create a new Android project with this criteria. Name the project CameraAdvanced. Use the package name co m.o reillyscho o l.andro id2.cameraadvanced. Uncheck the Creat e cust o m launcher ico n box. Assign the Andro id2_lesso ns working set to the project. Using the Camera API We'll start off by creating a new Activity that will take over the work done by the built-in camera application. For this we're going to need a new Activity class, a view, and some related manifest updates. First, though, let's update the strings. Open st rings.xm l and make these changes: CODE TO TYPE: /res/values/strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">camera Advanced</string> <string name="action_settings">settings</string> <string name="hello_world">hello World, MainActivity!</string> <string name="start_image_capture">take a New Picture</string> <string name="capture_image">snap it!</string> <string name="save_image">save Picture</string> <string name="recapture_image">retake Picture</string> <string name="capturing_image">taking New Picture</string> <string name="done">done</string> </resources> Next, open act ivit y_main.xml and make these changes:

134 CODE TO TYPE: activity_main.xml <RelativeLinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".mainactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <ImageView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="10dp" android:orientation="horizontal" android:gravity="center" > <Button android:layout_width="150dp" android:layout_height="wrap_content" /> <Button android:layout_width="150dp" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout> Next, create a new Android XML file, set its type to Layo ut, name it act ivit y_camera, ensure that its root element is LinearLayo ut, and make these changes:

135 CODE TO TYPE:activity_camera.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#999999" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" /> <FrameLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" > <ImageView android:layout_width="match_parent" android:layout_height="match_parent" /> <SurfaceView android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="10dp" android:gravity="center" android:orientation="horizontal" > <Button android:layout_width="150dp" android:layout_height="wrap_content" /> <Button android:layout_width="150dp" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout> Next, create a new class named CameraAct ivit y that extends andro id.app.act ivit y and make these changes:

136 CODE TO TYPE: CameraActivity.java package com.oreillyschool.android2.cameraadvanced; import java.io.ioexception; import android.app.activity; import android.content.intent; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.hardware.camera; import android.hardware.camera.picturecallback; import android.os.bundle; import android.view.surfaceholder; import android.view.surfaceview; import android.view.view; import android.view.view.onclicklistener; import android.widget.button; import android.widget.imageview; import android.widget.toast; public class CameraActivity extends Activity implements PictureCallback, SurfaceHolder. Callback { public static final String EXTRA_CAMERA_DATA = "camera_data"; private static final String KEY_IS_CAPTURING = "is_capturing"; private Camera mcamera; private ImageView mcameraimage; private SurfaceView mcamerapreview; private Button mcaptureimagebutton; private byte[] mcameradata; private boolean miscapturing; private OnClickListener mcaptureimagebuttonclicklistener = new OnClickListener() { public void onclick(view v) { captureimage(); ; private OnClickListener mrecaptureimagebuttonclicklistener = new OnClickListener() { public void onclick(view v) { setupimagecapture(); ; private OnClickListener mdonebuttonclicklistener = new OnClickListener() { public void onclick(view v) { if (mcameradata!= null) { Intent intent = new Intent(); intent.putextra(extra_camera_data, mcameradata); setresult(result_ok, intent); else { setresult(result_canceled); finish(); ; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_camera);

137 mcameraimage = (ImageView) findviewbyid(r.id.camera_image_view); mcameraimage.setvisibility(view.invisible); mcamerapreview = (SurfaceView) findviewbyid(r.id.preview_view); final SurfaceHolder surfaceholder = mcamerapreview.getholder(); surfaceholder.addcallback(this); surfaceholder.settype(surfaceholder.surface_type_push_buffers); mcaptureimagebutton = (Button) findviewbyid(r.id.capture_image_button); mcaptureimagebutton.setonclicklistener(mcaptureimagebuttonclicklistener); final Button donebutton = (Button) findviewbyid(r.id.done_button); donebutton.setonclicklistener(mdonebuttonclicklistener); miscapturing = true; protected void onsaveinstancestate(bundle savedinstancestate) { super.onsaveinstancestate(savedinstancestate); savedinstancestate.putboolean(key_is_capturing, miscapturing); protected void onrestoreinstancestate(bundle savedinstancestate) { super.onrestoreinstancestate(savedinstancestate); ; miscapturing = savedinstancestate.getboolean(key_is_capturing, mcameradata == null) if (mcameradata!= null) { setupimagedisplay(); else { setupimagecapture(); protected void onresume() { super.onresume(); ) if (mcamera == null) { try { mcamera = Camera.open(); mcamera.setpreviewdisplay(mcamerapreview.getholder()); if (miscapturing) { mcamera.startpreview(); catch (Exception e) { Toast.makeText(CameraActivity.this, "Unable to open camera.", Toast.LENGTH_LONG.show(); protected void onpause() { super.onpause(); if (mcamera!= null) { mcamera.release(); mcamera = null;

138 public void onpicturetaken(byte[] data, Camera camera) { mcameradata = data; setupimagedisplay(); public void surfacechanged(surfaceholder holder, int format, int width, int height) { if (mcamera!= null) { try { mcamera.setpreviewdisplay(holder); if (miscapturing) { mcamera.startpreview(); catch (IOException e) { Toast.makeText(CameraActivity.this, "Unable to start camera preview.", Toast.LE NGTH_LONG).show(); public void surfacecreated(surfaceholder holder) { public void surfacedestroyed(surfaceholder holder) { private void captureimage() { mcamera.takepicture(null, null, this); private void setupimagecapture() { mcameraimage.setvisibility(view.invisible); mcamerapreview.setvisibility(view.visible); mcamera.startpreview(); mcaptureimagebutton.settext(r.string.capture_image); mcaptureimagebutton.setonclicklistener(mcaptureimagebuttonclicklistener); private void setupimagedisplay() { Bitmap bitmap = BitmapFactory.decodeByteArray(mCameraData, 0, mcameradata.length); mcameraimage.setimagebitmap(bitmap); mcamera.stoppreview(); mcamerapreview.setvisibility(view.invisible); mcameraimage.setvisibility(view.visible); mcaptureimagebutton.settext(r.string.recapture_image); mcaptureimagebutton.setonclicklistener(mrecaptureimagebuttonclicklistener); Next, open Andro idmanif est.xml and make these changes:

139 CODE TO TYPE: Andro idmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.oreillyschool.android2.cameraadvanced" android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="10" android:targetsdkversion="10" /> <uses-permission android:name="android.permission.camera"/> <uses-permission android:name="android.permission.write_external_storage" /> <application android:allowbackup="true" > <activity android:name=".mainactivity" android:screenorientation="landscape" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <activity android:name=".cameraactivity" android:screenorientation="landscape" /> </application> </manifest> Finally, make the changes below to MainAct ivit y.java. It will be the same as the previous lesson's MainAct ivit y class, except for the lines that are highlighted as having been changed. If you're writing this class from scratch, be sure to add all this code:

140 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.cameraadvanced; import java.io.file; import java.io.fileoutputstream; import java.text.simpledateformat; import java.util.date; import java.util.locale; import android.app.activity; import android.content.intent; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.os.bundle; import android.os.environment; import android.provider.mediastore; import android.view.view; import android.view.view.onclicklistener; import android.widget.button; import android.widget.imageview; import android.widget.toast; public class MainActivity extends Activity { private static final int TAKE_PICTURE_REQUEST_B = 100; private ImageView mcameraimageview; private Bitmap mcamerabitmap; private Button msaveimagebutton; private OnClickListener mcaptureimagebuttonclicklistener = new OnClickListener() { public void onclick(view v) { startimagecapture(); ; private OnClickListener msaveimagebuttonclicklistener = new OnClickListener() { public void onclick(view v) { File savefile = openfileforimage(); if (savefile!= null) { saveimagetofile(savefile); else { Toast.makeText(MainActivity.this, "Unable to open file for saving image.", Toast.LENGTH_LONG).show(); ; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mcameraimageview = (ImageView) findviewbyid(r.id.camera_image_view); findviewbyid(r.id.capture_image_button).setonclicklistener(mcaptureimagebuttonclick Listener); msaveimagebutton = (Button) findviewbyid(r.id.save_image_button); msaveimagebutton.setonclicklistener(msaveimagebuttonclicklistener); msaveimagebutton.setenabled(false); protected void onactivityresult(int requestcode, int resultcode, Intent data) {

141 h); if (requestcode == TAKE_PICTURE_REQUEST_B) { if (resultcode == RESULT_OK) { // Recycle the previous bitmap. if (mcamerabitmap!= null) { mcamerabitmap.recycle(); mcamerabitmap = null; Bundle extras = data.getextras(); mcamerabitmap = (Bitmap) extras.get("data"); byte[] cameradata = extras.getbytearray(cameraactivity.extra_camera_data); if (cameradata!= null) { mcamerabitmap = BitmapFactory.decodeByteArray(cameraData, 0, cameradata.lengt mcameraimageview.setimagebitmap(mcamerabitmap); msaveimagebutton.setenabled(true); else { mcamerabitmap = null; msaveimagebutton.setenabled(false); private void startimagecapture() { startactivityforresult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE), TAKE_PICTURE_RE QUEST_B); startactivityforresult(new Intent(MainActivity.this, CameraActivity.class), TAKE_PI CTURE_REQUEST_B); private File openfileforimage() { File imagedirectory = null; String storagestate = Environment.getExternalStorageState(); if (storagestate.equals(environment.media_mounted)) { imagedirectory = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "com.oreillyschool.android2.camera"); if (!imagedirectory.exists() &&!imagedirectory.mkdirs()) { imagedirectory = null; else { SimpleDateFormat dateformat = new SimpleDateFormat("yyyy_mm_dd_hh_mm", Locale.getDefault()); return new File(imageDirectory.getPath() + File.separator + "image_" + dateformat.format(new Date()) + ".png"); return null; private void saveimagetofile(file file) { if (mcamerabitmap!= null) { FileOutputStream outstream = null; try { outstream = new FileOutputStream(file); if (!mcamerabitmap.compress(bitmap.compressformat.png, 100, outstream)) { Toast.makeText(MainActivity.this, "Unable to save image to file.", Toast.LENGTH_LONG).show(); else { Toast.makeText(MainActivity.this, "Saved image to: " + file.getpath(), Toast.LENGTH_LONG).show(); outstream.close(); catch (Exception e) { Toast.makeText(MainActivity.this, "Unable to save image to file.", Toast.LENGTH_LONG).show();

142 Now save all of the modified files and run the application. On startup, the application looks just about the same as before, though we did change the label text a little bit: Click the T ake a New Pict ure button to launch our new, custom camera activity: If our application were running on an actual device with a camera, we would be able to see a camera preview in the white space. Click the Snap it! button, and you see an emulated image the same Android placeholder that we saw befo re:

143 Click the Do ne button in the camera activity to return to the main activity and see the Save button, like we did before when we used the built-in camera applicatio n: Okay, that was a lot of code. Let's take a look at how we created our custom camera. The changes made to our main activity UI are relativly minor: changing some button text. The bulk of the new code was for the camera activity:

144 OBSERVE: activity_camera.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#999999" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" /> <FrameLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" > <ImageView android:layout_width="match_parent" android:layout_height="match_parent" /> <SurfaceView android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="10dp" android:gravity="center" android:orientation="horizontal" > <Button android:layout_width="150dp" android:layout_height="wrap_content" /> <Button android:layout_width="150dp" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout> The layout for the CameraActivity consists of two main areas: a FrameLayo ut that holds a Surf aceview and an ImageView, and a LinearLayo ut that contains but t o ns f o r user act io ns. The Android Camera object utilizes the Surf aceview we added to preview live images from the camera (if you aren't using the emulator). A Surf aceview is a type of View in Android reserved for drawing. The ImageView will hold the capture image. The first button allows the user to snap the photo. The second button allows the user to return to the main activity with the snapped image:

145 OBSERVE: CameraActivity.java... public class CameraActivity extends Activity implements PictureCallback, SurfaceHolder. Callback {... public static final String EXTRA_CAMERA_DATA = "camera_data"; private static final String KEY_IS_CAPTURING = "is_capturing"; private Camera mcamera; private ImageView mcameraimage; private SurfaceView mcamerapreview; private Button mcaptureimagebutton; private byte[] mcameradata; private boolean miscapturing; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_camera); mcameraimage = (ImageView) findviewbyid(r.id.camera_image_view); mcameraimage.setvisibility(view.invisible); mcamerapreview = (SurfaceView) findviewbyid(r.id.preview_view); final SurfaceHolder surfaceholder = mcamerapreview.getholder(); surfaceholder.addcallback(this); surfaceholder.settype(surfaceholder.surface_type_push_buffers); mcaptureimagebutton = (Button) findviewbyid(r.id.capture_image_button); mcaptureimagebutton.setonclicklistener(mcaptureimagebuttonclicklistener); final Button donebutton = (Button) findviewbyid(r.id.done_button); donebutton.setonclicklistener(mdonebuttonclicklistener); miscapturing = true; protected void onsaveinstancestate(bundle savedinstancestate) { super.onsaveinstancestate(savedinstancestate); savedinstancestate.putboolean(key_is_capturing, miscapturing); protected void onrestoreinstancestate(bundle savedinstancestate) { super.onrestoreinstancestate(savedinstancestate); ; miscapturing = savedinstancestate.getboolean(key_is_capturing, mcameradata == null) if (mcameradata!= null) { setupimagedisplay(); else { setupimagecapture(); protected void onresume() { super.onresume(); if (mcamera == null) { try {

146 ) mcamera = Camera.open(); mcamera.setpreviewdisplay(mcamerapreview.getholder()); if (miscapturing) { mcamera.startpreview(); catch (Exception e) { Toast.makeText(CameraActivity.this, "Unable to open camera.", Toast.LENGTH_LONG.show(); protected void onpause() { super.onpause(); if (mcamera!= null) { mcamera.release(); mcamera = null; public void onpicturetaken(byte[] data, Camera camera) { mcameradata = data; setupimagedisplay(); public void surfacechanged(surfaceholder holder, int format, int width, int height) { if (mcamera!= null) { try { mcamera.setpreviewdisplay(holder); if (miscapturing) { mcamera.startpreview(); catch (IOException e) { Toast.makeText(CameraActivity.this, "Unable to start camera preview.", Toast.LE NGTH_LONG).show();... The CameraAct ivit y grabs control of the camera and does the work of setting up the live preview and taking pictures, as well as passing back the image data to the activity that started it. The central class of the camera API is the Camera class. The Camera provides information about the device camera, access to camera settings, and control over picture preview and picture taking. To get control of the device camera, we creat e a Camera inst ance and call Camera.o pen(). If the camera is available, the application will have control of the camera. If any other applications try to open the camera, the call will throw a Runt imeexcept io n. To release control of the camera so that another application can use it, we call Camera.release(). It is vital to release camera resources if your application is not actively using them, including when your application is paused in the background. In fact, if you do not release the resources and try to open the camera again, you'll get a Runt imeexcept io n even if you already had control. To avoid that, we place our Camera.o pen() call in o nresume() and our Camera.release() call in o npause(). The Camera.o pen() call returns a Camera object that we store in a member variable. We will use this instance for all of our other camera functionality. Another piece of functionality that we start in o nresume is the live preview. Before we go over how to start the preview, we need to discuss the Surf aceview where the preview is actually drawn. The camera can use the Surf aceview to draw the live preview only after the surface has been created and sized. If we try to start the camera preview before the Surf aceview's surface fully initializes, we won't get any exceptions, but the live preview will not

147 render. In order to start the preview after the surface is initialized, we have CameraAct ivit y implement the Surf aceho lder.callback interface. Surf aceho lder is an interface for manipulating the surface of a Surf aceview and is in fact what we pass to the Camera for the preview. By having Camera implement the callback interface, we can have o ur Cam e ra instance start the preview after the surface initializes in Surf aceho lder.callback.surf acechanged.. So in o ncreat e, we get a reference to the Surf aceview, and then use it s Surf aceho lder t o add t he CameraAct ivit y as a callback and also set t he surf ace's t ype. Note The valid constant values for Surf aceho lder.set T ype are Surf ace Ho lde r.surface_t YPE_NORMAL and Surf ace Ho lde r.surface_t YPE_PUSH_BUFFERS. Ho wever, this metho d and these co nstants are deprecated as of API 11. So, in your projects, if your minimum build target is an API higher than 10, you do not need to call this method because the type is set automatically when needed. For more information, see the Andro id Develo per Do cumentatio n. The Surf aceho lder.callback consists of three methods: surf acecreat ed, surf acechanged, and suf acedest ro yed. The only one we need for our purposes is surf acechanged. In our implementation of surf acechanged, we check to determine whether CameraAct ivit y has a valid Camera instance; if it does we assign the Surf aceho lder to the Camera as its display surface. At the top of the class, we add a member variable, miscapt uring as a flag to differentiate when we are previewing the camera and when we are displaying a taken picture. So in surf acechanged, we check this flag; if we are currently trying to capture an image, we go ahead and call Camera.st art Preview() to start the preview drawing. These method calls are all wrapped in a try-catch block so that if there are any issues starting the preview, we can display a to ast message to the user indicating the pro blem. To take a picture, our capt ureimage() method simply calls the Camera.t akepict ure method. There are two versions of Camera.t akepict ure: public final vo id takepicture (Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg) public final vo id takepicture (Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback postview, Camera.PictureCallback jpeg) The first method calls the second method with all its parameters, but passes null for the Camera.Pict urecallback po st view argument. Let's analyze the second method. The first argument is a Camera.Shut t ercallback. This callback is called the moment the image is captured. The second argument is a Camera.Pict urecallback. It is called when raw image data is available. The third argument is a Camera.Pict urecallback. It is called when the scaled, processed "postview" image is available. The last argument for both is a Camera.Pict urecallback. It is called when JPEG image data is called. It's okay to pass null for any of these arguments if you don't care about that particular callback. In our application, we use the first version and only set a callback for JPEG image data. We set the others to null because we don't need them. We have the CameraAct ivit y implement Pict urecallback, so when the picture is taken and the JPEG data is available, o npict uret aken is called. The callback is passed a byte array containing the picture data, then we save the data to a member variable and call the set upimagedisplay() method to call Camera.st o ppreview(), hide the Surf aceview, and decode the byte array into a Bit map, which is then displayed in the ImageView. Also, we change the "Snap it!" button text to "Retake." When clicked, instead of calling Camera.t akepict ure, the "Retake" button, will call set upimagecapt ure, which hides the ImageView, shows the Surf aceview, calls Camera.st art Preview() to start the live preview again, and changes the "Retake" button text back to "Snap it!" The final piece is to return the image data to our MainAct ivit y. We accomplish this with the "Done" button. In the click listener for this button, we take the byte array received in o npict uret aken and place that as an extra in a new Int ent instance. Then we set this Int ent as the result for this activity via Act ivit y.set Result and call Act ivit y.f inish() to return to end the CameraAct ivit y, and return to the MainAct ivit y.

148 OBSERVE: MainActivity.java... protected void onactivityresult(int requestcode, int resultcode, Intent data) { if (requestcode == TAKE_PICTURE_REQUEST_B) { if (resultcode == RESULT_OK) { // Recycle the previous bitmap. if (mcamerabitmap!= null) { mcamerabitmap.recycle(); mcamerabitmap = null; Bundle extras = data.getextras(); byte[] cameradata = extras.getbytearray(cameraactivity.extra_camera_data); if (cameradata!= null) { mcamerabitmap = BitmapFactory.decodeByteArray(cameraData, 0, cameradata.lengt h); mcameraimageview.setimagebitmap(mcamerabitmap); msaveimagebutton.setenabled(true); else { mcamerabitmap = null; msaveimagebutton.setenabled(false); private void startimagecapture() { startactivityforresult(new Intent(MainActivity.this, CameraActivity.class), TAKE_PI CTURE_REQUEST_B);... Back in MainAct ivit y we made a couple of minor changes to our previous application. We can still retrieve the data from CameraAct ivit y in o nact ivit yresult like we did with the built-in camera. However, here t he key f o r t he value st o red in t he Int ent is different, because we made our own constant. Also, since we were passing back a byte array, instead of assigning a Bit map to the MainAct ivit y's ImageView, MainAct ivit y.o nact ivit yresult ret rieves t he byt e array, deco des it int o a Bit map, and then assigns it t o t he ImageView. Camera Parameters Most cameras on newer devices have many settings that you can access through the Camera API. Unfortunately, they are hard to test on an emulator. Regardless, let's still review how you would access these camera parameters, the kind of settings you can manipulate with the Camera.Paramet ers class, and adjust camera settings. The current settings for a Camera instance are obtained by calling Camera.get Paramet ers(). There are several settings you can change by calling setters on the Camera.Paramet ers instance returned. For example, you can set anti-banding, coloring effects (sepia, negative, and so on), flash mode, focus mode, scene mode, white balance, preview size, and JPEG quality, among other things. For complete details on settings and possible values, see the Android Developer Documentation. Remember that when you change values on the Camera.Paramet ers instance from Camera.get Paramet ers(), the settings are not actually changed until you call Camera.set Paramet ers (Camera.Paramet ers params) and pass the Camera.Paramet ers with the changed values. In relation to live previews, the Camera.Paramet ers actually provides a list of preview sizes from which you can select to find the most appropriate preview size (as a Camera.Size object) for your application. There are lots of useful features on Camera.Paramet ers that you can use to implement a custom camera. For more information on even more features, as well as API level limitations, see the Android Developer Documentation. Checking for a Camera and Handling Multiple Cameras If camera functionality is optional in your application and you need to check whether the device running your applicatio n has a camera, yo u can use the PackageManager to determine pro grammatically whether the device has a camera. You do that using this line of code from inside an Act ivit y:

149 OBSERVE: Using PackageManager to Check fo r a Camera boolean hascamera = getpackagemanager().hassystemfeature(packagemanager.feature_camera) ; Act ivit y.get PackageManager() retrieves the PackageManager for the current Act ivit y. Package Manage r.hassyst e m Fe at ure(st ring), when passed Package Manage r.feat URE_CAMERA, will return t rue if a camera is available. In our project, we did not discuss multiple cameras, and how you can choose which camera to open and manipulate. Unfortunately, it is difficult to test more than one camera on the emulator. Depending on how your AVD is set up, you can specify a back or a front camera or even both. However, when the emulator actually runs, it will only have one available, even if you set up two. This may change in future version of the Android emulator, but for now, we'll just review where you can get information about and gain access to a particular camera. Each camera has an ID associated with it. In our project, we used Camera.o pen(), which takes no arguments and opens the back-facing camera by default. There is another version of the method, Camera.o pen(int ), which will open the camera associated with the integer ID passed. The ID is really just a zero-based index. If we know the index of the camera that we want, we just pass that to Camera.o pen(int ). Getting the index of the back vs. the front camera involves a class called CameraInfo. For each camera on a device, you can populate a CameraInf o instance that will tell you the direction the camera faces and its orientation. Device cameras are essentially indexed by the Camera class. Camera.get NumberOf Cameras will give you the total number of cameras on the device. To grab the information, you create a new CameraInf o instance and pass it to Camera.get CameraInf o (int, CameraInf o ). Afterwards, your CameraInf o instance will have the relevant information. So to get the index of the front or back-facing camera, we can iterate through the camera information for each camera until we come across one that is facing the direction we want and return its index. Something like this: OBSERVE: Finding the Fro nt-facing Camera Sample Co de int cameraindex = -1; int cameracount = Camera.getNumberOfCameras(); for (int i = 0; i < cameracount && cameraindex == -1; i++) { CameraInfo info = new CameraInfo(); Camera.getCameraInfo(i, info); if (info.facing == CameraInfo.CAMERA_FACING_FRONT) { cameraindex = i; if (cameraindex!= -1) { Camera.open(cameraIndex); If you want to find the index of the back-facing camera, you can instead check that CameraInf o.f acing equals Cam e rainf o.camera_facing_back. Camera Features and the Android Manifest Befo re submitting an applicatio n that uses camera features, yo u need to specify which features yo ur camera application uses and also whether these features are optional, in the Android Manifest. To do that, you add <usesfeature/> tags and specify the andro id:name attribute fo r the particular feature. If the feature is o ptio nal, yo u also add the android:required attribute and set it to false. For example, for our application we can add this: OBSERVE: Example Feature Tags fo r Andro idmanifest.xml <uses-feature android:name="android.hardware.camera" /> This lets Google Play know that our application requires a camera. Google Play will then filter out our application for any devices browsing Google Play that do not have cameras. You can see a list of hardware feature descriptors to use with <uses-feature/> in the Andro id Develo per Do cumentatio n. Wrapping Up The Camera API is pretty complex and requires meticulous work to use properly. Fortunately, most of the time you

150 won't need it, but if you do, the API is expansive enough to allow you to create a fully-featured Camera application. While availability of certain features varies across Android API level and device, there are plenty of ways for you to assess a device's capabilities and take advantage of them accordingly. Hopefully, by now you are comfortable using the Camera API to grab control of camera resources, take images, store images, and (most importantly) release the camera back to the system. You should also now know where to look to find more advanced camera features to leverage in your application. See you next lesson! Copyright O'Reilly Media, Inc. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. See for more information.

151 BroadcastReceivers Lesson Objectives In this lesson you will: create a Bro adcastreceiver fo r receiving system events. create a Bro adcastreceiver fo r receiving service events. register a Bro adcastreceiver in the Andro id manifest. register a Bro adcastreceiver pro grammatically in an activity. use the Lo calbro adcastmanager fo r sending and receiving events in the same applicatio n pro cess. We've already worked with the Int ent class with the Act ivit y class and Service class. We can also use the Intent class to pass events and messages to the Bro adcast Receiver class. We can use Bro adcast Receiver to listen for Int ent s sent via Co nt ext.sendbro adcast. A Bro adcast Receiver can also listen to a number of Int ent s sent for system events, such as when a device battery gets low, a SMS is received, or when the user plugs in headphones. Bro adcast Receivers can also receive Int ent s sent from any Co nt ext, including other applications (if the application allows other applications to receive them). In this lesson, we'll discuss the basics of using the Bro adcast Receiver class, as well as the Lo calbro adcast Manager. Creating a BroadcastReceiver for System Events Let get started with Bro adcast Receivers. First, we'll create a simple application that listens for when the device receives a SMS and pops up a little toast message. Create a new Android project with this criteria: Name the project Bro adcast Receivers. Use the package name co m.o reillyscho o l.andro id2.bro adcast Receivers. Uncheck the Creat e cust o m launcher ico n box. Assign the Andro id2_lesso ns working set to the project. Now let's make our Broadcast Receiver. Create a new class named SMSReceiver that extends andro id.co nt ent.bro adcast Receiver. Make these changes to SMSReceiver.java: CODE TO TYPE: SMSReceiver.java package com.oreillyschool.android2.broadcastreceivers; import android.content.broadcastreceiver; import android.content.context; import android.content.intent; import android.widget.toast; public class SMSReceiver extends BroadcastReceiver { public void onreceive(context arg0context, Intent arg1intent) { // TODO Auto-generated method stub Toast.makeText(context, "Received an SMS!", Toast.LENGTH_LONG).show(); Next, make a new permission in Andro idmanif est.xml:

152 CODE TO TYPE: Andro idmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.oreillyschool.android2.broadcastreceivers" android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="10" android:targetsdkversion="10" /> <uses-permission android:name="android.permission.receive_sms" /> <application android:allowbackup="true" > <receiver android:name=".smsreceiver" android:enabled="true" > <intent-filter android:priority="999" > <action android:name="android.provider.telephony.sms_received" /> </intent-filter> </receiver> <!-- <activity android:name=".mainactivity" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> --> </application> </manifest> Save the modified files and run the application. You won't actually see an activity pop up for the application, you'll just see the home screen on the emulator. Note The next step removes this lesson content from your screen, so take note of the next few steps to follow until you return to the lesson using Windo w Clo se Perspect ive. We want to see what happens when the device receives an SMS message. We can simulate that in the emulator. Select Windo w Open Perspect ive Ot her... and select the DDMS perspective, then go to the Emulat o r Co nt ro l tab. From this tab, we can simulate events in the emulator, such as phone/sms events. In the T elepho ny Act io ns panel, type an incoming number, select SMS, type a message and click Send:

153 The emulator will simulate your SMS message. You see the typical notification for an SMS in the status bar. Click and drag it down to see the message content:

154 Let's review what we just did: OBSERVE: SMSReceiver.java... public class SMSReceiver extends BroadcastReceiver { public void onreceive(context context, Intent intent) { Toast.makeText(context, "Received an SMS!", Toast.LENGTH_LONG).show(); Here we create a basic Bro adcast Receiver subclass. The key to creating a Bro adcast Receiver is to implement the o nreceive method. This is the callback for whatever event your Bro adcast Receiver has registered to receive. In our o nreceive, we pop up a toast message saying that an SMS message was received by the device.

155 OBSERVE: Andro idmanifest.xml... <uses-permission android:name="android.permission.receive_sms" /> <application android:allowbackup="true" > <receiver android:name=".smsreceiver" android:enabled="true" > <intent-filter android:priority="999" > <action android:name="android.provider.telephony.sms_received" /> </intent-filter> </receiver> <!-- <activity android:name=".mainactivity" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> --> </application> </manifest> The first addition to the Manifest is a t ag t o request permissio n t o act ually receive bro adcast s o f any SMS messages. We also co mment ed o ut t he MainAct ivit y; since we aren't actually doing anything in the MainAct ivit y, we removed it from the application. We need to register our Bro adcast Receiver with the system, so we added the <receiver> tag. The tag has two attributes: the name o f o ur Bro adcast Receiver class and an enabled value. The <receiver> tag also contains an <int ent -f ilt er>. By adding this <int ent -f ilt er> to the receiver registration, we set up the Bro adcast Receiver to receive Int ent s with a particular action. We add a filter for the andro id.pro vide r.t e le pho ny.sms_received actio n, which the system bro adcasts whenever it receives a SMS message. Though it really doesn't matter in this application, we set a prio rit y o n t he int ent f ilt er just to demonstrate how it can be used with a broadcast receiver. By setting this value, you can enforce an order or a preference for the way broadcasts are received. For more information on optional attributes, see the documentation on the receiver tag and the intent-filter tag. By registering our broadcast receiver in the manifest, we allow the system to control its lifecycle. We do not have to enable or disable the broadcast receiver explicitly for it to be able to receive intents. The Android OS will take care of running its code in our application's process. The OS cannot guarantee the validity of our Bro adcast Receiver instance outside of the o nreceive method. While the system will treat the process running the Bro adcast Receiver.o nreceive code as a foreground process for the duration, if there are no other application components running after o nreceive finishes execution, the process will be considered empty and subsequently removed to free up resources. That's why the Android Documentation on the Bro adcastreceiver lifecycle warns against starting any asynchro no us o peratio ns fro m o nre ce ive the Bro adcast Receiver may no longer be valid when those operations return. Now we have an application that uses a Bro adcast Receiver to listen for and respond to a system event. There are several other system events that you can leverage in this way, including when device boot completes or a package is installed or changes. In the next section, we'll switch gears and use Bro adcast Receivers with our own application services. Creating a BroadcastReceiver for Service Events Let's get started with Bro adcast Receivers and Services. Suppose we want to make an application that listens for and displays news headlines as they come. We want to focus on the Bro adcast Receiver side of things, so we'll just

156 create a dummy service fo r the headlines. Create a new class named He adline Se rvice that extends andro id.app.se rvice and make these changes:

157 CODE TO TYPE: HeadlineService.java package com.oreillyschool.android2.broadcastreceivers; import java.util.arraylist; import java.util.arrays; import java.util.random; import java.util.timer; import java.util.timertask; import android.app.service; import android.content.intent; import android.os.ibinder; public class HeadlineService extends Service { public static final String ACTION_HEADLINE = "com.ost.android2.action.headline_sent"; public static final String EXTRA_HEADLINE = "com.ost.android2.extra.headline"; private static final int MINIMUM_HEADLINE_INTERVAL_SECONDS = 10; private static final int MAXIUMUM_HEADLINE_INTERVAL_SECONDS = 25; private static final int HEADLINE_INTERVAL_RANGE_SECONDS = MAXIUMUM_HEADLINE_INTERVAL _SECONDS - MINIMUM_HEADLINE_INTERVAL_SECONDS + 1; private static Random srandom = new Random(); private static int gettimerlength() { return (srandom.nextint(headline_interval_range_seconds) + MINIMUM_HEADLINE_INTERVA L_SECONDS) * 1000; private Timer mtimer; public IBinder onbind(intent arg0) { // TODO Auto-generated method stub return null; public void oncreate() { super.oncreate(); mtimer = new Timer(); mtimer.schedule(new BroadcastHeadlineTask(), gettimerlength()); public void ondestroy() { super.ondestroy(); if (mtimer!= null) { mtimer.cancel(); private class BroadcastHeadlineTask extends TimerTask { private ArrayList<String> mheadlines; public BroadcastHeadlineTask() { super(); mheadlines = new ArrayList<String>(Arrays.asList(getResources().getStringArray(R. array.headlines))); public void run() { Intent headlineintent = new Intent(ACTION_HEADLINE);

158 headlineintent.putextra(extra_headline, getheadline()); sendbroadcast(headlineintent); if (mheadlines.size() > 0) { mtimer.schedule(new BroadcastHeadlineTask(), gettimerlength()); else { mtimer.cancel(); mtimer = null; private String getheadline() { int index = srandom.nextint(mheadlines.size()); return mheadlines.remove(index); Now open st rings.xml and make these changes: CODE TO TYPE: /res/values/strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">broadcast Receivers</string> <string name="action_settings">settings</string> <string name="hello">hello World, MainActivity!</string> <string name="headlines_label">headlines</string> <string-array name="headlines"> <item>porcine Aeronautics Now Launching</item> <item>study Finds Apples and Oranges Are Actually Quite Alike</item> <item>ancient Tomb Discovered Contains Father of Lost Mummy</item> <item>four Pet Turtles Found inside Pizza Box in Sewers</item> <item>ashton Kocher Proclaims: I Caught Them All!</item> <item>feline/canine Precipitation Falls over Florida</item> <item>new Study: Ulnar Nerve, Not Humerus</item> </string-array> </resources> We'll need a view, so open act ivit y_main.xml and make these changes:

159 CODE TO TYPE: activity_main.xml <RelativeLinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".mainactivity" > <TextView android:layout_width="wrap_contentmatch_parent" android:layout_height="wrap_content" android:background="#cccccc" /> <ListView android:layout_width="match_parent" android:layout_height="wrap_content" /> </RelativeLinearLayout> No w, update the activity fo r the new view. Open MainAct ivit y.java and make these changes:

160 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.broadcastreceivers; import android.app.listactivity; import android.content.broadcastreceiver; import android.content.context; import android.content.intent; import android.content.intentfilter; import android.os.bundle; import android.view.menu; import android.widget.arrayadapter; public class MainActivity extends ListActivity { private NewsReceiver mnewsreceiver; private ArrayAdapter<String> madapter; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); madapter = new ArrayAdapter<String>(this, android.r.layout.simple_list_item_1); setlistadapter(madapter); startservice(new Intent(MainActivity.this, HeadlineService.class)); mnewsreceiver = new NewsReceiver(); protected void onresume() { super.onresume(); registerreceiver(mnewsreceiver, new IntentFilter(HeadlineService.ACTION_HEADLINE)); protected void onpause() { super.onpause(); unregisterreceiver(mnewsreceiver); private class NewsReceiver extends BroadcastReceiver { public void onreceive(context context, Intent intent) { if (intent.getaction().equals(headlineservice.action_headline)) { madapter.add(intent.getstringextra(headlineservice.extra_headline)); madapter.notifydatasetchanged(); public boolean oncreateoptionsmenu(menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getmenuinflater().inflate(r.menu.main, menu); return true; Finally, we can edit our manifest so that it presents only the service we want. Open Andro idmanif est.xml and make

161 these changes: CODE TO TYPE: Andro idmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.oreillyschool.android2.broadcastreceivers" android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="10" android:targetsdkversion="10" /> <uses-permission android:name="android.permission.receive_sms" /> <application android:allowbackup="true" > <service android:name=".headlineservice" /> <receiver android:name=".smsreceiver" android:enabled="true" > <intent-filter android:priority="999" > <action android:name="android.provider.telephony.sms_received" /> </intent-filter> </receiver> <!-- <activity android:name=".mainactivity" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> --> </application> </manifest> Save the modified files and run the application. You see a screen with a few "Headlines" at the top:

162 After you wait for a bit, headlines start to appear in the list at random intervals:

163 Here we start a dummy headline service that broadcasts headlines via Int ent s In our main activity, we use a Bro adcast Receiver to receive these broadcasts and display them to the user. Let's take a closer look at how we acco mplished that:

164 OBSERVE: HeadlineService.java... public class HeadlineService extends Service {... private class BroadcastHeadlineTask extends TimerTask { private ArrayList<String> mheadlines; public BroadcastHeadlineTask() { super(); mheadlines = new ArrayList<String>(Arrays.asList(getResources().getStringArray(R. array.headlines))); public void run() { Intent headlineintent = new Intent(ACTION_HEADLINE); headlineintent.putextra(extra_headline, getheadline()); sendbroadcast(headlineintent); if (mheadlines.size() > 0) { mtimer.schedule(new BroadcastHeadlineTask(), gettimerlength()); else { mtimer.cancel(); mtimer = null; private String getheadline() { int index = srandom.nextint(mheadlines.size()); return mheadlines.remove(index); The HeadlineService won't be our main focus here, but let's just take a quick look at how it works. Once it's started, the service sends out a string rando mly select ed f ro m an array o f st ring reso urces, at some random time interval. It sends the headline out by calling Co nt ext.sendbro adcast, and passing an Int ent o bject that contains o ur custo m actio n ACT ION_HEADLINE: OBSERVE: strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">broadcast Receiver</string> <string name="action_settings">settings</string> <string name="headlines_label">headlines</string> <string-array name="headlines"> <item>porcine Aeronautics Now Launching</item> <item>study Finds Apples and Oranges Are Actually Quite Alike</item> <item>ancient Tomb Discovered Contains Father of Lost Mummy</item> <item>four Pet Turtles Found inside Pizza Box in Sewers</item> <item>ashton Kocher Proclaims: I Caught Them All!</item> <item>feline/canine Precipitation Falls over Florida</item> <item>new Study: Ulnar Nerve, Not Humerus</item> </string-array> </resources> In strings.xml, we add some UI strings, including a st ring array o f f ake headlines for our dummy service:

165 OBSERVE: activity_main.xml <LinearLayout xmlns:android=" <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#cccccc" /> <ListView android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> For the layout of of our main activity in activity_main.xml, we add a T ext View at t he t o p f o r t he "Headlines" label and a List View t o ho ld t he received headlines:

166 OBSERVE: MainActivity.java... public class MainActivity extends ListActivity { private NewsReceiver mnewsreceiver; private ArrayAdapter<String> madapter; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); madapter = new ArrayAdapter<String>(this, android.r.layout.simple_list_item_1); setlistadapter(madapter); startservice(new Intent(MainActivity.this, HeadlineService.class)); mnewsreceiver = new NewsReceiver(); protected void onresume() { super.onresume(); registerreceiver(mnewsreceiver, new IntentFilter(HeadlineService.ACTION_HEADLINE)); protected void onpause() { super.onpause(); unregisterreceiver(mnewsreceiver); private class NewsReceiver extends BroadcastReceiver { public void onreceive(context context, Intent intent) { if (intent.getaction().equals(headlineservice.action_headline)) { madapter.add(intent.getstringextra(headlineservice.extra_headline)); madapter.notifydatasetchanged(); Most of the new logic is in MainAct ivit y. We change the MainAct ivit y into a subclass of andro id.app.list Act ivit y. In o ncreat e, we set up t he adapt er f o r t he list. We also st art t he HeadlineService. Finally, we inst ant iat e a bro adcast receiver. Our subclass of Bro adcast Receiver is actually an inner class, NewsReceiver. We make our receiver an inner class so that it can have access to the activity and its list. The NewsReceiver will listen for headlines broadcast by the HeadlineService and add them to the List View. In NewsReceiver.o nreceive, we pull the headline from the broadcast Int ent and then add it to the list adapter. To register and unregister our NewsReceiver, we call Co nt ext.regist erreceiver in o nresume and Co nt ext.unregist erreceiver in o npause. We place them in o nresume and o npause so that when our application is running in the background, the NewsReceiver will stop receiving broadcasts, which saves system reso urces while the activity is in the backgro und. WARNING When yo u have registered a bro adcast receiver pro grammatically with Co nt ext.regist erreceiver, you need to make sure to call Co nt ext.unregist erreceiver when you finish with the receiver, otherwise, the receiver will be leaked and the OS will throw an erro r indicating this. Also note that, in o nreceive, we verify the action of the Int ent. While we set up an Int ent Filt er to receive Int ent s where the action equals HeadlineService.ACT ION_HEADLINE, with Int ent Filt ers, an Int ent passes if its action matches any actions listed in the Int ent Filt er. If an Int ent does not have any specified action, it passes

167 automatically. Since it's possible for an action-less Int ent to pass to our Bro adcast Receiver, it's good practice to verify the Int ent action in o nreceive. OBSERVE: Andro idmanifest.xml... <application android:allowbackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/apptheme" > <service android:name=".headlineservice" /> <activity android:name=".mainactivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> Our final changes are in the Manifest. We remove the registration of the SMS receiver from the last time and add in a <service> tag to declare our HeadlineService. We also add t he MainAct ivit y back int o t he manif est. Now, we don't have to limit a Bro adcast Receiver to receiving just one action. We can change the Int ent Filt er so that the Bro adcast Receiver can receive multiple types of actions. Let's try that now. We'll make some changes to our application to include another dummy service and have our NewsReceiver handle broadcasts from it as well. Create a new class named T emperat ureservice that extends andro id.app.service, and make these changes:

168 CODE TO TYPE: TemperatureService.java package com.oreillyschool.android2.broadcastreceivers; import java.util.random; import java.util.timer; import java.util.timertask; import android.app.service; import android.content.intent; import android.os.ibinder; public class TemperatureService extends Service { public static final String ACTION_TEMPERATURE_UPDATE = "com.ost.android2.action.tempe RATURE_UPDATE"; public static final String EXTRA_TEMPERATURE = "com.ost.android2.extra.temperature"; private static final int MINIMUM_UPDATE_INTERVAL_SECONDS = 5; private static final int MAXIUMUM_UPDATE_INTERVAL_SECONDS = 10; private static final int UPDATE_INTERVAL_RANGE_SECONDS = MAXIUMUM_UPDATE_INTERVAL_SEC ONDS - MINIMUM_UPDATE_INTERVAL_SECONDS + 1; private static final int MINIMUM_TEMPERATURE = 60; private static final int TEMPERATURE_RANGE = 5; private static Random srandom = new Random(); private static int gettimerlength() { return (srandom.nextint(update_interval_range_seconds) + MINIMUM_UPDATE_INTERVAL_SE CONDS) * 1000; private Timer mtimer; public IBinder onbind(intent arg0) { // TODO Auto-generated method stub return null; public void oncreate() { super.oncreate(); mtimer = new Timer(); mtimer.schedule(new TemperatureUpdateTask(), gettimerlength()); public void ondestroy() { super.ondestroy(); if (mtimer!= null) { mtimer.cancel(); private class TemperatureUpdateTask extends TimerTask { public TemperatureUpdateTask() { super(); public void run() { Intent temperatureintent = new Intent(ACTION_TEMPERATURE_UPDATE); int temperature = gettemperature(); temperatureintent.putextra(extra_temperature, temperature);

169 sendbroadcast(temperatureintent); mtimer.schedule(new TemperatureUpdateTask(), gettimerlength()); private int gettemperature() { int change = srandom.nextint(temperature_range); return MINIMUM_TEMPERATURE + change; Next, open st rings.xml and make these changes: CODE TO TYPE: strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">broadcast Receiver</string> <string name="action_settings">settings</string> <string name="headlines_label">headlines</string> <string name="temperature_format">temperature: %d F</string> <string name="temperature_unavailable">temperature: N/A</string> <string-array name="headlines"> <item>porcine Aeronautics Now Launching</item> <item>study Finds Apples and Oranges Are Actually Quite Alike</item> <item>ancient Tomb Discovered Contains Father of Lost Mummy</item> <item>four Pet Turtles Found inside Pizza Box in Sewers</item> <item>ashton Kocher Proclaims: I Caught Them All!</item> <item>feline/canine Precipitation Falls over Florida</item> <item>new Study: Ulnar Nerve, Not Humerus</item> </string-array> </resources> Now open act ivit y_main.xml and make these changes:

170 CODE TO TYPE: activity_main.xml <LinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".mainactivity" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#cccccc" /> <ListView android:layout_width="match_parent" android:layout_height="match_parent0dp" /> android:layout_weight="1" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#3300ff" /> </LinearLayout> Open MainAct ivit y.java and make these changes:

171 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.broadcastreceivers; import android.app.listactivity; import android.content.broadcastreceiver; import android.content.context; import android.content.intent; import android.content.intentfilter; import android.os.bundle; import android.widget.arrayadapter; import android.widget.textview; public class MainActivity extends ListActivity { private NewsReceiver mnewsreceiver; private ArrayAdapter<String> madapter; private TextView mtemperaturetext; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); madapter = new ArrayAdapter<String>(this, android.r.layout.simple_list_item_1); setlistadapter(madapter); mtemperaturetext = (TextView) findviewbyid(r.id.temperature_text); startservice(new Intent(MainActivity.this, HeadlineService.class)); startservice(new Intent(MainActivity.this, TemperatureService.class)); mnewsreceiver = new NewsReceiver(); protected void onresume() { super.onresume(); E)); registerreceiver(mnewsreceiver, new IntentFilter(HeadlineService.ACTION_HEADLIN IntentFilter newsfilter = new IntentFilter(); newsfilter.addaction(headlineservice.action_headline); newsfilter.addaction(temperatureservice.action_temperature_update); registerreceiver(mnewsreceiver, newsfilter); protected void onpause() { super.onpause(); unregisterreceiver(mnewsreceiver); private class NewsReceiver extends BroadcastReceiver { public void onreceive(context context, Intent intent) { if (intent.getaction().equals(headlineservice.action_headline)) { madapter.add(intent.getstringextra(headlineservice.extra_headline)); madapter.notifydatasetchanged(); else if (intent.getaction().equals(temperatureservice.action_temperature_ UPDATE)) { int temperature = intent.getintextra(temperatureservice.extra_temperatu RE, Integer.MIN_VALUE); mtemperaturetext.settext(temperature!= Integer.MIN_VALUE? getstring(r.string.temperature_format, temperature)

172 : getstring(r.string.temperature_unavailable)); Finally, make this change to Andro idmanif est.xml: CODE TO TYPE: Andro idmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.oreillyschool.android2.broadcastreceivers;" android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="10" android:targetsdkversion="10" /> <application android:allowbackup="true" > <service android:name=".headlineservice" /> <service android:name=".temperatureservice" /> <activity android:name=".mainactivity" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> Save all the modified files and run the application. When the application starts, you see a blank headline list like before. You also see a blue box at the bottom with a dummy temperature reading. When the application is launched, the value is "N/A" because o ur applicatio n has no t received any temperature bro adcasts yet.

173 After a while, the headlines po pulate like they did befo re. Eventually the temperature will get a valid value and the occasional update will take place:

174 So now we have two services broadcasting, our application receives those broadcasts, and updates the UI. Let's take a closer look at how we did that.

175 OBSERVE: TemperatureService.java... private class TemperatureUpdateTask extends TimerTask { public TemperatureUpdateTask() { super(); public void run() { Intent temperatureintent = new Intent(ACTION_TEMPERATURE_UPDATE); int temperature = gettemperature(); temperatureintent.putextra(extra_temperature, temperature); sendbroadcast(temperatureintent); mtimer.schedule(new TemperatureUpdateTask(), gettimerlength()); private int gettemperature() { int change = srandom.nextint(temperature_range); return MINIMUM_TEMPERATURE + change; The T emperat ureservice is similar to the HeadlineService, only instead of broadcasting an Int ent with a random headline, the T emperat ureservice broadcasts a rando m t emperat ure reading. OBSERVE: Andro idmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.oreillyschool.android2.broadcastreceivers" android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="10" android:targetsdkversion="10" /> <application android:allowbackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/apptheme" > <service android:name=".headlineservice" /> <service android:name=".temperatureservice" /> <activity android:name=".mainactivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> In the Manifest, we add the declarat io n f o r t he T emperat ureservice.

176 OBSERVE: activity_main.xml <LinearLayout xmlns:android=" <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#cccccc" /> <ListView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#3300ff" /> </LinearLayout> In the main activity's layout, we add a T ext View at the bottom and change the List View to fill the space above it in the LinearLayo ut.

177 OBSERVE: MainActivity.java... protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); madapter = new ArrayAdapter<String>(this, android.r.layout.simple_list_item_1); setlistadapter(madapter); mtemperaturetext = (TextView) findviewbyid(r.id.temperature_text); startservice(new Intent(MainActivity.this, HeadlineService.class)); startservice(new Intent(MainActivity.this, TemperatureService.class)); mnewsreceiver = new NewsReceiver(); protected void onresume() { super.onresume(); IntentFilter newsfilter = new IntentFilter(); newsfilter.addaction(headlineservice.action_headline); newsfilter.addaction(temperatureservice.action_temperature_update); registerreceiver(mnewsreceiver, newsfilter); protected void onpause() { super.onpause(); unregisterreceiver(mnewsreceiver); private class NewsReceiver extends BroadcastReceiver { public void onreceive(context context, Intent intent) { if (intent.getaction().equals(headlineservice.action_headline)) { madapter.add(intent.getstringextra(headlineservice.extra_headline)); madapter.notifydatasetchanged(); else if (intent.getaction().equals(temperatureservice.action_temperature_update )) { int temperature = intent.getintextra(temperatureservice.extra_temperature, Integer.MIN_VALUE); mtemperaturetext.settext(temperature!= Integer.MIN_VALUE? getstring(r.string.temperature_format, temperature) : getstring(r.string.temperature_unavailable)); Again, the majority of our work and changes are in the MainAct ivit y class. In o ncreat e, we add a call to st art t he T emperat ureservice. In o nresume, we change how we instantiate the Int ent Filt er. We add t wo act io ns t o t he Int ent Filt er: HeadlineService.ACT ION_HEADLINE f o r t he HeadlineService and T emperat ureservice.act ION_T EMPERAT URE_UPDAT E f o r t he t emperat ure. An Int ent with either action will pass the Int ent Filt er and pass to o nreceive. When it receives a broadcast, o nreceive checks t he act io n and either updat es t he list wit h a headline or updat es t he t emperat ure t ext. So now we can handle a number of different kinds of Int ent s with the same Bro adcast Receiver. We can also use a Bro adcast Receiver to handle system events. When Co nt ext.sendbro adcast is called, it's possible for any registered Bro adcast Receiver to receive the Int ent. This includes Bro adcast Receivers in other applications. In other words, Co nt ext.sendbro adcast is sent globally across the system.

178 You will often find that you just need to broadcast between components in the same application. If this is the case, there is a better way to send and receive broadcasts that should remain local to an application: Lo calbro adcast Manager. We'll look at that next. Using the LocalBroadcastManager The Lo calbro adcast Manager is a class that has its own implementation of sendbro adcast, regist erreceiver, and unregist erreceiver. Int ent s broadcasted via the Lo calbro adcast Manager can only be received by Bro adcast Receivers that were registered with the Lo calbro adcast Manager. Also, Bro adcast Receivers registered with the Lo calbro adcast Manager cannot receive Int ent s sent by Co nt ext.sendbro adcast. If you only need broadcasting between components in the same application, use the Lo calbro adcast Manager. Using Lo calbro adcast Manager prevents data from your application from being broadcast to other applications and it prevents other applications broadcasting to yours. It's also more efficient than using a Co nt ext to broadcast. Let's add Lo calbro adcast Manager to our project. Open T emperat ureservice.java and make these changes:

179 CODE TO TYPE: TemperatureService.java package com.oreillyschool.android2.broadcastreceivers; import java.util.random; import java.util.timer; import java.util.timertask; import android.app.service; import android.content.intent; import android.os.ibinder; import android.support.v4.content.localbroadcastmanager; public class TemperatureService extends Service { public static final String ACTION_TEMPERATURE_UPDATE = "com.ost.android2.action.tempe RATURE_UPDATE"; public static final String EXTRA_TEMPERATURE = "com.ost.android2.extra.temperature"; private static final int MINIMUM_UPDATE_INTERVAL_SECONDS = 5; private static final int MAXIUMUM_UPDATE_INTERVAL_SECONDS = 10; private static final int UPDATE_INTERVAL_RANGE_SECONDS = MAXIUMUM_UPDATE_INTERVAL_SEC ONDS - MINIMUM_UPDATE_INTERVAL_SECONDS + 1; private static final int MINIMUM_TEMPERATURE = 60; private static final int TEMPERATURE_RANGE = 5; private static Random srandom = new Random(); private static int gettimerlength() { return (srandom.nextint(update_interval_range_seconds) + MINIMUM_UPDATE_INTERVAL_SE CONDS) * 1000; private Timer mtimer; public IBinder onbind(intent arg0) { return null; public void oncreate() { super.oncreate(); mtimer = new Timer(); mtimer.schedule(new TemperatureUpdateTask(), gettimerlength()); public void ondestroy() { super.ondestroy(); if (mtimer!= null) { mtimer.cancel(); private class TemperatureUpdateTask extends TimerTask { public TemperatureUpdateTask() { super(); public void run() { Intent temperatureintent = new Intent(ACTION_TEMPERATURE_UPDATE); int temperature = gettemperature(); temperatureintent.putextra(extra_temperature, temperature);

180 sendbroadcast(temperatureintent); LocalBroadcastManager.getInstance(TemperatureService.this).sendBroadcast(temperat ureintent); mtimer.schedule(new TemperatureUpdateTask(), gettimerlength()); private int gettemperature() { int change = srandom.nextint(temperature_range); return MINIMUM_TEMPERATURE + change; Now open HeadlineService.java and make these changes: CODE TO TYPE: HeadlineService.java package com.oreillyschool.android2.broadcastreceivers; import java.util.arraylist; import java.util.arrays; import java.util.random; import java.util.timer; import java.util.timertask; import android.app.service; import android.content.intent; import android.os.ibinder; import android.support.v4.content.localbroadcastmanager; public class HeadlineService extends Service {... private class BroadcastHeadlineTask extends TimerTask { public BroadcastHeadlineTask() { super(); public void run() { Intent headlineintent = new Intent(ACTION_HEADLINE); String headline = getheadline(); headlineintent.putextra(extra_headline, headline); sendbroadcast(headlineintent); LocalBroadcastManager.getInstance(HeadlineService.this).sendBroadcast(headlineInt ent); if (mheadlines.size() > 0) { mtimer.schedule(new BroadcastHeadlineTask(), gettimerlength()); else { mtimer.cancel(); mtimer = null;... Finally, make these changes to MainAct ivit y.java:

181 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.broadcastreceivers; import android.app.listactivity; import android.content.broadcastreceiver; import android.content.context; import android.content.intent; import android.content.intentfilter; import android.os.bundle; import android.support.v4.content.localbroadcastmanager; import android.widget.arrayadapter; import android.widget.textview; public class MainActivity extends ListActivity { private NewsReceiver mnewsreceiver; private ArrayAdapter<String> madapter; private TextView mtemperaturetext; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); madapter = new ArrayAdapter<String>(this, android.r.layout.simple_list_item_1); setlistadapter(madapter); mtemperaturetext = (TextView) findviewbyid(r.id.temperature_text); startservice(new Intent(MainActivity.this, HeadlineService.class)); startservice(new Intent(MainActivity.this, TemperatureService.class)); mnewsreceiver = new NewsReceiver(); IntentFilter newsfilter = new IntentFilter(); newsfilter.addaction(headlineservice.action_headline); newsfilter.addaction(temperatureservice.action_temperature_update); LocalBroadcastManager.getInstance(MainActivity.this).registerReceiver(mNewsReceiver, newsfilter); protected void ondestroy() { super.ondestroy(); LocalBroadcastManager.getInstance(MainActivity.this).unregisterReceiver(mNewsReceiv er); protected void onresume() { super.onresume(); IntentFilter newsfilter = new IntentFilter(); newsfilter.addaction(headlineservice.action_headline); newsfilter.addaction(temperatureservice.action_temperature_update); registerreceiver(mnewsreceiver, newsfilter); protected void onpause() { super.onpause(); unregisterreceiver(mnewsreceiver);

182 private class NewsReceiver extends BroadcastReceiver { public void onreceive(context context, Intent intent) { if (intent.getaction().equals(headlineservice.action_headline)) { madapter.add(intent.getstringextra(headlineservice.extra_headline)); madapter.notifydatasetchanged(); else if (intent.getaction().equals(temperatureservice.action_temperature_update )) { int temperature = intent.getintextra(temperatureservice.extra_temperature, Integer.MIN_VALUE); mtemperaturetext.settext(temperature!= Integer.MIN_VALUE? getstring(r.string.temperature_format, temperature) : getstring(r.string.temperature_unavailable)); Save all modified files and run the application. As before, you initially see a blank screen and temperature field, and the screen eventually fills with broadcasts as they enter. If you had hit the "Home" button or in some other way put the activity in the backgro und, the activity wo uld have sto pped receiving headline bro adcasts because we unregistered the Bro adcast Receiver in o npause. So, you would have conceivably missed broadcasts while the activity was in the background. This time even if the application is in the background, you will be able to receive broadcasts. Let's review our code to examine how and why we switched to Lo calbro adcast Manager: OBSERVE: TemperatureService.java... private class TemperatureUpdateTask extends TimerTask { public TemperatureUpdateTask() { super(); public void run() { Intent temperatureintent = new Intent(ACTION_TEMPERATURE_UPDATE); int temperature = gettemperature(); temperatureintent.putextra(extra_temperature, temperature); LocalBroadcastManager.getInstance(TemperatureService.this).sendBroadcast(temperat ureintent); mtimer.schedule(new TemperatureUpdateTask(), gettimerlength()); private int gettemperature() { int change = srandom.nextint(temperature_range); return MINIMUM_TEMPERATURE + change; In T emperat ureservice.java, switching to the Lo calbro adcast Manager required replacing the original call to Co nt ext.sendbro adcast with a call to Lo calbro adcast Manager. The Lo calbro adcast Manager is actually a singleton, so first we use Lo calbro adcast Manager.get Inst ance(andro id.co nt ent.co nt ext ) to get a reference to it and then call Lo calbro adcast Manager.sendBro adcast with the same Int ent as before:

183 OBSERVE: HeadlineService.java... private class BroadcastHeadlineTask extends TimerTask { public BroadcastHeadlineTask() { super(); public void run() { Intent headlineintent = new Intent(ACTION_HEADLINE); String headline = getheadline(); headlineintent.putextra(extra_headline, headline); LocalBroadcastManager.getInstance(HeadlineService.this).sendBroadcast(headlineInt ent); if (mheadlines.size() > 0) { mtimer.schedule(new BroadcastHeadlineTask(), gettimerlength()); else { mtimer.cancel(); mtimer = null; private String getheadline() { int index = srandom.nextint(mheadlines.size()); return mheadlines.remove(index); For HeadlineService.java, we make the same changes as for T emperat ureservice.java: we remove the previous call to Co nt ext.sendbro adcast and replace it with a call to Lo calbro adcast Manager.sendBro adcast, keeping the Int ent the same as before:

184 OBSERVE: MainActivity.java... public class MainActivity extends ListActivity { private NewsReceiver mnewsreceiver; private ArrayAdapter<String> madapter; private TextView mtemperaturetext; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); madapter = new ArrayAdapter<String>(this, android.r.layout.simple_list_item_1); setlistadapter(madapter); mtemperaturetext = (TextView) findviewbyid(r.id.temperature_text); startservice(new Intent(MainActivity.this, HeadlineService.class)); startservice(new Intent(MainActivity.this, TemperatureService.class)); mnewsreceiver = new NewsReceiver(); IntentFilter newsfilter = new IntentFilter(); newsfilter.addaction(headlineservice.action_headline); newsfilter.addaction(temperatureservice.action_temperature_update); LocalBroadcastManager.getInstance(MainActivity.this).registerReceiver(mNewsReceiver, newsfilter); protected void ondestroy() { super.ondestroy(); LocalBroadcastManager.getInstance(MainActivity.this).unregisterReceiver(mNewsReceiv er); private class NewsReceiver extends BroadcastReceiver { public void onreceive(context context, Intent intent) { if (intent.getaction().equals(headlineservice.action_headline)) { madapter.add(intent.getstringextra(headlineservice.extra_headline)); madapter.notifydatasetchanged(); else if (intent.getaction().equals(temperatureservice.action_temperature_update )) { int temperature = intent.getintextra(temperatureservice.extra_temperature, Integer.MIN_VALUE); mtemperaturetext.settext(temperature!= Integer.MIN_VALUE? getstring(r.string.temperature_format, temperature) : getstring(r.string.temperature_unavailable)); For MainAct ivit y.java, we made some more significant changes. We removed our implementations of o nresume and o npause. Instead, we mo ved creat io n o f t he Int ent Filt er and t he NewsReceiver t o o ncreat e. We call Lo calbro adcast Manager.regist erreceiver in oncreate as well. To make sure that we aren't in danger of leaking the NewsReceiver instance, we put a matching call to Lo calbro adcast Manager.unregist erreceiver in o ndest ro y. Since we placed the register and unregister calls in o ncreat e and o ndest ro y instead of in o nresume and o npause, our activity was still able to receive headline and temperature broadcasts even when it was in the backgro und. You have some options for how and where you register a Bro adcast Receiver. Your decision depends on your application needs, and performance and security considerations. If you are keeping broadcasts limited to your own

185 application needs, and performance and security considerations. If you are keeping broadcasts limited to your own application components, Lo calbro adcast Manager is the best option for you. Wrapping Up In this lesson, we went through the basics of utilizing the Bro adcast Receiver class. It's relatively straightforward to use and provides much flexibility. You can choose to register your receiver in the Android manifest or in code. You can choose to use the Co nt ext broadcast methods or use Lo calbro adcast Manager. You can listen for broadcasts from your own application, from other applications, and from the system. You can use an inner class for your Bro adcast Receiver or a stand-alone. The Bro adcast Receiver is a powerful tool for communication, but the trick is to use the options and implementatio ns that best fit yo ur applicatio n, and make sure that yo u always co nsider efficiency and security. Also, co nsider the lifecycle o f Bro adcast Re ce ive r instances, particularly when yo u register in the Andro id manifest. Well, that should get you a good start on Bro adcast Receivers. See you next lesson! Copyright O'Reilly Media, Inc. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. See for more information.

186 Media: Audio Lesson Objectives At the end of this lesson, you'll be able to: create a MediaPlayer and play an audio file. understand the different states o f the MediaPlayer. utilize the MediaPlayer co rrectly within the Activity lifecycle. respo nd to MediaPlayer events. In this lesson, we'll discuss how you can play and manage audio with the Android MediaPlayer. The MediaPlayer is used to play bo th audio and video fro m either files o r streams. The MediaPlayer is essentially a state machine. The vario us o peratio ns that you can call on a MediaPlayer send it into one of many states. The operations that you can call are dependent on the current state. For a good diagram showing the various MediaPlayer states and the methods move you between each state, take a look at the Android Developer Documentation for the MediaPlayer. The MediaPlayer is flexible, allo wing yo u to play lo cal files as well as HTTP streaming. It pro vides basic playback functio nality: start, stop, pause, seek, and loop. There are also several callbacks that allow your application to react to different events like buffering, seeking, completion, and so on. Creating a MediaPlayer and Playing an Audio File Create a new Android project with the following criteria. Name the project MediaAudio. Use the package name co m.o reillyscho o l.andro id2.mediaaudio. Uncheck the Creat e cust o m launcher ico n box. Assign the Andro id2_lesso ns working set to the project. First, we'll start setting up the UI. Modify st rings.xml as shown: CODE TO TYPE: /res/values/strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">mediaplayer Audio</string> <string name="action_settings">settings</string> <string name="hello_world">hello World, MainActivity!</string> <string name="start_button_label">start</string> <string name="stop_button_label">stop</string> <string name="song_01_info">"persephone" by snowflake (feat. Vidian, Dimitri Artemenk o)\nhttp://ccmixter.org/files/snowflake/22364\n\nlicensed under a Creative Commons lice nse:\nhttp://creativecommons.org/licenses/by/2.5/</string> <string name="error_io_message">there was a problem opening this file.</string> <string name="error_illegal_state_start_message">tried to start MediaPlayer in illega l state.</string> </resources> Next, modify act ivit y_main.xml as shown:

187 CODE TO TYPE: /res/layo ut/activity_main.xml <RelativeLinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="15dp" android:orientation="vertical" tools:context=".mainactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginleft="5dp" android:layout_marginright="5dp" android:layout_marginbottom="15dp" android:textsize="4pt" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" > <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> </LinearLayout> </RelativeLinearLayout> Now let's add an audio file to our project to play. To download the audio file, right-click on the link below and save the file to the project's /res/raw folder: Download Audio File. Note The project folders are located on the V drive in the /wo rkspace folder; the full path where you'll save the image is: V:\wo rkspace\mediaaudio \res\raw. You may need to create the /raw folder. Finally, make these changes to MainAct ivit y:

188 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.mediaaudio; import java.io.ioexception; import android.app.activity; import android.media.mediaplayer; import android.os.bundle; import android.view.menu; import android.view.view; import android.view.view.onclicklistener; import android.widget.button; import android.widget.toast; public class MainActivity extends Activity { private MediaPlayer mmediaplayer; private Button mstartbutton; private Button mstopbutton; public void start() { mmediaplayer.start(); // MediaPlayer is started. mstartbutton.setenabled(false); mstopbutton.setenabled(true); public void stop() { mmediaplayer.stop(); // MediaPlayer is stopped. mstartbutton.setenabled(true); mstopbutton.setenabled(false); private OnClickListener mstartonclicklistener = new OnClickListener() { public void onclick(view v) { start(); ; private OnClickListener mstoponclicklistener = new OnClickListener() { public void onclick(view v) { stop(); ; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mstartbutton = (Button)findViewById(R.id.start_button); mstopbutton = (Button)findViewById(R.id.stop_button); mstartbutton.setonclicklistener(mstartonclicklistener); mstopbutton.setonclicklistener(mstoponclicklistener); // Disabling start/stop buttons before MediaPlayer gets set up, in case it doesn't set up properly. mstartbutton.setenabled(false); mstopbutton.setenabled(false); mmediaplayer = new MediaPlayer(); // MediaPlayer is idle. try { mmediaplayer.setdatasource(getresources().openrawresourcefd(r.raw.persephone_by_s nowflake).getfiledescriptor()); // MediaPlayer is initialized. mmediaplayer.prepare(); // MediaPlayer is prepared. mstartbutton.setenabled(true);

189 catch (IOException ioe) { Toast.makeText(this, R.string.error_io_message, Toast.LENGTH_LONG); public boolean oncreateoptionsmenu(menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getmenuinflater().inflate(r.menu.main, menu); return true; Save all modified files and run the application. You see some text (which contains attribution for the audio we're using) and St art and St o p buttons. If you click St art, the music starts to play. After the music starts to play, you will be able to click St o p to stop the music.

190 Note In this lesson, we'll use a lot of audio and video files. If you try to run the application in the emulator and you get an INST ALL_FAILED_INSUFFICIENT _ST ORAGE error in the console, you need to increase the amount of storage in the emulator. To do this, select Run Debug Co nf igurat io ns,... In the Debug Co nf igurat io ns window, select the T arget tab. In the Emulator Launch Parameters section, in the Additional Emulator Command Line Options box, enter -part it io n-size The partition size is in megabytes; it's good practice to make sure that it is twice as big as your APK size. Let's take a closer look at the MediaPlayer code in MainAct ivit y:

191 OBSERVE: MainActivity.java... public class MainActivity extends Activity { private MediaPlayer mmediaplayer; private Button mstartbutton; private Button mstopbutton; public void start() { mmediaplayer.start(); // MediaPlayer is started. tton.setenabled(false); mstopbutton.setenabled(true); public void stop() { mmediaplayer.stop(); // MediaPlayer is stopped. mstartbutton.setenabled(true); mstopbutton.setenabled(false); private OnClickListener mstartonclicklistener = new OnClickListener() { public void onclick(view v) { start() ; private OnClickListener mstoponclicklistener = new OnClickListener() { public void onclick(view v) { stop(); ; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mstartbutton = (Button)findViewById(R.id.start_button); mstopbutton = (Button)findViewById(R.id.stop_button); mstartbutton.setonclicklistener(mstartonclicklistener); mstopbutton.setonclicklistener(mstoponclicklistener); // Disabling start/stop buttons before MediaPlayer gets set up, in case it doesn't set up properly. mstartbutton.setenabled(false); mstopbutton.setenabled(false); mmediaplayer = new MediaPlayer(); // MediaPlayer is idle. try { mmediaplayer.setdatasource(getresources().openrawresourcefd(r.raw.persephone_by_s nowflake).getfiledescriptor()); // MediaPlayer is initialized. mmediaplayer.prepare(); // MediaPlayer is prepared. mstartbutton.setenabled(true); catch (IOException ioe) { Toast.makeText(this, R.string.error_io_message, Toast.LENGTH_LONG); In the o ncreat e() method, we inst ant iat e an inst ance o f t he MediaPlayer using t he new keywo rd. After we instantiate the MediaPlayer, it is in the Idle state. Next, we set t he dat a so urce f o r t he MediaPlayer by calling set Dat aso urce wit h a FileDescript o r o bject creat ed f ro m t he id o f t he raw reso urce we saved t o t he pro ject : R.raw.persepho ne_by_sno wf lake. After setting the data source, the MediaPlayer is in the Initialized state.

192 The MediaPlayer, however, is still not set up for playback. For that, we call prepare() o n t he MediaPlayer. Now, the MediaPlayer is in a state that can play the audio file, so we enable t he St art but t o n. We wired up the St art but t o n t o call st art () and the St o p but t o n t o call st o p(). These methods do the work of calling MediaPlayer.st art () and MediaPlayer.st o p(), as well as enabling/disabling the "Start"/"Stop" buttons as appropriate. When we call MediaPlayer.st art, the MediaPlayer transitions to the Started state and now plays the file. So just to review, our MediaPlayer went fro m Idle (o n creatio n) to Initialized (after we set the data so urce) to Prepared (after we called MediaPlayer.prepare()) to Started (after we call MediaPlayer.start()). There's a problem with our code though. If you click St art again after clicking St o p, the music will not replay. In fact, if you go to LogCat, you'll see an error messages: After yo u call Me diaplaye r.st o p(), the MediaPlayer enters into the Sto pped state. In this state, the MediaPlayer is no longer prepared for playback. In order to start the playback again, we need to call MediaPlayer.prepare() again before calling MediaPlayer.st art (). Keep in mind that in music apps there is a convention that differentiates "stopping" from "pausing": "stopping" would move the play position back to the beginning, whereas "pausing" merely halts playback and maintains the same position in the song. The MediaPlayer.st o p() method does not move the MediaPlayer's position back to the beginning; it moves the MediaPlayer's state to the Stopped state. MediaPlayer does have a pause() method which stops playback, maintains the current position, and moves the MediaPlayer to the Paused state. Ho wever, the MediaPlayer can mo ve back to the Started state fro m the Paused just by calling MediaPlayer.st art (), rather than having to call MediaPlayer.prepare() again. Be aware of the difference between the convention of "stop" in a music player and what actually happens after MediaPlayer.st o p(). As we change our code, let's use MediaPlayer.pause(). Also, if you start the music and go to the home screen, the music still plays. For this MediaPlayer, let's say we want the music to stop when the application isn't visible. We'll need to make a few more changes to take make that happen. Handling MediaPlayer State and the Activity Lifecycle Our application doesn't handle changing of orientation or pausing/resuming yet. It is vital that we know how to handle these changes though, because they can have a big impact on the MediaPlayer and its state, so in this section, we'll impro ve o ur player's functio n by managing these scenario s. Let's change from a Start/Stop concept to a more conventional Play/Pause/Stop concept. Make these changes to st rings.xml:

193 CODE TO TYPE: strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">mediaplayer Audio</string> <string name="action_settings">settings</string> <string name="start_button_label">startplay</string> <string name="stop_button_label">stop</string> <string name="pause_button_label">pause</string> <string name="song_01_info">"persephone" by snowflake (feat. Vidian, Dimitri Arteme nko)\nhttp://ccmixter.org/files/snowflake/22364\n\nlicensed under a Creative Commons li cense:\nhttp://creativecommons.org/licenses/by/2.5/</string> <string name="error_io_message">there was a problem opening this file.</string> <string name="error_illegal_state_start_message">tried to start MediaPlayer in ille gal state.</string> </resources> Now make these changes to MainAct ivit y:

194 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.mediaaudio; import java.io.ioexception; import android.app.activity; import android.media.mediaplayer; import android.os.bundle; import android.view.view; import android.view.view.onclicklistener; import android.widget.button; import android.widget.toast; public class MainActivity extends Activity { private MediaPlayer mmediaplayer; private Button mstartbutton; private Button mstopbutton; private boolean mwasplaying; public void start() { public void play() { mmediaplayer.start(); // MediaPlayer is started. mstartbutton.settext(getresources().getstring(r.string.pause_button_label)); mstartbutton.setenabled(false); mstopbutton.setenabled(true); public void pause() { mmediaplayer.pause(); // MediaPlayer is paused. mstartbutton.settext(getresources().getstring(r.string.start_button_label)); public void stop() { mmediaplayer.stop(); // MediaPlayer is stopped. mmediaplayer.pause(); // MediaPlayer is paused. mmediaplayer.seekto(0); mstartbutton.settext(getresources().getstring(r.string.start_button_label)); mstartbutton.setenabled(true); mstopbutton.setenabled(false); private OnClickListener mstartonclicklistener = new OnClickListener() { public void onclick(view v) { start(); if (mmediaplayer.isplaying()) { pause(); else { play(); ; private OnClickListener mstoponclicklistener = new OnClickListener() { public void onclick(view v) { stop(); ; /** Called when the activity is first created. */ public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main);

195 mstartbutton = (Button)findViewById(R.id.start_button); mstopbutton = (Button)findViewById(R.id.stop_button); mstartbutton.setonclicklistener(mstartonclicklistener); mstopbutton.setonclicklistener(mstoponclicklistener); mstartbutton.settext(getresources().getstring(r.string.start_button_label)); // Disabling start/stop buttons before MediaPlayer gets set up, // in case it doesn't set up properly. mstartbutton.setenabled(false); mstopbutton.setenabled(false); mmediaplayer = new MediaPlayer(); // MediaPlayer is idle. try { mmediaplayer.setdatasource(getresources().openrawresourcefd(r.raw.persephone_by_s nowflake).getfiledescriptor()); // MediaPlayer is initialized. mmediaplayer.prepare(); // MediaPlayer is prepared. mstartbutton.setenabled(true); catch (IOException ioe) { Toast.makeText(this, R.string.error_io_message, Toast.LENGTH_LONG); mmediaplayer = MediaPlayer.create(this, R.raw.persephone_by_snowflake); // MediaPla yer is prepared. if (mmediaplayer!= null) { mstartbutton.setonclicklistener(mstartonclicklistener); mstopbutton.setonclicklistener(mstoponclicklistener); else { mstartbutton.setenabled(false); protected void onpause() { super.onpause(); mwasplaying = mmediaplayer.isplaying(); if (mwasplaying) { mmediaplayer.pause(); protected void onresume() { super.onresume(); if (mwasplaying) { play(); protected void onsaveinstancestate(bundle outstate) { super.onsaveinstancestate(outstate); outstate.putboolean("isplaying", mmediaplayer.isplaying()); outstate.putint("progress", mmediaplayer.getcurrentposition()); protected void onrestoreinstancestate(bundle savedinstancestate) { super.onrestoreinstancestate(savedinstancestate); mwasplaying = savedinstancestate.getboolean("isplaying"); mmediaplayer.seekto(savedinstancestate.getint("progress")); if (!mwasplaying && savedinstancestate.getint("progress") > 0) { mstartbutton.settext(getresources().getstring(r.string.start_button_label));

196 Now save the modified files and run the application. Instead of St art and St o p buttons, we now have Play and St o p buttons. When you press Play, the music begins to play and the Play button changes to Pause. Pressing Pause will halt the playback at its current position. This button will continue to toggle back and forth between Play and Pause as you click. If you press St o p, instead of just pausing the music, the playback stops and the next time you press Play the audio file plays from the beginning. When you rotate the emulator, the application maintains the correct state; if the audio was playing before the rotation, it will play after the rotation. If the audio was stopped or paused, it will be the same after the rotation. Similarly, if you go to the home screen and then come back, the application should be in the same state as when you left. Let's go over the changes we just made:

197 OBSERVE: MainActivity.java... public class MainActivity extends Activity {... private MediaPlayer mmediaplayer; private Button mstartbutton; private Button mstopbutton; private boolean mwasplaying; public void play() { mmediaplayer.start(); // MediaPlayer is started. mstartbutton.settext(getresources().getstring(r.string.pause_button_label)); mstopbutton.setenabled(true); public void pause() { mmediaplayer.pause(); // MediaPlayer is paused. mstartbutton.settext(getresources().getstring(r.string.start_button_label)); public void stop() { mmediaplayer.pause(); // MediaPlayer is paused. mmediaplayer.seekto(0); mstartbutton.settext(getresources().getstring(r.string.start_button_label)); mstartbutton.setenabled(true); mstopbutton.setenabled(false); public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mstartbutton = (Button)findViewById(R.id.start_button); mstopbutton = (Button)findViewById(R.id.stop_button); mstartbutton.settext(getresources().getstring(r.string.start_button_label)); mstopbutton.setenabled(false); mmediaplayer = MediaPlayer.create(this, R.raw.persephone_by_snowflake); // MediaPla yer is prepared. if (mmediaplayer!= null) { mstartbutton.setonclicklistener(mstartonclicklistener); mstopbutton.setonclicklistener(mstoponclicklistener); else { mstartbutton.setenabled(false); protected void onpause() { super.onpause(); mwasplaying = mmediaplayer.isplaying(); if (mwasplaying) { mmediaplayer.pause(); protected void onresume() { super.onresume(); if (mwasplaying) { play();

198 protected void onsaveinstancestate(bundle outstate) { super.onsaveinstancestate(outstate); outstate.putboolean("isplaying", mmediaplayer.isplaying()); outstate.putint("progress", mmediaplayer.getcurrentposition()); protected void onrestoreinstancestate(bundle savedinstancestate) { super.onrestoreinstancestate(savedinstancestate); mwasplaying = savedinstancestate.getboolean("isplaying"); mmediaplayer.seekto(savedinstancestate.getint("progress")); if (!mwasplaying &&savedinstancestate.getint("progress") > 0) { mstartbutton.settext(getresources().getstring(r.string.start_button_label)); In o ncre at e, instead o f creating the MediaPlayer using ne w and manually preparing the MediaPlayer and catching exceptions, we use t he st at ic creat e met ho d. Since we're only playing one resource audio file, MediaPlayer.creat e simplifies the work for us. It takes care of initializing and preparing the audio file, and if there are any pro blems, it returns null. Since the MediaPlayer relies o n state, yo u need to be aware that a MediaPlayer returned by the static MediaPlayer.creat e method is already in the Prepared state. Also, because we use the MediaPlayer.creat e method, the MediaPlayer object could potentially be null, so we must do a "null-check" o n t he mmediaplayer before actually regist ering t he list eners. We also change st art () to play() and add pause() to better fit music player conventions. Inside pause() and st o p() we use MediaPlayer.pause() rather than Me diaplaye r.st o p() to keep the MediaPlayer prepared fo r further playback instead o f having to re-call Me diaplaye r.pre pare() repeatedly when we want to play the audio file again. We also implement overrides for o npause and o nresume so that when either the orientation changes or the application pauses, we can stop the player and restart it if the audio file is already playing. We also implement overrides for o nsaveinst ancest at e and o nrest o reinst ancest at e to reset the MediaPlayer's progress in case the entire MainActivity is recreated. Since the MediaPlayer is so sensitive to its state, we do n't want to make excessive or unnecessary method calls on it which might cause us to lose track of its state. Therefore, instead of starting or pausing the MediaPlayer bo th when MainActivity pauses/resumes and when it saves/resto res its state, we use a new private variable, m WasPlaying to help us track the state. Handling MediaPlayer Events and UI Updates Aside from the methods used to command the player, MediaPlayer also declares several listeners so that you can implement callbacks fo r different player events. Fo r example, yo ur applicatio n can listen fo r when the MediaPlayer seeks a new playback position or when the media file completes playback. In this section, we'll use these callbacks to add a seek bar and clean up o ur player's behavio r. Let's get started! Make these changes to act ivit y_main.xml:

199 CODE TO TYPE: activity_main.xml <LinearLayout xmlns:android=" <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginleft="5dp" android:layout_marginright="5dp" android:layout_marginbottom="15dp" android:textsize="4pt" /> <SeekBar android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginbottom="15dp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" > <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> </LinearLayout> </LinearLayout> Next, make these changes to MainAct ivit y:

200 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.mediaaudio; import android.app.activity; import android.media.mediaplayer; import android.media.mediaplayer.oncompletionlistener; import android.media.mediaplayer.onseekcompletelistener; import android.os.bundle; import android.os.handler; import android.view.view; import android.view.view.onclicklistener; import android.widget.button; import android.widget.seekbar; import android.widget.seekbar.onseekbarchangelistener; public class MainActivity extends Activity { private MediaPlayer mmediaplayer; private Button mstartbutton; private Button mstopbutton; private boolean mwasplaying private SeekBar mseekbar; private Handler mhandler = new Handler(); public void play() { mmediaplayer.start(); // MediaPlayer is started. mstartbutton.settext(getresources().getstring(r.string.pause_button_label)); mhandler.postdelayed(mseekbarupdaterunnable, 200); mstopbutton.setenabled(true); public void pause() { mmediaplayer.pause(); // MediaPlayer is paused. mstartbutton.settext(getresources().getstring(r.string.start_button_label)); public void stop() { mmediaplayer.pause(); // MediaPlayer is paused. mmediaplayer.seekto(0); mstartbutton.settext(getresources().getstring(r.string.start_button_label)); mstartbutton.setenabled(true); mstopbutton.setenabled(false); private Runnable mseekbarupdaterunnable = new Runnable() { public void run() { if (mmediaplayer!= null) { mseekbar.setprogress(mmediaplayer.getcurrentposition()); if (mmediaplayer.isplaying()) { mhandler.postdelayed(mseekbarupdaterunnable, 200); ; private OnClickListener mstartonclicklistener = new OnClickListener() { public void onclick(view v) { if (mmediaplayer.isplaying()) { pause(); else { play(); ;

201 private OnClickListener mstoponclicklistener = new OnClickListener() { public void onclick(view v) { stop(); ; private OnSeekBarChangeListener mseekbarchangelistener = new OnSeekBarChangeListener( ) { public void onstarttrackingtouch(seekbar seekbar) { public void onstoptrackingtouch(seekbar seekbar) { public void onprogresschanged(seekbar seekbar, int progress, boolean fromuser) { if (progress!= mmediaplayer.getcurrentposition()) { mmediaplayer.seekto(progress); ; private OnCompletionListener mmediaplayercompletionlistener = new OnCompletionListene r() { public void oncompletion(mediaplayer mp) { mstartbutton.settext(getresources().getstring(r.string.start_button_label)); mstopbutton.setenabled(false); ; private OnSeekCompleteListener mmediaplayerseekcompletelistener = new OnSeekCompleteL istener() { public void onseekcomplete(mediaplayer mp) { mseekbar.setprogress(mp.getcurrentposition()); ; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mstartbutton = (Button)findViewById(R.id.start_button); mstopbutton = (Button)findViewById(R.id.stop_button); mseekbar = (SeekBar)findViewById(R.id.seek_bar); mstartbutton.settext(getresources().getstring(r.string.start_button_label)); mstopbutton.setenabled(false); mmediaplayer = MediaPlayer.create(this, R.raw.persephone_by_snowflake); // MediaPla yer is prepared. if (mmediaplayer!= null) { mstartbutton.setonclicklistener(mstartonclicklistener); mstopbutton.setonclicklistener(mstoponclicklistener); mseekbar.setmax(mmediaplayer.getduration()); mseekbar.setprogress(0); mseekbar.setonseekbarchangelistener(mseekbarchangelistener); mmediaplayer.setonseekcompletelistener(mmediaplayerseekcompletelistener); mmediaplayer.setoncompletionlistener(mmediaplayercompletionlistener); else { mstartbutton.setenabled(false);

202 protected void onpause() { super.onpause(); mwasplaying = mmediaplayer.isplaying(); if (mwasplaying) { mmediaplayer.pause(); protected void onresume() { super.onresume(); if (mwasplaying) { play(); protected void onsaveinstancestate(bundle outstate) { super.onsaveinstancestate(outstate); outstate.putboolean("isplaying", mmediaplayer.isplaying()); outstate.putint("progress", mmediaplayer.getcurrentposition()); protected void onrestoreinstancestate(bundle savedinstancestate) { super.onrestoreinstancestate(savedinstancestate); mwasplaying = savedinstancestate.getboolean("isplaying"); mmediaplayer.seekto(savedinstancestate.getint("progress")); if (!mwasplaying && savedinstancestate.getint("progress") > 0) { mstartbutton.settext(getresources().getstring(r.string.start_button_label)); Save the modified files and run the application. The seek bar now appears. If you play the audio file, the seek bar shows the progress. If you move the seek bar manually, the playback moves to the appropriate location in the audio file. If you stop or pause, so does the seek bar. If you wait until the audio file finishes playing, the Pause button becomes Play again and the St o p button disables. Pressing Play will play the song again from the beginning.

203 So let's take a look at the code we used to implement our seek bar:

204 OBSERVE: MainActivity.java... public class MainActivity extends Activity {... public void play() { mmediaplayer.start(); // MediaPlayer is started. mstartbutton.settext(getresources().getstring(r.string.pause_button_label)); mhandler.postdelayed(mseekbarupdaterunnable, 200); mstopbutton.setenabled(true);... private OnSeekBarChangeListener mseekbarchangelistener = new OnSeekBarChangeListener( ) { public void onstarttrackingtouch(seekbar seekbar) { public void onstoptrackingtouch(seekbar seekbar) { public void onprogresschanged(seekbar seekbar, int progress, boolean fromuser) { if (progress!= mmediaplayer.getcurrentposition()) { mmediaplayer.seekto(progress); ; private OnCompletionListener mmediaplayercompletionlistener = new OnCompletionListener( ) { public void oncompletion(mediaplayer mp) { mstartbutton.settext(getresources().getstring(r.string.start_button_label)); mstopbutton.setenabled(false); ; private OnSeekCompleteListener mmediaplayerseekcompletelistener = new OnSeekCompleteL istener() { public void onseekcomplete(mediaplayer mp) { mseekbar.setprogress(mp.getcurrentposition()); ; /** Called when the activity is first created. */ public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mstartbutton = (Button)findViewById(R.id.start_button); mstopbutton = (Button)findViewById(R.id.stop_button); mseekbar = (SeekBar)findViewById(R.id.seek_bar); mstartbutton.settext(getresources().getstring(r.string.start_button_label)); mstopbutton.setenabled(false); mmediaplayer = MediaPlayer.create(this, R.raw.persephone_by_snowflake); // MediaPla yer is prepared.

205 ... if (mmediaplayer!= null) { mstartbutton.setonclicklistener(mstartonclicklistener); mstopbutton.setonclicklistener(mstoponclicklistener); mseekbar.setmax(mmediaplayer.getduration()); mseekbar.setprogress(0); mseekbar.setonseekbarchangelistener(mseekbarchangelistener); mmediaplayer.setonseekcompletelistener(mmediaplayerseekcompletelistener); mmediaplayer.setoncompletionlistener(mmediaplayercompletionlistener); else { mstartbutton.setenabled(false); We added a SeekBar to the UI. After the MediaPlayer initializes with the audio file, t he SeekBar's maximum is set t o t he durat io n o f t he audio f ile. We also added an OnSeekBarChangeList ener to the SeekBar. By implementing o npro gresschanged on this listener, we can change the playback position when a user moves the seek bar slider. Ho wever, we first verify in o npro gre sschange d, that we actually have to update the playback position to match the seek bar. We also implemented an OnSeekCo mplet elist ener on the MediaPlayer so that when we pro grammatically mo ve the playback po sitio n, the seek bar will update as well. We implemente an OnCo mplet io nlist ener for the MediaPlayer so that when the audio file completes playback, the controls update to reflect that. When a file has co mpleted playback, the MediaPlayer go es into the PlaybackCompleted state. Finally, we want to update the seek bar as the audio file plays. To accomplish this, we create a Runnable that will get the current playback position and update the seek bar. We init iat e t he Runnable via a Handler whenever we st art playback, and while t he audio f ile is st ill playing, t he Runnable will execut e every 200 milliseco nds t o co nt inue updat ing t he seek bar. Note When using a Handler-posted Runnable to update the UI, don't make the interval between updates too small, or the application will spend most of its time updating the UI and the MediaPlayer playback will slow down. Wrapping Up Audio Before we finish up, let's talk about a few MediaPlayer states that we haven't discussed yet. The first occurs after an error occurs. Whenever an error occurs in the MediaPlayer, it goes into the Error state. When an error does occur though, you can implement the o nerro r callback to handle the error. You can move the MediaPlayer back to a usable state by calling the MediaPlayer.reset method, which will move the MediaPlayer to the Idle state. Secondly, there is an asynchronous alternative to MediaPlayer.prepared, MediaPlayer.prepareAsync. There is a callback named o npre pare d to no tify the caller when the MediaPlayer is prepared. Between the Initialized state when the data so urce is set and when this callback is called, the MediaPlayer is in the Preparing state. Finally, there is the End state. Whenever you have finished using a MediaPlayer object, a good practice is to call MediaPlayer.release, which releases all the resources used by the MediaPlayer. This not only frees up memory, but also can reduce battery co nsumptio n. After calling Me diaplaye r.re le ase, the MediaPlayer enters the End state fro m which it can no longer be used. Just to summarize, these are the states of the MediaPlayer: 1. Idle 2. Initialized 3. Preparing 4. Prepared 5. Started 6. Paused 7. PlaybackCo mpleted 8. Stopped 9. End 10. Eror

206 Now that you've had an introduction to the MediaPlayer and working with audio files, in the next lesson we'll start covering how to play video files. See you there! Copyright O'Reilly Media, Inc. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. See for more information.

207 Media: Video Lesson Objectives At the end of this lesson, you'll be able to: create a VideoView and play an video file. utilize the Video View co rrectly within the Activity lifecycle, and thro ugh co nfiguratio n changes. handle Video View events to add additio nal functio nality to yo ur applicatio n. In the last lesson, we started working with media: learning about the MediaPlayer and working with audio playback. Now we'll look at video playback. We'll learn how to get video running in your app with the VideoView. Video Playback with a VideoView Create a new Android project as usual, using this criteria: Name the project MediaVideo. Use the package name co m.o reillyscho o l.andro id2.mediavideo. Uncheck the Creat e cust o m launcher ico n box. Assign the Andro id2_lesso ns working set to the project. Modify act ivit y_main.xml as shown: CODE TO TYPE: /res/layo ut/activity_main.xml <RelativeLinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingbottom="@dimen/activity_vertical_margin" android:paddingleft="@dimen/activity_horizontal_margin" android:paddingright="@dimen/activity_horizontal_margin" android:paddingtop="@dimen/activity_vertical_margin" android:orientation="vertical" android:gravity="center" android:padding="20dp" > tools:context=".mainactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> <VideoView android:id="@+id/video_view" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLinearLayout> Now let's add some video to the project. Download the video below by right-clicking on the link and saving the file to the /res/raw folder: Download "Coffee Cup" Note The project folders are located on the V drive in the /wo rkspace folder; the full path where you should save the image is V:\wo rkspace\mediavideo \res\raw. Now, make these changes to MainAct ivit y:

208 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.mediavideo; import android.app.activity; import android.net.uri; import android.os.bundle; import android.view.menu; import android.widget.videoview; public class MainActivity extends Activity { private VideoView mvideoview; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mvideoview = (VideoView)findViewById(R.id.video_view); mvideoview.setvideouri(uri.parse("android.resource://" + getapplicationcontext().ge tpackagename() + "/" + R.raw.coffee_cup)); mvideoview.start(); public boolean oncreateoptionsmenu(menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getmenuinflater().inflate(r.menu.main, menu); return true; Save the modified files and run your application.

209 When your application starts up, the video loads and begins playing immediately. We haven't placed any other controls or views yet, so all it does is play the video. If you change the orientation of the emulator, the video plays again from the beginning. Let's talk abo ut what we did here. OBSERVE: /res/layo ut/activity_main.xml <VideoView android:layout_width="wrap_content" android:layout_height="wrap_content" /> To add a VideoView to the XML layout, we added the Video View tag and specified the layout dimensions:

210 OBSERVE: MainActivity.java... public class MainActivity extends Activity { private VideoView mvideoview; /** Called when the activity is first created. */ public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mvideoview = (VideoView)findViewById(R.id.video_view); mvideoview.setvideouri(uri.parse("android.resource://" + getapplicationcontext().ge tpackagename() + "/" + R.raw.angel_of_peace)); mvideoview.start(); In the MainAct ivit y class, we actually set the source for the VideoView. You can specify the source by a String path or by a Uri object, using either Video View.set Video Pat h or Video View.set Video URI. Here we used a Uri o bject f o r t he raw reso urce t hat we do wnlo aded previo usly.then we called Video View.st art () to play the video. The video will begin to play again when we change ro tatio n. Since we haven't implemented any state saving/resto ring behavior, when the orientation changes o ncreat e will run again, restarting the video. The Andro id co nventio n fo r embedded reso urce file paths is "andro id.reso urce://[package]/[res id]", where "[package]" is the application package name (such as com.oreillyschool.android2.mediavideo) and "[res id]" is the id generated in the R.java file that identifies the reso urce (such as "R.raw.co ffee_cup"). Note The video format we are displaying is H.263, which has the.3gp extension. When video is played in your Android application, make sure that it is in a format supported by Android. For a list of supported formats, see the Andro id Develo per Do cumentatio n. Adding a MediaController to a VideoView Now that video appears in our application, we'll want to give our users some control over the video playback. To do this, we'll provide a MediaController, which contains conventional playback controls: play/pause, forward, and rewind. Let's get started. Add the following changes to MainAct ivit y:

211 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.mediavideo; import android.app.activity; import android.net.uri; import android.os.bundle; import android.widget.mediacontroller; import android.widget.videoview; public class MainActivity extends Activity { private VideoView mvideoview; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mvideoview = (VideoView)findViewById(R.id.video_view); MediaController controller = new MediaController(this); mvideoview.setmediacontroller(controller); mvideoview.setvideouri(uri.parse("android.resource://" + getapplicationcontext().ge tpackagename() + "/" + R.raw.coffee_cup)); mvideoview.start(); Save the file and run the application again.

212 When the application starts up and the video starts playing, a panel of controls appears briefly at the bottom of the screen and then slides out of view. If you click on the playing video, the controls appear again. You can use the controls to toggle play/pause on the video, as well as move forward and backward. There is also a seek bar for navigatio n. Note Video may be slow or choppy on our servers. You might want to try these programs on a local machine to see the full effect.

213 OBSERVE: MainActivity.java public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mvideoview = (VideoView)findViewById(R.id.video_view); MediaController controller = new MediaController(this); mvideoview.setmediacontroller(controller); mvideoview.setvideouri(uri.parse("android.resource://" + getapplicationcontext().ge tpackagename() + "/" + R.raw.coffee_cup)); mvideoview.start(); Here we creat e a new MediaCo nt ro ller o bject wit h t he current Co nt ext and then call Video Player.set MediaCo nt ro ller wit h t his inst ance. VideoView Events and Methods The Video View class pro vides metho ds fo r co ntro lling playback pro grammatically, as well as callbacks fo r different events that you can use to add more functionality to your application. Let's explore some of these methods and callbacks. First, we'll grab a couple more videos to add to our project. Right-click on each of the links below and save the files to the /res/raw folder: Next, make these changes to st rings.xml: Download "Angel of Peace" Download "Window Blinds" CODE TO TYPE: /res/values/strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">mediavideo</string> <string name="action_settings">settings</string> <string name="hello_world">hello World!</string> <string name="loop_label">loop</string> <string name="next_label">next</string> <string name="previous_label">previous</string> <string name="size_text_format">%1$dx%2$d</string> </resources> Now make these changes to act ivit y_main.xml:

214 CODE TO TYPE: /res/layo ut/activity_main.xml <LinearLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".mainactivity" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" > <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <VideoView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" /> </LinearLayout> Finally, make these changes to MainAct ivit y.java:

215 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.mediavideo; import android.app.activity; import android.media.mediaplayer; import android.media.mediaplayer.oncompletionlistener; import android.media.mediaplayer.onpreparedlistener; import android.net.uri; import android.os.bundle; import android.view.view; import android.view.view.onclicklistener; import android.widget.checkbox; import android.widget.mediacontroller; import android.widget.textview; import android.widget.videoview; public class MainActivity extends Activity { final private int[] VIDEO_IDS = new int[] {R.raw.coffee_cup, R.raw.angel_of_peace, R. raw.window_blinds; private int mvideoindex = 0; private int mlastprogress = 0; private boolean mwasplaying = false; private TextView msizetext; private CheckBox mloopcheckbox; private VideoView mvideoview; private OnClickListener mnextonclicklistener = new OnClickListener() { public void onclick(view v) { mvideoindex = (mvideoindex + 1) % VIDEO_IDS.length; loadvideo(mvideoindex); ; private OnClickListener mpreviousonclicklistener = new OnClickListener() { public void onclick(view v) { mvideoindex = mvideoindex == 0? VIDEO_IDS.length - 1 : mvideoindex - 1; loadvideo(mvideoindex); ; private OnCompletionListener moncompletionlistener = new OnCompletionListener() { public void oncompletion(mediaplayer mp) { if (mloopcheckbox.ischecked()) { mvideoview.start(); ; private OnPreparedListener monpreparedlistener = new OnPreparedListener() { public void onprepared(mediaplayer mp) { msizetext.settext(string.format(getresources().getstring(r.string.size_text_forma t), mp.getvideowidth(), mp.getvideoheight())); ; private void loadvideo(int index) { mvideoview.setvideouri(uri.parse("android.resource://" + getapplicationcontext().ge tpackagename() + "/" + VIDEO_IDS[index])); mvideoview.start();

216 public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mloopcheckbox = (CheckBox)findViewById(R.id.loop_checkbox); msizetext = (TextView)findViewById(R.id.video_size_text); findviewbyid(r.id.next_button).setonclicklistener(mnextonclicklistener); findviewbyid(r.id.previous_button).setonclicklistener(mpreviousonclicklistener); mvideoview = (VideoView)findViewById(R.id.video_view); MediaController controller = new MediaController(this); mvideoview.setmediacontroller(controller); mvideoview.setvideouri(uri.parse("android.resource://" + getapplicationcontext(). getpackagename() + "/" + R.raw.coffee_cup)); mvideoview.start(); mvideoview.setonpreparedlistener(monpreparedlistener); mvideoview.setoncompletionlistener(moncompletionlistener); loadvideo(0); protected void onpause() { super.onpause(); if (mvideoview.isplaying()) { mvideoview.pause(); mwasplaying = true; else { mwasplaying = false; mlastprogress = mvideoview.getcurrentposition(); protected void onresume() { super.onresume(); mvideoview.setvideouri(uri.parse("android.resource://" + getapplicationcontext().ge tpackagename() + "/" + VIDEO_IDS[mVideoIndex])); mvideoview.seekto(mlastprogress); if (mwasplaying) mvideoview.start(); protected void onsaveinstancestate(bundle outstate) { super.onsaveinstancestate(outstate); outstate.putint("videoindex", mvideoindex); outstate.putint("progress", mvideoview.getcurrentposition()); outstate.putboolean("wasplaying", mvideoview.isplaying()); protected void onrestoreinstancestate(bundle savedinstancestate) { super.onrestoreinstancestate(savedinstancestate); mvideoindex = savedinstancestate.getint("videoindex"); mlastprogress = savedinstancestate.getint("progress"); mwasplaying = savedinstancestate.getboolean("wasplaying"); Now save the modified files and run the application:

217 So let's review what we've done.

218 OBSERVE: activity_main.xml... <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" > <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <VideoView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" /> </LinearLayout> First, we made changes to the UI. We added t wo but t o ns so t he user can mo ve bet ween dif f erent video s. We added a check bo x t hat, when clicked, will replay a video clip aut o mat ically o nce it reaches it s end; if it is unchecked, when the video reaches its end, it will simply stop playing. Videos play as soon as they have loaded. We also added so me t ext t hat displays t he size o f t he video being played. If you change the emulator's orientation or if you go to the home screen and return, you will notice that whenever the application reloads the UI, the video remains in the state it was before the change.

219 OBSERVE: MainActivity.java... public class MainActivity extends Activity { final private int[] VIDEO_IDS = new int[] {R.raw.coffee_cup, R.raw.angel_of_peace, R. raw.window_blinds; private int mvideoindex = 0; private int mlastprogress = 0; private boolean mwasplaying = false; private TextView msizetext; private CheckBox mloopcheckbox; private VideoView mvideoview; private OnClickListener mnextonclicklistener = new OnClickListener() { public void onclick(view v) { mvideoindex = (mvideoindex + 1) % VIDEO_IDS.length; loadvideo(mvideoindex); ; private OnClickListener mpreviousonclicklistener = new OnClickListener() { public void onclick(view v) { mvideoindex = mvideoindex == 0? VIDEO_IDS.length - 1 : mvideoindex - 1; loadvideo(mvideoindex); ; private OnCompletionListener moncompletionlistener = new OnCompletionListener() { public void oncompletion(mediaplayer mp) { if (mloopcheckbox.ischecked()) { mvideoview.start(); ; private OnPreparedListener monpreparedlistener = new OnPreparedListener() { public void onprepared(mediaplayer mp) { msizetext.settext(string.format(getresources().getstring(r.string.size_text_forma t), mp.getvideowidth(), mp.getvideoheight())); ; private void loadvideo(int index) { mvideoview.setvideouri(uri.parse("android.resource://" + getapplicationcontext().ge tpackagename() + "/" + VIDEO_IDS[index])); mvideoview.start();... In the MainAct ivit y, we moved the logic that started the VideoView into its own method, lo advideo. We also created a st at ic array o f t he reso urce IDs f o r t he video s t hat t he user can play. The lo advideo method takes an index into that array, and loads the video with the resource ID at that index. Then lo advideo starts the Video View. We added a couple of callbacks for VideoView events. The OnPreparedList ener for the VideoView lets us know

220 We added a couple of callbacks for VideoView events. The OnPreparedList ener for the VideoView lets us know when the VideoView is ready to start playback. Once the VideoView is prepared, we can look at properties of the video to be played. In this case, once the VideoView is prepared, we f ind o ut t he widt h and height o f t he video and display that in the UI. The OnCo mplet io nlist ener for the VideoView fires when the video playback is done. In our callback, we check t o det ermine whet her t he user has checked t he "Lo o p" check bo x; if they have, we start the video again. We also added callbacks fo r when MainAct ivit y pauses/resumes and saves/resto res state. We added class fields that ho ld the video's index, the current playback pro gress, and whether the video was actually playing when the activity was paused. Then we use the values of those fields to make sure that, when MainAct ivit y resumes, the VideoView is once again in the state as before the pause. Wrapping UP In this lesson, we learned how to display video in our application using the VideoView component. If you ever need to do advanced video playback, such as adding effects to the rendering, or you just want more control over the playback, you might want to look into using the MediaPlayer class and a SurfaceView to display video (though be warned that this method is much more complex and prone to bugs). VideoView actually uses a MediaPlayer and a SurfaceView internally to do its video rendering and takes care of the more complex details. If you need simple video display, VideoView is probably the best way to go. By now you have a good base on which to build video-enabled applications. Good luck and see you next lesson! Copyright O'Reilly Media, Inc. This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. See for more information.

221 WebView Lesson Objectives At the end of this lesson, you'll be able to: add a WebView to an Android application and load a web page. render custom HTML inside of a WebView. use the WebSet t ings class to adjust basic WebView settings. use the WebChro meclient class to customize the behavior of the WebView UI. use the WebViewClient class to customize the behavior of content rendering inside the WebView. enable JavaScript on pages loaded in the WebView. In this lesson we'll cover the WebView component. As you may have guessed, a WebView is for loading HTML content into yo ur applicatio n, typically (but no t necessarily) fro m the Internet. Let's get started! Create a new Android project with this criteria: Name the project WebView. Use the package name co m.o reillyscho o l.andro id2.webview. Uncheck the Creat e cust o m launcher ico n box. Assign the Andro id2_lesso ns working set to the project. WebView Basics Let's get started with a basic WebView. First, open Andro idmanif est.xml and make these changes: CODE TO TYPE: Andro idmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="com.oreillyschool.android2.webview" android:versioncode="1" android:versionname="1.0" > <uses-sdk android:minsdkversion="10" android:targetsdkversion="10" /> <uses-permission android:name="android.permission.internet" /> <application android:allowbackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/apptheme" > <activity android:name=".mainactivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> Now, modify act ivit y_main.xml as shown:

222 CODE TO TYPE: /res/layo ut/activity_main.xml <RelativeLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".mainactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <WebView android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> Next, mo dify MainAct ivit y.java as sho wn: CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.webview; import android.os.bundle; import android.app.activity; import android.view.menu; import android.webkit.webview; public class MainActivity extends Activity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); WebView webview = (WebView)findViewById(R.id.webview); webview.loadurl(" public boolean oncreateoptionsmenu(menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getmenuinflater().inflate(r.menu.main, menu); return true; Finally, open st rings.xml and make these changes:

223 CODE TO TYPE: /res/values/strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">webview</string> <string name="action_settings">settings</string> <string name="menu_settings">settings</string> <string name="hello_world">hello world!</string> </resources> Now save all modified files and run the application. You see the O'Reilly School of Technology home page under a title bar with the applicatio n's name "WebView." Now that our application is running, let's review the code.

224 OBSERVE: Andro idmanifest.xml... <uses-permission android:name="android.permission.internet" />... To load a web page inside of a WebView, first we add a <uses-permissio n> tag with the permission name "andro id.permissio n.int ERNET " to grant internet access to our application: OBSERVE: activity_main.xml <RelativeLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" > <WebView android:id="@+id/webview" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> Next, we add a WebView to our application's layout: OBSERVE: MainActivity.java... public class MainActivity extends Activity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); WebView webview = (WebView)findViewById(R.id.webview); webview.loadurl(" Then, we creat e a ref erence t o t he WebView in t he layo ut, and t ell it t o lo ad t he desired sit e by calling lo adurl and passing t he sit e's URL as a st ring. Since WebView.lo adurl is called in the o ncreat e method of MainAct ivit y, the site begins loading as soon as the application loads in the emulator. WebView doesn't just take URLs. We can also pass in HTML directly, and the WebView will render it. Let's try doing that; make these changes to MainAct ivit y.java:

225 CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.webview; import android.os.bundle; import android.app.activity; import android.webkit.webview; public class MainActivity extends Activity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); WebView webview = (WebView) findviewbyid(r.id.webview); webview.loadurl(" String html = "<html><body>" + "Hello, world!<br/>" + "</body></html>"; webview.loaddata(html, "text/html", null); Save the file and run the application. You see the application title bar and a simple, "Hello, world!" message.

226 Let's review the change that we just made.

227 OBSERVE: MainActivity.java... public class MainActivity extends Activity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); WebView webview = (WebView) findviewbyid(r.id.webview); String html = "<html><body>" + "Hello, world!<br/>" + "</body></html>"; webview.loaddata(html, "text/html", null); Rather than call WebView.lo adurl, we create a string co ntaining valid HTML and pass this string to webview.lo addat a. The second parameter is a st ring co nt aining t he MIME t ype o f t he dat a passed. The t hird paramet er specifies the encoding of the data: base64 or URL encoding. If we had base64 data, we would pass base64. For any other value, lo addat a will treat the data as ASCII data with URL encoding. For simplicity, we pass in null. Using WebSettings So far we've implemented a simple WebView that loads a URL or HTML. However, there are many different features we can access and customizations we can make. These features and customizations are controlled by a handful of Android classes. The first one that we'll look into is the WebSet t ings class. As its name suggests, the WebSet t ings class contains getters and setters for a number of properties related to the way the WebView accesses the Internet and displays content. Let's use the WebSet t ings class in our application. Mo dify MainAct ivit y.java again as sho wn: CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.webview; import android.app.activity; import android.os.bundle; import android.webkit.websettings; import android.webkit.webview; public class MainActivity extends Activity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); WebView webview = (WebView) findviewbyid(r.id.webview); String html = "<html><body>" + "Hello, world!<br/>" + "</body></html>"; webview.loaddata(html, "text/html", null); WebSettings settings = webview.getsettings(); settings.setbuiltinzoomcontrols(true); webview.loadurl(" Now save the file and run the application. Our application loaded the OST home page again. If you start scrolling the page, you'll see two buttons appear in the lower-right corner that allow you to zoom in and zoom out of the page.

228 Let's review what we did.

229 OBSERVE: MainActivity.java... public class MainActivity extends Activity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); WebView webview = (WebView) findviewbyid(r.id.webview); WebSettings settings = webview.getsettings(); settings.setbuiltinzoomcontrols(true); webview.loadurl(" We use lo adurl to load the OST home page. Then we add two new lines of code. First, we grab t he WebSet t ings inst ance t hat belo ngs t o t he WebView by calling WebView.get Set t ings(). Then we pass t rue t o WebSet t ings.set Built InZ o o mco nt ro ls t o t urn o n t he zo o m co nt ro ls. WebSettings has many different pro perties that we can set o r unset to access WebView features. Note The WebSettings instance from WebView.get Set t ings() is tied to the lifecycle of the WebView. If you try to execute methods on a WebSettings instance when its WebView no longer exists, it will result in exceptio ns and likely crash yo ur applicatio n. Let's look at another example of using WebSettings. Make these changes to MainAct ivit y: CODE TO TYPE: MainActivity.java package com.oreillyschool.android2.webview; import android.app.activity; import android.os.bundle; import android.webkit.websettings; import android.webkit.webview; public class MainActivity extends Activity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); WebView webview = (WebView) findviewbyid(r.id.webview); WebSettings settings = webview.getsettings(); settings.setbuiltinzoomcontrols(true); settings.setblocknetworkimage(true); webview.loadurl(" Save the file and run the application. Any images that previously loaded with the OST home page are now gone, and o nly a blank space remains in their previo us po sitio n.

230 Let's examine what we just did.

2. Click the download button for your operating system (Windows, Mac, or Linux).

2. Click the download button for your operating system (Windows, Mac, or Linux). Table of Contents: Using Android Studio 1 Installing Android Studio 1 Installing IntelliJ IDEA Community Edition 3 Downloading My Book's Examples 4 Launching Android Studio and Importing an Android Project

More information

Getting Started: Creating a Simple App

Getting Started: Creating a Simple App Getting Started: Creating a Simple App What You will Learn: Setting up your development environment Creating a simple app Personalizing your app Running your app on an emulator The goal of this hour is

More information

Android Development. http://developer.android.com/develop/ 吳 俊 興 國 立 高 雄 大 學 資 訊 工 程 學 系

Android Development. http://developer.android.com/develop/ 吳 俊 興 國 立 高 雄 大 學 資 訊 工 程 學 系 Android Development http://developer.android.com/develop/ 吳 俊 興 國 立 高 雄 大 學 資 訊 工 程 學 系 Android 3D 1. Design 2. Develop Training API Guides Reference 3. Distribute 2 Development Training Get Started Building

More information

ANDROID APPS DEVELOPMENT FOR MOBILE AND TABLET DEVICE (LEVEL I)

ANDROID APPS DEVELOPMENT FOR MOBILE AND TABLET DEVICE (LEVEL I) ANDROID APPS DEVELOPMENT FOR MOBILE AND TABLET DEVICE (LEVEL I) Who am I? Lo Chi Wing, Peter Lecture 1: Introduction to Android Development Email: Peter@Peter-Lo.com Facebook: http://www.facebook.com/peterlo111

More information

Boardies IT Solutions info@boardiesitsolutions.com Tel: 01273 252487

Boardies IT Solutions info@boardiesitsolutions.com Tel: 01273 252487 Navigation Drawer Manager Library H ow to implement Navigation Drawer Manager Library into your A ndroid Applications Boardies IT Solutions info@boardiesitsolutions.com Tel: 01273 252487 Contents Version

More information

Android Environment SDK

Android Environment SDK Part 2-a Android Environment SDK Victor Matos Cleveland State University Notes are based on: Android Developers http://developer.android.com/index.html 1 2A. Android Environment: Eclipse & ADT The Android

More information

Arduino & Android. A How to on interfacing these two devices. Bryant Tram

Arduino & Android. A How to on interfacing these two devices. Bryant Tram Arduino & Android A How to on interfacing these two devices Bryant Tram Contents 1 Overview... 2 2 Other Readings... 2 1. Android Debug Bridge -... 2 2. MicroBridge... 2 3. YouTube tutorial video series

More information

Android Environment SDK

Android Environment SDK Part 2-a Android Environment SDK Victor Matos Cleveland State University Notes are based on: Android Developers http://developer.android.com/index.html 1 Android Environment: Eclipse & ADT The Android

More information

Android Application Development: Hands- On. Dr. Jogesh K. Muppala muppala@cse.ust.hk

Android Application Development: Hands- On. Dr. Jogesh K. Muppala muppala@cse.ust.hk Android Application Development: Hands- On Dr. Jogesh K. Muppala muppala@cse.ust.hk Wi-Fi Access Wi-Fi Access Account Name: aadc201312 2 The Android Wave! 3 Hello, Android! Configure the Android SDK SDK

More information

Getting Started with Android Development

Getting Started with Android Development Getting Started with Android Development By Steven Castellucci (v1.1, January 2015) You don't always need to be in the PRISM lab to work on your 4443 assignments. Working on your own computer is convenient

More information

Lab 0 (Setting up your Development Environment) Week 1

Lab 0 (Setting up your Development Environment) Week 1 ECE155: Engineering Design with Embedded Systems Winter 2013 Lab 0 (Setting up your Development Environment) Week 1 Prepared by Kirill Morozov version 1.2 1 Objectives In this lab, you ll familiarize yourself

More information

IOIO for Android Beginners Guide Introduction

IOIO for Android Beginners Guide Introduction IOIO for Android Beginners Guide Introduction This is the beginners guide for the IOIO for Android board and is intended for users that have never written an Android app. The goal of this tutorial is to

More information

With a single download, the ADT Bundle includes everything you need to begin developing apps:

With a single download, the ADT Bundle includes everything you need to begin developing apps: Get the Android SDK The Android SDK provides you the API libraries and developer tools necessary to build, test, and debug apps for Android. The ADT bundle includes the essential Android SDK components

More information

Android Setup Phase 2

Android Setup Phase 2 Android Setup Phase 2 Instructor: Trish Cornez CS260 Fall 2012 Phase 2: Install the Android Components In this phase you will add the Android components to the existing Java setup. This phase must be completed

More information

How to develop your own app

How to develop your own app How to develop your own app It s important that everything on the hardware side and also on the software side of our Android-to-serial converter should be as simple as possible. We have the advantage that

More information

Login with Amazon Getting Started Guide for Android. Version 2.0

Login with Amazon Getting Started Guide for Android. Version 2.0 Getting Started Guide for Android Version 2.0 Login with Amazon: Getting Started Guide for Android Copyright 2016 Amazon.com, Inc., or its affiliates. All rights reserved. Amazon and the Amazon logo are

More information

Tutorial #1. Android Application Development Advanced Hello World App

Tutorial #1. Android Application Development Advanced Hello World App Tutorial #1 Android Application Development Advanced Hello World App 1. Create a new Android Project 1. Open Eclipse 2. Click the menu File -> New -> Other. 3. Expand the Android folder and select Android

More information

Getting Started with Android

Getting Started with Android Mobile Application Development Lecture 02 Imran Ihsan Getting Started with Android Before we can run a simple Hello World App we need to install the programming environment. We will run Hello World on

More information

How to build your first Android Application in Windows

How to build your first Android Application in Windows APPLICATION NOTE How to build your first Android Application in Windows 3/30/2012 Created by: Micah Zastrow Abstract This application note is designed to teach the reader how to setup the Android Development

More information

Android Programming. Høgskolen i Telemark Telemark University College. Cuong Nguyen, 2013.06.18

Android Programming. Høgskolen i Telemark Telemark University College. Cuong Nguyen, 2013.06.18 Høgskolen i Telemark Telemark University College Department of Electrical Engineering, Information Technology and Cybernetics Cuong Nguyen, 2013.06.18 Faculty of Technology, Postboks 203, Kjølnes ring

More information

Android Java Live and In Action

Android Java Live and In Action Android Java Live and In Action Norman McEntire Founder, Servin Corp UCSD Extension Instructor norman.mcentire@servin.com Copyright (c) 2013 Servin Corp 1 Opening Remarks Welcome! Thank you! My promise

More information

Download and Installation Instructions. Android SDK and Android Development Tools (ADT)

Download and Installation Instructions. Android SDK and Android Development Tools (ADT) Download and Installation Instructions for Android SDK and Android Development Tools (ADT) on Mac OS X Updated October, 2012 This document will describe how to download and install the Android SDK and

More information

Download and Installation Instructions. Android SDK and Android Development Tools (ADT) Microsoft Windows

Download and Installation Instructions. Android SDK and Android Development Tools (ADT) Microsoft Windows Download and Installation Instructions for Android SDK and Android Development Tools (ADT) on Microsoft Windows Updated May, 2012 This document will describe how to download and install the Android SDK

More information

Admin. Mobile Software Development Framework: Android Activity, View/ViewGroup, External Resources. Recap: TinyOS. Recap: J2ME Framework

Admin. Mobile Software Development Framework: Android Activity, View/ViewGroup, External Resources. Recap: TinyOS. Recap: J2ME Framework Admin. Mobile Software Development Framework: Android Activity, View/ViewGroup, External Resources Homework 2 questions 10/9/2012 Y. Richard Yang 1 2 Recap: TinyOS Hardware components motivated design

More information

Advantages. manage port forwarding, set breakpoints, and view thread and process information directly

Advantages. manage port forwarding, set breakpoints, and view thread and process information directly Part 2 a Android Environment SDK Victor Matos Cleveland State University Notes are based on: Android Developers http://developer.android.com/index.html 1 Android Environment: Eclipse & ADT The Android

More information

Developing NFC Applications on the Android Platform. The Definitive Resource

Developing NFC Applications on the Android Platform. The Definitive Resource Developing NFC Applications on the Android Platform The Definitive Resource Part 1 By Kyle Lampert Introduction This guide will use examples from Mac OS X, but the steps are easily adaptable for modern

More information

Setting Up Your Android Development Environment. For Mac OS X (10.6.8) v1.0. By GoNorthWest. 3 April 2012

Setting Up Your Android Development Environment. For Mac OS X (10.6.8) v1.0. By GoNorthWest. 3 April 2012 Setting Up Your Android Development Environment For Mac OS X (10.6.8) v1.0 By GoNorthWest 3 April 2012 Setting up the Android development environment can be a bit well challenging if you don t have all

More information

Now that we have the Android SDK, Eclipse and Phones all ready to go we can jump into actual Android development.

Now that we have the Android SDK, Eclipse and Phones all ready to go we can jump into actual Android development. Android Development 101 Now that we have the Android SDK, Eclipse and Phones all ready to go we can jump into actual Android development. Activity In Android, each application (and perhaps each screen

More information

UP L18 Enhanced MDM and Updated Email Protection Hands-On Lab

UP L18 Enhanced MDM and Updated Email Protection Hands-On Lab UP L18 Enhanced MDM and Updated Email Protection Hands-On Lab Description The Symantec App Center platform continues to expand it s offering with new enhanced support for native agent based device management

More information

Android Basics. Xin Yang 2016-05-06

Android Basics. Xin Yang 2016-05-06 Android Basics Xin Yang 2016-05-06 1 Outline of Lectures Lecture 1 (45mins) Android Basics Programming environment Components of an Android app Activity, lifecycle, intent Android anatomy Lecture 2 (45mins)

More information

RingCentral Office@Hand from AT&T Desktop App for Windows & Mac. User Guide

RingCentral Office@Hand from AT&T Desktop App for Windows & Mac. User Guide RingCentral Office@Hand from AT&T Desktop App for Windows & Mac User Guide RingCentral Office@Hand from AT&T User Guide Table of Contents 2 Table of Contents 3 Welcome 4 Download and install the app 5

More information

Android Mobile App Building Tutorial

Android Mobile App Building Tutorial Android Mobile App Building Tutorial Seidenberg-CSIS, Pace University This mobile app building tutorial is for high school and college students to participate in Mobile App Development Contest Workshop.

More information

Welcome to Bridgit @ CSU The Software Used To Data Conference.

Welcome to Bridgit @ CSU The Software Used To Data Conference. Welcome to Bridgit @ CSU The Software Used To Data Conference. Overview SMART Bridgit software is a client/server application that lets you share programs and information with anyone, anywhere in the world.

More information

In the same spirit, our QuickBooks 2008 Software Installation Guide has been completely revised as well.

In the same spirit, our QuickBooks 2008 Software Installation Guide has been completely revised as well. QuickBooks 2008 Software Installation Guide Welcome 3/25/09; Ver. IMD-2.1 This guide is designed to support users installing QuickBooks: Pro or Premier 2008 financial accounting software, especially in

More information

Mocean Android SDK Developer Guide

Mocean Android SDK Developer Guide Mocean Android SDK Developer Guide For Android SDK Version 3.2 136 Baxter St, New York, NY 10013 Page 1 Table of Contents Table of Contents... 2 Overview... 3 Section 1 Setup... 3 What changed in 3.2:...

More information

PTC Integrity Eclipse and IBM Rational Development Platform Guide

PTC Integrity Eclipse and IBM Rational Development Platform Guide PTC Integrity Eclipse and IBM Rational Development Platform Guide The PTC Integrity integration with Eclipse Platform and the IBM Rational Software Development Platform series allows you to access Integrity

More information

Introduction to Android Development. Daniel Rodrigues, Buuna 2014

Introduction to Android Development. Daniel Rodrigues, Buuna 2014 Introduction to Android Development Daniel Rodrigues, Buuna 2014 Contents 1. Android OS 2. Development Tools 3. Development Overview 4. A Simple Activity with Layout 5. Some Pitfalls to Avoid 6. Useful

More information

Introduction to Android Programming (CS5248 Fall 2015)

Introduction to Android Programming (CS5248 Fall 2015) Introduction to Android Programming (CS5248 Fall 2015) Aditya Kulkarni (email.aditya.kulkarni@gmail.com) August 26, 2015 *Based on slides from Paresh Mayami (Google Inc.) Contents Introduction Android

More information

NovaBACKUP. User Manual. NovaStor / November 2011

NovaBACKUP. User Manual. NovaStor / November 2011 NovaBACKUP User Manual NovaStor / November 2011 2011 NovaStor, all rights reserved. All trademarks are the property of their respective owners. Features and specifications are subject to change without

More information

Developing In Eclipse, with ADT

Developing In Eclipse, with ADT Developing In Eclipse, with ADT Android Developers file://v:\android-sdk-windows\docs\guide\developing\eclipse-adt.html Page 1 of 12 Developing In Eclipse, with ADT The Android Development Tools (ADT)

More information

ECWM511 MOBILE APPLICATION DEVELOPMENT Lecture 1: Introduction to Android

ECWM511 MOBILE APPLICATION DEVELOPMENT Lecture 1: Introduction to Android Why Android? ECWM511 MOBILE APPLICATION DEVELOPMENT Lecture 1: Introduction to Android Dr Dimitris C. Dracopoulos A truly open, free development platform based on Linux and open source A component-based

More information

Using Google Docs in the classroom: Simple as ABC

Using Google Docs in the classroom: Simple as ABC What is Google Docs? Google Docs is a free, Web-based word processing, presentations and spreadsheets program. Unlike desktop software, Google Docs lets people create web-based documents, presentations

More information

INTEGRATING MICROSOFT DYNAMICS CRM WITH SIMEGO DS3

INTEGRATING MICROSOFT DYNAMICS CRM WITH SIMEGO DS3 INTEGRATING MICROSOFT DYNAMICS CRM WITH SIMEGO DS3 Often the most compelling way to introduce yourself to a software product is to try deliver value as soon as possible. Simego DS3 is designed to get you

More information

Android Developer Fundamental 1

Android Developer Fundamental 1 Android Developer Fundamental 1 I. Why Learn Android? Technology for life. Deep interaction with our daily life. Mobile, Simple & Practical. Biggest user base (see statistics) Open Source, Control & Flexibility

More information

Android Development. Marc Mc Loughlin

Android Development. Marc Mc Loughlin Android Development Marc Mc Loughlin Android Development Android Developer Website:h:p://developer.android.com/ Dev Guide Reference Resources Video / Blog SeCng up the SDK h:p://developer.android.com/sdk/

More information

Introduction: The Xcode templates are not available in Cordova-2.0.0 or above, so we'll use the previous version, 1.9.0 for this recipe.

Introduction: The Xcode templates are not available in Cordova-2.0.0 or above, so we'll use the previous version, 1.9.0 for this recipe. Tutorial Learning Objectives: After completing this lab, you should be able to learn about: Learn how to use Xcode with PhoneGap and jquery mobile to develop iphone Cordova applications. Learn how to use

More information

Frequently Asked Questions: Cisco Jabber 9.x for Android

Frequently Asked Questions: Cisco Jabber 9.x for Android Frequently Asked Questions Frequently Asked Questions: Cisco Jabber 9.x for Android Frequently Asked Questions (FAQs) 2 Setup 2 Basics 4 Connectivity 8 Calls 9 Contacts and Directory Search 14 Voicemail

More information

Attix5 Pro Server Edition

Attix5 Pro Server Edition Attix5 Pro Server Edition V7.0.3 User Manual for Linux and Unix operating systems Your guide to protecting data with Attix5 Pro Server Edition. Copyright notice and proprietary information All rights reserved.

More information

MMI 2: Mobile Human- Computer Interaction Android

MMI 2: Mobile Human- Computer Interaction Android MMI 2: Mobile Human- Computer Interaction Android Prof. Dr. michael.rohs@ifi.lmu.de Mobile Interaction Lab, LMU München Android Software Stack Applications Java SDK Activities Views Resources Animation

More information

Tutorial on Basic Android Setup

Tutorial on Basic Android Setup Tutorial on Basic Android Setup EE368/CS232 Digital Image Processing, Spring 2015 Windows Version Introduction In this tutorial, we will learn how to set up the Android software development environment

More information

Presenting Android Development in the CS Curriculum

Presenting Android Development in the CS Curriculum Presenting Android Development in the CS Curriculum Mao Zheng Hao Fan Department of Computer Science International School of Software University of Wisconsin-La Crosse Wuhan University La Crosse WI, 54601

More information

MATLAB Distributed Computing Server with HPC Cluster in Microsoft Azure

MATLAB Distributed Computing Server with HPC Cluster in Microsoft Azure MATLAB Distributed Computing Server with HPC Cluster in Microsoft Azure Introduction This article shows you how to deploy the MATLAB Distributed Computing Server (hereinafter referred to as MDCS) with

More information

Installing the Android SDK

Installing the Android SDK Installing the Android SDK To get started with development, we first need to set up and configure our PCs for working with Java, and the Android SDK. We ll be installing and configuring four packages today

More information

Getting Started. Getting Started with Time Warner Cable Business Class. Voice Manager. A Guide for Administrators and Users

Getting Started. Getting Started with Time Warner Cable Business Class. Voice Manager. A Guide for Administrators and Users Getting Started Getting Started with Time Warner Cable Business Class Voice Manager A Guide for Administrators and Users Table of Contents Table of Contents... 2 How to Use This Guide... 3 Administrators...

More information

TUTORIAL ECLIPSE CLASSIC VERSION: 3.7.2 ON SETTING UP OPENERP 6.1 SOURCE CODE UNDER WINDOWS PLATFORM. by Pir Khurram Rashdi

TUTORIAL ECLIPSE CLASSIC VERSION: 3.7.2 ON SETTING UP OPENERP 6.1 SOURCE CODE UNDER WINDOWS PLATFORM. by Pir Khurram Rashdi TUTORIAL ON SETTING UP OPENERP 6.1 SOURCE CODE IN ECLIPSE CLASSIC VERSION: 3.7.2 UNDER WINDOWS PLATFORM by Pir Khurram Rashdi Web: http://www.linkedin.com/in/khurramrashdi Email: pkrashdi@gmail.com By

More information

Android Programming Family Fun Day using AppInventor

Android Programming Family Fun Day using AppInventor Android Programming Family Fun Day using AppInventor Table of Contents A step-by-step guide to making a simple app...2 Getting your app running on the emulator...9 Getting your app onto your phone or tablet...10

More information

Search help. More on Office.com: images templates

Search help. More on Office.com: images templates Page 1 of 14 Access 2010 Home > Access 2010 Help and How-to > Getting started Search help More on Office.com: images templates Access 2010: database tasks Here are some basic database tasks that you can

More information

For Introduction to Java Programming, 5E By Y. Daniel Liang

For Introduction to Java Programming, 5E By Y. Daniel Liang Supplement H: NetBeans Tutorial For Introduction to Java Programming, 5E By Y. Daniel Liang This supplement covers the following topics: Getting Started with NetBeans Creating a Project Creating, Mounting,

More information

Generate Android App

Generate Android App Generate Android App This paper describes how someone with no programming experience can generate an Android application in minutes without writing any code. The application, also called an APK file can

More information

Introduction to Android Development

Introduction to Android Development 2013 Introduction to Android Development Keshav Bahadoor An basic guide to setting up and building native Android applications Science Technology Workshop & Exposition University of Nigeria, Nsukka Keshav

More information

WA2087 Programming Java SOAP and REST Web Services - WebSphere 8.0 / RAD 8.0. Student Labs. Web Age Solutions Inc.

WA2087 Programming Java SOAP and REST Web Services - WebSphere 8.0 / RAD 8.0. Student Labs. Web Age Solutions Inc. WA2087 Programming Java SOAP and REST Web Services - WebSphere 8.0 / RAD 8.0 Student Labs Web Age Solutions Inc. 1 Table of Contents Lab 1 - WebSphere Workspace Configuration...3 Lab 2 - Introduction To

More information

How To Develop An Android App On An Android Device

How To Develop An Android App On An Android Device Lesson 2 Android Development Tools = Eclipse + ADT + SDK Victor Matos Cleveland State University Portions of this page are reproduced from work created and shared by Googleand used according to terms described

More information

REDUCING YOUR MICROSOFT OUTLOOK MAILBOX SIZE

REDUCING YOUR MICROSOFT OUTLOOK MAILBOX SIZE There are several ways to eliminate having too much email on the Exchange mail server. To reduce your mailbox size it is recommended that you practice the following tasks: Delete items from your Mailbox:

More information

Getting Started with Vision 6

Getting Started with Vision 6 Getting Started with Vision 6 Version 6.9 Notice Copyright 1981-2009 Netop Business Solutions A/S. All Rights Reserved. Portions used under license from third parties. Please send any comments to: Netop

More information

Eclipse with Mac OSX Getting Started Selecting Your Workspace. Creating a Project.

Eclipse with Mac OSX Getting Started Selecting Your Workspace. Creating a Project. Eclipse with Mac OSX Java developers have quickly made Eclipse one of the most popular Java coding tools on Mac OS X. But although Eclipse is a comfortable tool to use every day once you know it, it is

More information

Creating a 2D Game Engine for Android OS. Introduction

Creating a 2D Game Engine for Android OS. Introduction Creating a 2D Game Engine for Android OS Introduction This tutorial will lead you through the foundations of creating a 2D animated game for the Android Operating System. The goal here is not to create

More information

Hypercosm. Studio. www.hypercosm.com

Hypercosm. Studio. www.hypercosm.com Hypercosm Studio www.hypercosm.com Hypercosm Studio Guide 3 Revision: November 2005 Copyright 2005 Hypercosm LLC All rights reserved. Hypercosm, OMAR, Hypercosm 3D Player, and Hypercosm Studio are trademarks

More information

Home Course Catalog Schedule Pricing & Savings Training Options Resources About Us

Home Course Catalog Schedule Pricing & Savings Training Options Resources About Us 1 of 14 12/04/2012 06:46 PM Hello, Jonathan Earl My Account Logout GS-35F-0556S CONTACT US Search TOLL FREE 877-932-8228 Home Course Catalog Schedule Pricing & Savings Training Options Resources About

More information

The Power Loader GUI

The Power Loader GUI The Power Loader GUI (212) 405.1010 info@1010data.com Follow: @1010data www.1010data.com The Power Loader GUI Contents 2 Contents Pre-Load To-Do List... 3 Login to Power Loader... 4 Upload Data Files to

More information

Desktop, Web and Mobile Testing Tutorials

Desktop, Web and Mobile Testing Tutorials Desktop, Web and Mobile Testing Tutorials * Windows and the Windows logo are trademarks of the Microsoft group of companies. 2 About the Tutorial With TestComplete, you can test applications of three major

More information

Unified Communications Using Microsoft Office Live Meeting 2007

Unified Communications Using Microsoft Office Live Meeting 2007 Unified Communications Using Microsoft Office Live Meeting 2007 Text version of online course. Contents Unified Communications... 1 About Microsoft Office Live Meeting 2007... 3 Copyright Information...

More information

Frameworks & Android. Programmeertechnieken, Tim Cocx

Frameworks & Android. Programmeertechnieken, Tim Cocx Frameworks & Android Programmeertechnieken, Tim Cocx Discover thediscover world atthe Leiden world University at Leiden University Software maken is hergebruiken The majority of programming activities

More information

CRM Migration Manager 3.1.1 for Microsoft Dynamics CRM. User Guide

CRM Migration Manager 3.1.1 for Microsoft Dynamics CRM. User Guide CRM Migration Manager 3.1.1 for Microsoft Dynamics CRM User Guide Revision D Issued July 2014 Table of Contents About CRM Migration Manager... 4 System Requirements... 5 Operating Systems... 5 Dynamics

More information

Android: Setup Hello, World: Android Edition. due by noon ET on Wed 2/22. Ingredients.

Android: Setup Hello, World: Android Edition. due by noon ET on Wed 2/22. Ingredients. Android: Setup Hello, World: Android Edition due by noon ET on Wed 2/22 Ingredients. Android Development Tools Plugin for Eclipse Android Software Development Kit Eclipse Java Help. Help is available throughout

More information

App Distribution Guide

App Distribution Guide App Distribution Guide Contents About App Distribution 10 At a Glance 11 Enroll in an Apple Developer Program to Distribute Your App 11 Generate Certificates and Register Your Devices 11 Add Store Capabilities

More information

Using MioNet. 2006 Senvid Inc. User Manual Version 1.07

Using MioNet. 2006 Senvid Inc. User Manual Version 1.07 Using MioNet Copyright 2006 by Senvid, Inc. All rights reserved. 2445 Faber Place, Suite 200, Palo Alto, CA 94303 Voice: (650) 354-3613 Fax: (650) 354-8890 1 COPYRIGHT NOTICE No part of this publication

More information

One step login. Solutions:

One step login. Solutions: Many Lotus customers use Lotus messaging and/or applications on Windows and manage Microsoft server/client environment via Microsoft Active Directory. There are two important business requirements in this

More information

Beginning Android Programming

Beginning Android Programming Beginning Android Programming DEVELOP AND DESIGN Kevin Grant and Chris Haseman PEACHPIT PRESS WWW.PEACHPIT.COM C Introduction Welcome to Android xii xiv CHAPTER 1 GETTING STARTED WITH ANDROID 2 Exploring

More information

Backup Assistant. User Guide. NEC NEC Unified Solutions, Inc. March 2008 NDA-30282, Revision 6

Backup Assistant. User Guide. NEC NEC Unified Solutions, Inc. March 2008 NDA-30282, Revision 6 Backup Assistant User Guide NEC NEC Unified Solutions, Inc. March 2008 NDA-30282, Revision 6 Liability Disclaimer NEC Unified Solutions, Inc. reserves the right to change the specifications, functions,

More information

OpenCV on Android Platforms

OpenCV on Android Platforms OpenCV on Android Platforms Marco Moltisanti Image Processing Lab http://iplab.dmi.unict.it moltisanti@dmi.unict.it http://www.dmi.unict.it/~moltisanti Outline Intro System setup Write and build an Android

More information

Introducing Xcode Source Control

Introducing Xcode Source Control APPENDIX A Introducing Xcode Source Control What You ll Learn in This Appendix: u The source control features offered in Xcode u The language of source control systems u How to connect to remote Subversion

More information

Merak Outlook Connector User Guide

Merak Outlook Connector User Guide IceWarp Server Merak Outlook Connector User Guide Version 9.0 Printed on 21 August, 2007 i Contents Introduction 1 Installation 2 Pre-requisites... 2 Running the install... 2 Add Account Wizard... 6 Finalizing

More information

Setting up Sudoku example on Android Studio

Setting up Sudoku example on Android Studio Installing Android Studio 1 Setting up Sudoku example on Android Studio Installing Android Studio Android Studio provides everything you need to start developing apps for Android, including the Android

More information

User Guide For ipodder on Windows systems

User Guide For ipodder on Windows systems User Guide Page 1 User Guide For ipodder on Windows systems Part of the ipodder Documentation User Guide Page 2 Table Of Contents 1. Introduction (p3) 2. Getting Started (p3) 1. Requirements (p4) 2. Installation

More information

Running a Program on an AVD

Running a Program on an AVD Running a Program on an AVD Now that you have a project that builds an application, and an AVD with a system image compatible with the application s build target and API level requirements, you can run

More information

Backups. Backup Plan. How to use the Backup utility to back up files and folders in Windows XP Home Edition

Backups. Backup Plan. How to use the Backup utility to back up files and folders in Windows XP Home Edition Backups Backups are your insurance against data loss. Most organizations have an Information Technology Department (IT) which may be responsible for backing up organizational data and that is handled well

More information

WEARIT DEVELOPER DOCUMENTATION 0.2 preliminary release July 20 th, 2013

WEARIT DEVELOPER DOCUMENTATION 0.2 preliminary release July 20 th, 2013 WEARIT DEVELOPER DOCUMENTATION 0.2 preliminary release July 20 th, 2013 The informations contained in this document are subject to change without notice and should not be construed as a commitment by Si14

More information

Tutorial: Android Object API Application Development. SAP Mobile Platform 2.3 SP02

Tutorial: Android Object API Application Development. SAP Mobile Platform 2.3 SP02 Tutorial: Android Object API Application Development SAP Mobile Platform 2.3 SP02 DOCUMENT ID: DC01939-01-0232-01 LAST REVISED: May 2013 Copyright 2013 by Sybase, Inc. All rights reserved. This publication

More information

Introduction to NaviGenie SDK Client API for Android

Introduction to NaviGenie SDK Client API for Android Introduction to NaviGenie SDK Client API for Android Overview 3 Data access solutions. 3 Use your own data in a highly optimized form 3 Hardware acceleration support.. 3 Package contents.. 4 Libraries.

More information

Before you can use the Duke Ambient environment to start working on your projects or

Before you can use the Duke Ambient environment to start working on your projects or Using Ambient by Duke Curious 2004 preparing the environment Before you can use the Duke Ambient environment to start working on your projects or labs, you need to make sure that all configuration settings

More information

Cisco Cius Development Guide Version 1.0 September 30, 2010

Cisco Cius Development Guide Version 1.0 September 30, 2010 Cisco Cius Development Guide Version 1.0 September 30, 2010 Americas Headquarters Cisco Systems, Inc. 170 West Tasman Drive San Jose, CA 95134-1706 USA http://www.cisco.com Tel: 408 526-4000 800 553-NETS

More information

Android Programming. Android App. Høgskolen i Telemark Telemark University College. Cuong Nguyen, 2013.06.19

Android Programming. Android App. Høgskolen i Telemark Telemark University College. Cuong Nguyen, 2013.06.19 Høgskolen i Telemark Telemark University College Department of Electrical Engineering, Information Technology and Cybernetics Android Programming Cuong Nguyen, 2013.06.19 Android App Faculty of Technology,

More information

Polycom Converged Management Application (CMA ) Desktop for Mac OS X. Help Book. Version 5.1.0

Polycom Converged Management Application (CMA ) Desktop for Mac OS X. Help Book. Version 5.1.0 Polycom Converged Management Application (CMA ) Desktop for Mac OS X Help Book Version 5.1.0 Copyright 2010 Polycom, Inc. Polycom and the Polycom logo are registered trademarks and Polycom CMA Desktop

More information

Attix5 Pro Server Edition

Attix5 Pro Server Edition Attix5 Pro Server Edition V7.0.2 User Manual for Mac OS X Your guide to protecting data with Attix5 Pro Server Edition. Copyright notice and proprietary information All rights reserved. Attix5, 2013 Trademarks

More information

Gladinet Cloud Backup V3.0 User Guide

Gladinet Cloud Backup V3.0 User Guide Gladinet Cloud Backup V3.0 User Guide Foreword The Gladinet User Guide gives step-by-step instructions for end users. Revision History Gladinet User Guide Date Description Version 8/20/2010 Draft Gladinet

More information

Download and Installation Instructions. Android SDK and Android Development Tools (ADT) Microsoft Windows

Download and Installation Instructions. Android SDK and Android Development Tools (ADT) Microsoft Windows Download and Installation Instructions for Android SDK and Android Development Tools (ADT) on Microsoft Windows Updated September, 2013 This document will describe how to download and install the Android

More information

Building Your First App

Building Your First App uilding Your First App Android Developers http://developer.android.com/training/basics/firstapp/inde... Building Your First App Welcome to Android application development! This class teaches you how to

More information

VMware/Hyper-V Backup Plug-in User Guide

VMware/Hyper-V Backup Plug-in User Guide VMware/Hyper-V Backup Plug-in User Guide COPYRIGHT No part of this publication may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, electronic, mechanical, photocopying,

More information

Actualtests.C2010-508.40 questions

Actualtests.C2010-508.40 questions Actualtests.C2010-508.40 questions Number: C2010-508 Passing Score: 800 Time Limit: 120 min File Version: 5.6 http://www.gratisexam.com/ C2010-508 IBM Endpoint Manager V9.0 Fundamentals Finally, I got

More information