RSS

Search Engine

Thursday, September 30, 2010

Divide and Conquer

Years ago I was addicted to a simple game that I played on my then state-of-the-art Pentium-75. In this game, balls would bounce around, and I would try to partition them into small enough spaces so that I could go to the next level where more and more balls would be added. As of a couple of months ago, for the life of me, I couldn't remember the name of this game. So when I sat down to write an application for Android in my 20% time, I thought, why not try to recreate something similar? After completing most of the game and showing it to some of my friends at work, one of them said, "Oh, this reminds me of JezzBall!" Eureka! If working on this game does nothing more than reveal the name of one of the favorite games of my youth, I'll call it a success, but in the meantime, I'm happy to announce that the source of this application, named Divide and Conquer, is now available on apps-for-android.

The game starts with instructions:

and begins simply enough with one ball bouncing around. You drag your finger in a horizontal or vertical gesture on the screen to initiate a line that extends until it reaches the edges:

In each level, once you've shaved off 80% of the original area, you move to the next level. Here's a screen shot of level 6:

If a ball hits a line in progress, you lose a life:

Once you are out of lives, it is game over:

While this game isn't going to win any awards for cutting edge graphics, it demonstrates use of several Android features and APIs:

  • custom drawing and animation
  • touch input based gesture detection
  • overriding the default behavior of the back key in some situations (to pause the game)
  • creating custom Dialogs
  • configuring an application to be full screen with no title or status bar
  • use of the preferences framework
  • use of the vibrator API

DivideAndConquerView is a custom View that implements its own onDraw method using the Canvas methods, and gesture detection using onTouchEvent and a helper class DirectionPoint. It keeps track of the state of the game using BallEngine, and reports relevant events back to the main activity of the application, which, in turn, keeps track of and controls the state of the game. The application is configured to be full screen in its AndroidManifest.xml file.

Three new Samples: Triangle, SpriteText and Downloader

I've posted three new open source samples to the apps-for-android project: Triangle, SpriteText and Downloader.

The first two samples, Triangle and SpriteText, show techniques that would be useful to anyone using the OpenGL ES 3D graphics APIs to write Android applications. The samples contain several reusable classes that may eventually be incorporated (in some form) into the SDK. Chief among these is the GLView class, which abstracts the OpenGL ES book-keeping code from the rest of the application. GLView helps handle the extra work OpenGL ES applications have to do when the activity is paused and resumed, and when the display goes to sleep and wakes up. In the Pause/Resume case the OpenGL surface has to be recreated. In the display sleep / wake-up case the entire OpenGL context has to be recreated.

Triangle

The first sample, Triangle, shows how to use the GLView class and the OpenGL ES 3D library to display a spinning textured triangle. Think of it as the "hello, world" of OpenGL ES apps. Because it's relatively simple, it's a good place to start when experimenting with the OpenGL ES API.

SpriteText

The second sample, SpriteText, shows how to efficiently display screen-aligned text using the GL11Ext.glDrawTexiOES method. SpriteText contains a reusable LabelMaker class for drawing static text and screen-aligned images, as well as a Projector class for finding the 2D screen coordinates corresponding to a 3D point, and a MatrixTrackingGL class for keeping track of the current transformation matrix. Finally, it shows how to use these classes to display a milliseconds per frame counter. A ms/f counter can be helpful for tuning graphics performance.

Downloader

The third sample, Downloader, shows how to add a downloader activity to your application. The downloader activity runs at the beginning of your application and makes sure that a set of files have been downloaded from a web server to the phone's SD card. Downloader is useful for applications that need more local data than can fit into an .apk file. For example a game could use Downloader to download the game's artwork, sound effects, and level data. The Downloader activity is designed to be a drop-in addition to your application. You customize it by supplying the URL of an XML configuration file which lists the data files that need to be downloaded.

JADE-ANDROID Add-On 1.0 released

Version 1.0 of JADE-ANDROID, a software package that allows developing agent oriented applications based on JADE for the ANDROID platform, has been released. Android is the software stack for mobile devices including the operating system released by the Open Handset Alliance in November 2007. The possibility of combining the expressiveness of FIPA communication supported by JADE agents with the power of the ANDROID platform brings, in our opinion, a strong value in the development of innovative applications based on social models and peer-to-peer paradigms. See the JADE-ANDROID guide for more details.

