Friday 28 October 2011

Android First program Hello_World step by step.

 1.Download the eclipse -directly from here  (for window 32-bit only). 
    for other visit www.eclipse.org

2.Download window installer - from here

 3.Download ADT plug-in-- from here

 4.Now install the window installer downloaded in step-2

5.start the SDK manager.

6. Download the android platform (will take time). You must download at least one platform.

7.Open Eclipse and go to window option and then prefrence in eclipse and browse the android-sdk folder generally

it is located in C:\Program File\Android\android-sdk and click apply and then OK.

8.Now goto help and then install new software

9.Click on Add button Browse the ADT plug-in zip file(downloaded in step-3) click next now ADT plug-in

is installed.

.10.After finishing download in eclipse goto file->new project->Android

11.Give any name of project choose the sdk version (any one) give the application
name (anything).

Give the package name like com.example.myapp.

Give the activity name whatever you want.

Click finish

Your activity will open and something is already written in it

Like

Public void onCreate(……………..)

This is your main function .

112. now write it so that it look like.

package com.example.helloandroid;//your given package name

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

public class HelloAndroid extends Activity {
/** Called when the
activity is first created. */
@Override
public void onCreate(Bundle
savedInstanceState)
{
super.onCreate(savedInstanceState);
TextView tv =
new TextView(this);
tv.setText("Hello,
Android");
setContentView(tv);
} }

Now save it and right click on project and run it.

That is your hello, Android application.

Wednesday 12 October 2011

Android video player demo which stream vedio from the given URL

Today i am going to show you how can you make your own video player in android
which stream video on mobile
Make a new android project in eclipse.
Now copy the code given below in your main activity.


package org.android.sunny;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.URLUtil;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.Toast;
import android.widget.VideoView;

public class VideoViewDemo extends Activity {
    private static final String TAG = "VideoViewDemo";

    private VideoView mVideoView;
    private EditText mPath;
    private ImageButton mPlay;
    private ImageButton mPause;
    private ImageButton mReset;
    private ImageButton mStop;
    private String current;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.main);
        mVideoView = (VideoView) findViewById(R.id.surface_view);

        mPath = (EditText) findViewById(R.id.path);
        mPath.setText("http://daily3gp.com/vids/747.3gp");

        mPlay = (ImageButton) findViewById(R.id.play);
        mPause = (ImageButton) findViewById(R.id.pause);
        mReset = (ImageButton) findViewById(R.id.reset);
        mStop = (ImageButton) findViewById(R.id.stop);

        mPlay.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                playVideo();
            }
        });
        mPause.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                if (mVideoView != null) {
                    mVideoView.pause();
                }
            }
        });
        mReset.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                if (mVideoView != null) {
                    mVideoView.seekTo(0);
                }
            }
        });
        mStop.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                if (mVideoView != null) {
                    current = null;
                    mVideoView.stopPlayback();
                }
            }
        });
        runOnUiThread(new Runnable(){
            public void run() {
                playVideo();
               
            }
           
        });
    }

    private void playVideo() {
        try {
            final String path = mPath.getText().toString();
            Log.v(TAG, "path: " + path);
            if (path == null || path.length() == 0) {
                Toast.makeText(VideoViewDemo.this, "File URL/path is empty",
                        Toast.LENGTH_LONG).show();

            } else {
                // If the path has not changed, just start the media player
                if (path.equals(current) && mVideoView != null) {
                    mVideoView.start();
                    mVideoView.requestFocus();
                    return;
                }
                current = path;
                mVideoView.setVideoPath(getDataSource(path));
                mVideoView.start();
                mVideoView.requestFocus();

            }
        } catch (Exception e) {
            Log.e(TAG, "error: " + e.getMessage(), e);
            if (mVideoView != null) {
                mVideoView.stopPlayback();
            }
        }
    }

    private String getDataSource(String path) throws IOException {
        if (!URLUtil.isNetworkUrl(path)) {
            return path;
        } else {
            URL url = new URL(path);
            URLConnection cn = url.openConnection();
            cn.connect();
            InputStream stream = cn.getInputStream();
            if (stream == null)
                throw new RuntimeException("stream is null");
            File temp = File.createTempFile("mediaplayertmp", "dat");
            temp.deleteOnExit();
            String tempPath = temp.getAbsolutePath();
            FileOutputStream out = new FileOutputStream(temp);
            byte buf[] = new byte[128];
            do {
                int numread = stream.read(buf);
                if (numread <= 0)
                    break;
                out.write(buf, 0, numread);
            } while (true);
            try {
                stream.close();
            } catch (IOException ex) {
                Log.e(TAG, "error: " + ex.getMessage(), ex);
            }
            return tempPath;
        }
    }
}




Now your layout :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
        >
    <EditText android:id="@+id/path"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
            />
    <VideoView android:id="@+id/surface_view"
                 android:layout_width="fill_parent"
                 android:layout_height="fill_parent">
    </VideoView>
    <LinearLayout
            android:orientation="horizontal"
            android:layout_height="wrap_content"
            android:layout_width="fill_parent"
            >  
        <ImageButton android:id="@+id/play"
                     android:layout_height="wrap_content"
                     android:layout_width="wrap_content"
                     android:src="@drawable/play"/>

        <ImageButton android:id="@+id/pause"
                     android:layout_height="wrap_content"
                     android:layout_width="wrap_content"
                     android:src="@drawable/pause"/>
        <ImageButton android:id="@+id/reset"
                     android:layout_height="wrap_content"
                     android:layout_width="wrap_content"
                     android:src="@drawable/reset"/>
        <ImageButton android:id="@+id/stop"
                     android:layout_height="wrap_content"
                     android:layout_width="wrap_content"
                     android:src="@drawable/stop"/>
    </LinearLayout>
</LinearLayout>




 IMPORTANT NOTES: 1.DON'T FORGET TO PUT PLAY,PAUSE,STOP,RESET BUTTONS ( IMAGEVIEW ) .PNG IN DRAWABLE FOLDER YOU CAN DOWNLOAD THESE IMAGES JUST BY GOOGLING THEM.

2.DON'T FORGET TO ADD INTERNET PERMISSION IN YOUR MANIFEST FILE.

