Search This Blog

01 July 2010

Content Provider and Live Folder

We're going to create a basic ContentProvider example based on this tutorial.

Let's start with a multi-module project:

F:\work> mkdir ProviderTest
F:\work> cd ProviderTest

F:\work\ProviderTest> mvn archetype:generate -DarchetypeCatalog=http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml
2: http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml -> galatea-archetype (null)
Confirm properties configuration:
groupId: org.eoti.android.providertest.provider
artifactId: TestProvider
version: 1.0-SNAPSHOT
package: org.eoti.android.providertest.provider

F:\work\ProviderTest> mvn archetype:generate -DarchetypeCatalog=http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml
2: http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml -> galatea-archetype (null)
Confirm properties configuration:
groupId: org.eoti.android.providertest.consumer
artifactId: TestConsumer
version: 1.0-SNAPSHOT
package: org.eoti.android.providertest.consumer

F:\work\ProviderTest> mvn archetype:generate -DarchetypeCatalog=http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml
2: http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml -> galatea-archetype (null)
Confirm properties configuration:
groupId: org.eoti.android.providertest.generator
artifactId: TestGenerator
version: 1.0-SNAPSHOT
package: org.eoti.android.providertest.generator

Create a top-level pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>org.eoti.android.providertest</groupId>
    <version>1.0-SNAPSHOT</version>
    <artifactId>ProviderTest</artifactId>
    <packaging>pom</packaging>
    <name>ProviderTest</name>
    <description>Testing a simple Content Provider and consumer</description>

    <modules>
        <module>TestProvider</module>
        <module>TestConsumer</module>
        <module>TestGenerator</module>
    </modules>
</project>

Open the TestProvider\pom.xml and TestConsumer\pom.xml and add a parent to the pom.xml:
    <parent>
        <groupId>org.eoti.android.providertest</groupId>
        <version>1.0-SNAPSHOT</version>
        <artifactId>ProviderTest</artifactId>
    </parent>


F:\work\ProviderTest> mvn clean install

Replace TestProviderActivity.java with TestProvider.java:
package org.eoti.android.providertest.provider;

import android.app.Activity;
import android.content.*;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.provider.LiveFolders;
import android.text.TextUtils;
import android.util.Log;

import java.util.HashMap;

