Amazon ECS Webservice Sign

I spent a good part of today trying to learn how to do a simple Java-based query using Amazon’s Product Advertising API.  This entire exercise could have been finished within an hour had there been clear, concise documentation.

Note: My code was working 6 months back and now when i try to work with it it dosen’t work. Below are the reason

In hopes of saving others from encountering these difficulties, I present you with my findings.

Lesson 1 – Don’t Trust the Getting Started Guide

Admittedly the getting started guide gives a useful overview of the functionality and the approach to using the API. However, following the instructions step by step will only lead to frustration. The Java example for
Implementing a Product Advertising API Request DOES NOT WORK!. One would think that simply swapping in the proper access key where it says “YOUR ID” would be all that is needed, but upon execution I found it yields the following:

Exception in thread "main" com.sun.xml.internal.ws.client.ClientTransportException:
The server sent HTTP status code 400: Bad Request

Thinking I had omitted something small, I looked into resolving this error only to discover:

Lesson 2 – APA API’s Authentication has changed

As of August 15, 2009, the API requires a signature mechanism for authentication. In addition to invalidating all the code from the getting started guide, it also adds additional poorly documented steps to the process. Amazon does provide some detail, but it’s probably not the quickest path to get up and running.

Lesson 3 – There are some semi-functional examples

After digging around in the documentation, I found these two examples: Product Advertising API Signed Requests Sample Code – Java REST/QUERY and Product Advertising API Signed Requests Sample Code – Java SOAP. Since everything I had tried up until this point had been SOAP-centric, I decided to try the SOAP example first. Upon the code into Eclipse, I found that this example was fraught with too many errors and dependencies, so I turned to the REST example.

The REST code was clear and mostly error free. The few errors I saw were caused by the absence of the Apache Commons Codec library. After downloading this jar and adding it to my classpath, the example code finally compiled. Unfortunately, when I went to run it, I was greeted with this exception:

Server returned HTTP response code: 403 for URL: http://ecs.amazonaws.com/onca/xml?....

Lesson 4 – Apache Commons Codec 1.3 and 1.4 are different

After crawling through the forums looking for answers, I found out that the REST example above depended on Apache Commons Codec version 1.3, whereas the version I downloaded was 1.4. It turns out the old version appended extra CRLF (\r\n) characters onto the authentication signature, and the workaround is to force the new codec to exhibit the same behavior. If you read the codec’s documentation, you’ll see that the default behavior comes when you set the line length to 76 characters. To fix the REST example change method hmac of SignatureRequestHelper to:

Base64 encoder = new Base64(76, new byte[0]);

After doing all this, I finally got a small victory in the form of real output:

Map form example:
Signed Request is "http://ecs.amazonaws.com/onca/xml?AWSAccessKeyId=...."
Signed Title is "Harry Potter and the Deathly Hallows (Book 7)"

String form example:
Request is "http://ecs.amazonaws.com/onca/xml?AWSAccessKeyId=...."
Title is "Harry Potter and the Deathly Hallows (Book 7)"

Below is SignedRequestHelper that works

package com.northalley.amazon;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

/**
 * This class contains all the logic for signing requests
 * to the Amazon Product Advertising API.
 */
public class SignedRequestsHelper {
    /**
     * All strings are handled as UTF-8
     */
    private static final String UTF8_CHARSET = "UTF-8";

    /**
     * The HMAC algorithm required by Amazon
     */
    private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";

    /**
     * This is the URI for the service, don't change unless you really know
     * what you're doing.
     */
    private static final String REQUEST_URI = "/onca/xml";

    /**
     * The sample uses HTTP GET to fetch the response. If you changed the sample
     * to use HTTP POST instead, change the value below to POST.
     */
    private static final String REQUEST_METHOD = "GET";

    private String endpoint = null;
    private String awsAccessKeyId = null;
    private String awsSecretKey = null;

    private SecretKeySpec secretKeySpec = null;
    private Mac mac = null;

    static {
    	java.security.Security.addProvider(new com.sun.crypto.provider.SunJCE());
    }

    /**
     * You must provide the three values below to initialize the helper.
     *
     * @param endpoint          Destination for the requests.
     * @param awsAccessKeyId    Your AWS Access Key ID
     * @param awsSecretKey      Your AWS Secret Key
     */
    public static SignedRequestsHelper getInstance(
            String endpoint,
            String awsAccessKeyId,
            String awsSecretKey
    ) throws IllegalArgumentException, UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException
    {
        if (null == endpoint || endpoint.length() == 0)
            { throw new IllegalArgumentException("endpoint is null or empty"); }
        if (null == awsAccessKeyId || awsAccessKeyId.length() == 0)
            { throw new IllegalArgumentException("awsAccessKeyId is null or empty"); }
        if (null == awsSecretKey || awsSecretKey.length() == 0)
            { throw new IllegalArgumentException("awsSecretKey is null or empty"); }
        SignedRequestsHelper instance = new SignedRequestsHelper();
        instance.endpoint = endpoint.toLowerCase();
        instance.awsAccessKeyId = awsAccessKeyId;
        instance.awsSecretKey = awsSecretKey;

        byte[] secretyKeyBytes = instance.awsSecretKey.getBytes(UTF8_CHARSET);
        instance.secretKeySpec = new SecretKeySpec(secretyKeyBytes, HMAC_SHA256_ALGORITHM);
        instance.mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
        instance.mac.init(instance.secretKeySpec);

        return instance;
    }

    /**
     * The construct is private since we'd rather use getInstance()
     */
    private SignedRequestsHelper() {}

    /**
     * This method signs requests in hashmap form. It returns a URL that should
     * be used to fetch the response. The URL returned should not be modified in
     * any way, doing so will invalidate the signature and Amazon will reject
     * the request.
     */
    public String sign(Map params) {
        // Let's add the AWSAccessKeyId and Timestamp parameters to the request.
        params.put("AWSAccessKeyId", this.awsAccessKeyId);
        params.put("Timestamp", this.timestamp());

        // The parameters need to be processed in lexicographical order, so we'll
        // use a TreeMap implementation for that.
        SortedMap sortedParamMap = new TreeMap(params);

        // get the canonical form the query string
        String canonicalQS = this.canonicalize(sortedParamMap);

        // create the string upon which the signature is calculated
        String toSign =
            REQUEST_METHOD + "\n"
            + this.endpoint + "\n"
            + REQUEST_URI + "\n"
            + canonicalQS;

        // get the signature
        String hmac = this.hmac(toSign);
        String sig = this.percentEncodeRfc3986(hmac);

        // construct the URL
        String url =
            "http://" + this.endpoint + REQUEST_URI + "?" + canonicalQS + "&Signature=" + sig;

        return url;
    }

    /**
     * This method signs requests in query-string form. It returns a URL that
     * should be used to fetch the response. The URL returned should not be
     * modified in any way, doing so will invalidate the signature and Amazon
     * will reject the request.
     */
    public String sign(String queryString) {
        // let's break the query string into it's constituent name-value pairs
        Map params = this.createParameterMap(queryString);

        // then we can sign the request as before
        return this.sign(params);
    }

    /**
     * Compute the HMAC.
     *
     * @param stringToSign  String to compute the HMAC over.
     * @return              base64-encoded hmac value.
     */
    private String hmac(String stringToSign) {
        String signature = null;
        byte[] data;
        byte[] rawHmac;
        try {
            data = stringToSign.getBytes(UTF8_CHARSET);
            rawHmac = mac.doFinal(data);
            Base64 encoder = new Base64(76, new byte[0]);
            signature = new String(encoder.encode(rawHmac));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(UTF8_CHARSET + " is unsupported!", e);
        }
        return signature;
    }

    /**
     * Generate a ISO-8601 format timestamp as required by Amazon.
     *
     * @return  ISO-8601 format timestamp.
     */
    private String timestamp() {
        String timestamp = null;
        Calendar cal = Calendar.getInstance();
        DateFormat dfm = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        dfm.setTimeZone(TimeZone.getTimeZone("GMT"));
        timestamp = dfm.format(cal.getTime());
        return timestamp;
    }

    /**
     * Canonicalize the query string as required by Amazon.
     *
     * @param sortedParamMap    Parameter name-value pairs in lexicographical order.
     * @return                  Canonical form of query string.
     */
    private String canonicalize(SortedMap sortedParamMap) {
        if (sortedParamMap.isEmpty()) {
            return "";
        }

        StringBuffer buffer = new StringBuffer();
        Iterator<Map.Entry> iter = sortedParamMap.entrySet().iterator();

        while (iter.hasNext()) {
            Map.Entry kvpair = iter.next();
            buffer.append(percentEncodeRfc3986(kvpair.getKey()));
            buffer.append("=");
            buffer.append(percentEncodeRfc3986(kvpair.getValue()));
            if (iter.hasNext()) {
                buffer.append("&");
            }
        }
        String cannoical = buffer.toString();
        return cannoical;
    }

    /**
     * Percent-encode values according the RFC 3986. The built-in Java
     * URLEncoder does not encode according to the RFC, so we make the
     * extra replacements.
     *
     * @param s decoded string
     * @return  encoded string per RFC 3986
     */
    private String percentEncodeRfc3986(String s) {
        String out;
        try {
            out = URLEncoder.encode(s, UTF8_CHARSET)
                .replace("+", "%20")
                .replace("*", "%2A")
                .replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            out = s;
        }
        return out;
    }

    /**
     * Takes a query string, separates the constituent name-value pairs
     * and stores them in a hashmap.
     *
     * @param queryString
     * @return
     */
    private Map createParameterMap(String queryString) {
        Map map = new HashMap();
        String[] pairs = queryString.split("&");

        for (String pair: pairs) {
            if (pair.length() < 1) {
                continue;
            }

            String[] tokens = pair.split("=",2);
            for(int j=0; j<tokens.length; j++)
            {
                try {
                    tokens[j] = URLDecoder.decode(tokens[j], UTF8_CHARSET);
                } catch (UnsupportedEncodingException e) {
                }
            }
            switch (tokens.length) {
                case 1: {
                    if (pair.charAt(0) == =) {
                        map.put("", tokens[0]);
                    } else {
                        map.put(tokens[0], "");
                    }
                    break;
                }
                case 2: {
                    map.put(tokens[0], tokens[1]);
                    break;
                }
            }
        }
        return map;
    }
}

C# Watch Files in Drive FileSystemWatcher

Thereare many instances that require systems to perform certain tasks when files ordirectories are created or modified. One example of this is an import systemthat pulls flat files into a database. In this instance, a program must monitora directory for newly created files. When a file is created, the program mustpick up the file, parse it, and insert it into the database. Many timesthis type of functionality is accomplished by “polling” the directoryand enumerating any new files that have been created since the last poll. Withthe introduction of the .NETFramework, Microsoft has provided developers with an alternative to constantly polling adirectory for new files–the FileSystemWatcher object.

The FileSystemWatcher object does the work of monitoringa directory for you. When a file is created, updated, or deleted, the FileSystemWatcher fires an event to notify you that achange has occurred. This allows your program to know when a new file isavailable almost immediately after the file is created. Immediatenotification of changes allows your system to work much more efficiently sinceyou’re not constantly polling the directory for changes, and there is no timelapse between scans of the directory.

The FileSystemWatcher basics

There are a few basic properties and events you need to familiarize yourself with before working with the FileSystemWatcher object. Undoubtedly, the mostimportant property of this object is the “EnableRaisingEvents” property. This property determines whether ornot the object will fire events when it receives a change notification. If EnableRaisingEvents is set to false, the object willnot fire the change events. If it is set to true, the events will be fired.Below are several other important properties/events that you will use as youtake advantage of FileSystemWatcher:

Properties:

Path — This property tells the FileSystemWatcher which path it needs to monitor. For example, if we set this property to “C:\Temp\”, all changes in that directory would be monitored.
IncludeSubDirectories — This property indicates whether or not the FileSystemWatcher should monitor subdirectories for changes.
Filter — This property allows you to filter the changes for certain file types. For example, if we wanted to be notified only when TXT files are modified/created/deleted, we would set this property to “*.java”. This property is very handy when working with high-traffic or large directories.
Events:

Changed — This event is fired when a file has been modified in the directory thatis being monitored. It is important to note that this event may be fired multiple times, even when only one change to the content of the file has occurred. This is due to other properties of the file changing as the file is saved.
Created — This event is fired when a file is created in the directory that is being monitored. If you are planning to use this event to move the file that was created, you must write some error handling in your event handler that can handle situations where the file is currently in use by another process. The reason for this is that the Created event can be fired before the process that created the file has released the file. This will cause exceptions to be thrown if you have not prepared the code correctly.
Deleted — This event is fired when a file is deleted in the directory that is being watched.
Renamed — This event is fired when a file is renamed in the directory that is being watched.
Note: None of these events will be firedif you do not have EnableRaisingEvents set to true. If at any point your FileSystemWatcher does not seem to be working, check EnableRaisingEvents first to make sure it is set totrue.

Event processing

Whenan event handler is called by the FileSystemWatcher, it contains two arguments–an object called “sender”,and a FileSystemEventArgs object called “e”. Theargument we’re interested in is the FileSystemEventArgs argument. This object contains information aboutwhat caused the event to be fired. The following is available from the FileSystemEventArgs object:

Properties:

Name — This property contains the name of the file that caused the event to be fired. It DOES NOT contain that path to the file–only the file or directory name that caused the event to be fired.
ChangeType — This is a type of WatcherChangeTypes and indicates which type of event was fired. Valid values are:
Changed
Created
Deleted
Renamed
FullPath — This contains the full path to the file that caused the event to fire. It includes the filename and the directory name.
Example code


using System;
using System.IO;

public class DirectoryChangeListener
{
    public DirectoryChangeListener()
	{
	}
    
    public static void Main(){

       FileSystemWatcher watcher = new FileSystemWatcher();
       Console.WriteLine("Started....");

      //watcher.SynchronizingObject = this;

         watcher.Path =Path.GetDirectoryName(@"C:\Program Files");
         //watcher.Filter = Path.GetFileName(@"c:\a.txt");
         watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size;
         watcher.IncludeSubdirectories = true;
          watcher.Deleted += new System.IO.FileSystemEventHandler(OnDelete);
          watcher.Renamed += new System.IO.RenamedEventHandler(OnRenamed);
          watcher.Changed += new System.IO.FileSystemEventHandler(OnChanged);
          watcher.Created += new System.IO.FileSystemEventHandler(OnCreate);
          watcher.EnableRaisingEvents = true;

          Console.ReadLine();
       }
     
      public static void OnChanged(object source, FileSystemEventArgs e) {
         Console.WriteLine("File: {0} {1}", e.FullPath, e.ChangeType.ToString());
      }
 
 
      public static void OnRenamed(object source, RenamedEventArgs e){
         Console.WriteLine("File renamed from {0} to {1}", e.OldName, e.FullPath);
      }
 
 
      public static void OnDelete(object source, FileSystemEventArgs e)
      {
          Console.WriteLine("File: {0} Deleted", e.FullPath);
      }
      public static void OnCreate(object source, FileSystemEventArgs e)
      {
          Console.WriteLine("File: {0} Created", e.FullPath);
      }
  
   }

II_INSTALLATION must be set before the configuration utility is run

When rebooting a server with problems, it could lead to a corrupt ingress installation.

This leads to all kind of problems. When starting the Ingres Visual Manager you get an error: “II_INSTALLATION must be set before the configuration utility is run”.

To fix this problem you need to restore the symbols.tbl file located in the ingres\files directory. If you want to restore them manually, you need to know the original settings. You can get them back with the utility: ingsetenv.exe

If you restore the file, make sure ingres is down and run after the restore the following commands as you have in your Install.log

“C:\Program Files\CA\Ingres [II]\ingres\bin\ingsetenv.exe” II_LANGUAGE ENGLISH
“C:\Program Files\CA\Ingres [II]\ingres\bin\ingsetenv.exe” II_TIMEZONE_NAME NA-EASTERN
“C:\Program Files\CA\Ingres [II]\ingres\bin\ingsetenv.exe” TERM_INGRES IBMPCD
“C:\Program Files\CA\Ingres [II]\ingres\bin\ingsetenv.exe” II_INSTALLATION II
“C:\Program Files\CA\Ingres [II]\ingres\bin\ingsetenv.exe” II_CHARSETII WIN1252
“C:\Program Files\CA\Ingres [II]\ingres\bin\ingunset.exe” II_CHARSET
“C:\Program Files\CA\Ingres [II]\ingres\bin\ingsetenv.exe” II_DATE_FORMAT US
“C:\Program Files\CA\Ingres [II]\ingres\bin\ingsetenv.exe” II_MONEY_FORMAT L:$
“C:\Program Files\CA\Ingres [II]\ingres\bin\ingsetenv.exe” II_DECIMAL .
“C:\Program Files\CA\Ingres [II]\ingres\bin\ingsetenv.exe” II_TEMPORARY “C:\Program Files\CA\Ingres [II]\ingres\temp”
“C:\Program Files\CA\Ingres [II]\ingres\bin\ingsetenv.exe” II_CONFIG “C:\Program Files\CA\Ingres [II]\ingres\files”
“C:\Program Files\CA\Ingres [II]\ingres\bin\ingsetenv.exe” II_GCNII_LCL_VNODE “<YOUR_COMPUTER_NAME"

If you do not know the exact settings, you can try to take a look at the install.log. The settings of the ingres environment are also mentioned their.

Launching Different Applications from Android Activity Using Intent

As you might have gone through android till now,  the architecture is based on Events and Event Handlers i.e Intents and Their Corresponding Activities. Calling multiple activities in side an application is done using Intents . Now we shall see how to call different applications using Intents (Its very simple and straight forward)

To open other people’s application, you need to make sure that in their manifest file, the author specify the class to have the android.intent.action.MAIN intent-filter added to them.
final Intent intent = new Intent(Intent.ACTION_MAIN, null);

We then add category that this new intent will be launching something
intent.addCategory(Intent.CATEGORY_LAUNCHER);

Then we get identify the application we need to open by using ComponentName, here you specify the package name of the application as first argument and the class we want to open as the second one. You must understand that com.android.settings has a lot of classes that have Main intent-filter under it making the second argument to be the specific class that we need. (this is more than one line)
final ComponentName cn = new ComponentName(“com.android.settings”, “com.android.settings.fuelgauge.PowerUsageSummary”);

After we identify the component we want, we set it to our intent
intent.setComponent(cn);

We then tell the intent that open opening this one make it as a new task
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

Then finally start our intent
startActivity( intent);

 

Original article  is at http://www.tutorialforandroid.com/2009/10/launching-other-application-using-code.html

Following is the sample Application that is created to Show this feature


package com.linkwithweb.app;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class LaunchApp extends Activity {
	private Button showPowerButton;
	private Button launchLessonsButton;

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

		showPowerButton = (Button) findViewById(R.id.showPowerButton);

		showPowerButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {

				final Intent intent = new Intent(Intent.ACTION_MAIN, null);

				intent.addCategory(Intent.CATEGORY_LAUNCHER);

				final ComponentName cn = new ComponentName(
						"com.android.settings",
						"com.android.settings.fuelgauge.PowerUsageSummary");

				intent.setComponent(cn);

				intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

				startActivity(intent);

			}
		});

		launchLessonsButton = (Button) findViewById(R.id.launchLessonsButton);

		launchLessonsButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				final Intent intent = new Intent(Intent.ACTION_MAIN, null);

				intent.addCategory(Intent.CATEGORY_LAUNCHER);

				final ComponentName cn = new ComponentName("com.linkwithweb",
						"com.linkwithweb.AndroidLessonsMain");

				intent.setComponent(cn);

				intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

				startActivity(intent);
			}
		});

	}
}

Code has been uploaded following svn link
https://linkwithweb.googlecode.com/svn/trunk/Android

Custom Content Provider In Android

I’ve been searching over internet to find out one good example for Custom Content Provider but found very little help in trying to run examples rightaway from web. Lots of examples are 1/2 done or 1/2 written. I wanted to come out with a runnign example and here is the example. Again as before i’ve compiled and modified this example from code i have found over web so that some UI is added to it.

Content providers store and retrieve data and make it accessible to all applications. They’re the only way to share data across applications; there’s no common storage area that all Android packages can access.
Each content provider exposes a public URI (wrapped as a Uri object) that uniquely identifies its data set. A content provider that controls multiple data sets (multiple tables) exposes a separate URI for each one. All URIs for providers begin with the string “content://”. The content: scheme identifies the data as being controlled by a content provider

Querying a Content Provider

You need three pieces of information to query a content provider:

The URI that identifies the provider
The names of the data fields you want to receive
The data types for those fields
If you’re querying a particular record, you also need the ID for that record.

Below is the code that creates content provider (Here content provider acts like a wrapper to sqllite database)

package com.linkwithweb.providers;

import android.net.Uri;
import android.provider.BaseColumns;

/**
 * @author Ashwin Kumar
 *
 */
public class MyUsers {

	public static final String AUTHORITY = "com.linkwithweb.providers.MyContentProvider";

	// BaseColumn contains _id.
	public static final class User implements BaseColumns {

		public static final Uri CONTENT_URI = Uri
				.parse("content://com.linkwithweb.providers.MyContentProvider");

		// Table column
		public static final String USER_NAME = "USER_NAME";
	}
}
package com.linkwithweb.providers;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
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;

/**
 * @author Ashwin Kumar
 *
 */
public class MyContentProvider extends ContentProvider {

	private SQLiteDatabase sqlDB;

	private DatabaseHelper dbHelper;

	private static final String DATABASE_NAME = "Users.db";

	private static final int DATABASE_VERSION = 1;

	private static final String TABLE_NAME = "User";

	private static final String TAG = "MyContentProvider";

	private static class DatabaseHelper extends SQLiteOpenHelper {

		DatabaseHelper(Context context) {
			super(context, DATABASE_NAME, null, DATABASE_VERSION);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			// create table to store user names
			db.execSQL("Create table "
					+ TABLE_NAME
					+ "( _id INTEGER PRIMARY KEY AUTOINCREMENT, USER_NAME TEXT);");
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
			onCreate(db);
		}
	}

	@Override
	public int delete(Uri uri, String s, String[] as) {
		return 0;
	}

	@Override
	public String getType(Uri uri) {
		return null;
	}

	@Override
	public Uri insert(Uri uri, ContentValues contentvalues) {
		// get database to insert records
		sqlDB = dbHelper.getWritableDatabase();
		// insert record in user table and get the row number of recently inserted record
		long rowId = sqlDB.insert(TABLE_NAME, "", contentvalues);
		if (rowId > 0) {
			Uri rowUri = ContentUris.appendId(
					MyUsers.User.CONTENT_URI.buildUpon(), rowId).build();
			getContext().getContentResolver().notifyChange(rowUri, null);
			return rowUri;
		}
		throw new SQLException("Failed to insert row into " + uri);
	}

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

	@Override
	public Cursor query(Uri uri, String[] projection, String selection,
			String[] selectionArgs, String sortOrder) {
		SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
		SQLiteDatabase db = dbHelper.getReadableDatabase();
		qb.setTables(TABLE_NAME);
		Cursor c = qb.query(db, projection, selection, null, null, null,
				sortOrder);
		c.setNotificationUri(getContext().getContentResolver(), uri);
		return c;
	}

	@Override
	public int update(Uri uri, ContentValues contentvalues, String s,
			String[] as) {
		return 0;
	}
}

Now lets create an Activity and Corresponding view to display this in GUI

/**
 * 
 */
package com.linkwithweb;

import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.linkwithweb.providers.MyUsers;

/**
 * @author Ashwin Kumar
 * 
 */
public class CustomProviderDemo extends Activity {
	private EditText mContactNameEditText;
	private TextView mContactsText;
	private Button mContactSaveButton;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.custom_provider);

		// Obtain handles to UI objects
		mContactNameEditText = (EditText) findViewById(R.id.contactNameEditText);
		mContactSaveButton = (Button) findViewById(R.id.contactSaveButton);
		mContactsText = (TextView) findViewById(R.id.contactEntryText);

		
		mContactSaveButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
            	String name = mContactNameEditText.getText().toString();
            	insertRecord(name);
            	mContactsText.append("\n"+name);
            }
        });
		
		//insertRecord("MyUser");
		displayRecords();
	}

	private void insertRecord(String userName) {
		ContentValues values = new ContentValues();
		values.put(MyUsers.User.USER_NAME, userName);
		getContentResolver().insert(MyUsers.User.CONTENT_URI, values);
	}

	private void displayRecords() {
		// An array specifying which columns to return.
		String columns[] = new String[] { MyUsers.User._ID,
				MyUsers.User.USER_NAME };
		Uri myUri = MyUsers.User.CONTENT_URI;
		Cursor cur = managedQuery(myUri, columns, // Which columns to return
				null, // WHERE clause; which rows to return(all rows)
				null, // WHERE clause selection arguments (none)
				null // Order-by clause (ascending by name)
		);
		if (cur.moveToFirst()) {
			String id = null;
			String userName = null;
			do {
				// Get the field values
				id = cur.getString(cur.getColumnIndex(MyUsers.User._ID));
				userName = cur.getString(cur
						.getColumnIndex(MyUsers.User.USER_NAME));
				Toast.makeText(this, id + " " + userName, Toast.LENGTH_LONG)
						.show();
			} while (cur.moveToNext());
		}
	}
}