3. ENJOY!!!!!!!

Saturday 8 October 2011

Sending and Receiving messages in Android

Hello there, Today i am going to show you how can you send or receive messages.

 First make the a class which will extends the BroadcastReceiver as below

package YOUR PACKAGE NAME;


import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.widget.Toast;

class SMSReceiver extends BroadcastReceiver {
      @Override
      public void onReceive(Context context, Intent intent) {
        Bundle bundle = intent.getExtras();
        SmsMessage[] msgs = null;
        String str = "";
        if (bundle != null) {
          Object[] pdus = (Object[]) bundle.get("pdus");
          msgs = new SmsMessage[pdus.length];
          for (int i = 0; i < msgs.length; i++) {
            msgs[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
            str += "SMS from " + msgs[i].getOriginatingAddress();
            str += " :";
            str += msgs[i].getMessageBody().toString();
            str += "\n";
          }
          Toast.makeText(context, str, Toast.LENGTH_SHORT).show();
          Intent mainActivityIntent = new Intent(context, SMS.class);
          mainActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
          context.startActivity(mainActivityIntent);
          Intent broadcastIntent = new Intent();
          broadcastIntent.setAction("SMS_RECEIVED_ACTION");
          broadcastIntent.putExtra("sms", str);
          context.sendBroadcast(broadcastIntent);
        }
      }
    }

Now  your Main Activity should look like

package YOUR PACKAGE NAME;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;



public class SMS extends Activity {
      Button btnSendSMS;
      IntentFilter intentFilter;
      private BroadcastReceiver intentReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
          TextView SMSes = (TextView) findViewById(R.id.textView1);
          SMSes.setText(intent.getExtras().getString("sms"));
        }
      };

      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        intentFilter = new IntentFilter();
        intentFilter.addAction("SMS_RECEIVED_ACTION");
        registerReceiver(intentReceiver, intentFilter);
        btnSendSMS = (Button) findViewById(R.id.btnSendSMS);
        btnSendSMS.setOnClickListener(new View.OnClickListener() {
          public void onClick(View v) {
            sendSMS("5554", "Hello my friends!");
            Intent i = new Intent(android.content.Intent.ACTION_VIEW);
            i.putExtra("address", "5556; 5558; 5560");
            i.putExtra("sms_body", "Hello my friends!");
            i.setType("vnd.android-dir/mms-sms");
            startActivity(i);
          }
        });
      }

      @Override
      protected void onResume() {
        super.onResume();
      }

      @Override
      protected void onPause() {
        super.onPause();
      }

      @Override
      protected void onDestroy() {
        unregisterReceiver(intentReceiver);
        super.onPause();
      }

      /*
       * private void sendSMS(String phoneNumber, String message) { SmsManager sms
       * = SmsManager.getDefault(); sms.sendTextMessage(phoneNumber, null,
       * message, null, null); }
       */
      private void sendSMS(String phoneNumber, String message) {
        String SENT = "SMS_SENT";
        String DELIVERED = "SMS_DELIVERED";

        PendingIntent sentPI = PendingIntent.getBroadcast(this, 0, new Intent(
            SENT), 0);

        PendingIntent deliveredPI = PendingIntent.getBroadcast(this, 0,
            new Intent(DELIVERED), 0);

        registerReceiver(new BroadcastReceiver() {
          @Override
          public void onReceive(Context arg0, Intent arg1) {
            switch (getResultCode()) {
            case Activity.RESULT_OK:
              Toast.makeText(getBaseContext(), "SMS sent",
                  Toast.LENGTH_SHORT).show();
              break;
            case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
              Toast.makeText(getBaseContext(), "Generic failure",
                  Toast.LENGTH_SHORT).show();
              break;
            case SmsManager.RESULT_ERROR_NO_SERVICE:
              Toast.makeText(getBaseContext(), "No service",
                  Toast.LENGTH_SHORT).show();
              break;
            case SmsManager.RESULT_ERROR_NULL_PDU:
              Toast.makeText(getBaseContext(), "Null PDU",
                  Toast.LENGTH_SHORT).show();
              break;
            case SmsManager.RESULT_ERROR_RADIO_OFF:
              Toast.makeText(getBaseContext(), "Radio off",
                  Toast.LENGTH_SHORT).show();
              break;
            }
          }
        }, new IntentFilter(SENT));

        registerReceiver(new BroadcastReceiver() {
          @Override
          public void onReceive(Context arg0, Intent arg1) {
            switch (getResultCode()) {
            case Activity.RESULT_OK:
              Toast.makeText(getBaseContext(), "SMS delivered",
                  Toast.LENGTH_SHORT).show();
              break;
            case Activity.RESULT_CANCELED:
              Toast.makeText(getBaseContext(), "SMS not delivered",
                  Toast.LENGTH_SHORT).show();
              break;
            }
          }
        }, new IntentFilter(DELIVERED));

        SmsManager sms = SmsManager.getDefault();
        sms.sendTextMessage(phoneNumber, null, message, sentPI, deliveredPI);
      }

    }

XML file Layout :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<Button
    android:id="@+id/btnSendSMS" 
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Send SMS" />   
   
<TextView
    android:id="@+id/textView1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
   
</LinearLayout>


Just save all three files And most Impotent ADD THE PERMISSIONS IN YOUR MANIFEST FILE AS
<uses-permission android:name="android.permission.SEND_SMS">
    </uses-permission>
    <uses-permission android:name="android.permission.RECEIVE_SMS">
    </uses-permission>

Run the application.Now to test the application just start another application  or emulator now you have two emulator running .first in which your sms app is running and second is in which your other app(any) app is running. now to test receiving just send the sms from second emulator to the emulator in which your sms app is running.(you can send sms by pre installed sms app). Ph no. is simply the emulator number like 5554 or 5556. similarly you can send the sms from your app and test it on second emulator.

android application to connect to your own sqlite database created externlly

Hello there  today i am going to show you how can you connect your own sqlite database from android application.This application is a simple quote application which read the quotes(jokes,proverbs etc) in databases and display them in textview if you press next button the next quote will appear and if you press back button previous quote will appear.
You can make your database with sqlite browser.In your database make two fields
1."_id" (number count 1,2,3...)primary integer key
2.quote(text)