Dear JADE users and ANDROID application developers, We are pleased to announce the release of version 1.0 of JADE-ANDROID, a software package that allows developing agent oriented applications based on JADE (http://jade.tilab.com ) for the ANDROID platform. Android is the software stack for mobile devices including the operating system released by the Open Handset Alliance in November 2007 (refer to http://code.google.com/android/ and http://www.openhandsetalliance.com ). The programming language of ANDROID applications is JAVA and the completely new Dalvik JVM, allows accessing all core functionality of the mobile device. In particular on top of a JavaSE-like platform it provides a set of new ANDROID specific API by means of which it is possible to interact with the ANDROID Operating System, control the device hardware and develop appealing and responsive GUIs. The possibility of combining the expressiveness of FIPA (www.fipa.org) communication supported by JADE agents with the power of the ANDROID platform brings, in our opinion, a strong value in the development of innovative applications based on social models and peer-to-peer paradigms. JADE-ANDROID is distributed as an add-on of JADE and is available for download under the terms of the LGPL license from the add-ons area of the JADE web site. The add on also includes a Dummy Agent sample application that runs on ANDROID SDK m5-rc14. Detailed information about how to use JADE-ANDROID can be found in the user guide included in the add-on distribution file.

Tuesday, September 28, 2010

Some information on APIs removed in the Android 0.9 SDK beta

Earlier this week, we released a beta of the Android SDK. In the accompanying post, I mentioned that we had to remove some APIs from the platform for Android 1.0, and as a result they don't appear in the 0.9 beta SDK, and won't appear in 1.0-compatible SDKs. Today, I want to take a few minutes to explain why.

GTalkService
We were all really excited when the "XMPPService" (as it was called, at first) was included in the first early-look SDK. Once we brought in our security review team to examine Android, however, they soon realized that, as exciting as it is, the GTalkService has some fundamental security problems. Rich Cannings is one of our security researchers, and here's his explanation of the issues:

When I first read about GTalkService, I was both excited and scared. As a developer, I was interested in a feature that provided a simple interface to send messages between two Google Talk friends. The messages would appear on the receiving device as a standard Intent that was easy to handle. How simple and beautiful is that? Unfortunately, when I put my tin foil hat on, I recognized that things are a little more complicated than that.

We decided to postpone GTalkService's data-messaging functionality for the following reasons:

  1. "Repurposing" Google Talk Friends
    Google Talk friends are intended for a different purpose than that envisioned by the GTalkService. Your Google Talk friends can contact you at any time via IM. They can see your email address and often can see your real name. However, the idea of a Google Talk friend does not always line up with the types of people who may want to interact with via an Android application. For example, imagine a really cool mobile Massively Multiplayer Online Roleplaying Game using GTalkService. You would have to add all the players to your Google Talk friends list in order to play with them. Next time you log in to Google Talk from your desktop or on the web, you would notice that you have many new "friends". You may not want to chat with these friends -- and perhaps worse, you may not want them to know what your real name or email is. We do realize that Android users will want to interact with other Android users anonymously and for short periods of time, especially in gaming scenarios. Unfortunately, it turns out that using Instant Messaging is not really a good way to do that.

  2. Verifying Remote Intent Senders
    Intents were designed to send messages within the device. The Intent subsystem can conclusively determine who sent Intents only when the Intents originate from the same device that services the Intent. When Intents come from other devices, the Intent subsystem cannot determine what application sent the Intent. This can lead to a variety of problems. At first, remote applications could send arbitrary Intents, meaning that your Google Talk friends had almost the same control of your device as you did. Even once that issue was resolved, we recognized that we could not trust the identity of the application who sent the request. We could only trust the identity of the user. So a "bad" application on your friend's device could send a message to a "good" application on your device which would negatively affect the good application. In the end, we determined that the Intent system, as designed for local use, did not lend itself well to being the vehicle for a Remote Procedure Call (RPC).

  3. Placing Too Much Security Burden on Developers
    As originally designed, the GTalkService placed a significant burden on the application developer to avoid security flaws and perform user and relationship management. An Android application using GTalkService would be reachable from all of the user's Google Talk friends, and a flaw in that application could pose an inviting target to a malicious "friend" or automated malware. There are automated mechanisms that could be used to help protect vulnerable applications or stop the spread of malware, but the deployment of these technologies was not possible in time for the launch of the first Android handsets.

Although we would have loved to ship this service, in the end, the Android team decided to pull the API instead of exposing users to risk and breaking compatibility with a future, more secure version of the feature. We think it's obvious that this kind of functionality would be incredibly useful, and would open lots of new doors for developers. One of our top priorities after the first devices ship is to develop a device-to-device (and possibly device-to-server) RPC mechanism that is fast, reliable, and protective of developers and users alike.

As a final note, I want to point out that since the GTalkService was always a Google "value-added" service anyway, it was never guaranteed that it would be present on every Android device. That is, GTalkService was never part of core Android. As a result this change actually allows us the potential to build a new system that is part of the core of a future version of Android.

Bluetooth API
The 1.0 version of Android and the first devices will include support for Bluetooth; for instance, Android will support Bluetooth headsets. In the early-look SDKs, there was an incomplete draft of an API that exposed Bluetooth functionality to developers. Unfortunately we had to remove that API from the 1.0 release. To get the skinny on why, I contacted Nick Pelly, one of the Android engineers responsible for that functionality. Here's the story on Bluetooth, in Nick's words:

The reason is that we plain ran out of time. The Android Bluetooth API was pretty far along, but needs some clean-up before we can commit to it for the SDK. Keep in mind that putting it in the 1.0 SDK would have locked us into that API for years to come.

Here's an example of the problems in the API. Client code is required to pass around IBluetoothDeviceCallback objects in order to receive asynchronous callbacks, but IBluetoothDeviceCallback is meant to be an internal interface. That client code would break the moment we added new callbacks to IBluetoothDeviceCallback.aidl. This is not a recipe for future-proof apps.

To make things even more tricky, the recent introduction of the bluez 4.x series brings its own new API. The Android Bluetooth stack uses bluez for GAP and SDP so you'll see more than a passing resemblance to bluez's interfaces in Android. The bluez 4.x change requires us to carefully consider how to structure our API for the future. Again, remember that once we settle on an interface we need to support it for years going forward.

Rather than ship a broken API that we knew was going to change a lot, we chose not to include it. We absolutely intend to support a Bluetooth API in a future release, although we don't know exactly when that will be. This should include some tasty features, such as:

  • Bindings to GAP and SDP functionality.

  • Access to RFCOMM and SCO sockets.

  • Potentially, L2CAP socket support from Java. (This one is under consideration.)

  • An API to our headset and handsfree profiles.

On a personal note, Nick adds, "I would love nothing more than to start seeing some neat third-party applications and games over Bluetooth. In my opinion, Bluetooth is completely under-utilized on most mobile platforms and I'm excited to someday see what the developer community can do with Android."

I'm definitely bummed about these API removals. I was particularly looking forward to the P2P capabilities offered by GTalkService, but, as always, user security and privacy must come first. In all these cases, we'll work with the developer community to create some great APIs that balance these concerns.

Fancy ListViews Redux: 0.9 SDK and RatingBar

You may remember way back when (e.g., July 2008) when Building ‘Droids featured a six-post series on creating fancy ListView implementations, culminating in a CheckListView widget that could be used as a drop-in replacement for ListView.

They’re ba-ack!

Specifically, today, let’s take a look at the 0.9 SDK’s impact on the Fancy ListView series, and update one of the examples to take advantage of a new widget: the RatingBar.

In many respects, the code and techniques introduced in the Fancy ListViews series are still valid and relevant in the world of the 0.9 SDK. Perhaps the biggest change is that ViewInflate became LayoutInflater, requiring some search-and-replace edits of your older M5 source code. But the concepts of supplying custom views, of recycling views using the ViewHolder/ViewWrapper pattern, and the like are still very useful, even after the 0.9 SDK.

Of course, as promised, the 0.9 SDK has its own take on having checkable ListView widgets; this will be covered in a future blog post.

For now, though, let’s revisit the last sample from the Fancy ListViews series and update it to eschew the checkbox, switching to the new RatingBar.

RatingBar is a widget designed to users to provide a rating to something, such as rating a music track or a blog post or an Android development book up on Amazon.com (*cough*). A rating is a float, from 0 to a specified maximum (android:numStars). You can specify what the granularity of the rating is (android:stepSize) and whether it is merely an indicator (android:isIndicator) or if it is a user-input element to allow users to set the rating by sliding their finger across the stars.

As with just about any moderately-sized widget, the RatingBar can be used as part of a row in a ListView. In fact, replacing the CheckBox in CheckListView with a RatingBar is fairly straight-forward:

  1. public class RateableWrapper extends AdapterWrapper {
  2. Context ctxt=null;
  3. float[] rates=null;
  4. public RateableWrapper(Context ctxt, ListAdapter delegate) {
  5. super(delegate);
  6. this.ctxt=ctxt;
  7. this.rates=new float[delegate.getCount()];
  8. for (int i=0;i
  9. this.rates[i]=2.0f;
  10. }
  11. }
  12. public View getView(int position, View convertView, ViewGroup parent) {
  13. ViewWrapper wrap=null;
  14. View row=convertView;
  15. if (convertView==null) {
  16. LinearLayout layout=new LinearLayout(ctxt);
  17. RatingBar rate=new RatingBar(ctxt);
  18. rate.setNumStars(3);
  19. rate.setStepSize(1.0f);
  20. View guts=delegate.getView(position, null, parent);
  21. layout.setOrientation(LinearLayout.HORIZONTAL);
  22. rate.setLayoutParams(new LinearLayout.LayoutParams(
  23. LinearLayout.LayoutParams.WRAP_CONTENT,
  24. LinearLayout.LayoutParams.FILL_PARENT));
  25. guts.setLayoutParams(new LinearLayout.LayoutParams(
  26. LinearLayout.LayoutParams.FILL_PARENT,
  27. LinearLayout.LayoutParams.FILL_PARENT));
  28. RatingBar.OnRatingBarChangeListener l=new RatingBar.OnRatingBarChangeListener() {
  29. public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromTouch) {
  30. rates[(Integer)ratingBar.getTag()]=rating;
  31. }
  32. };
  33. rate.setOnRatingBarChangeListener(l);
  34. layout.addView(rate);
  35. layout.addView(guts);
  36. wrap=new ViewWrapper(layout);
  37. wrap.setGuts(guts);
  38. layout.setTag(wrap);
  39. rate.setTag(new Integer(position));
  40. rate.setRating(rates[position]);
  41. row=layout;
  42. }
  43. else {
  44. wrap=(ViewWrapper)convertView.getTag();
  45. wrap.setGuts(delegate.getView(position, wrap.getGuts(), parent));
  46. wrap.getRatingBar().setTag(new Integer(position));
  47. wrap.getRatingBar().setRating(rates[position]);
  48. }
  49. return(row);
  50. }
  51. }
  • Create and configure the RatingBar when it is lazy-instantiated in getView()
  • Hook in a RatingBar.OnRatingBarChangeListener to update a float[] of ratings, rather than a boolean[] of checkbox states
  • Update the rating in the RatingBar when it is recycled
  • Rename all the classes to something more logical (e.g., CheckableWrapper becomes RateableWrapper)

Unfortunately, the stock style for RatingBar is a bit big for lists:

A sample ListView using a RatingBar in each row

You can use a ratingBarStyleSmall style to shrink the size, but then the RatingBar becomes read-only, meaning you would need some other means to let people specify the rating itself.

Everything you need to know about the G1!

Launch Date

Ladies and gentlemen, the launch date is quickly approaching for T-Mobile USA’s shiny new flagship phone. The Android taunting HTC Dream is set to launch on October 13th, 2008 (confirmed)[props to the guys over at TmoNews for getting it right!]. Yes, that means that in less than 8 weeks, you can finally have the phone in your hands!



So what about the whole Pre-order rumors we keep hearing about?

Well, those rumors are true too! [hats down to TmoNews again on that one]. On September 17th (confirmed), T-Mobile has scheduled to make an announcement about the availability of the Dream for pre-order to existing customers. Effective that same day, existing T-Mobile customers will be able to pre-order the phone. Pre-orders will end October 3, 2008 at 5pm EST.

Who can pre-order this bad boy?

Only existing customers will be able to pre-order the phone. Pre-order will not be available to FlexPay, Prepaid, or Employees.

How do I pre-order my new baby?

Pre-orders will ONLY be done online. MyT-Mobile.com will have an area to re-direct you to the order site. That site will only handle phone orders, not accessories. If you wish to buy accessories, T-Mobile.com will handle that job (not myt-mobile.com)

I heard I had to have an expensive feature added on to my plan, that’s a deal breaker for me!

Actually, a feature will be required, but it’s not expensive at all. To features will be available for the device, the first will be $35 and will include Unlimited Data & Messaging. If you thing about it, T-Mobile currently offers Internet for 20 bucks and Messaging for 15 bucks (20+15=35) so really its actually the same price. They will also offer a $25 feature that will include Unlimited Data & 400 Messaging for those people who don’t do much messaging.

How much will the phone cost me? Do i have to sign a contract?

Here are all the pricing details:

New Activation 2 Year Contract $199.99
New Activation 1 Year Contract $249.99

Full Upgrade 2 Year Contract $217.99
Full Upgrade 1 Year Contract $267.99
Partial Upgrade $367.99

Pre-Order Full Upgrade 2 Year Contract $167.99
Pre-Order Full Upgrade 1 Year Contract $217.99
Pre-Order Partial Upgrade $317.99

Retail Price $399

(upgrade pricing includes the $18 upgrade fee)

The best part about it is that you will not have to mail in a rebate or anything like that. The ENTIRE subsidize will be INSTANT!

What does my money get me?

- Touch Screen
- Full Qwerty keyboard
- 3G/ WiFi
- Full HTML internet capabilities
- Easy access to all Google applications (Gmail, Gtalk, search)
- Maps
- Street view
- You Tube
- Phone
- IM/Text
- Email
- Camera 3.0mp
- Video (playback only, no recording)
- Music player & 1GB memory card pre-loaded
- Applications, all available in Google marketplace (icon on the homescreen)

(This is not the complete feature list!)

Color Options: Black, Brown, White

Is there anything else I should know?

In addition to the phone price, you will have to pay an upgrade fee of $18. There will be NO shipping fee. A Gmail account will be required for the phone to work. You will receive the phone the day it launches (Oct. 13, 2008).

ENJOY!!!

Sunday, September 26, 2010

Eclipse Memory Analyser (MAT)

The Java Garbege Collector releases Java objects from memory as long as no other object refers to this object.

A Java heap dump is an image of the complete Java object graph at a certain point in time. It includes all objects, Fields, Primitive types and object references.

It is possible to instruct the JVM to create automatically a heap dump in case of a OutOfMemoryError.

The Eclipse MAT helps to visualize based on Java heap dumps the references to objects and provides tools to identify potential memory leaks.

