dev.memento.MementoBrowser.java Source code

Java tutorial

Introduction

Here is the source code for dev.memento.MementoBrowser.java

Source

/**
 * MementoBrowser.java
 * 
 * Copyright 2010 Frank McCown
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  
 *  This is the Memento Browser activity which houses a customized web browser for
 *  performing http queries using Memento.
 *  
 *  Learn more about Memento:
 *  http://mementoweb.org/
 */

package dev.memento;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.DialogInterface.OnDismissListener;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebBackForwardList;
import android.webkit.WebChromeClient;
import android.webkit.WebHistoryItem;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

public class MementoBrowser extends Activity {

    public static final String LOG_TAG = "MementoBrowser_tag";

    static final int DIALOG_DATE = 0;
    static final int DIALOG_ERROR = 1;
    static final int DIALOG_MEMENTO_DATES = 2;
    static final int DIALOG_MEMENTO_YEARS = 3;
    static final int DIALOG_HELP = 4;

    private String mDefaultTimegateUri;
    private String[] mTimegateUris;

    private WebView mWebview;

    private TextView mLocation;

    private Button mNextButton;
    private Button mPreviousButton;

    // For showing the page loading progress
    private ProgressBar mProgressBar;

    private TextView mDateChosenButton;
    private TextView mDateDisplayedView;
    private SimpleDateTime mDateChosen;
    private SimpleDateTime mDateDisplayed;
    private SimpleDateTime mToday;

    private TimeBundle mTimeBundle;
    private TimeMap mTimeMap;
    private Memento mFirstMemento;
    private Memento mLastMemento;
    private MementoList mMementos;

    private final int MAX_NUM_MEMENTOS_IN_LIST = 20;

    // Used when selecting a memento
    int mSelectedYear = 0;

    // Used in http requests
    public String mUserAgent;

    // Hold favicons for certain websites.  This can be removed when we figure out
    // how to access the favicons for the WebView.  This is an outstanding problem
    // that I've solicited for help on StackOverflow:
    // http://stackoverflow.com/questions/3462582/display-the-android-webviews-favicon
    private HashMap<String, Bitmap> mFavicons;

    // The original URL that we are visiting
    private String mOriginalUrl;

    // The URL currently displayed in the browser
    private String mCurrentUrl;

    private String mPageTitle;

    // private String mRedirectUrl;

    private CharSequence mErrorMessage;

    // Need handler for callbacks to the UI thread
    final Handler mHandler = new Handler();

    // Create runnable for posting
    final Runnable mUpdateResults = new Runnable() {
        public void run() {
            updateResultsInUi();
        }
    };

    final Runnable mUpdateNextPrev = new Runnable() {
        public void run() {

            if (mErrorMessage == null) {
                setEnableForNextPrevButtons();
            } else {
                mNextButton.setEnabled(false);
                mPreviousButton.setEnabled(false);
                displayError(mErrorMessage.toString());
            }

            // Since making requests are over, hide progress bar
            // BUT... the page may still be downloading, so don't hide
            //mProgressBar.setVisibility(View.GONE);           
        }

    };

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        setContentView(R.layout.main);

        mUserAgent = getApplicationContext().getText(R.string.user_agent).toString();

        // Set the date and time format
        SimpleDateTime.mDateFormat = android.text.format.DateFormat.getDateFormat(getApplicationContext());
        SimpleDateTime.mTimeFormat = android.text.format.DateFormat.getTimeFormat(getApplicationContext());

        mDateChosenButton = (Button) findViewById(R.id.dateChosen);
        mDateDisplayedView = (TextView) findViewById(R.id.dateDisplayed);