First open eclipse go to FILE ->New->Android Project
Fill the form and choose your target sdk version.And give the Activity name as  "MyActivity"
click finish. Now in eclipse your activity should be open.
Now put your database in the "assets" folder of the project.

In the right side in eclipse click on your projct->scr-> and right click on  your package  name and make a new class name "DbH".
Now write the code as below in your DbH class


package YOUR PACKAGE NAME;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

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;

public class DbH extends SQLiteOpenHelper {
    private Context mycontext;

    private String DB_PATH = "/data/data/YOUR PACKAGE NAME/databases/";
     private static String DB_NAME = "YOUR DATABASE NAME";
    private static String DB_TABLE="YOUR DATABASE TABLE NAME";
   
    public SQLiteDatabase myDataBase;
   
    /*private String DB_PATH = "/data/data/"
                                + mycontext.getApplicationContext().getPackageName()
                                + "/databases/";
*/
   
    public DbH(Context context) throws IOException  {
        super(context,DB_NAME,null,1);
        this.mycontext=context;
       }

    public void createdatabase() throws IOException{
        boolean dbexist = checkdatabase();
        if(dbexist)
        {
            System.out.println(" Database exists.");
        }
        else{
            this.getReadableDatabase();
        try{
                copydatabase();
            }
            catch(IOException e){
                throw new Error("Error copying database");
            }
        }
    }
    private boolean checkdatabase() {
        //SQLiteDatabase checkdb = null;
        boolean checkdb = false;
        try{
            String myPath = DB_PATH + DB_NAME;
            File dbfile = new File(myPath);
            checkdb = SQLiteDatabase.openDatabase(myPath,null,SQLiteDatabase.OPEN_READWRITE) != null;
            checkdb = dbfile.exists();
        }
        catch(SQLiteException e){
            System.out.println("Database doesn't exist");
        }

        return checkdb;
    }
    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 byte to inputfile to 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();

    }

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

    }

    public synchronized void close(){
        if(myDataBase != null){
            myDataBase.close();
        }
        super.close();
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
// This will return a cursor containing database records
 public Cursor  data(){
   
     Cursor c;     
     c=myDataBase.query(DB_TABLE, null, null,null,null,null,null);
      return c;
      }

@Override
public void onCreate(SQLiteDatabase arg0) {
    // TODO Auto-generated method stub
   
}

}

Now open your Main Activity class "MyActivity"
and make this class looks like:







package YOUR PACAGE NAME;

import java.io.IOException;

import android.app.Activity;
import android.database.Cursor;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MyActivity extends Activity implements View.OnClickListener {
    /** Called when the activity is first createdl. */
     Cursor cur;
    TextView tv;
    DbH db;
    Button next,back;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        tv=(TextView)findViewById(R.id.text);
        next=(Button)findViewById(R.id.next);
        back=(Button)findViewById(R.id.back);
        next.setOnClickListener(this);
     back.setOnClickListener(this);
     
        try {
            db=new DbH(this);
        } catch (IOException e2) {
           
            e2.printStackTrace();
        }
       
      
            try {
                db.createdatabase();
            } catch (IOException e) {
               
                e.printStackTrace();
            }

         db.opendatabase();
          cur=db.data();
           cur.moveToFirst();
   
          tv.setText(cur.getString(1));
      
     }
@Override
public void onClick(View v) {
   
    switch(v.getId())
    {
    case R.id.next :
        if(cur.isLast())
        {
            cur.moveToFirst();
            tv.setText(""+cur.getString(1));
        }
        else
        {
            cur.moveToNext();
        tv.setText(""+cur.getString(1));
       
        }
    break;
    case R.id.back:
    {
        if(cur.isFirst())
        {
            cur.moveToLast();
            tv.setText(""+cur.getString(1));
        }
        else {cur.moveToPrevious();
        tv.setText(""+cur.getString(1));
        }
        break;
    }
   
    }
}
}

Now your layout file :
I am  not making it colorful or setting the buttons look awesome. It is a simple layout which will just work fine.You can change the layout as you want 


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView 
    android:id="@+id/text"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textColor="#00ff00"
    android:textSize="20px"
    />
 
    <Button
   
     android:id="@+id/next"
     android:text="NEXT"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:onClick="true"
     />
      <Button
   
     android:id="@+id/back"
     android:text="BACK"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:onClick="true"
     />
 
</LinearLayout>

Now save all the open files and right click on project and run it.("don't forget to change package name and database name and table name.")
If you find any difficulty let me know and  Enjoy!!!  
   
  
   





Monday 12 September 2011

ArrayList Example in java and find the maximum and minimum element and sum of arraylist

Here is the code:
//Import the util package and classes
import java.util.ArrayList;
import java.util.Collections;
//import java.util.Comparator;
import java.util.Random;
   
    
    public class ArrayListExample{
      
        public static void main(String args[]){
      
            // constructs a new empty ArrayList   
            ArrayList<Integer> arrayList = new ArrayList<Integer>();
          
        
          
            arrayList.add(new Integer(1)); //adding value to ArrayList
            arrayList.add(new Integer(1));
            arrayList.add(new Integer(2));
            arrayList.add(new Integer(18));
            arrayList.add(new Integer(34));
            arrayList.add(new Integer(232));
            arrayList.add(new Integer(34));
            arrayList.add(new Integer(13));
            arrayList.add(new Integer(13));
            arrayList.add(new Integer(5));
            arrayList.add(new Integer(5));
           
            System.out.println("ArrayList contains " + arrayList.size() + " elements.");
           //sorting the arraylist.
           for(int i=0;i<arrayList.size();i++)
          System.out.print("  "+arrayList.get(i));
       //    Comparator comparator = Collections.reverseOrder();
     //      Collections.sort(arrayList,comparator);
           System.out.println("\nafter sorting the array:");
           for(int i=0;i<arrayList.size();i++)
               System.out.print("  "+arrayList.get(i));
        int sum=0;
//finding the sum of the arraylist
       for(int i=0;i<arrayList.size();i++)
        {
            sum+=arrayList.get(i);
        }

        System.out.println("\nSum of the list is ="+sum);

//getting the maximum and minimum element from arraylist.
        System.out.println(" minimum element is:"+Collections.min(arrayList));
        System.out.println(" maximum element is:"+Collections.max(arrayList));
        Random r = new Random();
        int h=r.nextInt(10);
        System.out.println(h);
        Collections.sort(arrayList);
        for(int i=0;i<arrayList.size();i++)
            System.out.print("  "+arrayList.get(i));
        }
      }
   
   