public class TestProvider
extends ContentProvider
{
    // Derived from http://developer.android.com/resources/samples/NotePad/src/com/example/android/notepad/NotePadProvider.html
   
    public static final String AUTHORITY = "org.eoti.android.providertest";
    private static class Logs implements BaseColumns
    {
        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/logs");
        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.eoti.logs";
        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.eoti.log";
        public static final String DEFAULT_SORT_ORDER = "modified DESC";
        public static final String LOG = "log";
        public static final String TIMESTAMP = "timestamp";
    }

    private static String TAG = "TestProvider";
    private static final String DATABASE_NAME = "providertest.db";
    private static final int DATABASE_VERSION = 1;
    private static final String TABLE_NAME = "logs";
    private static final int LOGS = 1;
    private static final int LOG_ID = 2;
    private static final int LIVE_FOLDER_LOGS = 3;
    private static HashMap<String, String> logsProjectionMap;
    private static HashMap<String, String> folderProjectionMap;
    private static final UriMatcher uriMatcher;


    private static class DatabaseHelper extends SQLiteOpenHelper
    {
        public DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
           
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("CREATE TABLE " + TABLE_NAME + " ("
                + Logs._ID + " INTEGER PRIMARY KEY,"
                + Logs.LOG + " TEXT,"
                + Logs.TIMESTAMP + " INTEGER"
                + ");");
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data");
            db.execSQL("DROP TABLE IF EXISTS notes");
            onCreate(db);
        }
    }
    private DatabaseHelper dbHelper;
   

    @Override
    public boolean onCreate() {
        dbHelper = new DatabaseHelper(getContext());
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        qb.setTables(TABLE_NAME);

        switch (uriMatcher.match(uri)) {
            case LOGS:
                qb.setProjectionMap(logsProjectionMap);
                break;
            case LOG_ID:
                qb.setProjectionMap(logsProjectionMap);
                qb.appendWhere(Logs._ID + "=" + uri.getPathSegments().get(1));
                break;
            case LIVE_FOLDER_LOGS:
                qb.setProjectionMap(folderProjectionMap);
                break;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }

        String orderBy = (TextUtils.isEmpty(sortOrder) ? Logs.DEFAULT_SORT_ORDER : sortOrder);

        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
        c.setNotificationUri(getContext().getContentResolver(), uri);
        return c;
    }

    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case LOGS:
            case LIVE_FOLDER_LOGS:
                return Logs.CONTENT_TYPE;
            case LOG_ID:
                return Logs.CONTENT_ITEM_TYPE;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues initialValues) {
        if (uriMatcher.match(uri) != LOGS)
            throw new IllegalArgumentException("Unknown URI " + uri);

        ContentValues values = (initialValues == null) ? new ContentValues() : new ContentValues(initialValues);

        if(!values.containsKey(Logs.TIMESTAMP))
            values.put(Logs.TIMESTAMP, System.currentTimeMillis());

        if(!values.containsKey(Logs.LOG))
            values.put(Logs.LOG, "");

        SQLiteDatabase db = dbHelper.getWritableDatabase();
        long rowId = db.insert(TABLE_NAME, Logs.LOG, values);
        if (rowId > 0) {
            Uri noteUri = ContentUris.withAppendedId(Logs.CONTENT_URI, rowId);
            getContext().getContentResolver().notifyChange(noteUri, null);
            return noteUri;
        }

        throw new SQLException("Failed to insert row into " + uri);
    }

    @Override
    public int delete(Uri uri, String where, String[] whereArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count;
        switch (uriMatcher.match(uri)) {
            case LOGS:
                count = db.delete(TABLE_NAME, where, whereArgs);
                break;
            case LOG_ID:
                String noteId = uri.getPathSegments().get(1);
                count = db.delete(TABLE_NAME, Logs._ID + "=" + noteId + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
                break;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }

        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count;
        switch (uriMatcher.match(uri)) {
            case LOGS:
                count = db.update(TABLE_NAME, values, where, whereArgs);
                break;
            case LOG_ID:
                String noteId = uri.getPathSegments().get(1);
                count = db.update(TABLE_NAME, values, Logs._ID + "=" + noteId + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
                break;   
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }

        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

    static{
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, "logs", LOGS);
        uriMatcher.addURI(AUTHORITY, "logs/#", LOG_ID);
        uriMatcher.addURI(AUTHORITY, "livefolders/logs", LIVE_FOLDER_LOGS);

        logsProjectionMap = new HashMap<String,String>();
        logsProjectionMap.put(Logs._ID, Logs._ID);
        logsProjectionMap.put(Logs.LOG, Logs.LOG);
        logsProjectionMap.put(Logs.TIMESTAMP, Logs.TIMESTAMP);

        folderProjectionMap = new HashMap<String, String>();
        folderProjectionMap.put(LiveFolders._ID, Logs._ID + " AS " + LiveFolders._ID);
        folderProjectionMap.put(LiveFolders.NAME, Logs.LOG + " AS " + LiveFolders.NAME);
    }
}


Change your TestProvider\AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="org.eoti.android.providertest.provider">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <provider android:name="TestProvider" android:authorities="org.eoti.android.providertest"/>
    </application>
</manifest>


Replace TestConsumerActivity.java with TestConsumer.java:
package org.eoti.android.providertest.consumer;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.LiveFolders;

public class TestConsumer extends Activity
{
    public static final String AUTHORITY = "org.eoti.android.providertest";

    private static String TAG = "TestConsumer";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/livefolders/logs");
    public static final Uri LOG_URI = Uri.parse("content://" + AUTHORITY + "/logs/#");

    @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))
        {
            final Intent liveFolderIntent = new Intent();

            liveFolderIntent.setData(CONTENT_URI);
            liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, getString(R.string.app_name));
            liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON, Intent.ShortcutIconResource.fromContext(this, R.drawable.icon));
            liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE, LiveFolders.DISPLAY_MODE_LIST);
            liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT, new Intent(Intent.ACTION_EDIT, LOG_URI));
            setResult(RESULT_OK, liveFolderIntent);
        } else {
            setResult(RESULT_CANCELED);
        }

        finish();
    }
}



Change your TestConsumer\AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="org.eoti.android.providertest.consumer">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name="TestConsumer" android:label="@string/app_name" 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>
    </application>
</manifest>


Update your TestGeneratorActivity:
package org.eoti.android.providertest.generator;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestGeneratorActivity extends Activity {
    public static final String AUTHORITY = "org.eoti.android.providertest";
    private static String TAG = "TestGenerator";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/logs");
    private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        ContentValues values = new ContentValues();
        long time = System.currentTimeMillis();
        values.put("timestamp", time);
        values.put("log", sdf.format(new Date(time)));

        Uri uri = getContentResolver().insert(CONTENT_URI, values);
        Toast.makeText(getApplicationContext(), (uri == null) ? " null " : uri.toString(), Toast.LENGTH_SHORT).show();
        finish();
    }
}


Redeploy (mvn clean install).  Launch the generator app.  Add the live folder to your desktop.  Launch it.  Run the generator a few more times. Launch the folder again.

1 comment:

  1. Oops, db.execSQL("DROP TABLE IF EXISTS notes"); could have been db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);

    ReplyDelete