To tell the JVM to create a heapdump in case of an OutOfMemoryError use the option -XX:+HeapDumpOnOutOfMemoryError

Installation

Install Eclipse MAT via the eclipse update manager . Select "General Purpose Tools " and install "Memory Analyser (Incubation)" and "Memory Analyser (Charts)".

Example

3.1. Create Project

Create the Java project "sawan.modi.mat.first" and the package "sawan.modi.mat.first". Create the following class.

   
package sawan.modi.mat.first;

import java.util.ArrayList;
import java.util.List;

public class Main {

/**
* @param args
*/
public static void main(String[] args) {
List list = new ArrayList();
while (1<2){>

3.2. Run Project

In Eclipse add the -XX:+HeapDumpOnOutOfMemoryError to the runtime configuration.

Run the project.

3.3. Use MAT

You should get a new file in your project (.hprof). You may need to refresh your project (F5 on the project). Double-click it and select "Leak Suspects Report".

Switch back to the overview and start your analyses of the heap dump. I believe the user interface is quite intuitive. Expecially the dominator tree gives quickly an overview of the used objects.

Thank you

Thank you for practicing with this tutorial.

I maintain this tutorial in my private time.

Wednesday, September 22, 2010

Mylyn

1.1. Overview

Programmers perform certain activities, e.g. fixing bugs. These activities are called tasks in Mylyn. Eclipse Mylyn allows to capture the context of a activity in Eclipse by remembering which files were open and filtering away the files which were not involved in this tasks. This sounds simple but can be very effective as the programming can activate a certain task and will be presented with only the files he already opened.

1.2. Getting started

To open the Mylyn task editor select Window -> Show View -> Other. Select "Mylyn" and "Task List".

To create a new task press "New Task" (or press right mouse click -> Select New -> Task). Select Local Repository.

To start working on a task, select activate task. This will remove all elements from your view as you have not selected anything yet.

Remove the task filter by clicking the highlighted button. Now all files are displayed. If you open a file it will be added to your task. If you close the file it will be removed to your task.

Tip

If a object is filtered you can press Alt+Click with the mouse on the object to show all its elements.

Once you have identified all necessary elements you can focus again to the task to filter out the unnecessary elements.

If you have to switch to another task you simply can activate it. The content of the task will be restored.

1.3. Export / Import

Mylyn allow you to export and import your local tasks. Select your categories, right mouse click on your and choose export / import.

Working in team with Mylyn

2.1. Overview

Eclipse Mylyn allows to share issues via a bug database with others. The following will describe only the connection to one example but lots of connectors, e.g. for bugzilla, jira are available.

2.2. Mylyn and Google code integration

Google code offer to host your source try. See Google code hosting .

It is possible to use the Mylyn webconnector to connect your issues with Google code hosting.

Install the Web templates from the Mylyn Incubator update update site: See http://download.eclipse.org/tools/mylyn/update/incubator .

Choose Windows -> Show View -> Other and open the view "Task repositories".

Right mouse click, select "Add Task Repository". Select then Web Templates (Advanced). Please next and select "Eclipse Outliner (Google Code)". Replace the server address with your web address. Leave the /issues at the end.

You can now add a new query for the tasks from your Google code site.

You Google issues will now appear in the task list. If you create new issues they will uploaded to Google.

Summary

Mylyn allow to group and structure activities and relevant files which are related to a certain tasks. What I really starting to appreciate is that if I stop doing something that the task remembers the files which were opened and the placed there I looked. I can only recommend to try this tool it certainly helped me a bit in re-finding the information I used during a certain task.

Thank you

Thank you for practicing with this tutorial.

I maintain this tutorial in my private time.

Monday, September 20, 2010

Separating Lists with Headers in Android 0.9

Earlier today the latest Android 0.9 SDK was released, and it’s packed full of wonderful changes. As you play around, you might see ListViews split into sections using separating headers. (Example shown on the right is the browser settings list.)

There isn’t an easy way of creating these separated lists, so I’ve put together SeparatedListAdapter which does it quickly. To summarize, we’re creating a new BaseAdapter that can contain several other Adapters, each with their own section headers.

First let’s create some simple XML layouts to be used for our lists: first the header view, then two item views that we’ll use later for the individual lists. (Thanks to Romain Guy for helping me find existing styles to keep these XML layouts nice and tidy.)


  1. <TextView
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:id="@+id/list_header_title"
  4. android:layout_width="fill_parent"
  5. android:layout_height="wrap_content"
  6. android:paddingTop="2dip"
  7. android:paddingBottom="2dip"
  8. android:paddingLeft="5dip"
  9. style="?android:attr/listSeparatorTextViewStyle" />


  10. <TextView
  11. xmlns:android="http://schemas.android.com/apk/res/android"
  12. android:id="@+id/list_item_title"
  13. android:layout_width="fill_parent"
  14. android:layout_height="fill_parent"
  15. android:paddingTop="10dip"
  16. android:paddingBottom="10dip"
  17. android:paddingLeft="15dip"
  18. android:textAppearance="?android:attr/textAppearanceLarge"
  19. />


  20. <LinearLayout
  21. xmlns:android="http://schemas.android.com/apk/res/android"
  22. android:layout_width="fill_parent"
  23. android:layout_height="wrap_content"
  24. android:orientation="vertical"
  25. android:paddingTop="10dip"
  26. android:paddingBottom="10dip"
  27. android:paddingLeft="15dip"
  28. >
  29. <TextView
  30. android:id="@+id/list_complex_title"
  31. android:layout_width="fill_parent"
  32. android:layout_height="wrap_content"
  33. android:textAppearance="?android:attr/textAppearanceLarge"
  34. />
  35. <TextView
  36. android:id="@+id/list_complex_caption"
  37. android:layout_width="fill_parent"
  38. android:layout_height="wrap_content"
  39. android:textAppearance="?android:attr/textAppearanceSmall"
  40. />
  41. LinearLayout>

Now let’s create the actual SeparatedListAdapter class which provides a single interface to multiple sections of other Adapters. After using addSection() to construct the child sections, you can easily use ListView.setAdapter() to present the now-separated list to users.

As for the Adapter internals, to correctly find the selected item among the child Adapters, we walk through subtracting from the original position until we find either a header (position = 0) or item in the current child Adapter (position <>

Here’s the source for SeparatedListAdapter:

  1. public class SeparatedListAdapter extends BaseAdapter {

  2. public final Map sections = new LinkedHashMap();
  3. public final ArrayAdapter headers;
  4. public final static int TYPE_SECTION_HEADER = 0;

  5. public SeparatedListAdapter(Context context) {
  6. headers = new ArrayAdapter(context, R.layout.list_header);
  7. }

  8. public void addSection(String section, Adapter adapter) {
  9. this.headers.add(section);
  10. this.sections.put(section, adapter);
  11. }

  12. public Object getItem(int position) {
  13. for(Object section : this.sections.keySet()) {
  14. Adapter adapter = sections.get(section);
  15. int size = adapter.getCount() + 1;

  16. // check if position inside this section
  17. if(position == 0) return section;
  18. if(position < class="keyword">return adapter.getItem(position - 1);

  19. // otherwise jump into next section
  20. position -= size;
  21. }
  22. return null;
  23. }

  24. public int getCount() {
  25. // total together all sections, plus one for each section header
  26. int total = 0;
  27. for(Adapter adapter : this.sections.values())
  28. total += adapter.getCount() + 1;
  29. return total;
  30. }

  31. public int getViewTypeCount() {
  32. // assume that headers count as one, then total all sections
  33. int total = 1;
  34. for(Adapter adapter : this.sections.values())
  35. total += adapter.getViewTypeCount();
  36. return total;
  37. }

  38. public int getItemViewType(int position) {
  39. int type = 1;
  40. for(Object section : this.sections.keySet()) {
  41. Adapter adapter = sections.get(section);
  42. int size = adapter.getCount() + 1;

  43. // check if position inside this section
  44. if(position == 0) return TYPE_SECTION_HEADER;
  45. if(position < class="keyword">return type + adapter.getItemViewType(position - 1);

  46. // otherwise jump into next section
  47. position -= size;
  48. type += adapter.getViewTypeCount();
  49. }
  50. return -1;
  51. }

  52. public boolean areAllItemsSelectable() {
  53. return false;
  54. }

  55. public boolean isEnabled(int position) {
  56. return (getItemViewType(position) != TYPE_SECTION_HEADER);
  57. }

  58. @Override
  59. public View getView(int position, View convertView, ViewGroup parent) {
  60. int sectionnum = 0;
  61. for(Object section : this.sections.keySet()) {
  62. Adapter adapter = sections.get(section);
  63. int size = adapter.getCount() + 1;

  64. // check if position inside this section
  65. if(position == 0) return headers.getView(sectionnum, convertView, parent);
  66. if(position < class="keyword">return adapter.getView(position - 1, convertView, parent);

  67. // otherwise jump into next section
  68. position -= size;
  69. sectionnum++;
  70. }
  71. return null;
  72. }

  73. @Override
  74. public long getItemId(int position) {
  75. return position;
  76. }

  77. }

As expected, it correctly prevents the section headers from being selected, and seamlessly stiches together the various Adapters.

This approach also uses convertView correctly as long as the child Adapters return getItemViewType() and getViewTypeCount() normally. No special changes are needed for an Adapter to become a child.

Now let’s use SeparatedListAdapter in some example code. We use the XML layouts defined earlier to create an ArrayAdapter and an advanced two-row SimpleAdapter, and then add both as sections to our SeparatedListAdapter.

  1. public class ListSample extends Activity {

  2. public final static String ITEM_TITLE = "title";
  3. public final static String ITEM_CAPTION = "caption";

  4. public Map createItem(String title, String caption) {
  5. Map item = new HashMap();
  6. item.put(ITEM_TITLE, title);
  7. item.put(ITEM_CAPTION, caption);
  8. return item;
  9. }

  10. @Override
  11. public void onCreate(Bundle icicle) {
  12. super.onCreate(icicle);

  13. List> security = new LinkedList>();
  14. security.add(createItem("Remember passwords", "Save usernames and passwords for Web sites"));
  15. security.add(createItem("Clear passwords", "Save usernames and passwords for Web sites"));
  16. security.add(createItem("Show security warnings", "Show warning if there is a problem with a site's security"));

  17. // create our list and custom adapter
  18. SeparatedListAdapter adapter = new SeparatedListAdapter(this);
  19. adapter.addSection("Array test", new ArrayAdapter(this,
  20. R.layout.list_item, new String[] { "First item", "Item two" }));
  21. adapter.addSection("Security", new SimpleAdapter(this, security, R.layout.list_complex,
  22. new String[] { ITEM_TITLE, ITEM_CAPTION }, new int[] { R.id.list_complex_title, R.id.list_complex_caption }));

  23. ListView list = new ListView(this);
  24. list.setAdapter(adapter);
  25. this.setContentView(list);

  26. }

  27. }

The resulting interface behaves just like the browser preferences list, and you could easily create other custom Adapters to insert into the various sections, such as including icons or checkboxes.

These section headers can really help separate out otherwise-cluttered activities. I used them several places in my CompareEverywhere application which lets you easily compare prices and read reviews for any product with a ba

Amarok 1.4 remote in Android using DCOP+Python

We’ve all seen the Apple app that lets you control iTunes remotely, and there totally needs to be something like this for Android. I’m an avid fan of Amarok, so I’ve whipped together a simple remote control that runs on Android. It only took about 3 hours tonight, and I’m releasing everything GPLv3 here. First some details on the architecture:

Amarok can easily be controlled via DCOP, including fetching currently playing information and album art. DCOP is a local, safe IPC architecture that usually comes already enabled with KDE applications. Just a reminder that DCOP is being phased out in KDE 4 with D-Bus taking its place. Speaking of the D-Bus future, there is an awesome standard called Media Player Remote Interface Specification (MPRIS) that is being put together by the folks at XMMS, VLC, Amarok, and others. It doesn’t seem to be in stable releases yet, but will be soon. Back to the present, I’m going to just focus on getting Amarok 1.4 working with older DCOP calls.

First, I built a Python bridge that offers a simple HTTP REST-like API that will help us relay pre-approved DCOP commands from Android to Amarok. The Python is pretty simple:

  1. from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
  2. import pydcop, re
  3. port = 8484
  4. allowed = ["status", "trackCurrentTime", "trackTotalTime", "album", "artist",
  5. "title", "next", "playPause", "prev", "volumeDown", "volumeUp",
  6. "coverImage", "seek"]
  7. resafe = re.compile("[^A-Za-z0-9/]")
  8. class AmarokHandler(BaseHTTPRequestHandler):
  9. def do_GET(self):
  10. # pull out action and simple variable
  11. safe = resafe.sub('', self.path)
  12. blank, action, var = tuple(safe.split('/'))
  13. # skip if action has not been approved
  14. if not action in allowed: return
  15. # check if image request
  16. if action == "coverImage":
  17. self.send_response(200)
  18. self.send_header('Content-type', 'image/jpeg')
  19. self.end_headers()
  20. cover = open((pydcop.DCOPMethod("amarok", "player", "coverImage"))())
  21. self.wfile.write(cover.read())
  22. cover.close()
  23. return
  24. # make dcop call over to amarok
  25. if len(var) > 0: reply = (pydcop.DCOPMethod("amarok", "player", action))(int(var))
  26. else: reply = (pydcop.DCOPMethod("amarok", "player", action))()
  27. # write back any dcop response
  28. self.send_response(200)
  29. self.send_header('Content-type', 'text/plain')
  30. self.end_headers()
  31. self.wfile.write(reply)
  32. return
  33. try:
  34. server = HTTPServer(('', port), AmarokHandler)
  35. print 'started amarokremote server on port %s' % (port)
  36. server.serve_forever()
  37. except KeyboardInterrupt:
  38. server.socket.close()

Essentially we are using a URL of the form http://ipaddress:port/command/variable. For example, in the Android emulator you could call http://10.0.2.2:8282/volumeUp/ to increase the volume.

Carefully note that we are screening the commands against an “allowed” list before running off to Amarok with them. We’re also scrubbing the incoming calls to prevent any buffer overflows. Usually we are just calling the Amarok DCOP method and returning any result. One exception is for coverImage requests, because Amarok just returns a local path. We help that image over the Python bridge by sending the local JPEG as the response.

On the Android side of things, we have a simple screen layout and are hooking up the various API calls to buttons. There’s also a background thread that keeps Android in-sync with what Amarok is currently playing. Finally, we’re using some simple preferences to store the server string and update interval. (Tap the menu button to change these settings.)

Here’s a tarball of the Eclipse project, along with the APK that’s ready to run on the new 0.9 SDK. Start the Python server above on your the computer running Amarok, change the IP address if needed, and you should be ready to go.

Also, a reminder that 10.0.2.2 is magic on the Android emulator because it points to the loopback adapter of the parent computer. So, if you’re running the emulator and Amarok on the same computer, this IP address will work perfectly.

Saturday, September 18, 2010

Fun with Fonts

Inevitably, you’ll get the question “hey, can we change this font?” when doing application development. The answer depends on what fonts come with the platform, whether you can add other fonts, and how to apply them to the widget or whatever needs the font change.

Android is no different. It comes with some fonts plus a means for adding new fonts. Though, as with any new environment, there are a few idiosyncrasies to deal with.

Android natively knows three fonts, by the shorthand names of “sans”, “serif”, and “monospace”. These fonts are actually the Droid series of fonts, created for the Open Handset Alliance by Ascender.

For those fonts, you can just reference them in your layout XML, if you choose, such as:

  1. xml version="1.0" encoding="utf-8"?>
  2. <TableLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. android:stretchColumns="1">
  7. <TableRow>
  8. <TextView
  9. android:text="sans:"
  10. android:layout_marginRight="4px"
  11. android:textSize="20sp"
  12. />
  13. <TextView
  14. android:id="@+id/sans"
  15. android:text="Hello, world!"
  16. android:typeface="sans"
  17. android:textSize="20sp"
  18. />
  19. TableRow>
  20. <TableRow>
  21. <TextView
  22. android:text="serif:"
  23. android:layout_marginRight="4px"
  24. android:textSize="20sp"
  25. />
  26. <TextView
  27. android:id="@+id/serif"
  28. android:text="Hello, world!"
  29. android:typeface="serif"
  30. android:textSize="20sp"
  31. />
  32. TableRow>
  33. <TableRow>
  34. <TextView
  35. android:text="monospace:"
  36. android:layout_marginRight="4px"
  37. android:textSize="20sp"
  38. />
  39. <TextView
  40. android:id="@+id/monospace"
  41. android:text="Hello, world!"
  42. android:typeface="monospace"
  43. android:textSize="20sp"
  44. />
  45. TableRow>
  46. <TableRow>
  47. <TextView
  48. android:text="Custom:"
  49. android:layout_marginRight="4px"
  50. android:textSize="20sp"
  51. />
  52. <TextView
  53. android:id="@+id/custom"
  54. android:text="Hello, world!"
  55. android:textSize="20sp"
  56. />
  57. TableRow>
  58. TableLayout>

This layout builds a table showing short samples of four fonts. Notice how the first three have the android:typeface attribute, whose value is one of the three built-in font faces (e.g., “sans”).

The three built-in fonts are very nice. However, it may be that a designer, or a manager, or a customer wants a different font than one of those three. Or perhaps you want to use a font for specialized purposes, such as a “dingbats” font instead of a series of PNG graphics.

The easiest way to accomplish this is to package the desired font(s) with your application. To do this, simply create an assets/ folder in the project root, and put your fonts (in TrueType, or TTF, form) in the assets. You might, for example, create assets/fonts/ and put your TTF files in there.

Then, you need to tell your widgets to use that font. Unfortunately, you can no longer use layout XML for this, since the XML does not know about any fonts you may have tucked away as an application asset. Instead, you need to make the change in Java code:

  1. public class FontSampler extends Activity {
  2. @Override
  3. public void onCreate(Bundle icicle) {
  4. super.onCreate(icicle);
  5. setContentView(R.layout.main);

  6. TextView tv=(TextView)findViewById(R.id.custom);
  7. Typeface face=Typeface.createFromAsset(getAssets(), "fonts/HandmadeTypewriter.ttf");

  8. tv.setTypeface(face);
  9. }
  10. }

Here, we grab the TextView for our “custom” sample, then create a Typeface object via the static createFromAsset() builder method. This takes the application’s AssetManager (from getAssets()) and a path within your assets/ directory to the font you want.

Then, it is just a matter of telling the TextView to setTypeface(), providing the Typeface you just created. In this case, we are using the Handmade Typewriter font.

The results?

Font Sampler

Note that Android does not seem to like all TrueType fonts. I spent a lot of time trying to figure out what I was doing wrong with this sample, before switching to a different font, and everything “just worked”. When Android dislikes a custom font, rather than raise an Exception, it seems to substitute Droid Sans (”sans”) quietly. So, if you try to use a different font and it does not seem to be working, it may be that the font in question is incompatible with Android, for whatever reason.

Also note that TrueType fonts can be rather pudgy, particularly if they support an extensive subset of the available Unicode characters. The Handmade Typewriter font used here runs over 70KB; the DejaVu free fonts can run upwards of 500KB apiece. Even compressed, these add bulk to your application, so be careful not to go overboard with custom fonts, lest your application take up too much room on your users’ phones.

Announcing a beta release of the Android SDK

You can read about the new Android 0.9 SDK beta at the Android Developers' Site, or if you want to get straight to the bits, you can visit the download page. Once you've got it, be sure to visit our Developer Forum if you have any questions.

Back in November, we made some SDK builds available that we referred to as "early look" SDKs. The goal was to give developers insight into the platform as early on as possible, and to get some initial feedback. Since then, we've been working with our Open Handset Alliance partners to incorporate much of that feedback, and finish the first devices. Since those devices are shipping in the fourth quarter, the platform is now converging on a final "Android 1.0" version.

The beta SDK that we're releasing today is the first big step on the SDK's road to compatibility with 1.0. Since this is a beta release, applications developed with it may not quite be compatible with devices running the final Android 1.0. However, the APIs are now pretty stable and we don't expect any major changes. If you're one of the many developers who were waiting for something a bit more mature, this might be a good time to take another look.

Since we're now moving quickly toward 1.0, it may also help to know which direction we're headed. To help out, we've also prepared a development roadmap. This will be a living document, and we'll keep it up to date as the Android landscape evolves. Currently it covers the next few months, roughly through the end of the year and a bit into next year. We'll update it with additional detail as we are able to, but even right now it can help give you a picture of how things will play out as the first phones draw near.

Enough of that though -- you're probably wondering what's actually new in the SDK. Well, you should read the Release Notes, the Change Overview and the API Delta Report for all the details, but here are a few highlights:

  • First and most obviously, the new Home screen is included, along with a ton of UI changes for 1.0.

  • Some new applications are included: an Alarm Clock, Calculator, Camera, Music player, Picture viewer, and Messaging (for SMS/MMS conversations.)

  • Several new development tools were added, such as a graphical preview for XML layouts for users of Eclipse, and a tool for constructing 9-patch images.

  • Since we've got a new Home screen application now, we thought the now-obsolete version from the M5 early-look SDK might be helpful to developers, so its source is included as a sample.

  • A number of new APIs are fleshed out and improved, and others are now close to their final forms for 1.0.

  • Tons of bugs were fixed, of course. (If you had problems with the MediaPlayer, try it now!)

There are a lot of changes -- the ones in the list above are just my personal favorites, so you should check out the links above for the full story. Not all the changes are additions, though: I'm sorry to say that we had to remove a few things, such as the GTalkService (for security reasons), and the Bluetooth API. There's a bit more detail in the links above, and we'll follow up on those in particular here in this blog to give you the scoop. In fact, we've got a little list of topics we want to talk about here, so stay tuned.

Friday, September 17, 2010

Phonecalls

Again, there was a bit of a break in the blog. One reason is personal: I moved to London to take a new job. The other reason is that this time I went into the hairy issue which is call handling in Android.

I worked with the m3-rc37a version of the emulator and I can tell you that emulator crashes are really common if you play with the telephony stuff in this version. Don't be surprised if the example program distributed with this blog entry does not show the behaviour I promise but crashes instead. It just happens with this emulator version.



You can find the example program here.

The task I gave myself is catching incoming and outgoing calls which is easy. There needs to be just an IntentReceiver defined in the manifest and the onReceiveIntent method implemented in the intent receiver class. The manifest entry declares the filter for the IntentReceiver (again, the XML is mangled due to limitations of the blog engine).

[receiver class=".PhoneIntentReceiver"]
[intent-filter]
[action android:value="android.intent.action.PHONE_STATE" /]
[/intent-filter]
[/receiver]


PHONE_STATE is a general intent for signalling all sorts of phone events. The example program dumps the event bundle when the event comes in. In case of outgoing call, the event is delivered with state=OFFHOOK when the call starts then state=IDLE when the call finishes (state is a key in the intent bundle ("extras") and its string values are OFFHOOK and IDLE). In case of the incoming call (you can simulate it by telnet localhost 5554 then issuing the gsm call phone_number command on the console) the state transition is RINGING then IDLE again.

So far so good. Having a notification on incoming and outgoing calls is less useful, however, if the caller or the called number is not known. This turned out to be a really hairy issue for which I did not find the answer (just desperate help requests). I remembered the good old Series60 days and went for the call log.

The call log is surprisingly co-located in the database of the Contacts application. Try this:

adb shell
#cd /data/data/com.google.android.providers.contacts/databases

# sqlite3 contacts.db

SQLite version 3.5.0
Enter ".help" for instructions
sqlite> .dump

...

CREATE TABLE calls (_id INTEGER PRIMARY KEY,number TEXT,number_key TEXT,number_type TEXT,date INTEGER,duration INTEGER,type INTEGER,person INTEGER,new INTEGER);

INSERT INTO "calls" VALUES(1,'+44444','44444+','Home',1200263250247,0,3,NULL,0);
INSERT INTO "calls" VALUES(2,'+4444445','5444444+','Home',1200263859817,5,2,NULL,1);
INSERT INTO "calls" VALUES(3,'+1234','4321+','Home',1200263990161,2,2,NULL,1);


There is the log. Unfortunately (or fortunately? :-)) Android enforces strict database separation for applications so the Contacts database cannot be accessed from within another Android application. Luckily, the Contacts application decided to share the data as Content Provider. There is even a facilitator class: CallLog.Calls. Dumping the call log is relatively easy after that (look at the PhoneIntent class which is an Activity that makes this dump on pressing a button).

Now the only thing remained to see how the database is updated with regards to phone calls. It turned out that the number is not visible when the call goes out or comes in (RINGING or OFFHOOK state) but is accessible through the content provider when the IDLE transition event comes in. I was not able, therefore, to capture the number of the currently ongoing call but I was able to capture the number of the call that has just finished.

Testing the example application was a pain for me. When working with calls, the emulator tended to crash on my PC without installing any applications. In order to monitor the event transitions, you have to bring up the phone application because it does not come to the front by itself. You can do that by pressing some digits at the emulator's opening screen. If you don't bring the phone app to front manually, you won't be able to answer or reject the call.




I propose that you don't run adb logcat while the call goes on - it tends to increase the chance of emulator crash. You can examine whether the number was retrieved succesfully from the call log by running adb logcat after the call was finished.

Flipping Your Views

Let’s face it: phones are small.

Even if you have a phone with excellent screen resolution, the physical screen size is still rarely over 3″x5″, since most people want phones that can fit in a pocket, purse, pouch, or poncho.

This means your Android activities can only display so much stuff at one time without individual widgets or text getting too small to be easily read. There are any number of ways to handle this:

  • You could break the one activity into several…but this can get cumbersome
  • You can use ScrollView to allow users to scroll through a longer activity…but this may or may not be easy for the user depending on the device (e.g., a haptic interface using a swipe for scrolling assumes there’s a place clear in the UI for them to actually swipe without accidentally toggling a button or something)

  • You can use TabView, as described in the previous post…but you are locked into a particular presentation pattern (e.g., tabs)
  • You can use ViewFlipper and work out a UI pattern that’s right for you

Not surprisingly, today, we’re going to look at the latter option.

ViewFlipper inherits from FrameLayout, just like we used to describe the innards of a TabView in that previous post. However, initially, it just shows the first child view. It is up to you to arrange for the views to flip, either manually by user interaction, or automatically via a timer.

For example, here is a layout for a simple activity using a Button and a ViewFlipper:

  1. xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. >
  7. <Button android:id="@+id/flip_me"
  8. android:layout_width="fill_parent"
  9. android:layout_height="wrap_content"
  10. android:text="Flip Me!"
  11. />
  12. <ViewFlipper android:id="@+id/details"
  13. android:layout_width="fill_parent"
  14. android:layout_height="fill_parent"
  15. >
  16. <TextView
  17. android:layout_width="fill_parent"
  18. android:layout_height="wrap_content"
  19. android:textStyle="bold"
  20. android:textColor="#FF00FF00"
  21. android:text="This is the first panel"
  22. />
  23. <TextView
  24. android:layout_width="fill_parent"
  25. android:layout_height="wrap_content"
  26. android:textStyle="bold"
  27. android:textColor="#FFFF0000"
  28. android:text="This is the second panel"
  29. />
  30. <TextView
  31. android:layout_width="fill_parent"
  32. android:layout_height="wrap_content"
  33. android:textStyle="bold"
  34. android:textColor="#FFFFFF00"
  35. android:text="This is the third panel"
  36. />
  37. ViewFlipper>
  38. LinearLayout>

Notice that the layout defines three child views for the ViewFlipper, each a TextView with a simple message. Of course, you could have very complicated child views, if you so chose.

To manually flip the views, we need to hook into the Button and flip them ourselves when the button is clicked:

  1. public class FlipperDemo extends Activity {
  2. ViewFlipper flipper;

  3. @Override
  4. public void onCreate(Bundle icicle) {
  5. super.onCreate(icicle);
  6. setContentView(R.layout.main);

  7. flipper=(ViewFlipper)findViewById(R.id.details);

  8. Button btn=(Button)findViewById(R.id.flip_me);

  9. btn.setOnClickListener(new View.OnClickListener() {
  10. public void onClick(View view) {
  11. flipper.showNext();
  12. }
  13. });
  14. }
  15. }

This is just a matter of calling showNext() on the ViewFlipper, like you can on any ViewAnimator class.

The result is a trivial activity: click the button, and the next TextView in sequence is displayed, wrapping around to the first after viewing the last:

ViewFlipper demo, on the first view

Same demo, next view, after clicking button

This, of course, could be handled more simply by having a single TextView and changing the text and color on each click. However, you can imagine that the ViewFlipper contents could be much more complicated, like the contents you might put into a TabView.

As with the TabView in the previous post, sometimes, your ViewFlipper contents may not be known at compile time. As with TabView, though, you can add new contents on the fly with ease.

For example, let’s look at another sample activity, using this layout:

  1. xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. >
  7. <ViewFlipper android:id="@+id/details"
  8. android:layout_width="fill_parent"
  9. android:layout_height="fill_parent"
  10. >
  11. ViewFlipper>
  12. LinearLayout>

Notice that the ViewFlipper has no contents at compile time. Also note that there is no Button for flipping between the contents — more on this in a moment.

For the ViewFlipper contents, we will create large Button widgets, each containing one of the random words used in many an Android tutorial. And, we will set up the ViewFlipper to automatically rotate between the Button widgets, using an animation for transition:

  1. public class FlipperDemo2 extends Activity {
  2. static String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
  3. "consectetuer", "adipiscing", "elit",
  4. "morbi", "vel", "ligula", "vitae",
  5. "arcu", "aliquet", "mollis", "etiam",
  6. "vel", "erat", "placerat", "ante",
  7. "porttitor", "sodales", "pellentesque",
  8. "augue", "purus"};
  9. ViewFlipper flipper;

  10. @Override
  11. public void onCreate(Bundle icicle) {
  12. super.onCreate(icicle);
  13. setContentView(R.layout.main);

  14. flipper=(ViewFlipper)findViewById(R.id.details);

  15. flipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_left_in));
  16. flipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_left_out));

  17. for (String item : items) {
  18. Button btn=new Button(this);

  19. btn.setText(item);

  20. flipper.addView(btn,
  21. new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
  22. ViewGroup.LayoutParams.FILL_PARENT));
  23. }

  24. flipper.setFlipInterval(2000);
  25. flipper.startFlipping();
  26. }
  27. }

After getting our ViewFlipper widget from the layout, we first set up the “in” and “out” animations. In Android terms, an animation is a description of how a widget leaves (”out”) or enters (”in”) the viewable area. Animations are a complex beast, worthy of a blog post or two in their own right. For now, realize that animations are resources, stored in res/anim/ in your project. For this tutorial, we are using a pair of animations supplied by the SDK samples, available under the Apache 2.0 license. As their names suggest, widgets are “pushed” to the left, either to enter or leave the viewable area.

After iterating over the funky words, turning each into a Button, and adding the Button as a child of the ViewFlipper, we set up the flipper to automatically flip between children (flipper.setFlipInterval(2000);) and to start flipping (flipper.startFlipping();).

The result is an endless series of buttons, each appearing, then sliding out to the left after 1.5 seconds, being replaced by the next button in sequence, wrapping around to the first after the last has been shown:

Automatic ViewFlipper flip, shown in mid-animation

In this case, the above screenshot shows the ViewFlipper in mid-transition, animating away from one button to the next.

The auto-flipping ViewFlipper is useful for status panels or other situations where you have a lot of information to display, but not much room. The key is that, since it automatically flips between views, expecting users to interact with individual views is dicey — the view might switch away part-way through their interaction.