Source code of a simple Android application to download stuff.

Here is the source code:

package com.google.android.downloader;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.Intent;
import org.apache.http.impl.client.DefaultHttpClient;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import java.security.MessageDigest;
import android.util.Log;
import android.util.Xml;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

public class DownloaderActivity extends Activity {

    /**
     * Checks if data has been downloaded. If so, returns true. If not,
     * starts an activity to download the data and returns false. If this
     * function returns false the caller should immediately return from its
     * onCreate method. The calling activity will later be restarted
     * (using a copy of its original intent) once the data download completes.
     * @param activity The calling activity.
     * @param customText A text string that is displayed in the downloader UI.
     * @param fileConfigUrl The URL of the download configuration URL.
     * @param configVersion The version of the configuration file.
     * @param dataPath The directory on the device where we want to store the
     * data.
     * @param userAgent The user agent string to use when fetching URLs.
     * @return true if the data has already been downloaded successfully, or
     * false if the data needs to be downloaded.
     */
    public static boolean ensureDownloaded(Activity activity,
            String customText, String fileConfigUrl,
            String configVersion, String dataPath,
            String userAgent) {
        File dest = new File(dataPath);
        if (dest.exists()) {
            // Check version
            if (versionMatches(dest, configVersion)) {
                Log.i(LOG_TAG, "Versions match, no need to download.");
                return true;
            }
        }
        Intent intent = PreconditionActivityHelper.createPreconditionIntent(
                activity, DownloaderActivity.class);
        intent.putExtra(EXTRA_CUSTOM_TEXT, customText);
        intent.putExtra(EXTRA_FILE_CONFIG_URL, fileConfigUrl);
        intent.putExtra(EXTRA_CONFIG_VERSION, configVersion);
        intent.putExtra(EXTRA_DATA_PATH, dataPath);
        intent.putExtra(EXTRA_USER_AGENT, userAgent);
        PreconditionActivityHelper.startPreconditionActivityAndFinish(
                activity, intent);
        return false;
    }

    /**
     * Delete a directory and all its descendants.
     * @param directory The directory to delete
     * @return true if the directory was deleted successfully.
     */
    public static boolean deleteData(String directory) {
        return deleteTree(new File(directory), true);
    }

    private static boolean deleteTree(File base, boolean deleteBase) {
        boolean result = true;
        if (base.isDirectory()) {
            for (File child : base.listFiles()) {
                result &= deleteTree(child, true);
            }
        }
        if (deleteBase) {
            result &= base.delete();
        }
        return result;
    }

    private static boolean versionMatches(File dest, String expectedVersion) {
        Config config = getLocalConfig(dest, LOCAL_CONFIG_FILE);
        if (config != null) {
            return config.version.equals(expectedVersion);
        }
        return false;
    }