        // Launch the DatePicker dialog box
        mDateChosenButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                showDialog(DIALOG_DATE);
            }
        });

        // Set the current date
        mToday = new SimpleDateTime();

        // Handle change in orientation gracefully
        if (savedInstanceState == null) {
            mCurrentUrl = getApplicationContext().getText(R.string.homepage).toString();
            mOriginalUrl = mCurrentUrl;

            setChosenDate(mToday);
            setDisplayedDate(mToday);

            mMementos = new MementoList();
        } else {
            mCurrentUrl = savedInstanceState.getString("mCurrentUrl");
            mDateChosen = (SimpleDateTime) savedInstanceState.getSerializable("mDateChosen");
            mDateDisplayed = (SimpleDateTime) savedInstanceState.getSerializable("mDateDisplayed");

            setChosenDate(mDateChosen);
            setDisplayedDate(mDateDisplayed);
        }

        mTimegateUris = getResources().getStringArray(R.array.listTimegates);

        // Add some favicons of web archives used by proxy server
        mFavicons = new HashMap<String, Bitmap>();
        mFavicons.put("ia", BitmapFactory.decodeResource(getResources(), R.drawable.ia_favicon));
        mFavicons.put("webcite", BitmapFactory.decodeResource(getResources(), R.drawable.webcite_favicon));
        mFavicons.put("national-archives",
                BitmapFactory.decodeResource(getResources(), R.drawable.national_archives_favicon));

        mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
        mProgressBar.setVisibility(View.GONE);

        mLocation = (TextView) findViewById(R.id.locationEditText);
        mLocation.setSelectAllOnFocus(true);

        mLocation.setOnFocusChangeListener(new OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                // Replace title with URL when focus is lost
                if (hasFocus)
                    mLocation.setText(mCurrentUrl);
                else if (mPageTitle.length() > 0)
                    mLocation.setText(mPageTitle);
            }
        });

        mLocation.setOnKeyListener(new OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                //Log.d(LOG_TAG, "keyCode = " + keyCode + "   event = " + event.getAction());

                // Go to URL if user presses Go button
                if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {

                    mOriginalUrl = fixUrl(mLocation.getText().toString());

                    // Access live version if date is today or in the future
                    if (mToday.compareTo(mDateChosen) <= 0) {
                        Log.d(LOG_TAG, "Browsing to " + mOriginalUrl);
                        mWebview.loadUrl(mOriginalUrl);

                        // Clear since we are visiting a different page in the present
                        mMementos.clear();
                    } else
                        makeMementoRequests();

                    // Hide the virtual keyboard
                    ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
                            .hideSoftInputFromWindow(mLocation.getWindowToken(), 0);
                    return true;
                }

                return false;
            }

        });

        // TEST        
        /*
        Context context = getBaseContext();
        Drawable image = getImage(context, "http://web.archive.org/favicon.ico");
        if (image == null) {
           System.out.println("image is null !!");
        }
        else {
           //image.setBounds(5, 5, 5, 5);
         //ImageView imgView = new ImageView(context);
         //ImageView imgView = (ImageView)findViewById(R.id.imagetest);
         //imgView.setImageDrawable(image);
           mLocation.setCompoundDrawablesWithIntrinsicBounds(image, null, null, null);
        }
        */

        mNextButton = (Button) findViewById(R.id.next);
        mNextButton.setEnabled(false);
        mNextButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // Advance to next Memento

                // This could happen if the index has not been set yet
                if (mMementos.getCurrentIndex() < 0) {
                    int index = mMementos.getIndex(mDateDisplayed);
                    if (index < 0) {
                        Log.d(LOG_TAG, "Could not find next Memento after date " + mDateDisplayed);
                        return;
                    } else
                        mMementos.setCurrentIndex(index);
                }

                // Locate the next Memento in the list
                Memento nextMemento = mMementos.getNext();

                if (nextMemento == null) {
                    Log.d(LOG_TAG, "Still could not find next Memento!");
                    Log.d(LOG_TAG, "Current index is " + mMementos.getCurrentIndex());
                } else {
                    SimpleDateTime date = nextMemento.getDateTime();
                    setChosenDate(nextMemento.getDateTime());
                    showToast("Time travelling to next Memento on " + mDateChosen.dateFormatted());

                    mDateDisplayed = date;

                    String redirectUrl = nextMemento.getUrl();
                    Log.d(LOG_TAG, "Sending browser to " + redirectUrl);
                    mWebview.loadUrl(redirectUrl);

                    // Just in case it wasn't already enabled
                    mPreviousButton.setEnabled(true);

                    // If this is the last memento, disable button
                    if (mMementos.isLast(date))
                        mNextButton.setEnabled(false);
                }
            }
        });
        mPreviousButton = (Button) findViewById(R.id.previous);
        mPreviousButton.setEnabled(false);
        mPreviousButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // Advance to previous Memento

                // This could happen if the index has not been set yet
                if (mMementos.getCurrentIndex() < 0) {
                    int index = mMementos.getIndex(mDateDisplayed);
                    if (index < 0) {
                        Log.d(LOG_TAG, "Could not find previous Memento before date " + mDateDisplayed);
                        return;
                    } else
                        mMementos.setCurrentIndex(index);
                }

                // Locate the prev Memento in the list
                Memento prevMemento = mMementos.getPrevious();

                if (prevMemento == null) {
                    Log.d(LOG_TAG, "Still could not find previous Memento!");
                    Log.d(LOG_TAG, "Current index is " + mMementos.getCurrentIndex());
                } else {
                    SimpleDateTime date = prevMemento.getDateTime();
                    setChosenDate(date);
                    showToast("Time travelling to previous Memento on " + mDateChosen.dateFormatted());

                    mDateDisplayed = date;

                    String redirectUrl = prevMemento.getUrl();
                    Log.d(LOG_TAG, "Sending browser to " + redirectUrl);
                    mWebview.loadUrl(redirectUrl);

                    // Just in case it wasn't already enabled
                    mNextButton.setEnabled(true);

                    // If this is the first memento, disable button
                    if (mMementos.isFirst(date))
                        mPreviousButton.setEnabled(false);
                }
            }
        });

        mWebview = (WebView) findViewById(R.id.webview);
        mWebview.setWebViewClient(new MementoWebViewClient());
        mWebview.setWebChromeClient(new MementoWebChromClient());
        mWebview.getSettings().setJavaScriptEnabled(true);
        mWebview.loadUrl(mCurrentUrl);

        mWebview.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {

                // Set focus here so focus is removed from the location text field
                // which will change the URL into the page's title.
                // There really should be a better way to do this, but it's a general
                // problem that other developers have ran into as well:
                // http://groups.google.com/group/android-developers/browse_thread/thread/9d1681a01f05e782?pli=1

                if (mLocation.hasFocus()) {
                    mWebview.requestFocus();
                    return true;
                }

                // Hide the virtual keyboard
                ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
                        .hideSoftInputFromWindow(mLocation.getWindowToken(), 0);

                return false;
            }
        });

        //testMementos();
    }

    @Override
    public void onResume() {
        super.onResume();

        // Get default timegate that was selected in the settings
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        mDefaultTimegateUri = prefs.getString("defaultTimegate", mTimegateUris[0]);

        Log.d(LOG_TAG, "mDefaultTimegateUri = " + mDefaultTimegateUri);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.d(LOG_TAG, "-- onSaveInstanceState");

        outState.putSerializable("mDateChosen", mDateChosen);
        outState.putSerializable("mDateDisplayed", mDateDisplayed);
        outState.putString("mCurrentUrl", mCurrentUrl);
        outState.putString("mOriginalUrl", mOriginalUrl);
        outState.putString("mPageTitle", mPageTitle);
        Log.d(LOG_TAG, "** Num of mementos: " + mMementos.size());
        outState.putSerializable("mMementos", mMementos);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.d(LOG_TAG, "-- onRestoreInstanceState");

        mOriginalUrl = savedInstanceState.getString("mOriginalUrl");
        mPageTitle = savedInstanceState.getString("mPageTitle");
        mMementos = (MementoList) savedInstanceState.getSerializable("mMementos");
        Log.d(LOG_TAG, "** Num of mementos: " + mMementos.size());

        if (mMementos != null) {
            mFirstMemento = mMementos.getFirst();
            mLastMemento = mMementos.getLast();
        }

        // Only enable buttons if viewing Mementos
        if (!mToday.equalsDate(mDateChosen))
            setEnableForNextPrevButtons();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.options_menu, menu);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);

        // Only enable the "List" menu if there are Memento dates to display
        MenuItem item = menu.findItem(R.id.menu_list);
        item.setEnabled(mMementos.size() != 0);

        // Only enable "Return to Present" if we are not viewing the present
        item = menu.findItem(R.id.menu_off);
        item.setEnabled(!mToday.equalsDate(mDateChosen));

        return true;
    }

    private void returnToPresent() {

        mToday = new SimpleDateTime();
        setChosenDate(mToday);
        setDisplayedDate(mToday);
        showToast("Returning to the present.");
        mNextButton.setEnabled(false);
        mPreviousButton.setEnabled(false);

        // It's possible if the user was going back a page to 
        // be viewing an archived page.  This is just a hack for IA pages,
        // so a more comprehensive solution should be implemented.
        if (mOriginalUrl.startsWith("http://web.archive.org/"))
            mOriginalUrl = convertIaUrlBack(mOriginalUrl);

        mWebview.loadUrl(mOriginalUrl);
        mMementos.setCurrentIndex(-1);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_off:
            // Turn off Memento and return to the present           
            returnToPresent();
            return true;

        case R.id.menu_settings:

            startActivityForResult(new Intent(this, Settings.class), 0);

            return true;

        case R.id.menu_list:

            // We don't want to overwhelm the user with too many choices
            if (mMementos.size() > MAX_NUM_MEMENTOS_IN_LIST)
                showDialog(DIALOG_MEMENTO_YEARS);
            else
                showDialog(DIALOG_MEMENTO_DATES);

            return true;

        case R.id.menu_help:
            // Open a browser to the project's Help page
            String url = getApplicationContext().getText(R.string.help_page).toString();
            Uri uri = Uri.parse(url);
            startActivity(new Intent(Intent.ACTION_VIEW, uri));

            return true;
        }

        return false;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.d(LOG_TAG, "--onActivityResult requestCode = " + requestCode);

        if (requestCode == 0) {
            // If the date/time settings were changed            
            SimpleDateTime.mDateFormat = android.text.format.DateFormat.getDateFormat(getApplicationContext());
            SimpleDateTime.mTimeFormat = android.text.format.DateFormat.getTimeFormat(getApplicationContext());

            refreshChosenDate();
            refreshDisplayedDate();
        }
    }

    public Object fetchUrl(String address) throws MalformedURLException, IOException {
        URL url = new URL(address);
        Object content = url.getContent();
        return content;
    }

    private void setEnableForNextPrevButtons() {

        // Making these use equalsDate instead of equals could mean that the buttons
        // are disabled when there are multiple mementos with the same date at the
        // front or back of the list, but there's no great way to set this otherwise
        // since we are dealing with date granularity at times.

        // Make prev and next enabled only if we're not viewing the first and last mementos
        if (mFirstMemento != null)
            mPreviousButton.setEnabled(!mFirstMemento.getDateTime().equals(mDateDisplayed));
        else
            Log.d(LOG_TAG, "mFirstMemento is null !!");

        if (mLastMemento != null)
            mNextButton.setEnabled(!mLastMemento.getDateTime().equals(mDateDisplayed));
        else
            Log.d(LOG_TAG, "mLastMemento is null !!");
    }

    private String fixUrl(String url) {
        if (!url.startsWith("http://") && !url.startsWith("https://"))
            url = "http://" + url;
        return url;
    }

    private void setChosenDate(SimpleDateTime date) {
        mDateChosen = date;
        mDateChosenButton.setText(mDateChosen.dateFormatted());
    }

    /**
     * Set the chosen (request) date button.
     * @param day
     * @param month
     * @param year
     */
    private void setChosenDate(int day, int month, int year) {
        Log.d(LOG_TAG, "setChosenDate: " + day + ":" + month + ":" + year);
        setChosenDate(new SimpleDateTime(day, month, year));
    }

    private void refreshChosenDate() {
        mDateChosenButton.setText(mDateChosen.dateFormatted());
    }

    private void setDisplayedDate(SimpleDateTime date) {
        mDateDisplayed = date;
        mDateDisplayedView.setText(mDateDisplayed.dateFormatted());
    }

    private void refreshDisplayedDate() {
        mDateDisplayedView.setText(mDateDisplayed.dateFormatted());
    }

    /**
     * Start http requests on a new thread to retrieve the Mementos for the current URL. 
     */
    protected void makeMementoRequests() {

        // Ideally we should show some type of progress bar, but probably not the browsers'
        // which is downloading pages.
        //mProgressBar.setVisibility(View.VISIBLE);

        // Fire off a thread to do some work that we shouldn't do directly in the UI thread
        Thread t = new Thread() {
            public void run() {

                makeHttpRequests(mOriginalUrl);

                // Enable or disable Next and Previous buttons
                mHandler.post(mUpdateNextPrev);
            }
        };
        t.start();
    }

    /**
     * Ran from other threads to update the UI.  Shows a dialog box if there's an error. 
     */
    private void updateResultsInUi() {

        // Back in the UI thread        
        if (mErrorMessage == null) {

            // If we couldn't load the exact requested date, show the date 
            // that's being loaded.
            if (!mDateDisplayed.equalsDate(mDateChosen)) {
                showToast("Closest available is " + mDateDisplayed.dateFormatted());

                this.refreshDisplayedDate();
            }
        } else
            showDialog(DIALOG_ERROR);
    }

    /**
     * Make http requests using the Memento protocol to obtain a Memento or list
     * of Mementos. 
     */
    private void makeHttpRequests(String initUrl) {

        // Contact Memento proxy with chosen Accept-Datetime:
        // http://mementoproxy.lanl.gov/aggr/timegate/http://example.com/
        // Accept-Datetime: Tue, 24 Jul 2001 15:45:04 GMT             

        HttpClient httpclient = new DefaultHttpClient();

        // Disable automatic redirect handling so we can process the 302 ourself 
        httpclient.getParams().setParameter(ClientPNames.HANDLE_REDIRECTS, false);

        String url = mDefaultTimegateUri + initUrl;
        HttpGet httpget = new HttpGet(url);

        // Change the request date to 23:00:00 if this is the first memento.
        // Otherwise we'll be out of range.

        String acceptDatetime;

        if (mFirstMemento != null && mFirstMemento.getDateTime().equals(mDateChosen)) {
            Log.d(LOG_TAG, "Changing chosen time to 23:59 since datetime matches first Memento.");
            SimpleDateTime dt = new SimpleDateTime(mDateChosen);
            dt.setToLastHour();
            acceptDatetime = dt.longDateFormatted();
        } else {
            acceptDatetime = mDateChosen.longDateFormatted();
        }

        httpget.setHeader("Accept-Datetime", acceptDatetime);
        httpget.setHeader("User-Agent", mUserAgent);

        //Log.d(LOG_TAG, getHeadersAsString(response.getAllHeaders()));

        Log.d(LOG_TAG, "Accessing: " + httpget.getURI());
        Log.d(LOG_TAG, "Accept-Datetime: " + acceptDatetime);

        HttpResponse response = null;
        try {
            response = httpclient.execute(httpget);

            Log.d(LOG_TAG, "Response code = " + response.getStatusLine());

            //Log.d(LOG_TAG, getHeadersAsString(response.getAllHeaders()));
        } catch (ClientProtocolException e) {
            mErrorMessage = "Unable to contact proxy server. ClientProtocolException exception.";
            Log.e(LOG_TAG, getExceptionStackTraceAsString(e));
            return;
        } catch (IOException e) {
            mErrorMessage = "Unable to contact proxy server. IOException exception.";
            Log.e(LOG_TAG, getExceptionStackTraceAsString(e));
            return;
        } finally {
            // Deallocate all system resources
            httpclient.getConnectionManager().shutdown();
        }

        // Get back:
        // 300 (TCN: list with multiple Mementos to choose from)
        // or 302 (TCN: choice) 
        // or 404 (no Mementos for this URL)
        // or 406 (TCN: list with only first and last Mementos)

        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 300) {
            // TODO: Implement.  Right now the lanl proxy doesn't appear to be returning this
            // code, so let's just ignore it for now.
            Log.d(LOG_TAG, "Pick a URL from list");
        } else if (statusCode == 302) {
            // Send browser to Location URL
            // Note that the date/time of this memento is not given in the Location.

            Header[] headers = response.getHeaders("Location");
            if (headers.length == 0) {
                mErrorMessage = "Sorry, but there was an unexpected error that will "
                        + "prevent the Memento from being displayed. Try again in 5 minutes.";
                Log.e(LOG_TAG, "Error: Location header not found in response headers.");
            } else {
                String redirectUrl = headers[0].getValue();

                // Find out the datetime of this resource
                /*SimpleDateTime d = getResourceDatetime(redirectUrl);
                if (d != null)
                   mDateDisplayed = d;
                   */

                Log.d(LOG_TAG, "Sending browser to " + redirectUrl);
                mWebview.loadUrl(redirectUrl);

                // We can't update the view directly since we're running
                // in a thread, so use mUpdateResults to show a toast message
                // if accessing a different date than what was requested.

                //mHandler.post(mUpdateResults);

                // Parse various Links
                headers = response.getHeaders("Link");
                if (headers.length == 0) {
                    Log.e(LOG_TAG, "Error: Link header not found in response headers.");
                    mErrorMessage = "Sorry, but the Memento could not be accessed. Try again in 5 minutes.";
                } else {
                    String linkValue = headers[0].getValue();

                    mTimeMap = null;
                    mTimeBundle = null;

                    // Get the datetime of this mememnto which should be supplied in the
                    // Link: headers
                    mDateDisplayed = parseCsvLinks(linkValue);

                    // Now that we know the date, update the UI to reflect it
                    mHandler.post(mUpdateResults);

                    if (mTimeMap != null)
                        if (!accessTimeMap())
                            mErrorMessage = "There were problems accessing the Memento's TimeMap.";
                }
            }
        } else if (statusCode == 404) {
            mErrorMessage = "Sorry, but there are no Mementos for this URL.";
        } else if (statusCode == 406) {

            // Parse various Links
            Header[] headers = response.getHeaders("Link");

            if (headers.length == 0) {
                Log.d(LOG_TAG, "Error: Link header not found in 406 response headers.");
                //mErrorMessage = "Sorry, but there was an error in retreiving this Memento.";

                // The lanl proxy has it wrong.  It should return 404 when the URL is not
                // present, so we'll just pretend this is a 404.
                mErrorMessage = "Sorry, but there are no Mementos for this URL.";

                //Log.d(LOG_TAG, "BODY: " + EntityUtils.toString(response.getEntity());                     
            } else {
                String linkValue = headers[0].getValue();

                mTimeMap = null;
                mTimeBundle = null;

                parseCsvLinks(linkValue);

                if (mTimeMap != null)
                    accessTimeMap();

                if (mFirstMemento == null || mLastMemento == null) {
                    Log.e(LOG_TAG, "Could not find first or last Memento in 406 response for " + url);
                    mErrorMessage = "Sorry, but there was an error in retreiving this Memento.";
                } else {
                    Log.d(LOG_TAG, "Not available in this date range (" + mFirstMemento.getDateTimeSimple() + " to "
                            + mLastMemento.getDateTimeSimple() + ")");

                    // According to Rob Sanderson (LANL), we will only get 406 when the date is too
                    // early, so redirect to first Memento

                    mDateDisplayed = new SimpleDateTime(mFirstMemento.getDateTime());
                    String redirectUrl = mFirstMemento.getUrl();
                    Log.d(LOG_TAG, "Sending browser to " + redirectUrl);
                    mWebview.loadUrl(redirectUrl);

                    mHandler.post(mUpdateResults);
                }
            }
        } else {
            mErrorMessage = "Sorry, but there was an unexpected error that will "
                    + "prevent the Memento from being displayed. Try again in 5 minutes.";
            Log.e(LOG_TAG, "Unexpected response code in makeHttpRequests = " + statusCode);
        }
    }

    /**
     * Parse the links in CSV format and return the date of the last item with rel="memento" since
     * this information is needed when getting a 302 and needing to find the resource's datetime.
     * 
     * Example data:
     *     <http://mementoproxy.lanl.gov/aggr/timebundle/http://www.harding.edu/fmccown/>;rel="timebundle",
     *     <http://www.harding.edu/fmccown/>;rel="original",
     *     <http://web.archive.org/web/20010724154504/www.harding.edu/fmccown/>;rel="first memento";datetime="Tue, 24 Jul 2001 15:45:04 GMT",
     *     <http://web.archive.org/web/20010910203350/www.harding.edu/fmccown/>;rel="memento";datetime="Mon, 10 Sep 2001 20:33:50 GMT",
     * 
     * Another example:
     *   <http://mementoproxy.lanl.gov/google/timebundle/http://www.digitalpreservation.gov/>;rel="timebundle",
     *   <http://www.digitalpreservation.gov/>;rel="original",
     *   <http://mementoproxy.lanl.gov/google/timemap/link/http://www.digitalpreservation.gov/>;rel="timemap";type="application/link-format",
     *   <http://webcache.googleusercontent.com/search?q=cache:http://www.digitalpreservation.gov/>;rel="first last memento";datetime="Tue, 07 Sep 2010 11:54:29 GMT"
     *   
     * @param links
     * @return The datetime of the last item marked rel="memento"
     */
    public SimpleDateTime parseCsvLinks(String links) {
        mMementos.clear();
        mFirstMemento = null;
        mLastMemento = null;

        SimpleDateTime date = null;

        // Use a temporary list instead of the actual mMemento list so that we don't 
        // show a list of available dates until they have all been parsed.
        MementoList tempList = new MementoList();

        Log.d(LOG_TAG, "Start parsing links");
        String[] linkStrings = links.split("\",");

        // Place all Links into the array and then sort it based on date
        for (String linkStr : linkStrings) {

            // Add back "
            if (!linkStr.endsWith("\""))
                linkStr += "\"";

            //Log.d(LOG_TAG, linkStr);   

            linkStr = linkStr.trim();

            Link link = new Link(linkStr);
            String rel = link.getRel();
            if (rel.contains("memento")) {
                Memento m = new Memento(link);
                tempList.add(m);

                //Log.d(LOG_TAG, "Added memento " + m.toString());

                // Peel out all values in rel which are separated by white space
                String[] items = link.getRelArray();
                for (String r : items) {
                    r = r.toLowerCase();

                    //Log.d(LOG_TAG, "Processing rel [" + r + "]");

                    // Change the Showing date to the memento's date
                    //if (link.mRel.equals("first-memento"))
                    if (r.contains("first")) {
                        mFirstMemento = m;
                    }
                    if (r.contains("last")) {
                        mLastMemento = m;
                    }
                    if (r.equals("memento")) {
                        date = link.getDatetime();
                    }
                }
            } else if (rel.equals("timemap")) {
                mTimeMap = new TimeMap(link);
            } else if (rel.equals("timebundle")) {
                mTimeBundle = new TimeBundle(link);
            }
        }

        // Sorting can take a time.  Since the Lanl proxy already sorts them, let's
        // comment this out for now.
        //Log.d(LOG_TAG, "Starting sort...");
        //Collections.sort(tempList);

        Log.d(LOG_TAG, "Finished parsing, found " + tempList.size() + " links");

        synchronized (mMementos) {
            mMementos = tempList;
        }

        if (date != null)
            Log.d(LOG_TAG, "parseCsvLinks returning " + date.toString());
        else
            Log.d(LOG_TAG, "parseCsvLinks returning null");

        return date;
    }

    /**
     * Callback when the user sets the date
     */
    private DatePickerDialog.OnDateSetListener mDateSetListener = new DatePickerDialog.OnDateSetListener() {

        public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
            setChosenDate(dayOfMonth, monthOfYear + 1, year);

            if (mToday.equalsDate(mDateChosen)) {
                returnToPresent();
            } else if (mToday.compareTo(mDateChosen) < 0) {
                showToast("We can't see the future.\nHow about the present?");
                returnToPresent();
            } else {
                showToast("Time travelling to " + mDateChosen.dateFormatted());
                makeMementoRequests();
            }
        }
    };

    /**
     * Display toast message.
     * @param message to display
     */
    private void showToast(String message) {
        Toast.makeText(getBaseContext(), message, Toast.LENGTH_LONG).show();
    }

    /**
     * Show error message in a dialog box.
     * @param errorMsg
     */
    private void displayError(String errorMsg) {
        mErrorMessage = errorMsg;
        showDialog(DIALOG_ERROR);
    }

    /**
     * Change IA URLs back to their original.
     * 
     * Example of IA URLs: 
     * 
     * http://www.foo.org.wstub.archive.org/links.html
     * http://web.archive.org/web/20071222090517/http://www.foo.org/
     * http://web.archive.org/web/20070127071850rn_1/www.harding.edu/USER/fmccown/WWW/
     */
    private String convertIaUrlBack(String iaUrl) {
        String url = iaUrl;

        url = url.replace(".wstub.archive.org", "");

        String pattern = "^http://web.archive.org/.+\\d{14}.*?/";

        // Create a Pattern object
        Pattern r = Pattern.compile(pattern);

        // Now create matcher object.
        Matcher m = r.matcher(iaUrl);
        if (m.find()) {
            System.out.println("Found value: " + m.group(0));
            url = m.replaceFirst("");
        }

        if (!url.startsWith("http://") && !url.startsWith("https://"))
            url = "http://" + url;

        return url;
    }

    /**
     * This is used to get the favicon from the web page, but it is not working...
     * the onReceivedIcon() method is never called.
     *
     */
    private class MementoWebChromClient extends WebChromeClient {

        @Override
        public void onReceivedIcon(WebView view, Bitmap icon) {
            Log.d(LOG_TAG, "onReceivedIcon icon = " + icon.toString());
        }

        @Override
        public void onReceivedTitle(WebView view, String title) {
            Log.d(LOG_TAG, "onReceivedTitle title = " + title);

            // Since we are replacing the URL with the page's title,
            // we should swap the URL back when the user tries to enter a new URL.          
            if (title.length() > 0)
                mLocation.setText(title);

            mPageTitle = title;
        }

    }

    /**
     * Callbacks for state changes in the WebView.
     *
     */
    private class MementoWebViewClient extends WebViewClient {

        // Note: this method is *not* called when calling WebView's loadUrl().

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Log.d(LOG_TAG, "-- shouldOverrideUrlLoading");

            // Fix partial URLs
            if (!url.startsWith("http://") && !url.startsWith("https://"))
                url = "http://" + url;

            Log.d(LOG_TAG, "Click on link " + url);

            // Only time travel if selected date is in the past!
            if (mToday.compareTo(mDateChosen) <= 0) {
                view.loadUrl(url);

                // It's possible if the user was going back a page to 
                // be viewing an archived page.  This is just a hack for IA pages,
                // so a more comprehensive solution should be implemented.
                if (url.startsWith("http://web.archive.org/"))
                    url = convertIaUrlBack(url);

                mCurrentUrl = url;

                Log.d(LOG_TAG, "mOriginalUrl = " + mOriginalUrl);

                //if (!mMementos.getAssociatedUrl().equals(url)) {
                //   Log.d(LOG_TAG, "(1) Clearing all Mementos for new URL " + url);
                //   mMementos.clear();
                //}

                if (!mOriginalUrl.equals(url)) {
                    Log.d(LOG_TAG, "(2) Clearing all Mementos for new URL " + url);
                    mMementos.clear();
                }

                mOriginalUrl = url;
            } else {
                // User has clicked on a URL in an archived page, so we need the original
                // URL so we can find all its mementos
                url = convertIaUrlBack(url);
                Log.d(LOG_TAG, "Converted IA URL = " + url);

                mOriginalUrl = url;
                makeMementoRequests();
            }

            return true;
        }

        @Override
        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
            //displayError(description);
            Log.d(LOG_TAG,
                    "WebViewClient Error: [code=" + errorCode + "] " + description + " [URL=" + failingUrl + "]");
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            Log.d(LOG_TAG, "-- onPageStarted");

            mCurrentUrl = url;

            mProgressBar.setVisibility(View.VISIBLE);

            // Show date here because it can take a long time before it finishes downloading
            Log.d(LOG_TAG, "mDateDisplayed: " + mDateDisplayed.dateFormatted());
            mDateDisplayedView.setText(mDateDisplayed.dateFormatted());

            /* THIS WAS A HACK TO GET THE FAVICON, BUT I DO NOT SUGGEST USING IT
             * SINCE THERE ARE MANY DIFFERENT METHODS USED TO PUBLISH FAVICONS, AND
             * THIS METHOD IS NOT USING CACHING.
             * 
            // Grab website favicon
            Context context = view.getContext();
             Drawable image = getImage(context, getBaseUrl(url) + "favicon.ico");
             if (image == null) {
                System.out.println("image is null !!");
             }
             else {
                mLocation.setCompoundDrawablesWithIntrinsicBounds(image, null, null, null);
             }
             */

            if (favicon == null) {
                // Display a favicon for some of the web archives

                Log.d(LOG_TAG, "No favicon - null");

                // Use our built-in favicons since this isn't working
                if (url.startsWith("http://webcitation.org"))
                    favicon = mFavicons.get("webcite");
                else if (url.startsWith("http://web.archive.org"))
                    favicon = mFavicons.get("ia");
                else if (url.startsWith("http://webarchive.nationalarchives"))
                    favicon = mFavicons.get("national-archives");
                else
                    mLocation.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);

                if (favicon != null) {
                    BitmapDrawable bd = new BitmapDrawable(favicon);
                    mLocation.setCompoundDrawablesWithIntrinsicBounds(bd, null, null, null);
                }

            } else {
                Log.d(LOG_TAG, "favicon h and w = " + favicon.getHeight() + " " + favicon.getWidth());
                BitmapDrawable bd = new BitmapDrawable(favicon);
                mLocation.setCompoundDrawablesWithIntrinsicBounds(bd, null, null, null);
            }

            /*  THIS CODE IS NOT WORKING EITHER
            favicon = view.getFavicon();
            if (favicon == null) {
               Log.d(LOG_TAG, "No favicon from getFavicon - null");
            }
            else {
               Log.d(LOG_TAG, "getFavicon favicon h and w = " + favicon.getHeight() + " " + favicon.getWidth());
               BitmapDrawable bd = new BitmapDrawable(favicon);           
               mLocation.setCompoundDrawablesWithIntrinsicBounds(bd, null, null, null);              
            }
            */
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            mProgressBar.setVisibility(View.GONE);

            //if (mLocation.isInEditMode()) {
            if (mLocation.isSelected())
                Log.d(LOG_TAG, "Editor is in edit mode, so don't erase text!");
            //else
            //   mLocation.setText(url);

            Log.d(LOG_TAG, "-- onPageFinished... mDateDisplayed: " + mDateDisplayed.dateFormatted());

            /*
             * THIS CODE IS NOT WORKING EITHER.
            Bitmap favicon = view.getFavicon();
            if (favicon == null) {
               Log.d(LOG_TAG, "No favicon in onPageFinished - null");
            }
            else {
               Log.d(LOG_TAG, "onPageFinished favicon h and w = " + favicon.getHeight() + " " + favicon.getWidth());
               BitmapDrawable bd = new BitmapDrawable(favicon);           
               mLocation.setCompoundDrawablesWithIntrinsicBounds(bd, null, null, null);              
            }
            */
        }
    }

    @Override
    protected Dialog onCreateDialog(int id) {

        Dialog dialog = null;
        AlertDialog.Builder builder = new AlertDialog.Builder(this);

        switch (id) {
        case DIALOG_DATE:
            dialog = new DatePickerDialog(this, mDateSetListener, mDateChosen.getYear(), mDateChosen.getMonth() - 1,
                    mDateChosen.getDay());
            break;

        case DIALOG_ERROR:
            builder = new AlertDialog.Builder(this);
            builder.setMessage("error message").setCancelable(false).setPositiveButton("OK", null);
            dialog = builder.create();
            break;

        case DIALOG_MEMENTO_YEARS:
            builder = new AlertDialog.Builder(this);
            final CharSequence[] years = mMementos.getAllYears();

            // Select the year of the Memento currently displayed
            int selectedYear = -1;

            for (int i = 0; i < years.length; i++) {
                if (mDateDisplayed.getYear() == Integer.parseInt(years[i].toString())) {
                    selectedYear = i;
                    break;
                }
            }

            builder.setSingleChoiceItems(years, selectedYear, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int item) {
                    dialog.dismiss();

                    mSelectedYear = Integer.parseInt(years[item].toString());
                    showDialog(DIALOG_MEMENTO_DATES);
                }
            });

            dialog = builder.create();

            // Cause the dialog to be freed whenever it is dismissed.
            // This is necessary because the items are dynamic.  
            dialog.setOnDismissListener(new OnDismissListener() {
                @Override
                public void onDismiss(DialogInterface arg0) {
                    removeDialog(DIALOG_MEMENTO_YEARS);
                }
            });

            break;

        case DIALOG_MEMENTO_DATES:
            builder = new AlertDialog.Builder(this);

            final CharSequence[] dates;

            if (mSelectedYear == 0)
                dates = mMementos.getAllDates();
            else
                dates = mMementos.getDatesForYear(mSelectedYear);

            Log.d(LOG_TAG, "Number of dates = " + dates.length);

            // This shouldn't happen, but just in case.
            if (dates.length == 0) {
                showToast("No Mementos to select from.");
                return null;
            }

            int selected = -1;

            // Select the radio button for the current Memento if it's in the selected year.
            if (mSelectedYear == 0 || mSelectedYear == mDateDisplayed.getYear()) {

                int index = mMementos.getIndex(mDateDisplayed);
                if (index < 0)
                    Log.d(LOG_TAG, "Could not find Memento in the list with date " + mDateDisplayed);
                else
                    mMementos.setCurrentIndex(index);

                Memento m = mMementos.getCurrent();
                if (m != null) {
                    for (int i = 0; i < dates.length; i++) {
                        if (m.getDateTime().dateAndTimeFormatted().equals(dates[i])) {
                            selected = i;
                            break;
                        }
                    }
                } else
                    Log.d(LOG_TAG, "There is no current Memento");
            }

            Log.d(LOG_TAG, "Selected index = " + selected);

            builder.setSingleChoiceItems(dates, selected, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int item) {
                    dialog.dismiss();

                    int index = mMementos.getIndex(dates[item].toString());
                    Memento m = mMementos.get(index);
                    if (m == null) {
                        Log.e(LOG_TAG, "Could not find Memento with date " + mDateChosen + ".");
                        displayError("The date selected could not be accessed. Please select another.");
                    } else {
                        // Display this Memento

                        Log.d(LOG_TAG, "index for [" + dates[item] + "] is " + index);

                        SimpleDateTime d = m.getDateTime();
                        setChosenDate(d);

                        if (index == mMementos.getCurrentIndex()) {
                            showToast("Memento is already displayed.");
                        } else {
                            mMementos.setCurrentIndex(index);
                            showToast("Time travelling to " + mDateChosen.dateFormatted());

                            // Find the Memento URL for the selected date                      

                            mDateDisplayed = new SimpleDateTime(mDateChosen);
                            String redirectUrl = m.getUrl();
                            Log.d(LOG_TAG, "Sending browser to " + redirectUrl);
                            mWebview.loadUrl(redirectUrl);

                            setEnableForNextPrevButtons();
                        }
                    }
                }
            });

            dialog = builder.create();

            // Cause the dialog to be freed whenever it is dismissed.
            // This is necessary because the items are dynamic.  I couldn't find
            // a better way to solve this problem.
            dialog.setOnDismissListener(new OnDismissListener() {
                @Override
                public void onDismiss(DialogInterface arg0) {
                    removeDialog(DIALOG_MEMENTO_DATES);
                }
            });

            break;

        case DIALOG_HELP:

            Context context = getApplicationContext();
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
            View layout = inflater.inflate(R.layout.about_dialog, null);
            builder.setView(layout);
            builder.setPositiveButton("OK", null);
            dialog = builder.create();
            break;
        }

        return dialog;
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
        super.onPrepareDialog(id, dialog);

        switch (id) {
        case DIALOG_DATE:
            // To fix a bug described here: http://www.zunisoft.com/?p=1140 
            DatePickerDialog dlg = (DatePickerDialog) dialog;
            DateFormat longDateFormat = new SimpleDateFormat("EEEE, MMMM d, yyyy");
            dlg.setTitle(longDateFormat.format(mDateChosen.getDate()));
            dlg.updateDate(mDateChosen.getYear(), mDateChosen.getMonth() - 1, mDateChosen.getDay());
            break;
        case DIALOG_ERROR:
            AlertDialog ad = (AlertDialog) dialog;
            ad.setMessage(mErrorMessage);
            mErrorMessage = null;
            break;
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebview.canGoBack()) {

            // Get the previous URL to update our internal copy 

            WebBackForwardList list = mWebview.copyBackForwardList();
            int curr = list.getCurrentIndex();
            WebHistoryItem item = list.getItemAtIndex(curr - 1);
            Bitmap favicon = item.getFavicon();

            if (favicon == null)
                Log.d(LOG_TAG, "No favicon in WebHistoryItem - null");
            else
                Log.d(LOG_TAG, "WebHistoryItem favicon W = " + favicon.getWidth());

            mOriginalUrl = item.getUrl();
            Log.d(LOG_TAG, "GO BACK TO " + mOriginalUrl);

            mWebview.goBack();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    /**
     * Retrieve the TimeMap from the Web and parse out the Mementos.
     * Currently this only recognizes TimeMaps using CSV formats. 
     * Other formats to be implemented: RDF/XML, N3, and HTML.
     * @return true if TimeMap was successfully retreived, false otherwise.
     */
    private boolean accessTimeMap() {

        HttpClient httpclient = new DefaultHttpClient();

        String url = mTimeMap.getUrl();
        HttpGet httpget = new HttpGet(url);
        httpget.setHeader("User-Agent", mUserAgent);

        Log.d(LOG_TAG, "Accessing TimeMap: " + httpget.getURI());

        HttpResponse response = null;
        try {
            response = httpclient.execute(httpget);

            Log.d(LOG_TAG, "Response code = " + response.getStatusLine());

            //Log.d(LOG_TAG, getHeadersAsString(response.getAllHeaders()));
        } catch (ClientProtocolException e) {
            Log.e(LOG_TAG, getExceptionStackTraceAsString(e));
            return false;
        } catch (IOException e) {
            Log.e(LOG_TAG, getExceptionStackTraceAsString(e));
            return false;
        }

        // Should get back 200

        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) {

            // See if MIME type is the same as Type      
            Header type = response.getFirstHeader("Content-Type");
            if (type == null)
                Log.w(LOG_TAG, "Could not find the Content-Type for " + url);
            else if (!type.getValue().contains(mTimeMap.getType()))
                Log.w(LOG_TAG, "Content-Type is [" + type.getValue() + "] but TimeMap type is ["
                        + mTimeMap.getType() + "] for " + url);

            // Timemap MUST be "application/link-format", but leave csv for
            // backwards-compatibility with earlier Memento implementations
            if (mTimeMap.getType().equals("text/csv") || mTimeMap.getType().equals("application/link-format")) {
                try {
                    String responseBody = EntityUtils.toString(response.getEntity());
                    parseCsvLinks(responseBody);
                } catch (ParseException e) {
                    Log.e(LOG_TAG, getExceptionStackTraceAsString(e));
                    return false;
                } catch (IOException e) {
                    Log.e(LOG_TAG, getExceptionStackTraceAsString(e));
                    return false;
                }
            } else {
                Log.e(LOG_TAG, "Unable to handle TimeMap type " + mTimeMap.getType());
                return false;
            }
        } else {
            Log.d(LOG_TAG, "Unexpected response code in accessTimeMap = " + statusCode);
            return false;
        }

        // Deallocate all system resources
        httpclient.getConnectionManager().shutdown();

        return true;
    }

    public static String getExceptionStackTraceAsString(Exception exception) {
        StringWriter sw = new StringWriter();
        exception.printStackTrace(new PrintWriter(sw));
        return sw.toString();
    }

    /**
     * Purely for testing.
     */
    @SuppressWarnings("unused")
    private void testMementos() {

        String[] urls = { "http://www.foo.org.wstub.archive.org/links.html",
                "http://web.archive.org/web/20071222090517/http://www.foo.org/",
                "http://web.archive.org/web/20070127071850rn_1/www.harding.edu/USER/fmccown/WWW/" };

        for (String u : urls) {
            System.out.println("Convert " + u + " to " + convertIaUrlBack(u));
        }

        SimpleDateTime date = new SimpleDateTime();
        System.out.println("date = " + date);

        SimpleDateTime date2 = new SimpleDateTime();
        System.out.println("date2 = " + date2);

        int comp = date.compareTo(date2);
        System.out.println("compareTo = " + comp);

        date = new SimpleDateTime(31, 12, 2010);
        date.setDateFormat(android.text.format.DateFormat.getDateFormat(getApplicationContext()));
        date.setTimeFormat(android.text.format.DateFormat.getTimeFormat(getApplicationContext()));
        System.out.println("date formatted = " + date.dateFormatted());
        System.out.println("date and time formatted = " + date.dateAndTimeFormatted());
        System.out.println("long formatted = " + date.longDateFormatted());

        //System.exit(0);
        //this.finish();

        String url = "<http://web.archive.org/web/20010910203350/www.harding.edu/fmccown/>;rel=\"memento\";datetime=\"Mon, 10 Sep 2001 20:33:50 GMT\"";
        Memento m1 = new Memento(new Link(url));
        System.out.println("m1=" + m1.toString());
        System.out.println("getDateTimeString: " + m1.getDateTimeString());
        System.out.println("getDateTimeSimple: " + m1.getDateTimeSimple());

        Memento m2 = new Memento(new Link(url));

        System.out.println("m2=" + m2.toString());
        System.out.println("getDateTimeString: " + m2.getDateTimeString());
        System.out.println("getDateTimeSimple: " + m2.getDateTimeSimple());

        System.out.println("\ncompare m1,m2: " + m1.compareTo(m2));
        System.out.println("\ncompare m2,m1: " + m2.compareTo(m1));

        String newDatetime = "Sun, 09 Sep 2001 20:33:50 GMT";
        m2.setDateTime(newDatetime);

        System.out.println("getDateTimeString: " + m2.getDateTimeString());
        System.out.println("getDateTimeSimple: " + m2.getDateTimeSimple());

        System.out.println("\ncompare m1,m2: " + m1.compareTo(m2));
        System.out.println("\ncompare m2,m1: " + m2.compareTo(m1));

        m2.getDateTime().setToLastHour();

        System.out.println("New value for getDateTimeString: " + m2.getDateTimeString());

        String links = "<http://mementoproxy.lanl.gov/aggr/timebundle/http://www.harding.edu/fmccown/>;rel=\"timebundle\","
                + "<http://www.harding.edu/fmccown/>;rel=\"original\",<http://mementoproxy.lanl.gov/aggr/timemap/link/http://www.harding.edu/fmccown/>;rel=\"timemap\";type=\"text/csv\","
                + "<http://web.archive.org/web/20010724154504/www.harding.edu/fmccown/>;rel=\"first prev memento\";datetime=\"Tue, 24 Jul 2001 15:45:04 GMT\","
                + "<http://web.archive.org/web/20071222090517/www.harding.edu/fmccown/>;rel=\"last memento\";datetime=\"Sat, 22 Dec 2007 09:05:17 GMT\","
                + "<http://web.archive.org/web/20020104194811/www.harding.edu/fmccown/>;rel=\"next memento\";datetime=\"Fri, 04 Jan 2002 19:48:11 GMT\","
                + "<http://web.archive.org/web/20010910203350/www.harding.edu/fmccown/>;rel=\"memento\";datetime=\"Mon, 10 Sep 2001 20:33:50 GMT\","
                + "<http://webcache.googleusercontent.com/search?q=cache:http://www.digitalpreservation.gov/>;rel=\"first last memento\";datetime=\"Tue, 07 Sep 2010 11:54:29 GMT\"";

        parseCsvLinks(links);
        mMementos.displayAll();

        System.exit(0);

        System.out.println(mTimeMap.toString());
        System.out.println(mTimeBundle.toString());

        System.out.println("\nAll years:");
        for (CharSequence year : mMementos.getAllYears()) {
            System.out.println(year);
        }

        System.out.println("\nAll for 2001:");
        for (CharSequence year : mMementos.getDatesForYear(2001)) {
            System.out.println(year);
        }

        System.out.println("\nAll for 2000:");
        for (CharSequence year : mMementos.getDatesForYear(2000)) {
            System.out.println(year);
        }

        //date = getResourceDatetimeForWebcite("http://webcitation.org/query?id=1218127693715930");
        //System.out.println("Date returned from getResourceDatetimeForWebcite: " + date.toString());

        //accessTimeMap();
    }
}