Below is the view code

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    <TableLayout android:layout_width="match_parent"
                 android:layout_height="match_parent">

        <TableRow>
            <TextView android:layout_width="wrap_content"
                      android:layout_height="wrap_content"
                      android:text="@string/contactNameLabel"/>
        </TableRow>
        <TableRow>
            <EditText android:id="@+id/contactNameEditText"
                      android:layout_height="wrap_content"
                      android:layout_width="wrap_content"
                      android:layout_weight="1"/>
        </TableRow>
        <TableRow>
            <Button android:layout_height="wrap_content"
                    android:text="@string/save"
                    android:id="@+id/contactSaveButton"
                    android:layout_width="match_parent"
                    android:layout_weight="1"/>
        </TableRow>
        <TableRow>
	        <TextView android:text="@+id/contactEntryText"
	              android:id="@+id/contactEntryText"
	              android:layout_width="match_parent"
	              android:layout_height="wrap_content"/>
   		 </TableRow>
        
    </TableLayout>
</ScrollView>

Below is the configuration you can define in Manifest file

		<activity android:name=".CustomProviderDemo" android:label="CustomProviderDemo">
			<intent-filter>
				<action android:name="android.intent.action.MAIN" />
				<category android:name="android.intent.category.SAMPLE_CODE" />
			</intent-filter>
		</activity>	
		
		
		<provider android:name="com.linkwithweb.providers.MyContentProvider" android:authorities="com.linkwithweb.providers.MyContentProvider" />		

Code has been checked in to below url. You can checkout from the below svn url for workign code of whole project with all other lessons
https://linkwithweb.googlecode.com/svn/trunk/Android/AndroidLessons

Build Android Application using Ant

Following post will explain how to create ant build script any android application

Below are sample file which you can create in your android application
build.xml

<?xml version="1.0" encoding="UTF-8"?>
<project name="MainActivity" default="help">

    <!-- The local.properties file is created and updated by the 'android' tool.
         It contains the path to the SDK. It should *NOT* be checked in in Version
         Control Systems. -->
    <property file="local.properties" />

    <!-- The build.properties file can be created by you and is never touched
         by the 'android' tool. This is the place to change some of the default property values
         used by the Ant rules.
         Here are some properties you may want to change/update:

         application.package
             the name of your application package as defined in the manifest. Used by the
             'uninstall' rule.
         source.dir
             the name of the source directory. Default is 'src'.
         out.dir
             the name of the output directory. Default is 'bin'.

         Properties related to the SDK location or the project target should be updated
          using the 'android' tool with the 'update' action.

         This file is an integral part of the build system for your application and
         should be checked in in Version Control Systems.

         -->
    <property file="build.properties" />

    <!-- The default.properties file is created and updated by the 'android' tool, as well
         as ADT.
         This file is an integral part of the build system for your application and
         should be checked in in Version Control Systems. -->
    <property file="default.properties" />

    <!-- Custom Android task to deal with the project target, and import the proper rules.
         This requires ant 1.6.0 or above. -->
    <path id="android.antlibs">
		<fileset dir="${sdk.dir}/tools/lib/" includes="*.jar" />
    </path>

    <taskdef name="setup"
        classname="com.android.ant.SetupTask"
        classpathref="android.antlibs" />

    <!-- Execute the Android Setup task that will setup some properties specific to the target,
         and import the build rules files.

         The rules file is imported from
            <SDK>/platforms/<target_platform>/templates/android_rules.xml

         To customize some build steps for your project:
         - copy the content of the main node <project> from android_rules.xml
         - paste it in this build.xml below the <setup /> task.
         - disable the import by changing the setup task below to <setup import="false" />

         This will ensure that the properties are setup correctly but that your customized
         build steps are used.
    -->
    <setup/>
    
    <!-- Below is the area imported from platforms\android-4\templates\android_rules.xml -->

    <!--
        This rules file is meant to be imported by the custom Ant task:
            com.android.ant.AndroidInitTask

        The following properties are put in place by the importing task:
            android.jar, android.aidl, aapt, aidl, and dx

        Additionnaly, the task sets up the following classpath reference:
            android.target.classpath
        This is used by the compiler task as the boot classpath.
    -->
<target name="start-emulator" description="Start an emulator.">
	<android:adb><arg value="start-server"/></android:adb>
	<android:start-emulator>
		<arg line="-skin 320x480 -no-boot-anim"/>
	</android:start-emulator>
</target>

<target name="stop-emulator" description="Stop the emulator we started.">
	<android:stop-emulator/>
</target>

</project>

default.properties

# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
# 
# This file must be checked in Version Control Systems.
# 
# To customize properties used by the Ant build system use,
# "build.properties", and override values to adapt the script to your
# project structure.

# Project target.
target=android-10

build.properties


# Turn on or off logging.
config.logging=true

#
# Set the keystore properties for signing the application.
#
key.store=keystore
key.alias=www.linkwithweb.com

key.store.password=password
key.alias.password=password

local.properties

# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
# 
# This file must *NOT* be checked in Version Control Systems,
# as it contains information specific to your local configuration.

# location of the SDK. This is only used by Ant
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=C:\\setupAndroid\\android-sdk-windows

Android Database Example ,database usage, AsyncTask , Database Export

This is continuation of previous lessons on Android. In this article i would like to discuss Database Operations in Android and Concept of AsyncTask in android
AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers. This can be used in Timeconsuming processes so that UI is not obstructed

Note: As previously defined i’ve been compiling opensource Tutorials on android and selecting and modifying them to create a easy tutorial on major topics

Before working with Database ,lets create a Utility classes which can create/update/drop/insert database/tables

Here are 2 utility classes which are self explanatory


package com.linkwithweb.sqllite;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.provider.BaseColumns;
import android.util.Log;

/**
 * @author Ashwin Kumar
 * 
 */
public class DataBaseHelper extends SQLiteOpenHelper {

	// The Android's default system path of your application database.
	private static String DB_PATH = "/data/data/linkwithweb/databases/";

	private static String DB_NAME = "myDBName.db";

	private static final int DATABASE_VERSION = 1;

	// Table name
	public static final String TABLE_NAME = "TEST";

	private SQLiteDatabase myDataBase;

	private final Context myContext;

	private SQLiteStatement insertStmt;
	private static final String INSERT = "insert into " + TABLE_NAME
			+ "(name) values (?)";

	/**
	 * Constructor
	 * Takes and keeps a reference of the passed context in order to access to the application assets and resources.
	 * 
	 * @param context
	 */
	public DataBaseHelper(Context context) {
		super(context, DB_NAME, null, DATABASE_VERSION);
		this.myContext = context;
		this.myDataBase = getWritableDatabase();
		this.insertStmt = this.myDataBase.compileStatement(INSERT);
	}

	/**
	 * Creates a empty database on the system and rewrites it with your own database.
	 * */
	public void createDataBase() throws IOException {

		boolean dbExist = checkDataBase();

		if (dbExist) {
			// do nothing - database already exist
		} else {

			// By calling this method and empty database will be created into the default system path
			// of your application so we are gonna be able to overwrite that database with our database.
			this.getReadableDatabase();

			try {

				copyDataBase();

			} catch (IOException e) {

				throw new Error("Error copying database");

			}
		}

	}

	/**
	 * Check if the database already exist to avoid re-copying the file each time you open the application.
	 * 
	 * @return true if it exists, false if it doesn't
	 */
	private boolean checkDataBase() {

		SQLiteDatabase checkDB = null;

		try {
			String myPath = DB_PATH + DB_NAME;
			checkDB = SQLiteDatabase.openDatabase(myPath, null,
					SQLiteDatabase.OPEN_READONLY);

		} catch (SQLiteException e) {

			// database does't exist yet.

		}

		if (checkDB != null) {

			checkDB.close();

		}

		return checkDB != null ? true : false;
	}

	/**
	 * Copies your database from your local assets-folder to the just created empty database in the
	 * system folder, from where it can be accessed and handled.
	 * This is done by transfering bytestream.
	 * */
	private void copyDataBase() throws IOException {

		// Open your local db as the input stream
		InputStream myInput = myContext.getAssets().open(DB_NAME);

		// Path to the just created empty db
		String outFileName = DB_PATH + DB_NAME;

		// Open the empty db as the output stream
		OutputStream myOutput = new FileOutputStream(outFileName);

		// transfer bytes from the inputfile to the outputfile
		byte[] buffer = new byte[1024];
		int length;
		while ((length = myInput.read(buffer)) > 0) {
			myOutput.write(buffer, 0, length);
		}

		// Close the streams
		myOutput.flush();
		myOutput.close();
		myInput.close();

	}

	/**
	 * @throws SQLException
	 */
	public void openDataBase() throws SQLException {
		// Open the database
		String myPath = DB_PATH + DB_NAME;
		myDataBase = SQLiteDatabase.openDatabase(myPath, null,
				SQLiteDatabase.OPEN_READONLY);

	}

	@Override
	public synchronized void close() {
		if (myDataBase != null)
			myDataBase.close();

		super.close();

	}

	public SQLiteDatabase getDb() {
		return myDataBase;
	}

	/**
	 * Compiled Insert Statement
	 * 
	 * @param name
	 * @return
	 */
	public long insert(String name) {
		this.insertStmt.bindString(1, name);
		return this.insertStmt.executeInsert();
	}

	/**
	 * 
	 */
	public void deleteAll() {
		this.myDataBase.delete(TABLE_NAME, null, null);
	}

	/**
	 * @return
	 */
	public List selectAll() {
		List list = new ArrayList();
		Cursor cursor = this.myDataBase.query(TABLE_NAME,
				new String[] { "name" }, null, null, null, null, "name desc");
		if (cursor.moveToFirst()) {
			do {
				list.add(cursor.getString(0));
			} while (cursor.moveToNext());
		}
		if (cursor != null && !cursor.isClosed()) {
			cursor.close();
		}
		return list;
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		String sql = "create table " + TABLE_NAME + "( " + BaseColumns._ID
				+ " integer primary key autoincrement, name text not null);";
		Log.d("EventsData", "onCreate: " + sql);
		db.execSQL(sql);
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		if (oldVersion >= newVersion)
			return;

		String sql = null;
		if (oldVersion == 1)
			sql = "alter table " + TABLE_NAME + " add COLUMN_2 text;";
		if (oldVersion == 2)
			sql = "";

		Log.d("EventsData", "onUpgrade	: " + sql);
		if (sql != null)
			db.execSQL(sql);
	}

	// Add your public helper methods to access and get content from the database.
	// You could return cursors by doing "return myDataBase.query(....)" so it'd be easy
	// to you to create adapters for your views.

}

Now let me define my database xml exporter. Just exports any database to XML format


package com.linkwithweb.sqllite;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;
import android.util.Log;

import com.linkwithweb.MyApplication;

/**
 * Android DataExporter that allows the passed in SQLiteDatabase 
 * to be exported to external storage (SD card) in an XML format.
 * 
 * To backup a SQLite database you need only copy the database file itself
 * (on Android /data/data/APP_PACKAGE/databases/DB_NAME.db) -- you *don't* need this
 * export to XML step.
 * 
 * XML export is useful so that the data can be more easily transformed into
 * other formats and imported/exported with other tools (not for backup per se).  
 * 
 * The kernel of inspiration for this came from: 
 * http://mgmblog.com/2009/02/06/export-an-android-sqlite-db-to-an-xml-file-on-the-sd-card/. 
 * (Though I have made many changes/updates here, I did initially start from that article.)
 * 
 * @author ccollins
 *
 */
public class DataXmlExporter {

   private static final String DATASUBDIRECTORY = "exampledata";

   private SQLiteDatabase db;
   private XmlBuilder xmlBuilder;

   public DataXmlExporter(SQLiteDatabase db) {
      this.db = db;
   }

   public void export(String dbName, String exportFileNamePrefix) throws IOException {
      Log.i(MyApplication.APP_NAME, "exporting database - " + dbName + " exportFileNamePrefix=" + exportFileNamePrefix);

      this.xmlBuilder = new XmlBuilder();
      this.xmlBuilder.start(dbName);

      // get the tables
      String sql = "select * from sqlite_master";
      Cursor c = this.db.rawQuery(sql, new String[0]);
      Log.d(MyApplication.APP_NAME, "select * from sqlite_master, cur size " + c.getCount());
      if (c.moveToFirst()) {
         do {
            String tableName = c.getString(c.getColumnIndex("name"));
            Log.d(MyApplication.APP_NAME, "table name " + tableName);

            // skip metadata, sequence, and uidx (unique indexes)
            if (!tableName.equals("android_metadata") && !tableName.equals("sqlite_sequence")
                     && !tableName.startsWith("uidx")) {
               this.exportTable(tableName);
            }
         } while (c.moveToNext());
      }
      String xmlString = this.xmlBuilder.end();
      this.writeToFile(xmlString, exportFileNamePrefix + ".xml");
      Log.i(MyApplication.APP_NAME, "exporting database complete");
   }

   private void exportTable(final String tableName) throws IOException {
      Log.d(MyApplication.APP_NAME, "exporting table - " + tableName);
      this.xmlBuilder.openTable(tableName);
      String sql = "select * from " + tableName;
      Cursor c = this.db.rawQuery(sql, new String[0]);
      if (c.moveToFirst()) {
         int cols = c.getColumnCount();
         do {
            this.xmlBuilder.openRow();
            for (int i = 0; i < cols; i++) {
               this.xmlBuilder.addColumn(c.getColumnName(i), c.getString(i));
            }
            this.xmlBuilder.closeRow();
         } while (c.moveToNext());
      }
      c.close();
      this.xmlBuilder.closeTable();
   }

   private void writeToFile(String xmlString, String exportFileName) throws IOException {
      File dir = new File(Environment.getExternalStorageDirectory(), DATASUBDIRECTORY);
      if (!dir.exists()) {
         dir.mkdirs();
      }
      File file = new File(dir, exportFileName);
      file.createNewFile();

      ByteBuffer buff = ByteBuffer.wrap(xmlString.getBytes());
      FileChannel channel = new FileOutputStream(file).getChannel();
      try {
         channel.write(buff);
      } finally {
         if (channel != null)
            channel.close();
      }
   }

   /**
    * XmlBuilder is used to write XML tags (open and close, and a few attributes)
    * to a StringBuilder. Here we have nothing to do with IO or SQL, just a fancy StringBuilder. 
    * 
    * @author ccollins
    *
    */
   class XmlBuilder {
      private static final String OPEN_XML_STANZA = "";
      private static final String CLOSE_WITH_TICK = "'>";
      private static final String DB_OPEN = "<database name='";
      private static final String DB_CLOSE = "";
      private static final String TABLE_OPEN = "<table name='";
      private static final String TABLE_CLOSE = "";
      private static final String ROW_OPEN = "";
      private static final String ROW_CLOSE = "";
      private static final String COL_OPEN = "<col name='";
      private static final String COL_CLOSE = "";

      private final StringBuilder sb;

      public XmlBuilder() throws IOException {
         this.sb = new StringBuilder();
      }

      void start(String dbName) {
         this.sb.append(OPEN_XML_STANZA);
         this.sb.append(DB_OPEN + dbName + CLOSE_WITH_TICK);
      }

      String end() throws IOException {
         this.sb.append(DB_CLOSE);
         return this.sb.toString();
      }

      void openTable(String tableName) {
         this.sb.append(TABLE_OPEN + tableName + CLOSE_WITH_TICK);
      }

      void closeTable() {
         this.sb.append(TABLE_CLOSE);
      }

      void openRow() {
         this.sb.append(ROW_OPEN);
      }

      void closeRow() {
         this.sb.append(ROW_CLOSE);
      }

      void addColumn(final String name, final String val) throws IOException {
         this.sb.append(COL_OPEN + name + CLOSE_WITH_TICK + val + COL_CLOSE);
      }
   }
}


Now lets create Activity and AsyncTasks to Delete/Insert data into Database tables. Below one is sample Async Task


	private class InsertDataTask extends AsyncTask {
		private final ProgressDialog dialog = new ProgressDialog(
				DatabaseActivity.this);

		// can use UI thread here
		protected void onPreExecute() {
			this.dialog.setMessage("Inserting data...");
			this.dialog.show();
		}

		// automatically done on worker thread (separate from UI thread)
		protected Void doInBackground(final String... args) {
			DatabaseActivity.this.application.getDataHelper().insert(args[0]);
			return null;
		}

		// can use UI thread here
		protected void onPostExecute(final Void unused) {
			if (this.dialog.isShowing()) {
				this.dialog.dismiss();
			}
			// reset the output view by retrieving the new data
			// (note, this is a naive example, in the real world it might make sense
			// to have a cache of the data and just append to what is already there, or such
			// in order to cut down on expensive database operations)
			new SelectDataTask().execute();
		}
	}

Here is the Database Activity Listed. This just inserts some data into database on button click and deletes all data on another button click


/**
 * 
 */
package com.linkwithweb;

import java.util.List;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.linkwithweb.sqllite.ManageData;

/**
 * @author Ashwin Kumar
 * 
 */
public class DatabaseActivity extends Activity {

	private static final String NAME = "NAME";

	private static final int MENU_MANAGE = 0;

	private EditText input;
	private Button saveButton;
	private Button deleteButton;
	private TextView output;

	private MyApplication application;

	@Override
	public void onCreate(final Bundle savedInstanceState) {
		Log.d(MyApplication.APP_NAME, "onCreate");
		super.onCreate(savedInstanceState);

		this.setContentView(R.layout.dbview);

		// get "Application" object for shared state or creating of expensive resources - like DataHelper
		// (this is not recreated as often as each Activity)
		this.application = (MyApplication) this.getApplication();

		// inflate views
		this.input = (EditText) this.findViewById(R.id.in_text);
		this.saveButton = (Button) this.findViewById(R.id.save_button);
		this.deleteButton = (Button) this.findViewById(R.id.del_button);
		this.output = (TextView) this.findViewById(R.id.out_text);

		// initially populate "output" view from database
		new SelectDataTask().execute();

		// save new data to database (when save button is clicked)
		this.saveButton.setOnClickListener(new OnClickListener() {
			public void onClick(final View v) {
				new InsertDataTask().execute(DatabaseActivity.this.input
						.getText().toString());
				DatabaseActivity.this.input.setText("");
			}
		});

		// delete all data from database (when delete button is clicked)
		this.deleteButton.setOnClickListener(new OnClickListener() {
			public void onClick(final View v) {
				new DeleteDataTask().execute();
			}
		});
	}

	@Override
	public void onSaveInstanceState(final Bundle b) {
		Log.d(MyApplication.APP_NAME, "onSaveInstanceState");
		if ((this.input.getText().toString() != null)
				&& (this.input.getText().toString().length() > 0)) {
			b.putString(DatabaseActivity.NAME, this.input.getText().toString());
		}
		super.onSaveInstanceState(b);
	}

	@Override
	public void onRestoreInstanceState(final Bundle b) {
		super.onRestoreInstanceState(b);
		Log.d(MyApplication.APP_NAME, "onRestoreInstanceState");
		String name = b.getString(DatabaseActivity.NAME);
		if (name != null) {
			// use onSaveInstanceState/onRestoreInstance state to manage state when orientation is changed (and whenever restarted)
			// put some text in input box, then rotate screen, text should remain
			// COMMENT this out, and try again, text won't be there - you need to maintain this state - esp for orientation changes
			// (you can rotate the screen in the emulator by pressing 9 on numeric keypad)
			this.input.setText(name);
		}
	}

	@Override
	public boolean onCreateOptionsMenu(final Menu menu) {
		menu.add(0, DatabaseActivity.MENU_MANAGE, 1, "Manage Database")
				.setIcon(android.R.drawable.ic_menu_manage);
		return super.onCreateOptionsMenu(menu);
	}

	@Override
	public boolean onOptionsItemSelected(final MenuItem item) {
		switch (item.getItemId()) {
		case MENU_MANAGE:
			this.startActivity(new Intent(DatabaseActivity.this,
					ManageData.class));
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
	}

	private class InsertDataTask extends AsyncTask {
		private final ProgressDialog dialog = new ProgressDialog(
				DatabaseActivity.this);

		// can use UI thread here
		protected void onPreExecute() {
			this.dialog.setMessage("Inserting data...");
			this.dialog.show();
		}

		// automatically done on worker thread (separate from UI thread)
		protected Void doInBackground(final String... args) {
			DatabaseActivity.this.application.getDataHelper().insert(args[0]);
			return null;
		}

		// can use UI thread here
		protected void onPostExecute(final Void unused) {
			if (this.dialog.isShowing()) {
				this.dialog.dismiss();
			}
			// reset the output view by retrieving the new data
			// (note, this is a naive example, in the real world it might make sense
			// to have a cache of the data and just append to what is already there, or such
			// in order to cut down on expensive database operations)
			new SelectDataTask().execute();
		}
	}

	private class SelectDataTask extends AsyncTask {
		private final ProgressDialog dialog = new ProgressDialog(
				DatabaseActivity.this);

		// can use UI thread here
		protected void onPreExecute() {
			this.dialog.setMessage("Selecting data...");
			this.dialog.show();
		}

		// automatically done on worker thread (separate from UI thread)
		protected String doInBackground(final String... args) {
			List names = DatabaseActivity.this.application
					.getDataHelper().selectAll();
			StringBuilder sb = new StringBuilder();
			for (String name : names) {
				sb.append(name + "\n");
			}
			return sb.toString();
		}

		// can use UI thread here
		protected void onPostExecute(final String result) {
			if (this.dialog.isShowing()) {
				this.dialog.dismiss();
			}
			DatabaseActivity.this.output.setText(result);
		}
	}

	private class DeleteDataTask extends AsyncTask {
		private final ProgressDialog dialog = new ProgressDialog(
				DatabaseActivity.this);

		// can use UI thread here
		protected void onPreExecute() {
			this.dialog.setMessage("Deleting data...");
			this.dialog.show();
		}

		// automatically done on worker thread (separate from UI thread)
		protected Void doInBackground(final String... args) {
			DatabaseActivity.this.application.getDataHelper().deleteAll();
			return null;
		}

		// can use UI thread here
		protected void onPostExecute(final Void unused) {
			if (this.dialog.isShowing()) {
				this.dialog.dismiss();
			}
			// reset the output view by retrieving the new data
			// (note, this is a naive example, in the real world it might make sense
			// to have a cache of the data and just append to what is already there, or such
			// in order to cut down on expensive database operations)
			new SelectDataTask().execute();
		}
	}
}


Attached is the eclipse project with all examples(Rename .doc to .zip to extract to workspace)
AndroidLessons

You can also checkout code from
http://linkwithweb.googlecode.com/svn/trunk/Android/AndroidLessons/

Integrating Google Maps in Android

This is my third article in sequence. I have used same Code base which i’ve been using. Added one more menu for maps

Adding maps to Android requires that your application Android Target is set to GoogleAPI’s(These contain Google map addon’s) Rather than Android version one’s

Remember : Adding Maps.jar externally to target will not work at runtime so always change Build Target to Google Addon’s

Create a Virtual Device for your version tergetting Google API’s

Now once the above steps are completed you need to follow the below steps

  1. 1. Either use debug.keystore or create keystore certificate using android tools(Debug.keystore is located in “C:\Documents and Settings\\.android” Directory.
  2. Now generate MD5 using keytool command of java
  3. Generate Google API key using this MD5

Finally build your application and Run in debug mode (As debug mode automatically sign’s you application using debug.keystore which we configured for maps

Remember to add <uses-library android:name=”com.google.android.maps” /> inside application tag else system will exit with some errors

Attached is the AndroidLessions with new Demo added for google maps. (Rename to .zip to extract eclipse project)
You can learn about more features in google maps from Android website
AndroidLessons(Rename to .zip to extract eclipse project)

Using Web View in Android and Loading Data Over network

This is extension to previous Article i posted (This is my second article). I’m learning Android in process of creating these articles. All my examples are compiled and Integrated from various solutions over the web.

Note (This version of code dosen’t work in 2.3.2 version of emulator . There is a bug in Emulator for Webview and Javascript Interfacing)

In this example i have presented my Lessons in more systematic manner.

My First screen shows list of examples

AndroidLessonsMain is my Main activity and is starting point of my application. It just shows some List containing links to corresponding demo activities

package com.linkwithweb;
/*  Copyright 2011  Ashwin Kumar  (email : ashwin@linkwithweb.com)

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2, as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

/**
 * 
 * @author Ashwin Kumar
 * 
 */
public class AndroidLessonsMain extends ListActivity {
	static final String[] COUNTRIES = new String[] { "GraphViewDemo",
			"WebViewDemo" };

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setListAdapter(new ArrayAdapter(this, R.layout.list_item,
				COUNTRIES));

		ListView lv = getListView();
		lv.setTextFilterEnabled(true);

		lv.setOnItemClickListener(new OnItemClickListener() {
			public void onItemClick(AdapterView parent, View view,
					int position, long id) {
				// When clicked, show a toast with the TextView text
				if (((TextView) view).getText().equals("GraphViewDemo")) {
					Intent myIntent = new Intent(AndroidLessonsMain.this,
							GraphViewDemo.class);
					AndroidLessonsMain.this.startActivity(myIntent);
				} else {
					Intent myIntent = new Intent(AndroidLessonsMain.this,
							NetworkAndWebView.class);
					AndroidLessonsMain.this.startActivity(myIntent);
				}

				/*
				 * Toast.makeText(getApplicationContext(),
				 * ((TextView) view).getText(), Toast.LENGTH_SHORT).show();
				 */
			}
		});
	}
}
MainScreen
MainScreen


As you see the code each item in list has Trigger to Corresponding Demo Activities. First Activity is already Described in Previous Example
Now i’ll describe WebViewDemo

This activity starts with a screen which has a Textbox and a button to get the Data of the URL entered. I have uploaded a sample xml i’m going to parse in
http://www.northalley.com/SampleStats.xml

Once you click the get Button. I parse the response xml and create WebView which displays Graph usng flot (JQuery Graphing Library)
Network request is done as Asynchronous process here
Once the response is processed i start webview using Javascript Native Interface and rest is using Flot library in Javscript to draw the graph

It interacts with Android code using some buttons in bottom

NetworkAndWebViewMain
NetworkAndWebViewMain

Here is sample code

/*  Copyright 2011  Ashwin Kumar  (email : ashwin@linkwithweb.com)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License, version 2, as
    published by the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package com.linkwithweb;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

/**
 * The main activity class
 * 
 * @author "Ashwin Kumar" (https://ashwinrayaprolu.wordpress.com)
 * 
 */
public class NetworkAndWebView extends Activity {
	private EditText mFeedUrl;
	private Button mGetButton;

	private Context mContext;
	private StatsGraphHandler mGraphHandler;

	private static final String FEEDBURNER_API_URL = "https://feedburner.google.com/awareness/1.0/GetFeedData?uri=";
	static final int PROGRESS_DIALOG = 0;
	static final int ABOUT_DIALOG = 1;

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

		mFeedUrl = (EditText) findViewById(R.id.feedUrl);
		mFeedUrl.setText("http://www.northalley.com/SampleStats.xml");
		mGetButton = (Button) findViewById(R.id.getGraph);
		mContext = this;

		// mFeedUrl.setText("http://feeds.feedburner.com/SudarBlogs"); //for debugging

		Button getGraph = (Button) findViewById(R.id.getGraph);
		getGraph.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				// get the feed url and validate

				String feedUrl = mFeedUrl.getText().toString();
				if (feedUrl == null || feedUrl.equals("http://")
						|| feedUrl.equals("")) {
					Toast.makeText(
							mContext,
							mContext.getResources().getString(
									R.string.feed_url_empty),
							Toast.LENGTH_SHORT).show();
					return;
				}

				// start the dialog
				showDialog(PROGRESS_DIALOG);
				new GetStatsTask().execute(feedUrl);
			}
		});
	}

	/**
	 * Before the dialog is created
	 * 
	 * @see android.app.Activity#onCreateDialog(int)
	 */
	@Override
	protected Dialog onCreateDialog(int id) {
		switch (id) {
		case PROGRESS_DIALOG:
			ProgressDialog dialog = ProgressDialog.show(mContext, "",
					getResources().getString(R.string.loading_msg), true);
			return dialog;
		case ABOUT_DIALOG:
			AlertDialog.Builder builder;
			Dialog dialog2;

			LayoutInflater inflater = (LayoutInflater) mContext
					.getSystemService(LAYOUT_INFLATER_SERVICE);
			View layout = inflater.inflate(R.layout.about,
					(ViewGroup) findViewById(R.id.layout_root));

			builder = new AlertDialog.Builder(mContext);
			builder.setView(layout);
			builder.setMessage("").setPositiveButton(
					this.getString(R.string.ok),
					new DialogInterface.OnClickListener() {
						public void onClick(DialogInterface dialog, int id) {
							dialog.cancel();
						}
					});

			dialog2 = builder.create();

			View projectUrl = layout.findViewById(R.id.project_url);
			projectUrl.setOnClickListener(new OnClickListener() {
				@Override
				public void onClick(View v) {
					// When the project url is clicked
					Uri uri = Uri.parse(getString(R.string.about_project_url));
					Intent intent = new Intent(Intent.ACTION_VIEW, uri);
					startActivity(intent);
				}
			});

			return dialog2;
		default:
			return null;
		}
	}

	/**
	 * Create options menu
	 * 
	 * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
	 */
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.menu, menu);
		return true;
	}

	/**
	 * When the menu item is selected
	 * 
	 * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
	 */
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case R.id.about:
			displayAboutBox();
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
	}

	/**
	 * When the activity is resumed
	 */
	@Override
	protected void onResume() {
		super.onResume();
		// TODO: Need to handle screen orientation changes properly.
	}

	/**
	 * Display About box
	 */
	protected void displayAboutBox() {
		showDialog(ABOUT_DIALOG);
	}

	/**
	 * Task to fetch and parse feeds
	 * 
	 * @author "Sudar Muthu"
	 * 
	 */
	private class GetStatsTask extends
			AsyncTask<String, Void, Map> {
		private String errorMsg;

		/**
		 * Before the task is started
		 */
		@Override
		protected void onPreExecute() {
			mGetButton.setEnabled(false);
		}

		/**
		 * Start the background process
		 */
		protected Map doInBackground(String... feedUrl) {
			Map stats = new HashMap();
			FeedStatsHandler feedStatsHandler = new FeedStatsHandler(stats);

			// Add date query

			Calendar c = Calendar.getInstance();
			c.add(Calendar.DATE, -1); // we should start with previous day
			String endDate = c.get(Calendar.YEAR) + "-"
					+ (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DATE);
			c.add(Calendar.DATE, -30);
			String startDate = c.get(Calendar.YEAR) + "-"
					+ (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DATE);

			// get data from feedburner
			try {
				// URL url = new URL(FEEDBURNER_API_URL + feedUrl[0] + "&dates=" + startDate + "," + endDate);
				URL url = new URL("http://www.northalley.com/SampleStats.xml");

				/* Get a SAXParser from the SAXPArserFactory. */
				SAXParserFactory spf = SAXParserFactory.newInstance();
				SAXParser sp = spf.newSAXParser();

				/* Get the XMLReader of the SAXParser we created. */
				XMLReader xr = sp.getXMLReader();
				/* Create a new ContentHandler and apply it to the XML-Reader */
				xr.setContentHandler(feedStatsHandler);

				/* Parse the xml-data from our URL. */
				xr.parse(new InputSource(url.openStream()));
				/* Parsing has finished. */

				if (!feedStatsHandler.isError()) {
					stats = feedStatsHandler.getStats();
				} else {
					errorMsg = feedStatsHandler.getErrorMsg();
					stats = null;
				}

			} catch (MalformedURLException e) {
				handleError(e);
				stats = null;
			} catch (IOException e) {
				handleError(e);
				stats = null;
			} catch (ParserConfigurationException e) {
				handleError(e);
				stats = null;
			} catch (SAXException e) {
				handleError(e);
				stats = null;
			} catch (Exception e) {
				handleError(e);
				stats = null;
			}

			return stats;
		}

		/**
		 * When the background process is complete
		 */
		protected void onPostExecute(Map stats) {
			if (stats != null && stats.size() > 0) {

				// Show the webview
				WebView wv = (WebView) findViewById(R.id.wv1);

				mGraphHandler = new StatsGraphHandler(wv, stats);

				wv.getSettings().setJavaScriptEnabled(true);
				wv.loadUrl("file:///android_asset/flot/stats_graph.html");
				wv.addJavascriptInterface(mGraphHandler, "testhandler");
			} else {
				// show error message
				Toast.makeText(mContext, errorMsg, Toast.LENGTH_LONG).show();
			}
			mGetButton.setEnabled(true);
			dismissDialog(PROGRESS_DIALOG);
		}

		/**
		 * Print Error message
		 * 
		 * @param e
		 */
		private void handleError(Exception e) {
			Log.d(this.getClass().getSimpleName(), "Caught some exceiton");
			e.printStackTrace();
			errorMsg = e.getMessage();
		}
	}
}

And Here is code which handles Javascript and Android Code Interfacing


/*  Copyright 2011  Ashwin Kumar  (email : ashwin@linkwithweb.com)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License, version 2, as
    published by the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

/**
 * Graph Handler
 */
package com.linkwithweb;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Map;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;
import android.webkit.WebView;

/**
 * Handles data between JavaWorld and JavaScript World 
 * 
 *
 */
public class StatsGraphHandler {
	private WebView mAppView;
	private Map mStats;
	
	public StatsGraphHandler(WebView appView, Map stats) {
		mAppView = appView;
		mStats = stats;
	}
	
	/**
	 * Set the title of the graph
	 * 
	 * @return
	 */
	public String getGraphTitle() {
		//TODO: Move it to string.xml
		return "Your Feed stats";
	}

	/**
	 * Load the default graph
	 */
	public void loadGraph() {
		JSONArray data = new JSONArray();
		SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
		
		Object[] key = mStats.keySet().toArray();
		Arrays.sort(key); // keys should be sorted, otherwise the graph will not work properly.
		
		for (int i =0; i < key.length; i++) {
			JSONArray entry = new JSONArray();
			
			try {
				entry.put(formatter.parse(key[i] + " 00:00:00").getTime());
			} catch (ParseException e) {
				Log.d(this.getClass().getSimpleName(), "Some problem in parsing dates");
				e.printStackTrace();
			}
			
			entry.put(Integer.parseInt(mStats.get(key[i])));
			data.put(entry);
		}
		
		loadGraph(data);
	}
	
	/**
	 * Load Graph data
	 */
	private void loadGraph(JSONArray data) {
		JSONArray arr = new JSONArray();

		JSONObject result = new JSONObject();
			 try {
				result.put("data", data);//will ultimately look like: {"data": p[x1,y1],[x2,y2],[x3,y3],[]....]},
				result.put("lines", getLineOptionsJSON()); // { "lines": { "show" : true }},
				result.put("points", getPointOptionsJSON()); // { "points": { "show" : true }}
			} catch (JSONException e) {
				Log.d(this.getClass().getSimpleName(), "Got an exception while trying to parse JSON");
				e.printStackTrace();
			} 
			arr.put(result);
			
		// return arr.toString(); //This _WILL_ return the data in a good looking JSON string, but if you pass it straight into the Flot Plot method, it will not work!
			Log.d(this.getClass().getSimpleName(), arr.toString());
		mAppView.loadUrl("javascript:GotGraph(" + arr.toString() + ")"); // this callback works!
	}

	/**
	 * Get Points action
	 * @return
	 */
	private JSONArray getPointOptionsJSON() {
		JSONArray pointOption = new JSONArray();
		pointOption.put("show");
		pointOption.put(true);
		
		return pointOption;		
	}

	/**
	 * Get Lines option
	 * 
	 * @return
	 */
	private JSONArray getLineOptionsJSON() {
		JSONArray lineOption = new JSONArray();
		lineOption.put("show");
		lineOption.put(true);
		
		return lineOption;
	}
}

And Here is how it looks finally

WebViewGraph
WebViewGraph

And here is eclipse Project Attached. Rename to .zip to extract project

AndroidLessons

Custom View In Android ( Paint on Screen ) Graph as sample

I had a requirement to Create My Own view for Android Application and i was searching for a sample which gives me a first cut overview of what a Custom View/Component is in Android. Here is the example i have compiled from my search over internet

This sample creates a Barchart using a custom view (Everthing is painted on GUI)


/**
 * 
 */
package com.linkwithweb;

/**
 * @author Ashwin Kumar
 *
 */
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.view.View;

/**
 * @author Ashwin Kumar
 * 
 */
public class GraphView extends View {

	public static boolean BAR = true;
	public static boolean LINE = false;

	private Paint paint;
	private float[] values;
	private String[] horlabels;
	private String[] verlabels;
	private String title;
	private boolean type;

	public GraphView(Context context, float[] values, String title,
			String[] horlabels, String[] verlabels, boolean type) {
		super(context);
		if (values == null)
			values = new float[0];
		else
			this.values = values;
		if (title == null)
			title = "";
		else
			this.title = title;
		if (horlabels == null)
			this.horlabels = new String[0];
		else
			this.horlabels = horlabels;
		if (verlabels == null)
			this.verlabels = new String[0];
		else
			this.verlabels = verlabels;
		this.type = type;
		paint = new Paint();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		float border = 20;
		float horstart = border * 2;
		float height = getHeight();
		float width = getWidth() - 1;
		float max = getMax();
		float min = getMin();
		float diff = max - min;
		float graphheight = height - (2 * border);
		float graphwidth = width - (2 * border);

		paint.setTextAlign(Align.LEFT);
		int vers = verlabels.length - 1;
		for (int i = 0; i < verlabels.length; i++) {
			paint.setColor(Color.DKGRAY);
			float y = ((graphheight / vers) * i) + border;
			canvas.drawLine(horstart, y, width, y, paint);
			paint.setColor(Color.WHITE);
			canvas.drawText(verlabels[i], 0, y, paint);
		}
		int hors = horlabels.length - 1;
		for (int i = 0; i < horlabels.length; i++) {
			paint.setColor(Color.DKGRAY);
			float x = ((graphwidth / hors) * i) + horstart;
			canvas.drawLine(x, height - border, x, border, paint);
			paint.setTextAlign(Align.CENTER);
			if (i == horlabels.length - 1)
				paint.setTextAlign(Align.RIGHT);
			if (i == 0)
				paint.setTextAlign(Align.LEFT);
			paint.setColor(Color.WHITE);
			canvas.drawText(horlabels[i], x, height - 4, paint);
		}

		paint.setTextAlign(Align.CENTER);
		canvas.drawText(title, (graphwidth / 2) + horstart, border - 4, paint);

		if (max != min) {
			paint.setColor(Color.LTGRAY);
			if (type == BAR) {
				float datalength = values.length;
				float colwidth = (width - (2 * border)) / datalength;
				for (int i = 0; i < values.length; i++) {
					float val = values[i] - min;
					float rat = val / diff;
					float h = graphheight * rat;
					canvas.drawRect((i * colwidth) + horstart, (border - h)
							+ graphheight, ((i * colwidth) + horstart)
							+ (colwidth - 1), height - (border - 1), paint);
				}
			} else {
				float datalength = values.length;
				float colwidth = (width - (2 * border)) / datalength;
				float halfcol = colwidth / 2;
				float lasth = 0;
				for (int i = 0; i  0)
						canvas.drawLine(((i - 1) * colwidth) + (horstart + 1)
								+ halfcol, (border - lasth) + graphheight,
								(i * colwidth) + (horstart + 1) + halfcol,
								(border - h) + graphheight, paint);
					lasth = h;
				}
			}
		}
	}

	private float getMax() {
		float largest = Integer.MIN_VALUE;
		for (int i = 0; i  largest)
				largest = values[i];
		return largest;
	}

	private float getMin() {
		float smallest = Integer.MAX_VALUE;
		for (int i = 0; i < values.length; i++)
			if (values[i] < smallest)
				smallest = values[i];
		return smallest;
	}

}



package com.linkwithweb;

import android.app.Activity;
import android.os.Bundle;

/**
 * GraphViewDemo creates some dummy data to demonstrate the GraphView component.
 * @author Ashwin Kumar
 *
 */
public class GraphViewDemo extends Activity {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		float[] values = new float[] { 2.0f,1.5f, 2.5f, 1.0f , 3.0f };
		String[] verlabels = new String[] { "great", "ok", "bad" };
		String[] horlabels = new String[] { "today", "tomorrow", "next week", "next month" };
		GraphView graphView = new GraphView(this, values, "GraphViewDemo",horlabels, verlabels, GraphView.BAR);
		setContentView(graphView);
	}
}

More Android Tutorials coming on the way.

Attached is sample project which can be run from eclipse (I use Android-9) (Rename attachment to .zip to extract project)
AndroidLessons

Code is also checked in to following SVN URL
http://linkwithweb.googlecode.com/svn/trunk/Android