    private static Config getLocalConfig(File destPath, String configFilename) {
        File configPath = new File(destPath, configFilename);
        FileInputStream is;
        try {
            is = new FileInputStream(configPath);
        } catch (FileNotFoundException e) {
            return null;
        }
        try {
            Config config = ConfigHandler.parse(is);
            return config;
        } catch (Exception e) {
            Log.e(LOG_TAG, "Unable to read local config file", e);
            return null;
        } finally {
            quietClose(is);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = getIntent();
        requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
        setContentView(R.layout.downloader);
        getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,
                R.layout.downloader_title);
        ((TextView) findViewById(R.id.customText)).setText(
                intent.getStringExtra(EXTRA_CUSTOM_TEXT));
        mProgress = (TextView) findViewById(R.id.progress);
        mTimeRemaining = (TextView) findViewById(R.id.time_remaining);
        Button button = (Button) findViewById(R.id.cancel);
        button.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                if (mDownloadThread != null) {
                    mSuppressErrorMessages = true;
                    mDownloadThread.interrupt();
                }
            }
        });
        startDownloadThread();
    }

    private void startDownloadThread() {
        mSuppressErrorMessages = false;
        mProgress.setText("");
        mTimeRemaining.setText("");
        mDownloadThread = new Thread(new Downloader(), "Downloader");
        mDownloadThread.setPriority(Thread.NORM_PRIORITY - 1);
        mDownloadThread.start();
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSuppressErrorMessages = true;
        mDownloadThread.interrupt();
        try {
            mDownloadThread.join();
        } catch (InterruptedException e) {
            // Don't care.
        }
    }

    private void onDownloadSucceeded() {
        Log.i(LOG_TAG, "Download succeeded");
        PreconditionActivityHelper.startOriginalActivityAndFinish(this);
    }

    private void onDownloadFailed(String reason) {
        Log.e(LOG_TAG, "Download stopped: " + reason);
        String shortReason;
        int index = reason.indexOf('\n');
        if (index >= 0) {
            shortReason = reason.substring(0, index);
        } else {
            shortReason = reason;
        }
        AlertDialog alert = new Builder(this).create();
        alert.setTitle(R.string.download_activity_download_stopped);

        if (!mSuppressErrorMessages) {
            alert.setMessage(shortReason);
        }

        alert.setButton(getString(R.string.download_activity_retry),
                new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                startDownloadThread();
            }

        });
        alert.setButton2(getString(R.string.download_activity_quit),
                new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                finish();
            }

        });
        try {
            alert.show();
        } catch (WindowManager.BadTokenException e) {
            // Happens when the Back button is used to exit the activity.
            // ignore.
        }
    }

    private void onReportProgress(int progress) {
        mProgress.setText(mPercentFormat.format(progress / 10000.0));
        long now = SystemClock.elapsedRealtime();
        if (mStartTime == 0) {
            mStartTime = now;
        }
        long delta = now - mStartTime;
        String timeRemaining = getString(R.string.download_activity_time_remaining_unknown);
        if ((delta > 3 * MS_PER_SECOND) && (progress > 100)) {
            long totalTime = 10000 * delta / progress;
            long timeLeft = Math.max(0L, totalTime - delta);
            if (timeLeft > MS_PER_DAY) {
                timeRemaining = Long.toString(
                    (timeLeft + MS_PER_DAY - 1) / MS_PER_DAY)
                    + " "
                    + getString(R.string.download_activity_time_remaining_days);
            } else if (timeLeft > MS_PER_HOUR) {
                timeRemaining = Long.toString(
                        (timeLeft + MS_PER_HOUR - 1) / MS_PER_HOUR)
                        + " "
                        + getString(R.string.download_activity_time_remaining_hours);
            } else if (timeLeft > MS_PER_MINUTE) {
                timeRemaining = Long.toString(
                        (timeLeft + MS_PER_MINUTE - 1) / MS_PER_MINUTE)
                        + " "
                        + getString(R.string.download_activity_time_remaining_minutes);
            } else {
                timeRemaining = Long.toString(
                        (timeLeft + MS_PER_SECOND - 1) / MS_PER_SECOND)
                        + " "
                        + getString(R.string.download_activity_time_remaining_seconds);
            }
        }
        mTimeRemaining.setText(timeRemaining);
    }

    private void onReportVerifying() {
        mProgress.setText(getString(R.string.download_activity_verifying));
        mTimeRemaining.setText("");
    }

    private static void quietClose(InputStream is) {
        try {
            if (is != null) {
                is.close();
            }
        } catch (IOException e) {
            // Don't care.
        }
    }

    private static void quietClose(OutputStream os) {
        try {
            if (os != null) {
                os.close();
            }
        } catch (IOException e) {
            // Don't care.
        }
    }

    private static class Config {
        long getSize() {
            long result = 0;
            for(File file : mFiles) {
                result += file.getSize();
            }
            return result;
        }
        static class File {
            public File(String src, String dest, String md5, long size) {
                if (src != null) {
                    this.mParts.add(new Part(src, md5, size));
                }
                this.dest = dest;
            }
            static class Part {
                Part(String src, String md5, long size) {
                    this.src = src;
                    this.md5 = md5;
                    this.size = size;
                }
                String src;
                String md5;
                long size;
            }
            ArrayList<Part> mParts = new ArrayList<Part>();
            String dest;
            long getSize() {
                long result = 0;
                for(Part part : mParts) {
                    if (part.size > 0) {
                        result += part.size;
                    }
                }
                return result;
            }
        }
        String version;
        ArrayList<File> mFiles = new ArrayList<File>();
    }

    /**
     * <config version="">
     *   <file src="http:..." dest ="b.x" />
     *   <file dest="b.x">
     *     <part src="http:..." />
     *     ...
     *   ...
     * </config>
     *
     */
    private static class ConfigHandler extends DefaultHandler {

        public static Config parse(InputStream is) throws SAXException,
            UnsupportedEncodingException, IOException {
            ConfigHandler handler = new ConfigHandler();
            Xml.parse(is, Xml.findEncodingByName("UTF-8"), handler);
            return handler.mConfig;
        }

        private ConfigHandler() {
            mConfig = new Config();
        }

        @Override
        public void startElement(String uri, String localName, String qName,
                Attributes attributes) throws SAXException {
            if (localName.equals("config")) {
                mConfig.version = getRequiredString(attributes, "version");
            } else if (localName.equals("file")) {
                String src = attributes.getValue("", "src");
                String dest = getRequiredString(attributes, "dest");
                String md5 = attributes.getValue("", "md5");
                long size = getLong(attributes, "size", -1);
                mConfig.mFiles.add(new Config.File(src, dest, md5, size));
            } else if (localName.equals("part")) {
                String src = getRequiredString(attributes, "src");
                String md5 = attributes.getValue("", "md5");
                long size = getLong(attributes, "size", -1);
                int length = mConfig.mFiles.size();
                if (length > 0) {
                    mConfig.mFiles.get(length-1).mParts.add(
                            new Config.File.Part(src, md5, size));
                }
            }
        }

        private static String getRequiredString(Attributes attributes,
                String localName) throws SAXException {
            String result = attributes.getValue("", localName);
            if (result == null) {
                throw new SAXException("Expected attribute " + localName);
            }
            return result;
        }

        private static long getLong(Attributes attributes, String localName,
                long defaultValue) {
            String value = attributes.getValue("", localName);
            if (value == null) {
                return defaultValue;
            } else {
                return Long.parseLong(value);
            }
        }

        public Config mConfig;
    }

    private class DownloaderException extends Exception {
        public DownloaderException(String reason) {
            super(reason);
        }
    }

    private class Downloader implements Runnable {
        public void run() {
            Intent intent = getIntent();
            mFileConfigUrl = intent.getStringExtra(EXTRA_FILE_CONFIG_URL);
            mConfigVersion = intent.getStringExtra(EXTRA_CONFIG_VERSION);
            mDataPath = intent.getStringExtra(EXTRA_DATA_PATH);
            mUserAgent = intent.getStringExtra(EXTRA_USER_AGENT);

            mDataDir = new File(mDataPath);

            try {
                // Download files.
                mHttpClient = new DefaultHttpClient();
                Config config = getConfig();
                filter(config);
                persistantDownload(config);
                verify(config);
                cleanup();
                reportSuccess();
            } catch (Exception e) {
                reportFailure(e.toString() + "\n" + Log.getStackTraceString(e));
            }
        }

        private void persistantDownload(Config config)
        throws ClientProtocolException, DownloaderException, IOException {
            while(true) {
                try {
                    download(config);
                    break;
                } catch(java.net.SocketException e) {
                    if (mSuppressErrorMessages) {
                        throw e;
                    }
                } catch(java.net.SocketTimeoutException e) {
                    if (mSuppressErrorMessages) {
                        throw e;
                    }
                }
                Log.i(LOG_TAG, "Network connectivity issue, retrying.");
            }
        }

        private void filter(Config config)
        throws IOException, DownloaderException {
            File filteredFile = new File(mDataDir, LOCAL_FILTERED_FILE);
            if (filteredFile.exists()) {
                return;
            }

            File localConfigFile = new File(mDataDir, LOCAL_CONFIG_FILE_TEMP);
            HashSet<String> keepSet = new HashSet<String>();
            keepSet.add(localConfigFile.getCanonicalPath());

            HashMap<String, Config.File> fileMap =
                new HashMap<String, Config.File>();
            for(Config.File file : config.mFiles) {
                String canonicalPath =
                    new File(mDataDir, file.dest).getCanonicalPath();
                fileMap.put(canonicalPath, file);
            }
            recursiveFilter(mDataDir, fileMap, keepSet, false);
            touch(filteredFile);
        }

        private void touch(File file) throws FileNotFoundException {
            FileOutputStream os = new FileOutputStream(file);
            quietClose(os);
        }

        private boolean recursiveFilter(File base,
                HashMap<String, Config.File> fileMap,
                HashSet<String> keepSet, boolean filterBase)
        throws IOException, DownloaderException {
            boolean result = true;
            if (base.isDirectory()) {
                for (File child : base.listFiles()) {
                    result &= recursiveFilter(child, fileMap, keepSet, true);
                }
            }
            if (filterBase) {
                if (base.isDirectory()) {
                    if (base.listFiles().length == 0) {
                        result &= base.delete();
                    }
                } else {
                    if (!shouldKeepFile(base, fileMap, keepSet)) {
                        result &= base.delete();
                    }
                }
            }
            return result;
        }

        private boolean shouldKeepFile(File file,
                HashMap<String, Config.File> fileMap,
                HashSet<String> keepSet)
        throws IOException, DownloaderException {
            String canonicalPath = file.getCanonicalPath();
            if (keepSet.contains(canonicalPath)) {
                return true;
            }
            Config.File configFile = fileMap.get(canonicalPath);
            if (configFile == null) {
                return false;
            }
            return verifyFile(configFile, false);
        }

        private void reportSuccess() {
            mHandler.sendMessage(
                    Message.obtain(mHandler, MSG_DOWNLOAD_SUCCEEDED));
        }

        private void reportFailure(String reason) {
            mHandler.sendMessage(
                    Message.obtain(mHandler, MSG_DOWNLOAD_FAILED, reason));
        }

        private void reportProgress(int progress) {
            mHandler.sendMessage(
                    Message.obtain(mHandler, MSG_REPORT_PROGRESS, progress, 0));
        }

        private void reportVerifying() {
            mHandler.sendMessage(
                    Message.obtain(mHandler, MSG_REPORT_VERIFYING));
        }

        private Config getConfig() throws DownloaderException,
            ClientProtocolException, IOException, SAXException {
            Config config = null;
            if (mDataDir.exists()) {
                config = getLocalConfig(mDataDir, LOCAL_CONFIG_FILE_TEMP);
                if ((config == null)
                        || !mConfigVersion.equals(config.version)) {
                    if (config == null) {
                        Log.i(LOG_TAG, "Couldn't find local config.");
                    } else {
                        Log.i(LOG_TAG, "Local version out of sync. Wanted " +
                                mConfigVersion + " but have " + config.version);
                    }
                    config = null;
                }
            } else {
                Log.i(LOG_TAG, "Creating directory " + mDataPath);
                mDataDir.mkdirs();
                mDataDir.mkdir();
                if (!mDataDir.exists()) {
                    throw new DownloaderException(
                            "Could not create the directory " + mDataPath);
                }
            }
            if (config == null) {
                File localConfig = download(mFileConfigUrl,
                        LOCAL_CONFIG_FILE_TEMP);
                InputStream is = new FileInputStream(localConfig);
                try {
                    config = ConfigHandler.parse(is);
                } finally {
                    quietClose(is);
                }
                if (! config.version.equals(mConfigVersion)) {
                    throw new DownloaderException(
                            "Configuration file version mismatch. Expected " +
                            mConfigVersion + " received " +
                            config.version);
                }
            }
            return config;
        }

        private void noisyDelete(File file) throws IOException {
            if (! file.delete() ) {
                throw new IOException("could not delete " + file);
            }
        }

        private void download(Config config) throws DownloaderException,
            ClientProtocolException, IOException {
            mDownloadedSize = 0;
            getSizes(config);
            Log.i(LOG_TAG, "Total bytes to download: "
                    + mTotalExpectedSize);
            for(Config.File file : config.mFiles) {
                downloadFile(file);
            }
        }

        private void downloadFile(Config.File file) throws DownloaderException,
                FileNotFoundException, IOException, ClientProtocolException {
            boolean append = false;
            File dest = new File(mDataDir, file.dest);
            long bytesToSkip = 0;
            if (dest.exists() && dest.isFile()) {
                append = true;
                bytesToSkip = dest.length();
                mDownloadedSize += bytesToSkip;
            }
            FileOutputStream os = null;
            long offsetOfCurrentPart = 0;
            try {
                for(Config.File.Part part : file.mParts) {
                    // The part.size==0 check below allows us to download
                    // zero-length files.
                    if ((part.size > bytesToSkip) || (part.size == 0)) {
                        MessageDigest digest = null;
                        if (part.md5 != null) {
                            digest = createDigest();
                            if (bytesToSkip > 0) {
                                FileInputStream is = openInput(file.dest);
                                try {
                                    is.skip(offsetOfCurrentPart);
                                    readIntoDigest(is, bytesToSkip, digest);
                                } finally {
                                    quietClose(is);
                                }
                            }
                        }
                        if (os == null) {
                            os = openOutput(file.dest, append);
                        }
                        downloadPart(part.src, os, bytesToSkip,
                                part.size, digest);
                        if (digest != null) {
                            String hash = getHash(digest);
                            if (!hash.equalsIgnoreCase(part.md5)) {
                                Log.e(LOG_TAG, "web MD5 checksums don't match. "
                                        + part.src + "\nExpected "
                                        + part.md5 + "\n     got " + hash);
                                quietClose(os);
                                dest.delete();
                                throw new DownloaderException(
                                      "Received bad data from web server");
                            } else {
                               Log.i(LOG_TAG, "web MD5 checksum matches.");
                            }
                        }
                    }
                    bytesToSkip -= Math.min(bytesToSkip, part.size);
                    offsetOfCurrentPart += part.size;
                }
            } finally {
                quietClose(os);
            }
        }

        private void cleanup() throws IOException {
            File filtered = new File(mDataDir, LOCAL_FILTERED_FILE);
            noisyDelete(filtered);
            File tempConfig = new File(mDataDir, LOCAL_CONFIG_FILE_TEMP);
            File realConfig = new File(mDataDir, LOCAL_CONFIG_FILE);
            tempConfig.renameTo(realConfig);
        }

        private void verify(Config config) throws DownloaderException,
        ClientProtocolException, IOException {
            Log.i(LOG_TAG, "Verifying...");
            String failFiles = null;
            for(Config.File file : config.mFiles) {
                if (! verifyFile(file, true) ) {
                    if (failFiles == null) {
                        failFiles = file.dest;
                    } else {
                        failFiles += " " + file.dest;
                    }
                }
            }
            if (failFiles != null) {
                throw new DownloaderException(
                        "Possible bad SD-Card. MD5 sum incorrect for file(s) "
                        + failFiles);
            }
        }

        private boolean verifyFile(Config.File file, boolean deleteInvalid)
                throws FileNotFoundException, DownloaderException, IOException {
            Log.i(LOG_TAG, "verifying " + file.dest);
            reportVerifying();
            File dest = new File(mDataDir, file.dest);
            if (! dest.exists()) {
                Log.e(LOG_TAG, "File does not exist: " + dest.toString());
                return false;
            }
            long fileSize = file.getSize();
            long destLength = dest.length();
            if (fileSize != destLength) {
                Log.e(LOG_TAG, "Length doesn't match. Expected " + fileSize
                        + " got " + destLength);
                if (deleteInvalid) {
                    dest.delete();
                    return false;
                }
            }
            FileInputStream is = new FileInputStream(dest);
            try {
                for(Config.File.Part part : file.mParts) {
                    if (part.md5 == null) {
                        continue;
                    }
                    MessageDigest digest = createDigest();
                    readIntoDigest(is, part.size, digest);
                    String hash = getHash(digest);
                    if (!hash.equalsIgnoreCase(part.md5)) {
                        Log.e(LOG_TAG, "MD5 checksums don't match. " +
                                part.src + " Expected "
                                + part.md5 + " got " + hash);
                        if (deleteInvalid) {
                            quietClose(is);
                            dest.delete();
                        }
                        return false;
                    }
                }
            } finally {
                quietClose(is);
            }
            return true;
        }

        private void readIntoDigest(FileInputStream is, long bytesToRead,
                MessageDigest digest) throws IOException {
            while(bytesToRead > 0) {
                int chunkSize = (int) Math.min(mFileIOBuffer.length,
                        bytesToRead);
                int bytesRead = is.read(mFileIOBuffer, 0, chunkSize);
                if (bytesRead < 0) {
                    break;
                }
                updateDigest(digest, bytesRead);
                bytesToRead -= bytesRead;
            }
        }

        private MessageDigest createDigest() throws DownloaderException {
            MessageDigest digest;
            try {
                digest = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                throw new DownloaderException("Couldn't create MD5 digest");
            }
            return digest;
        }

        private void updateDigest(MessageDigest digest, int bytesRead) {
            if (bytesRead == mFileIOBuffer.length) {
                digest.update(mFileIOBuffer);
            } else {
                // Work around an awkward API: Create a
                // new buffer with just the valid bytes
                byte[] temp = new byte[bytesRead];
                System.arraycopy(mFileIOBuffer, 0,
                        temp, 0, bytesRead);
                digest.update(temp);
            }
        }

        private String getHash(MessageDigest digest) {
            StringBuilder builder = new StringBuilder();
            for(byte b : digest.digest()) {
                builder.append(Integer.toHexString((b >> 4) & 0xf));
                builder.append(Integer.toHexString(b & 0xf));
            }
            return builder.toString();
        }


        /**
         * Ensure we have sizes for all the items.
         * @param config
         * @throws ClientProtocolException
         * @throws IOException
         * @throws DownloaderException
         */
        private void getSizes(Config config)
            throws ClientProtocolException, IOException, DownloaderException {
            for (Config.File file : config.mFiles) {
                for(Config.File.Part part : file.mParts) {
                    if (part.size < 0) {
                        part.size = getSize(part.src);
                    }
                }
            }
            mTotalExpectedSize = config.getSize();
        }

        private long getSize(String url) throws ClientProtocolException,
            IOException {
            url = normalizeUrl(url);
            Log.i(LOG_TAG, "Head " + url);
            HttpHead httpGet = new HttpHead(url);
            HttpResponse response = mHttpClient.execute(httpGet);
            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                throw new IOException("Unexpected Http status code "
                    + response.getStatusLine().getStatusCode());
            }
            Header[] clHeaders = response.getHeaders("Content-Length");
            if (clHeaders.length > 0) {
                Header header = clHeaders[0];
                return Long.parseLong(header.getValue());
            }
            return -1;
        }

        private String normalizeUrl(String url) throws MalformedURLException {
            return (new URL(new URL(mFileConfigUrl), url)).toString();
        }

        private InputStream get(String url, long startOffset,
                long expectedLength)
            throws ClientProtocolException, IOException {
            url = normalizeUrl(url);
            Log.i(LOG_TAG, "Get " + url);

            mHttpGet = new HttpGet(url);
            int expectedStatusCode = HttpStatus.SC_OK;
            if (startOffset > 0) {
                String range = "bytes=" + startOffset + "-";
                if (expectedLength >= 0) {
                    range += expectedLength-1;
                }
                Log.i(LOG_TAG, "requesting byte range " + range);
                mHttpGet.addHeader("Range", range);
                expectedStatusCode = HttpStatus.SC_PARTIAL_CONTENT;
            }
            HttpResponse response = mHttpClient.execute(mHttpGet);
            long bytesToSkip = 0;
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != expectedStatusCode) {
                if ((statusCode == HttpStatus.SC_OK)
                        && (expectedStatusCode
                                == HttpStatus.SC_PARTIAL_CONTENT)) {
                    Log.i(LOG_TAG, "Byte range request ignored");
                    bytesToSkip = startOffset;
                } else {
                    throw new IOException("Unexpected Http status code "
                            + statusCode + " expected "
                            + expectedStatusCode);
                }
            }
            HttpEntity entity = response.getEntity();
            InputStream is = entity.getContent();
            if (bytesToSkip > 0) {
                is.skip(bytesToSkip);
            }
            return is;
        }

        private File download(String src, String dest)
            throws DownloaderException, ClientProtocolException, IOException {
            File destFile = new File(mDataDir, dest);
            FileOutputStream os = openOutput(dest, false);
            try {
                downloadPart(src, os, 0, -1, null);
            } finally {
                os.close();
            }
            return destFile;
        }

        private void downloadPart(String src, FileOutputStream os,
                long startOffset, long expectedLength, MessageDigest digest)
            throws ClientProtocolException, IOException, DownloaderException {
            boolean lengthIsKnown = expectedLength >= 0;
            if (startOffset < 0) {
                throw new IllegalArgumentException("Negative startOffset:"
                        + startOffset);
            }
            if (lengthIsKnown && (startOffset > expectedLength)) {
                throw new IllegalArgumentException(
                        "startOffset > expectedLength" + startOffset + " "
                        + expectedLength);
            }
            InputStream is = get(src, startOffset, expectedLength);
            try {
                long bytesRead = downloadStream(is, os, digest);
                if (lengthIsKnown) {
                    long expectedBytesRead = expectedLength - startOffset;
                    if (expectedBytesRead != bytesRead) {
                        Log.e(LOG_TAG, "Bad file transfer from server: " + src
                                + " Expected " + expectedBytesRead
                                + " Received " + bytesRead);
                        throw new DownloaderException(
                                "Incorrect number of bytes received from server");
                    }
                }
            } finally {
                is.close();
                mHttpGet = null;
            }
        }

        private FileOutputStream openOutput(String dest, boolean append)
            throws FileNotFoundException, DownloaderException {
            File destFile = new File(mDataDir, dest);
            File parent = destFile.getParentFile();
            if (! parent.exists()) {
                parent.mkdirs();
            }
            if (! parent.exists()) {
                throw new DownloaderException("Could not create directory "
                        + parent.toString());
            }
            FileOutputStream os = new FileOutputStream(destFile, append);
            return os;
        }

        private FileInputStream openInput(String src)
            throws FileNotFoundException, DownloaderException {
            File srcFile = new File(mDataDir, src);
            File parent = srcFile.getParentFile();
            if (! parent.exists()) {
                parent.mkdirs();
            }
            if (! parent.exists()) {
                throw new DownloaderException("Could not create directory "
                        + parent.toString());
            }
            return new FileInputStream(srcFile);
        }

        private long downloadStream(InputStream is, FileOutputStream os,
                MessageDigest digest)
                throws DownloaderException, IOException {
            long totalBytesRead = 0;
            while(true){
                if (Thread.interrupted()) {
                    Log.i(LOG_TAG, "downloader thread interrupted.");
                    mHttpGet.abort();
                    throw new DownloaderException("Thread interrupted");
                }
                int bytesRead = is.read(mFileIOBuffer);
                if (bytesRead < 0) {
                    break;
                }
                if (digest != null) {
                    updateDigest(digest, bytesRead);
                }
                totalBytesRead += bytesRead;
                os.write(mFileIOBuffer, 0, bytesRead);
                mDownloadedSize += bytesRead;
                int progress = (int) (Math.min(mTotalExpectedSize,
                        mDownloadedSize * 10000 /
                        Math.max(1, mTotalExpectedSize)));
                if (progress != mReportedProgress) {
                    mReportedProgress = progress;
                    reportProgress(progress);
                }
            }
            return totalBytesRead;
        }

        private DefaultHttpClient mHttpClient;
        private HttpGet mHttpGet;
        private String mFileConfigUrl;
        private String mConfigVersion;
        private String mDataPath;
        private File mDataDir;
        private String mUserAgent;
        private long mTotalExpectedSize;
        private long mDownloadedSize;
        private int mReportedProgress;
        private final static int CHUNK_SIZE = 32 * 1024;
        byte[] mFileIOBuffer = new byte[CHUNK_SIZE];
    }

    private final static String LOG_TAG = "Downloader";
    private TextView mProgress;
    private TextView mTimeRemaining;
    private final DecimalFormat mPercentFormat = new DecimalFormat("0.00 %");
    private long mStartTime;
    private Thread mDownloadThread;
    private boolean mSuppressErrorMessages;

    private final static long MS_PER_SECOND = 1000;
    private final static long MS_PER_MINUTE = 60 * 1000;
    private final static long MS_PER_HOUR = 60 * 60 * 1000;
    private final static long MS_PER_DAY = 24 * 60 * 60 * 1000;

    private final static String LOCAL_CONFIG_FILE = ".downloadConfig";
    private final static String LOCAL_CONFIG_FILE_TEMP = ".downloadConfig_temp";
    private final static String LOCAL_FILTERED_FILE = ".downloadConfig_filtered";
    private final static String EXTRA_CUSTOM_TEXT = "DownloaderActivity_custom_text";
    private final static String EXTRA_FILE_CONFIG_URL = "DownloaderActivity_config_url";
    private final static String EXTRA_CONFIG_VERSION = "DownloaderActivity_config_version";
    private final static String EXTRA_DATA_PATH = "DownloaderActivity_data_path";
    private final static String EXTRA_USER_AGENT = "DownloaderActivity_user_agent";

    private final static int MSG_DOWNLOAD_SUCCEEDED = 0;
    private final static int MSG_DOWNLOAD_FAILED = 1;
    private final static int MSG_REPORT_PROGRESS = 2;
    private final static int MSG_REPORT_VERIFYING = 3;

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MSG_DOWNLOAD_SUCCEEDED:
                onDownloadSucceeded();
                break;
            case MSG_DOWNLOAD_FAILED:
                onDownloadFailed((String) msg.obj);
                break;
            case MSG_REPORT_PROGRESS:
                onReportProgress(msg.arg1);
                break;
            case MSG_REPORT_VERIFYING:
                onReportVerifying();
                break;
            default:
                throw new IllegalArgumentException("Unknown message id "
                        + msg.what);
            }
        }

    };

}

Any comment: