24-Apr-09 (Created: 24-Apr-09) | More in 'Older Android Notes'

chapter 12

We will talk about three main topics in this chapter. These three are OpenGL, Live Folders, and the future of Android.

As we have pointed out in chapter 10, Android SDK is in need of a simplified framework for OpenGL starting programmers. In chapater 10 we have introduced one such framework loosely based on 1.1 Android samples. In Android release 1.5, Android team has also recognized this and introduced a very similar framework to hide the OpenGL initialization coding patterns. In this section we will talk about this framework and introduce to the set of new classes. We will then reimplement the simple triangle drawing example using these new classes.

A new Android concept called Live Folders has garnered a lot publicity as the early reviewers looked at the 1.5 features. Live folders in 1.5 and on, allow developers to expose content providers such as contacts, notes, media etc on the home pane of the device. As data changes in the underlying store the folders on the home page will be dynamically updated similar to an RSS feed. We will explain in this chapter in detail what these live folders are, how to implement one, and how to make them "live".

As we have stated in chapter 1, Android OS is set to power the next generation of PCs, namely netbooks and various other internet enabled devices that are capable of general purpose computing and also media friendly. We will explore this thread further in the last section of this chapter.

Let us see in our first section now the new OpenGL capabiliites in the Android SDK.

OpenGL ES in 1.5

Introduce the need for a simplified OpenGL ES framework

The changes in Android SDK 1.5 for OpenGL are primarily aimed at providing a simplified interface to the OpenGL drawing capabilities. This need is discussed in great detail in chapter 10 and as a remedy we have designed an OpenGL test harness that exhibits the following characteristics


1. Hide how one needs to initialize and get an EGLContext
2. Hide the complexities of drawing on a SurfaceView using a secondary thread
3. Expose as only the interfaces that are dedicated to core OpenGL drawing apis

In that framework we only needed to worry about inheriting from an AbstractRenderer to start drawing. The changes in SDK 1.5 followed a similar pattern and introduced the following key new classes and interfaces into the android.opengl package:

1. GLSurfaceView - Responsible for drawing on a surface view using a secondary thread. This class is equivalent to OpenGLTestHarness and GLThread classes in chapter

2. GLSuraceView.Renderer - All rendering sub classes need to implement this interface.

The suggested approach is as follows

1. Implement Renderer and provide the necessary OpenGL setup such as Camera. overide the onDraw method to draw.

2. Instantiate a GLSurfaceView and associate it with the sub classed Renderer in step 1

3. Set the GLSurfaceView object in the activity.

Let us take a look the Renderer interface to see what it looks like


public static interface GLSurfaceView.Renderer
{
   void onDrawFrame(GL10 gl);
   void onSuraceChanged(GL10 gl, int width, int height);
   void onSurfaceCreated(GL10 gl, EGLConfig config);
}

By following the test harness in chapter 10, we can gain simplicity by having all our renderers have an AbstractRenderer as a parent. Let us reimplment this AbstractRenderer using this new standardized interface. Here is how it looks


//filename: AbstractRenderer.java
public abstract class AbstractRenderer 
implements GLSurfaceView.Renderer
{
    public int[] getConfigSpec() {
        int[] configSpec = {
                EGL10.EGL_DEPTH_SIZE, 0,
                EGL10.EGL_NONE
        };
        return configSpec;
    }

    public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
        gl.glDisable(GL10.GL_DITHER);
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
                GL10.GL_FASTEST);
        gl.glClearColor(.5f, .5f, .5f, 1);
        gl.glShadeModel(GL10.GL_SMOOTH);
        gl.glEnable(GL10.GL_DEPTH_TEST);
    }
    
    public void onSurfaceChanged(GL10 gl, int w, int h) {
        gl.glViewport(0, 0, w, h);
        float ratio = (float) w / h;
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);
    }
    
    public void onDrawFrame(GL10 gl) 
    {
        gl.glDisable(GL10.GL_DITHER);
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();
        GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        draw(gl);
    }
    protected abstract void draw(GL10 gl);
}

Reimplement the simple traingle drawing

With the AbstractRenderer in place let us see how we can create a small activity that draws a simple OpenGL triangle as in chapter 10. We will be using the following files to demonstrate this exercise.


1. AbstractRenderer.java
2. SimpleTriangleRenderer.java
3. OpenGL15TestHarnessActivity.java

We have already presented the code for AbstractRenderer above. Let us see the code for SimpleTriangleRenderrer

2. SimpleTriangleRenderer.java

The following code is the same as the code in Listing 10-30 except that the classes inherit from a different AbstractRenderer.


//filename: SimpleTriangleRenderer.java
public class SimpleTriangleRenderer extends AbstractRenderer
{
   //Number of points or vertices we want to use
    private final static int VERTS = 3;
    
    //A raw native buffer to hold the point coordinates
    private FloatBuffer mFVertexBuffer;
    
    //A raw native buffer to hold indices
    //allowing a reuse of points.
    private ShortBuffer mIndexBuffer;
    
    public SimpleTriangleRenderer(Context context) 
    {
        ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4);
        vbb.order(ByteOrder.nativeOrder());
        mFVertexBuffer = vbb.asFloatBuffer();

        ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2);
        ibb.order(ByteOrder.nativeOrder());
        mIndexBuffer = ibb.asShortBuffer();

        float[] coords = {
                -0.5f, -0.5f, 0, // (x1,y1,z1)
                 0.5f, -0.5f, 0,
                 0.0f,  0.5f, 0
        };
        for (int i = 0; i < VERTS; i++) {
            for(int j = 0; j < 3; j++) {
                mFVertexBuffer.put(coords[i*3+j]);
            }
        }
        short[] myIndecesArray = {0,1,2};
        for (int i=0;i<3;i++)
        {
           mIndexBuffer.put(myIndecesArray[i]);
        }
        mFVertexBuffer.position(0);
        mIndexBuffer.position(0);
    }

   //overriden method
    protected void draw(GL10 gl)
    {
       gl.glColor4f(1.0f, 0, 0, 0.5f);
       gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
        gl.glDrawElements(GL10.GL_TRIANGLES, VERTS,
                GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
    }
}

OpenGL15TestHarnessActivity

Once we have the above renderer we need let us see how we would use this renderer to draw using a GLSurfaceView. This is demonstrated in the following simple activity code.


//filename: OpenGLTestHarnessActivity.java
public class OpenGL15TestHarnessActivity extends Activity {
   private GLSurfaceView mTestHarness;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mTestHarness = new GLSurfaceView(this);
        mTestHarness.setEGLConfigChooser(false);
        mTestHarness.setRenderer(new SimpleTriangleRenderer(this));
        mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
        setContentView(mTestHarness);
    }
    @Override
    protected void onResume()    {
        super.onResume();
        mTestHarness.onResume();
    }
    @Override
    protected void onPause() {
        super.onPause();
        mTestHarness.onPause();
    }
}

In this code we have first instantiated a GLSurfaceView. Using the setEGLConfigChoose(false) we have advised the SDK to choose a config as close to 16-bit RGB as possible, with or without an optional depth buffer as close to 16-bits as possible. Refer to the 1.5 SDK documentation on this method for more advanced options. We then set the triangle renderer in the GLSurfaceView. GLSurfaceView has two rendering modes. One is to allow animation and the other is to draw just when needed. The default is set for animation where the renderer is called again and again. So we have indicated otherwise by chosing RENDERMODE_WHEN_DIRTY. Then we simply set the view in the activity.

You can run this activity from any meny option you may have by doing


    private void invoke15SimpleTriangle()
    {
      Intent intent = new Intent(this,OpenGL15TestHarnessActivity.class);
      startActivity(intent);
    }

Ofcourse you will have to register the activity in the android manifest file


   <activity android:name=".OpenGL15TestHarnessActivity"
                  android:label="OpenGL 15 Test Harness"/>

