This document contains
1. Sample code for a live folder based on contacts
2. Uses a cursor wrapper to make it live to respond to changes in the underlying contacts
3. Contains pictures of using a live folder
satya - Thursday, April 23, 2009 3:39:44 PM
manifest file
<?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>
satya - Thursday, April 23, 2009 3:40:23 PM
SimpleActivity
package com.ai.android.livefolders;
import android.app.Activity;
import android.os.Bundle;
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);
}
}
satya - Thursday, April 23, 2009 3:41:06 PM
AllContactsLiveFolderCreatorActivity
package com.ai.android.livefolders;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.LiveFolders;
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;
}
}
satya - Thursday, April 23, 2009 3:43:08 PM
MyContactsProvider
package com.ai.android.livefolders;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.LiveFolders;
import android.provider.Contacts.People;
import android.util.Log;
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");
}
}
satya - Thursday, April 23, 2009 3:43:46 PM
MyCursor
package com.ai.android.livefolders;
import android.content.ContentProvider;
import android.database.MatrixCursor;
import android.util.Log;
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();
}
}
satya - Thursday, April 23, 2009 3:45:02 PM
BetterCursorWrapper
package com.ai.android.livefolders;
import android.content.ContentResolver;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
import android.database.CrossProcessCursor;
import android.database.CursorWindow;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;
/**
* Wrapper class for Cursor that delegates
* all calls to the actual cursor object
*/
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);
}
public CursorWindow getWindow() {
return internalCursor.getWindow();
}
public boolean onMove(int arg0, int arg1) {
return internalCursor.onMove(arg0, arg1);
}
public void close() {
internalCursor.close();
}
public void copyStringToBuffer(int arg0, CharArrayBuffer arg1) {
internalCursor.copyStringToBuffer(arg0, arg1);
}
public void deactivate() {
internalCursor.deactivate();
}
public byte[] getBlob(int columnIndex) {
return internalCursor.getBlob(columnIndex);
}
public int getColumnCount() {
return internalCursor.getColumnCount();
}
public int getColumnIndex(String columnName) {
return internalCursor.getColumnIndex(columnName);
}
public int getColumnIndexOrThrow(String columnName)
throws IllegalArgumentException {
return internalCursor.getColumnIndexOrThrow(columnName);
}
public String getColumnName(int columnIndex) {
return internalCursor.getColumnName(columnIndex);
}
public String[] getColumnNames() {
return internalCursor.getColumnNames();
}
public int getCount() {
return internalCursor.getCount();
}
public double getDouble(int columnIndex) {
return internalCursor.getDouble(columnIndex);
}
public Bundle getExtras() {
return internalCursor.getExtras();
}
public float getFloat(int columnIndex) {
return internalCursor.getFloat(columnIndex);
}
public int getInt(int columnIndex) {
return internalCursor.getInt(columnIndex);
}
public long getLong(int columnIndex) {
return internalCursor.getLong(columnIndex);
}
public int getPosition() {
return internalCursor.getPosition();
}
public short getShort(int columnIndex) {
return internalCursor.getShort(columnIndex);
}
public String getString(int columnIndex) {
return internalCursor.getString(columnIndex);
}
public boolean getWantsAllOnMoveCalls() {
return internalCursor.getWantsAllOnMoveCalls();
}
public boolean isAfterLast() {
return internalCursor.isAfterLast();
}
public boolean isBeforeFirst() {
return internalCursor.isBeforeFirst();
}
public boolean isClosed() {
return internalCursor.isClosed();
}
public boolean isFirst() {
return internalCursor.isFirst();
}
public boolean isLast() {
return internalCursor.isLast();
}
public boolean isNull(int columnIndex) {
return internalCursor.isNull(columnIndex);
}
public boolean move(int offset) {
return internalCursor.move(offset);
}
public boolean moveToFirst() {
return internalCursor.moveToFirst();
}
public boolean moveToLast() {
return internalCursor.moveToLast();
}
public boolean moveToNext() {
return internalCursor.moveToNext();
}
public boolean moveToPosition(int position) {
return internalCursor.moveToPosition(position);
}
public boolean moveToPrevious() {
return internalCursor.moveToPrevious();
}
public void registerContentObserver(ContentObserver observer) {
internalCursor.registerContentObserver(observer);
}
public void registerDataSetObserver(DataSetObserver observer) {
internalCursor.registerDataSetObserver(observer);
}
public boolean requery() {
return internalCursor.requery();
}
public Bundle respond(Bundle extras) {
return internalCursor.respond(extras);
}
public void setNotificationUri(ContentResolver cr, Uri uri) {
internalCursor.setNotificationUri(cr, uri);
}
public void unregisterContentObserver(ContentObserver observer) {
internalCursor.unregisterContentObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
internalCursor.unregisterDataSetObserver(observer);
}
}
satya - Friday, April 24, 2009 8:48:45 AM
Here is the look of 1.5 desktop on the emulator
satya - Friday, April 24, 2009 8:49:46 AM
Use a long click to see the folder options on the home page
satya - Friday, April 24, 2009 8:50:32 AM
Open up the folders menu item
satya - Friday, April 24, 2009 8:51:25 AM
Click on New Live Folder item to create the following live folder
satya - Friday, April 24, 2009 8:52:35 AM
Click on Contacts LF live folder to see a list of contacts
satya - Friday, April 24, 2009 8:54:04 AM
Click on one of the contacts to see it displayed like the following
satya - Friday, April 24, 2009 8:54:40 AM
Click on the menu tab to see options for that single contact
satya - Friday, April 24, 2009 8:55:18 AM
Click on edit contact to see contact details or change them
satya - Friday, April 24, 2009 8:59:04 AM
Here are the key elements you need to know to make live folders to work
1. You will need to create a cursor that has a set of predefined standard column names
2. You will need a cursor that is capable of "requery" semantics
3. At the end of the query you will need to register your cursor for dynamic updates aginst the underlying content provider as you are likely to wrap that content provider inorder to translate the column names.
4. I needed to wrap the cross process cursor to make it work
satya - Monday, May 04, 2009 11:35:04 AM
Click here to download the zip file for the livefolders project
Click here to download the zip file for the livefolders project