When you run this code you will see the triangle just like in Figure 10-30. As you could see SDK 1.5 substantially simplified OpenGL drawing.

We have indicated in chapter 10 that we could implement animation quite easily. Let us see how we do that in this new framework by considering the following example.

Show an example of animation through OpenGL ES

Animation in this new OpenGL approach is easily accomodated by changing the rendering mode on the GLSurfaceView object.


//get a GLSurfaceView
GLSurfaceView openGLView;

//Set the mode to continuous draw mode
openGLView.setRenderingMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);

However the default mode for GLSurfaceView is the continuous mode. So by default animation is enabled. Once the rendering mode is continuous, it is upto the renderer's onDraw method to do the necessary thing to affect animation. To demonstrate this we will show you an example where the triangle drawn is rotated in a circular fashion. This example has three files. This example has the following two files


1. AnimatedSimpleTriangleRenderer.java //responsible for drawing
2. AnimatedTriangleActivity.java //a simple activity to host the GLSurfaceView

Let us consider these files one at a time starting with the activity.

AnimatedTriangleActivity

The key parts of this activity is high lighted. we basically took the previous activity that we used for a simple drawing and commented out the rendering mode. This would let the GLSurfaceView to default the rendering mode to continuous. This would call the onDraw method of the renderer, in this case AnimatedSimpleTriangleRenderer, repeatedly.


//filename: AnimatedTriangleActivity.java
public class AnimatedTriangleActivity extends Activity {
   private GLSurfaceView mTestHarness;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mTestHarness = new GLSurfaceView(this);
        mTestHarness.setEGLConfigChooser(false);
        mTestHarness.setRenderer(new AnimatedSimpleTriangleRenderer(this));
        //mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
        setContentView(mTestHarness);
    }
    @Override
    protected void onResume()    {
        super.onResume();
        mTestHarness.onResume();
    }
    @Override
    protected void onPause() {
        super.onPause();
        mTestHarness.onPause();
    }
}

Let us see now the animated renderer.

AnimatedSimpleTriangleRenderer

This class is very similar to the previous triangle renderer except for what happens in the onDraw method. In this method we set a new rotation angle every four seconds. As this gets down repeatedly you will see the triangle spinning slowly.


//filename: AnimatedSimpleTriangleRenderer.java
public class AnimatedSimpleTriangleRenderer extends AbstractRenderer
{
   private int scale = 1;
   //Number of points or vertices we want to use
    private final static int VERTS = 3;
    
    //A raw native buffer to hold the point coordinates
    private FloatBuffer mFVertexBuffer;
    
    //A raw native buffer to hold indices
    //allowing a reuse of points.
    private ShortBuffer mIndexBuffer;
    
    public AnimatedSimpleTriangleRenderer(Context context) 
    {
        ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4);
        vbb.order(ByteOrder.nativeOrder());
        mFVertexBuffer = vbb.asFloatBuffer();

        ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2);
        ibb.order(ByteOrder.nativeOrder());
        mIndexBuffer = ibb.asShortBuffer();

        float[] coords = {
                -0.5f, -0.5f, 0, // (x1,y1,z1)
                 0.5f, -0.5f, 0,
                 0.0f,  0.5f, 0
        };
        for (int i = 0; i < VERTS; i++) {
            for(int j = 0; j < 3; j++) {
                mFVertexBuffer.put(coords[i*3+j]);
            }
        }
        short[] myIndecesArray = {0,1,2};
        for (int i=0;i<3;i++)
        {
           mIndexBuffer.put(myIndecesArray[i]);
        }
        mFVertexBuffer.position(0);
        mIndexBuffer.position(0);
    }

   //overriden method
    protected void draw(GL10 gl)
    {
       long time = SystemClock.uptimeMillis() % 4000L;
       float angle = 0.090f * ((int) time);

       gl.glRotatef(angle, 0, 0, 1.0f);

       gl.glColor4f(1.0f, 0, 0, 0.5f);
       gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
        gl.glDrawElements(GL10.GL_TRIANGLES, VERTS,
                GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
    }
}

Now that you have both these files you can invoke this animated activity from any menu item by calling the following method


    private void invoke15SimpleTriangle()
    {
      Intent intent = new Intent(this,AnimatedTriangleActivity.class);
      startActivity(intent);
    }

Ofcourse you will have to register the activity in the android manifest file


   <activity android:name=".AnimatedTriangleActivity"
                  android:label="OpenGL Animated Test Harness"/>

These changes outlined here makes OpenGL lot more approachable in the 1.5 SDK. However the background we have covered in chapter 10 is still essential for working with OpenGL on Android. The recommendation to refer to external OpenGL resources for further research still holds. Let us move on now to explore the Live Folders in SDK 1.5.

Live Folders

A live folder in Android is to content providers what an RSS reader is to a publishing website. Ofcourse this begs explnation. Lets go into that. We have developed an argument in chapter 3 that content providers are similar to websites that provide information based on URIs. And as the websites proliferated and each publish their own information in unique ways there came a need to aggregate information from multiple sites so that one can follow the developments using a single reader. To this end came the design of RSS. RSS forced us to see a common pattern among disperate sets of information. Having a common pattern would allow us to design readers one time and use it to read for any type of content as long as the content is presented in a uniform way.

The live folders are not that different in their concept. A live folder protocol defines a standard cursor with the same column names. As long as a content provider delivers its contents in this common format then the reader for that content can be the same.

Based on this common format idea, here is how live folders work

1. Create an icon on the desktop representing a collection of rows coming from a content provider. This connection is done by specifying a URI along with the icon

2. When that icon is clicked on, the system will take the uri and makes a call to the content provider through the URI. Content provider will return a collection of rows through a Cursor.

3. As long as this cursor has columns expected by the live folder (name, description, program to invoke when clicked on that row) the system will present these rows as a list view or a grid view

4. Because the list views and grid views are capable of updating their data when the underlying data store changes, these views are called "live" and hence the name "live folders".

So there are two key principles that are at work in live folders. The first is the set of same column names across cursors. And the second is that the views knows how to look for any updates and change the views accordingly.

Now that we know what live folders are let us go ahead and build one and then we will show you how to drag an icon on to the home page to use that live folder. we will also show you how the "live" part works. The sample we present here has the following files

AndroidManifest.xml - defines which activity needs to be called to create the definition for a live folder

SimpleActivity.xml - A simple activity needed for the creation of a project.

AllContactsLiveFolderCreatorActivity.java - Activity that is responsible for supplying the definition for a live folder

MyContactsProvider.java - Provider that will respond to the live folder URI that will return a cursor

MyCursor.java - A specialized cursor that knows how to perform a "requery" when underlying data changes

BetterCursorWrapper.java - Needed by MyCursor to orchestrate the "requery"

Let us consider each of these files one by one as that would give us a detail understanding of how live folders work.

androidmanifest.xml

Here is the manifest file for our example. The live folders section is demarcated with a commment. This section indicates that we have an activity called "AllContactsLiveFolderCreatorActivity" that is responsible for creating a live folder. This fact is expressed by declaring an intent whose action is "android.intent.action.CREATE_LIVE_FOLDER". The label of the activity will show up in the context menu of the home page. (You can get to the context menu of the home page by long-clicking on the home page).


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.ai.android.livefolders"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".SimpleActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
       <!-- LIVE FOLDERS -->
        <activity
            android:name=".AllContactsLiveFolderCreatorActivity"
            android:label="New live folder activity"
            android:icon="@drawable/icon">

            <intent-filter>
                <action android:name="android.intent.action.CREATE_LIVE_FOLDER" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

      <provider android:authorities="com.ai.livefolders.contacts"
      android:multiprocess="true"
            android:name=".MyContactsProvider" />
        
    </application>
    <uses-sdk android:minSdkVersion="3" />
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
</manifest>

Another notable point is the "provider" declaration which is anchored at the url "content://com.ai.livefolders.contacts" and serviced by the provider class "MyContactsProvider". This provider is responsible for providing a cursor to populate the list view that would be opened when the live folder icon on the desktop corresponding to this live folder is clicked on.

According to the live folders protocol the "CREATE_LIVE_FOLDER" intent will allow the home page context menu to show the AllContactsLiveFolderCreatorActivity as an option. Clicking on this menu option will create an icon on the desktop. Let us look at the following figures to see how this works.

The first figure illustrates what the 1.5 SDK home page looks like.

Figure 1 - Android 1.5 SDK home page

If you long click on this home page you will see the following figure showing the context menu for the home page. This figure looks like this

Figure 2 - Context Menu of the home page

If you click on the "Folders" sub option, Android will open up another menu that looks like the following and shows the activity label represented by "AllContactsLiveFolderCreatorActivity". The option will read "New live folder activity" taken from the manifest file.

This will create an icon on the desktop as shown in the following figure

Figure 3 - Live Folder Icon on the desktop

This icon will have an image and also a label. It is the responsiblity of the AllContactsLiveFolderCreatorActivity to specify what these are. So let us take a look at the source code for this live folder creator.

AllContactsLiveFolderCreatorActivity

This class has one responsiblity. Tell the invoker, in this case the home page or live folder framework, what the name of the live folder is, what the icon is, and what is the url where the data is available, and how should the data be displayed (either as a list or a grid).

Note: See the SDK 1.5 documentation for the class "android.content.LifeFolder" for all the contracts needed by a live folder.


public class AllContactsLiveFolderCreatorActivity extends Activity 
{
    @Override
    protected void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);

        final Intent intent = getIntent();
        final String action = intent.getAction();
        
        if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) 
        {
            setResult(RESULT_OK, 
                  createLiveFolder(MyContactsProvider.CONTACTS_URI,
                           "Contacts LF",
                           R.drawable.icon)
                  );
        } else 
        {
            setResult(RESULT_CANCELED);
        }
        finish();
    }
    
    private Intent createLiveFolder(Uri uri, String name, int icon) 
    {
        final Intent intent = new Intent();
        intent.setData(uri);
        intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, name);
        intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON,
                Intent.ShortcutIconResource.fromContext(this, icon));
        intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE, 
                    LiveFolders.DISPLAY_MODE_LIST);
        return intent;
    }
}
The createLiveFolder method is essentially setting these values on the intent that invoked it. When this intent is returned to the caller the caller will know the following

1. live folder name
2. live folder icon
3. display mode, list or grid
4. data uri to invoke for data
This information is sufficient to create the live folder icon as show in figure 3. Let us see now what happens when you click on this icon. The system will call the URI to retrieve data. It is upto the content provider identified by this URI to provide the standardized cursor. Here is the code for that content provider.

MyContactsProvider

This class MyContactsProvider has the following responsibilities:

1. Identifying the incoming uri that looks like "content://com.ai.livefolders.contacts/contacts".

2. Make an internal call to the real Contacts content provider identified by "content://contacts/people/"

3. Read every row from the cursor and map it back to a MatrixCursor with proper column names required by the live folder framework

4. Wrap the MatrixCursor in another cursor so that the "requery" on this wrapped cursor will make call to the Contacts content provider when needed.

Here is the code or MyContactsProvider. Significant items are highlighted.


public class MyContactsProvider extends ContentProvider {

    public static final String AUTHORITY = "com.ai.livefolders.contacts";
   
    //Uri that goes as input to the livefolder creation
    public static final Uri CONTACTS_URI = Uri.parse("content://" +
            AUTHORITY + "/contacts"   );

    //To distinguish this URI
    private static final int TYPE_MY_URI = 0;
    private static final UriMatcher URI_MATCHER;
    static{
      URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
      URI_MATCHER.addURI(AUTHORITY, "contacts", TYPE_MY_URI);
    }
    
    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public int bulkInsert(Uri arg0, ContentValues[] values) {
      return 0; //nothing to insert
    }

    //Set of columns needed by a LiveFolder
    //This is the live folder contract
    private static final String[] CURSOR_COLUMNS = new String[]{
      BaseColumns._ID, 
      LiveFolders.NAME, 
      LiveFolders.DESCRIPTION, 
      LiveFolders.INTENT, 
      LiveFolders.ICON_PACKAGE, 
      LiveFolders.ICON_RESOURCE
    };
    
    //In case there are no rows
    //use this stand in as an error message
    //Notice it has the same set of columns of a live folder
    private static final String[] CURSOR_ERROR_COLUMNS = new String[]{
      BaseColumns._ID, 
      LiveFolders.NAME, 
      LiveFolders.DESCRIPTION
    };
    
    
    //The error message row
    private static final Object[] ERROR_MESSAGE_ROW = 
         new Object[]
         {
          -1, //id
          "No contacts found", //name 
          "Check your contacts database" //description
         };
    
    //The error cursor to use
    private static MatrixCursor sErrorCursor = new MatrixCursor(CURSOR_ERROR_COLUMNS);
    static {
      sErrorCursor.addRow(ERROR_MESSAGE_ROW);
    }

    //Columns to be retrieved from the contacts database
    private static final String[] CONTACTS_COLUMN_NAMES = new String[]{
      People._ID, 
      People.DISPLAY_NAME, 
      People.TIMES_CONTACTED, 
      People.STARRED
    };
    
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) 
    {
       //Figure out the uri and return error if not matching
      int type = URI_MATCHER.match(uri);
      if(type == UriMatcher.NO_MATCH)
      {
        return sErrorCursor;
      }

      Log.i("ss", "query called");
      
      try 
      {
        MatrixCursor mc = loadNewData(this);
	    mc.setNotificationUri(getContext().getContentResolver(),  
              Uri.parse("content://contacts/people/"));
        MyCursor wmc = new MyCursor(mc,this);
        return wmc;
      } 
      catch (Throwable e) 
      {
        return sErrorCursor;
      }
    }
    
    public static MatrixCursor loadNewData(ContentProvider cp)
    {
       MatrixCursor mc = new MatrixCursor(CURSOR_COLUMNS);
        Cursor allContacts = null;
        try
        {
           allContacts = cp.getContext().getContentResolver().query(
              People.CONTENT_URI, 
              CONTACTS_COLUMN_NAMES, 
              null, //row filter 
              null, 
              People.DISPLAY_NAME); //order by
           
           while(allContacts.moveToNext())
           {
             String timesContacted = "Times contacted: "+allContacts.getInt(2);
             
             Object[] rowObject = new Object[]
             {
                 allContacts.getLong(0),    //id
                 allContacts.getString(1),    //name
                 timesContacted,          //description
                 Uri.parse("content://contacts/people/"+allContacts.getLong(0)), //intent
                 cp.getContext().getPackageName(), //package
                 R.drawable.icon   //icon
             };
             mc.addRow(rowObject);
           }
          return mc;
        }
        finally
        {
           allContacts.close();
        }
        
    }
    
    
    @Override
    public String getType(Uri uri) 
    {
      //indicates the MIME type for a given URI
      //targeted for this wrapper provider
      //This usually looks like 
      // "vnd.android.cursor.dir/vnd.google.note"
      return People.CONTENT_TYPE;
    }

    public Uri insert(Uri uri, ContentValues initialValues) {
      throw new UnsupportedOperationException(
            "no insert as this is just a wrapper");
    }
    
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException(
        "no delete as this is just a wrapper");
    }

    public int update(Uri uri, ContentValues values, 
            String selection, String[] selectionArgs) 
   {
        throw new UnsupportedOperationException(
        "no update as this is just a wrapper");
    }
}

Notice that the following set of columns are the standard columns needed by a live folder


    private static final String[] CURSOR_COLUMNS = new String[]{
      BaseColumns._ID, 
      LiveFolders.NAME, 
      LiveFolders.DESCRIPTION, 
      LiveFolders.INTENT, 
      LiveFolders.ICON_PACKAGE, 
      LiveFolders.ICON_RESOURCE
    };

Most of these fields are self explanatory except for the "intent". This field points to an intent or a uri that needs to invoked when a user clicks on the item in the live folder.

Also notice that the content provider also tells the cursor that it needs to watch the data for any changes by executing the following code


        MatrixCursor mc = loadNewData(this);
	    mc.setNotificationUri(getContext().getContentResolver(),  
              Uri.parse("content://contacts/people/"));

It should be an interesting fact that the uri to watch is not the uri of this content provider but the content provider that belongs to the contacts. Because this "MyContactsProvider" is just a wrapper for the real content provider. So this cursor needs to watch the real content provider and not the wrapper.

It is also important that we wrap the MatrixCursor in our own cursor as shown in the following code


        MatrixCursor mc = loadNewData(this);
	    mc.setNotificationUri(getContext().getContentResolver(),  
              Uri.parse("content://contacts/people/"));
        MyCursor wmc = new MyCursor(mc,this);

To know the reason for this we have to step into how views operate to update changed content. A content provider typically tells a cursor that it needs to watch for changes by registering a uri. This is done through cursor.setNotificationUri. The cursor then will register for this uri and all its children URIS. Then when an insert or delete happens on the content provider the code for insert and delete need to raise an event that a particular uri identified by those rows have changed.

This will trigger the cursor to update itself using requery. The view will inturn update. But unfortunately the MatrixCursor is not geared for this "requery". SQLLiteCursor is geared for it. But we can't use the SQLLiteCursor here due to the mapping and of the columns to a new set of columns.

To accomodate this we have wrapped the MatrixCursor in a cursor wrapper and overrode the "requery" method to drop the internal MatrixCursor and create a new one with the udpated data.

You will see this illustrated in the following two classes.

MyCursor

Notice how MyCursor is intialized with a MatrixCursor in the begining. Then on the "requery" will call back the provider to return a MatrixCursor. Then the new MatrixCursor will replace the old one by using the "set" method.

We could have done this by overriding the "requery" of the MatrixCursor but that class did not provide a way to clear the data and start all over again. So this is a reasonable work around.


public class MyCursor extends BetterCursorWrapper 
{
   private ContentProvider mcp = null;
   
    public MyCursor(MatrixCursor mc, ContentProvider inCp) 
    {
        super(mc);
        mcp = inCp;
    }   
    public boolean requery() 
    {
       Log.i("ss", "requery called");
       MatrixCursor mc = MyContactsProvider.loadNewData(mcp);
       this.setInternalCursor(mc);
       return super.requery();
    }
}

Let us now look at the BetterCursorWrapper to get an idea how to wrap a cursor.

BetterCursorWrapper

This is a class very similar to the CursorWrapper class in the framework. But we needed two additional things that were not there in the CursorWrapper. First it didn't have a "set" method to replace the internal cursor from the "requery" method. Secondly live folders need a "CrossProcessCursor" and not just a plain cursor.


public class BetterCursorWrapper implements CrossProcessCursor
{
   //Holds the internal cursor to delegate methods to
   protected CrossProcessCursor internalCursor;
   
   //Constructor takes a crossprocesscursor as an input
   public BetterCursorWrapper(CrossProcessCursor inCursor)
   {
      this.setInternalCursor(inCursor);
   }
   
   //You can reset in one of the derived classes methods
   public void setInternalCursor(CrossProcessCursor inCursor)
   {
      internalCursor = inCursor;
   }
   
   //All delegated methods follow
   public void fillWindow(int arg0, CursorWindow arg1) {
      internalCursor.fillWindow(arg0, arg1);
   }
   // ..... other delegated methods
} 			  

We haven't shown you the entire class but it is easy to generate the rest of it by using eclipse quite easily. Once you have this partial class loaded into eclipse do the following


1. Place your cursor on the variable named "internalCursor"
2. right-click/source/generate delegated methods
3. eclipse will then populate the rest of the class for you

let us now show you the simple activity to complete this sample project.

SimpleActivity

This is not an essential class for live folders but being in the project gives you a common pattern for all your projects and also allows you deploy it and see it on the screen when you are debugging through eclipse.


public class SimpleActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

Now you have all the classes you need to build, deploy and run through eclipse. Let us conclude this section on live folders by showing you what happens when you access the live folder.

Using the live folders

Go to the home page and you will see the icon for the folder we have created as shown in figure 3. Click on the icon and you will see the list populated with contacts as shown in the following figure.

Figure 4 - show contacts

Depending on the number of contacts you may have this list will look different.

You can click on one of the contacts and it will take you to that single contact as shown in figure 5.

Figure 5 - single contact

You can click on the "Menu" at the bottom to see what you can do with the single item. You will see the following menu options when you click on the "menu"

Figure 6 - menu options for a contact

When you chose to edit the contact you will see the following figure displaying the details of a contact.

Figure 7 - Contact details

You can also further test this by adding new contacts and seeing the list get updated automatically.

As we come to the conclusion of this book let us shift our attention to the future of Android and how 1.5 SDK is enabling some of those expectations.

Future of Android and 1.5

The type of devices already available on Android include


1. T-Mobile G1
2. Google Dev Phone I
3. GiiNii Movit

T-Mobile G1 is one of the first products to be released based on SDK 1.0. It has most of the bells and whistles of a general purpose computing device with a phone attached. Android did not support at that point a virtual onscreen key board. This is made available in 1.5 and very soon these devices could be released with out a physical keyboard.

Google has also released Google Dev Phone to developers so that they can test their applications. This device costs around 400 dollars and offers the following features


touch screen
track ball
3.2 mega pixel camera
wifi
physical keyboard
SDCard

The GiiNii Movit (http://www.giinii.com/movit_detail.html), marketed as an internet device comes with the following features


wifi
skype
microphone/speaker
video/audio
built in support for myspace, twitter, facebook

This device is truly a shift to netbooks where smaller computers are powered by the Android OS. The anticipated manufactures to follow this trend include


HP
Dell
ASUS

To support this new push SDK 1.5 comes with the following key features


virtual key board
Home screen widgets
music player
calendar
Picassa
YouTube
GTalk
Improved Gmail

The following cell phone manufacturers are expecting new devices that support android os


HTC
LG
Motorola
Samsung
Sony
Ericson

Some Key SDK 1.5 URLs

We would like to conclude this chapter by listing some key urls that would be handy as you discover more about SDK 1.5 and future releases of Android.

1. home page (http://developer.android.com). This is the main entry page for android developers. As new SDKs are anounced this page will lead you to the right URLs.

2. Guide (http://developer.android.com/guide/basics/what-is-android.html) - You will find the android developers guide for the most current release here.

3. cupcake roadmap (http://source.android.com/roadmap/cupcake) - The 1.5 release is sometimes referred to as the cupcake release. You can use this url to find more about the features of cupcake.

4. features (http://developer.android.com/sdk/preview/features.html) - You can use this url to find out about the current preview release (1.5). As new releases come on board, this url may change.

5. download (http://developer.android.com/sdk/preview/) - You can use this url to download 1.5 sdk

6. source (http://source.android.com/) - If you are looking for android sdk source code, you will find it here.

7. Google IO (http://code.google.com/events/io/) - You will find here google conference content including sessions on Android.

8. Roadmap (http://source.android.com/roadmap) - You can use this url for a general roadmap put forward by Google.

9. Git (http://book.git-scm.com/) - You may need this URL if you are intending on exploring Android source code.