Switch the history management over to the new provider.

Also fix up a bunch of bugs causing things like
thumbnails to not be stored properly.

Change-Id: I5758ee108734d9b50e741822dcbe86b7cc834e8e
diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java
index 62ed7e3..3ead203 100644
--- a/src/com/android/browser/Bookmarks.java
+++ b/src/com/android/browser/Bookmarks.java
@@ -24,8 +24,6 @@
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.AsyncTask;
-import android.provider.Browser;
-import android.provider.Browser.BookmarkColumns;
 import android.provider.BrowserContract;
 import android.util.Log;
 import android.webkit.WebIconDatabase;
@@ -103,40 +101,26 @@
             ContentResolver cr, String url, String title) {
         Cursor cursor = null;
         try {
-            cursor = cr.query(
-                    Browser.BOOKMARKS_URI,
-                    Browser.HISTORY_PROJECTION,
-                    "url = ? AND title = ?",
+            cursor = cr.query(BrowserContract.Bookmarks.CONTENT_URI,
+                    new String[] { BrowserContract.Bookmarks._ID },
+                    BrowserContract.Bookmarks.URL + " = ? AND " +
+                            BrowserContract.Bookmarks.TITLE + " = ?",
                     new String[] { url, title },
                     null);
-            boolean first = cursor.moveToFirst();
+
             // Should be in the database no matter what
-            if (!first) {
+            if (!cursor.moveToFirst()) {
                 throw new AssertionError("URL is not in the database! " + url
                         + " " + title);
             }
+
             // Remove from bookmarks
             WebIconDatabase.getInstance().releaseIconForPageUrl(url);
-            Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
-                    cursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
-            int numVisits = cursor.getInt(
-                    Browser.HISTORY_PROJECTION_VISITS_INDEX);
-            if (0 == numVisits) {
-                cr.delete(uri, null, null);
-            } else {
-                // It is no longer a bookmark, but it is still a visited
-                // site.
-                ContentValues values = new ContentValues();
-                values.put(Browser.BookmarkColumns.BOOKMARK, 0);
-                try {
-                    cr.update(uri, values, null, null);
-                } catch (IllegalStateException e) {
-                    Log.e("removeFromBookmarks", "no database!");
-                }
-            }
+            Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI,
+                    cursor.getLong(0));
+            cr.delete(uri, null, null);
             if (context != null) {
-                Toast.makeText(context, R.string.removed_from_bookmarks,
-                        Toast.LENGTH_LONG).show();
+                Toast.makeText(context, R.string.removed_from_bookmarks, Toast.LENGTH_LONG).show();
             }
         } catch (IllegalStateException e) {
             Log.e(LOGTAG, "removeFromBookmarks", e);
@@ -168,8 +152,15 @@
         return false;
     }
 
+    static final String QUERY_BOOKMARKS_WHERE =
+            BrowserContract.Bookmarks.IS_FOLDER + " == 0 AND (" + 
+            BrowserContract.Bookmarks.URL + " == ? OR " +
+            BrowserContract.Bookmarks.URL + " == ? OR " +
+            BrowserContract.Bookmarks.URL + " LIKE ? || '%' OR " +
+            BrowserContract.Bookmarks.URL + " LIKE ? || '%')";
+
     /* package */ static Cursor queryBookmarksForUrl(ContentResolver cr,
-            String originalUrl, String url, boolean onlyBookmarks) {
+            String originalUrl, String url) {
         if (cr == null || url == null) {
             return null;
         }
@@ -192,18 +183,10 @@
         // http://www.google.com/m, search for
         // http://www.google.com/m?some_query)
         final String[] selArgs = new String[] {
-            originalUrlNoQuery, urlNoQuery, originalUrl, url };
-        String where = BookmarkColumns.URL + " == ? OR "
-                + BookmarkColumns.URL + " == ? OR "
-                + BookmarkColumns.URL + " LIKE ? || '%' OR "
-                + BookmarkColumns.URL + " LIKE ? || '%'";
-        if (onlyBookmarks) {
-            where = "(" + where + ") AND " + BookmarkColumns.BOOKMARK + " == 1";
-        }
-        final String[] projection =
-                new String[] { Browser.BookmarkColumns._ID };
-        return cr.query(Browser.BOOKMARKS_URI, projection, where, selArgs,
-                null);
+                originalUrlNoQuery, urlNoQuery, originalUrl, url };
+        final String[] projection = new String[] { BrowserContract.Bookmarks._ID };
+        return cr.query(BrowserContract.Bookmarks.CONTENT_URI, projection, QUERY_BOOKMARKS_WHERE,
+                selArgs, null);
     }
 
     // Strip the query from the given url.
@@ -233,7 +216,7 @@
             @Override
             protected Void doInBackground(Void... unused) {
                 final Cursor c =
-                        Bookmarks.queryBookmarksForUrl(cr, originalUrl, url, true);
+                        Bookmarks.queryBookmarksForUrl(cr, originalUrl, url);
                 if (c == null) {
                     return null;
                 }
@@ -242,11 +225,11 @@
                     final ByteArrayOutputStream os =
                             new ByteArrayOutputStream();
                     favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
-                    values.put(Browser.BookmarkColumns.FAVICON,
+                    values.put(BrowserContract.Bookmarks.FAVICON,
                             os.toByteArray());
                     do {
                         cr.update(ContentUris.withAppendedId(
-                                Browser.BOOKMARKS_URI, c.getInt(0)),
+                                BrowserContract.Bookmarks.CONTENT_URI, c.getLong(0)),
                                 values, null, null);
                     } while (c.moveToNext());
                 }
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 36be5e4..99bf017 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -62,6 +62,7 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.provider.Browser;
+import android.provider.BrowserContract;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.Intents.Insert;
 import android.provider.Downloads;
@@ -2506,18 +2507,18 @@
                 Cursor c = null;
                 try {
                     c = Bookmarks.queryBookmarksForUrl(
-                            cr, originalUrl, url, true);
+                            cr, originalUrl, url);
                     if (c != null) {
                         if (c.moveToFirst()) {
                             ContentValues values = new ContentValues();
                             final ByteArrayOutputStream os
                                     = new ByteArrayOutputStream();
                             bm.compress(Bitmap.CompressFormat.PNG, 100, os);
-                            values.put(Browser.BookmarkColumns.THUMBNAIL,
+                            values.put(BrowserContract.Bookmarks.THUMBNAIL,
                                     os.toByteArray());
                             do {
                                 cr.update(ContentUris.withAppendedId(
-                                        Browser.BOOKMARKS_URI, c.getInt(0)),
+                                        BrowserContract.Bookmarks.CONTENT_URI, c.getLong(0)),
                                         values, null, null);
                             } while (c.moveToNext());
                         }
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index 6a51b98..a01f6fa 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -386,7 +386,7 @@
     public void updateRow(Bundle map) {
 
         // Find the record
-        int id = map.getInt("id");
+        long id = map.getLong("id");
         int position = -1;
         Cursor cursor = mAdapter.getCursor();
         for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
@@ -411,12 +411,13 @@
         }
 
         if (map.getBoolean("invalidateThumbnail") == true) {
-            values.put(Browser.BookmarkColumns.THUMBNAIL, new byte[0]);
+            values.putNull(Browser.BookmarkColumns.THUMBNAIL);
         }
 
         if (values.size() > 0) {
-            getContentResolver().update(Browser.BOOKMARKS_URI, values,
-                    "_id = ?", new String[] { Integer.toString(id) });
+            getContentResolver().update(
+                    ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id),
+                    values, null, null);
         }
     }
 
diff --git a/src/com/android/browser/BrowserDownloadAdapter.java b/src/com/android/browser/BrowserDownloadAdapter.java
index f22c9fe..6f498b6 100644
--- a/src/com/android/browser/BrowserDownloadAdapter.java
+++ b/src/com/android/browser/BrowserDownloadAdapter.java
@@ -26,7 +26,6 @@
 import android.drm.mobile1.DrmRawContent;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.Handler;
 import android.provider.Downloads;
 import android.text.format.Formatter;
 import android.view.LayoutInflater;
@@ -56,9 +55,9 @@
     private int mMimetypeColumnId;
     private int mDateColumnId;
 
-    public BrowserDownloadAdapter(Context context, Cursor c, int index,
-            Handler handler) {
-        super(context, c, index, handler);
+    public BrowserDownloadAdapter(Context context, Cursor c, int index) {
+        super(context, index);
+        changeCursor(c);
         mTitleColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TITLE);
         mDescColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_DESCRIPTION);
         mStatusColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS);
diff --git a/src/com/android/browser/BrowserDownloadPage.java b/src/com/android/browser/BrowserDownloadPage.java
index bbf1191..a897f99 100644
--- a/src/com/android/browser/BrowserDownloadPage.java
+++ b/src/com/android/browser/BrowserDownloadPage.java
@@ -18,13 +18,10 @@
 
 import android.app.AlertDialog;
 import android.app.ExpandableListActivity;
-import android.content.ActivityNotFoundException;
+import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.ContentUris;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
@@ -34,17 +31,13 @@
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
 import android.view.Menu;
-import android.view.MenuItem;
 import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.AdapterView;
 import android.widget.ExpandableListView;
 
 import java.io.File;
-import java.util.List;
 
 /**
  *  View showing the user's current browser downloads
@@ -100,7 +93,7 @@
             // Create a list "controller" for the data
             mDownloadAdapter = new BrowserDownloadAdapter(this, 
                     mDownloadCursor, mDownloadCursor.getColumnIndexOrThrow(
-                    Downloads.Impl.COLUMN_LAST_MODIFICATION), mHandler);
+                    Downloads.Impl.COLUMN_LAST_MODIFICATION));
 
             setListAdapter(mDownloadAdapter);
             mListView.setOnCreateContextMenuListener(this);
diff --git a/src/com/android/browser/BrowserHistoryPage.java b/src/com/android/browser/BrowserHistoryPage.java
index 5298b9a..8116b97 100644
--- a/src/com/android/browser/BrowserHistoryPage.java
+++ b/src/com/android/browser/BrowserHistoryPage.java
@@ -18,33 +18,30 @@
 
 import android.app.Activity;
 import android.app.ExpandableListActivity;
+import android.app.LoaderManager.LoaderCallbacks;
 import android.content.ClipboardManager;
 import android.content.Context;
+import android.content.CursorLoader;
 import android.content.Intent;
+import android.content.Loader;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.ServiceManager;
 import android.provider.Browser;
 import android.provider.BrowserContract.History;
-import android.util.Log;
 import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
-import android.view.ContextMenu.ContextMenuInfo;
 import android.view.ViewStub;
 import android.webkit.WebIconDatabase.IconListener;
-import android.widget.ExpandableListAdapter;
 import android.widget.ExpandableListView;
 import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
 import android.widget.Toast;
@@ -53,21 +50,41 @@
  * Activity for displaying the browser's history, divided into
  * days of viewing.
  */
-public class BrowserHistoryPage extends ExpandableListActivity {
-    private HistoryAdapter          mAdapter;
-    private boolean                 mDisableNewWindow;
-    private HistoryItem             mContextHeader;
+public class BrowserHistoryPage extends ExpandableListActivity
+        implements LoaderCallbacks<Cursor> {
 
-    private final static String LOGTAG = "browser";
+    static final int LOADER_HISTORY = 1;
+
+    HistoryAdapter mAdapter;
+    boolean mDisableNewWindow;
+    HistoryItem mContextHeader;
 
     // Implementation of WebIconDatabase.IconListener
-    private class IconReceiver implements IconListener {
+    class IconReceiver implements IconListener {
+        @Override
         public void onReceivedIcon(String url, Bitmap icon) {
-            setListAdapter(mAdapter);
+            mAdapter.notifyDataSetChanged();
         }
     }
+
     // Instance of IconReceiver
-    private final IconReceiver mIconReceiver = new IconReceiver();
+    final IconReceiver mIconReceiver = new IconReceiver();
+
+    static interface HistoryQuery {
+        static final String[] PROJECTION = new String[] {
+                History._ID, // 0
+                History.DATE_LAST_VISITED, // 1
+                History.TITLE, // 2
+                History.URL, // 3
+                History.FAVICON, // 4
+        };
+
+        static final int INDEX_ID = 0;
+        static final int INDEX_DATE_LAST_VISITED = 1;
+        static final int INDEX_TITE = 2;
+        static final int INDEX_URL = 3;
+        static final int INDEX_FAVICON = 4;
+    }
 
     /**
      * Report back to the calling activity to load a site.
@@ -90,81 +107,73 @@
         cm.setText(text);
     }
 
-    private static final int ADAPTER_CREATED = 1000;
-    private Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case ADAPTER_CREATED:
-                    mAdapter = (HistoryAdapter) msg.obj;
-                    setListAdapter(mAdapter);
-                    final ExpandableListView list = getExpandableListView();
-                    // Add an empty view late, so it does not claim an empty
-                    // history before the adapter is present
-                    View v = new ViewStub(BrowserHistoryPage.this,
-                            R.layout.empty_history);
-                    addContentView(v, new LayoutParams(
-                            LayoutParams.MATCH_PARENT,
-                            LayoutParams.MATCH_PARENT));
-                    list.setEmptyView(v);
-                    list.setOnCreateContextMenuListener(
-                            BrowserHistoryPage.this);
-                    // Do not post the runnable if there is nothing in the list.
-                    if (list.getExpandableListAdapter().getGroupCount() > 0) {
-                        list.post(new Runnable() {
-                            public void run() {
-                                // In case the history gets cleared before this
-                                // event happens
-                                if (list.getExpandableListAdapter()
-                                        .getGroupCount() > 0) {
-                                    list.expandGroup(0);
-                                }
-                            }
-                        });
-                    }
-                    break;
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        switch (id) {
+            case LOADER_HISTORY: {
+                CursorLoader loader = new CursorLoader(this, History.CONTENT_URI,
+                        HistoryQuery.PROJECTION, null, null, null);
+                return loader;
+            }
+
+            default: {
+                throw new IllegalArgumentException();
             }
         }
-    };
+    }
 
     @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        switch (loader.getId()) {
+            case LOADER_HISTORY: {
+                mAdapter.changeCursor(data);
+
+                // Add an empty view late, so it does not claim an empty
+                // history before the adapter is present
+                final ExpandableListView list = getExpandableListView();
+                View v = new ViewStub(this, R.layout.empty_history);
+                addContentView(v, new LayoutParams(
+                        LayoutParams.MATCH_PARENT,
+                        LayoutParams.MATCH_PARENT));
+                list.setEmptyView(v);
+
+                // Do not post the runnable if there is nothing in the list.
+                if (list.getExpandableListAdapter().getGroupCount() > 0) {
+                    list.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            // In case the history gets cleared before this
+                            // event happens
+                            if (list.getExpandableListAdapter()
+                                    .getGroupCount() > 0) {
+                                list.expandGroup(0);
+                            }
+                        }
+                    });
+                }
+                break;
+            }
+
+            default: {
+                throw new IllegalArgumentException();
+            }
+        }
+    }
+    
+    @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         setTitle(R.string.browser_history);
 
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... unused) {
-                final String whereClause = Browser.BookmarkColumns.VISITS
-                        + " > 0"
-                        // In AddBookmarkPage, where we save new bookmarks, we
-                        // add three visits to newly created bookmarks, so that
-                        // bookmarks that have not been visited will show up in
-                        // the most visited, and higher in the goto search box.
-                        // However, this puts the site in the history, unless
-                        // we ignore sites with a DATE of 0, which the next
-                        // line does.
-                        + " AND " + Browser.BookmarkColumns.DATE + " > 0";
-                final String orderBy = Browser.BookmarkColumns.DATE + " DESC";
+        getExpandableListView().setOnCreateContextMenuListener(this);
+        
+        mAdapter = new HistoryAdapter(this);
+        setListAdapter(mAdapter);
 
-                Cursor cursor = managedQuery(
-                        Browser.BOOKMARKS_URI,
-                        Browser.HISTORY_PROJECTION,
-                        whereClause, null, orderBy);
-
-                HistoryAdapter adapter = new HistoryAdapter(
-                        BrowserHistoryPage.this, cursor,
-                        Browser.HISTORY_PROJECTION_DATE_INDEX, mHandler);
-                mHandler.obtainMessage(ADAPTER_CREATED, adapter).sendToTarget();
-                return null;
-            }
-        }.execute();
-        mDisableNewWindow = getIntent().getBooleanExtra("disable_new_window",
-                false);
+        mDisableNewWindow = getIntent().getBooleanExtra("disable_new_window", false);
 
         // Register to receive icons in case they haven't all been loaded.
-        CombinedBookmarkHistoryActivity.getIconListenerSet()
-                .addListener(mIconReceiver);
+        CombinedBookmarkHistoryActivity.getIconListenerSet().addListener(mIconReceiver);
 
         Activity parent = getParent();
         if (null == parent
@@ -172,14 +181,17 @@
             throw new AssertionError("history page can only be viewed as a tab"
                     + "in CombinedBookmarkHistoryActivity");
         }
+
         // initialize the result to canceled, so that if the user just presses
         // back then it will have the correct result
         setResultToParent(RESULT_CANCELED, null);
+
+        // Start the loader
+        getLoaderManager().initLoader(LOADER_HISTORY, null, this);
     }
 
     @Override
     protected void onDestroy() {
-        mHandler.removeCallbacksAndMessages(null);
         super.onDestroy();
         CombinedBookmarkHistoryActivity.getIconListenerSet()
                 .removeListener(mIconReceiver);
@@ -195,7 +207,8 @@
 
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
-        menu.findItem(R.id.clear_history_menu_id).setVisible(Browser.canClearHistory(this.getContentResolver()));
+        menu.findItem(R.id.clear_history_menu_id).setVisible(
+                Browser.canClearHistory(this.getContentResolver()));
         return true;
     }
     
@@ -208,7 +221,6 @@
                 // CombinedBookmarkHistoryActivity
                 ((CombinedBookmarkHistoryActivity) getParent())
                         .removeParentChildRelationShips();
-                if (mAdapter != null) mAdapter.refreshData();
                 return true;
                 
             default:
@@ -292,7 +304,6 @@
                 return true;
             case R.id.delete_context_menu_id:
                 Browser.deleteFromHistory(getContentResolver(), url);
-                if (mAdapter != null) mAdapter.refreshData();
                 return true;
             case R.id.homepage_context_menu_id:
                 BrowserSettings.getInstance().setHomePage(this, url);
@@ -304,12 +315,12 @@
         }
         return super.onContextItemSelected(item);
     }
-    
+
     @Override
     public boolean onChildClick(ExpandableListView parent, View v,
             int groupPosition, int childPosition, long id) {
-        if (v instanceof HistoryItem) {
-            loadUrl(((HistoryItem) v).getUrl(), false);
+        if (v instanceof BookmarkItem) {
+            loadUrl(((BookmarkItem) v).getUrl(), false);
             return true;
         }
         return false;
@@ -324,17 +335,16 @@
     }
 
     private class HistoryAdapter extends DateSortedExpandableListAdapter {
-        HistoryAdapter(Context context, Cursor cursor, int index,
-                Handler handler) {
-            super(context, cursor, index, handler);
-            
+        HistoryAdapter(Context context) {
+            super(context, HistoryQuery.INDEX_DATE_LAST_VISITED);
         }
 
+        @Override
         public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
                 View convertView, ViewGroup parent) {
-            HistoryItem item;
-            if (null == convertView || !(convertView instanceof HistoryItem)) {
-                item = new HistoryItem(BrowserHistoryPage.this);
+            BookmarkItem item;
+            if (null == convertView || !(convertView instanceof BookmarkItem)) {
+                item = new BookmarkItem(BrowserHistoryPage.this);
                 // Add padding on the left so it will be indented from the
                 // arrows on the group views.
                 item.setPadding(item.getPaddingLeft() + 10,
@@ -342,16 +352,18 @@
                         item.getPaddingRight(),
                         item.getPaddingBottom());
             } else {
-                item = (HistoryItem) convertView;
+                item = (BookmarkItem) convertView;
             }
+
             // Bail early if the Cursor is closed.
             if (!moveCursorToChildPosition(groupPosition, childPosition)) {
                 return item;
             }
-            item.setName(getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
-            String url = getString(Browser.HISTORY_PROJECTION_URL_INDEX);
+
+            item.setName(getString(HistoryQuery.INDEX_TITE));
+            String url = getString(HistoryQuery.INDEX_URL);
             item.setUrl(url);
-            byte[] data = getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+            byte[] data = getBlob(HistoryQuery.INDEX_FAVICON);
             if (data != null) {
                 item.setFavicon(BitmapFactory.decodeByteArray(data, 0,
                         data.length));
@@ -359,8 +371,6 @@
                 item.setFavicon(CombinedBookmarkHistoryActivity
                         .getIconListenerSet().getFavicon(url));
             }
-            item.setIsBookmark(1 ==
-                    getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX));
             return item;
         }
     }
diff --git a/src/com/android/browser/DateSortedExpandableListAdapter.java b/src/com/android/browser/DateSortedExpandableListAdapter.java
index f8261d8..a48efe6 100644
--- a/src/com/android/browser/DateSortedExpandableListAdapter.java
+++ b/src/com/android/browser/DateSortedExpandableListAdapter.java
@@ -17,65 +17,55 @@
 package com.android.browser;
 
 import android.content.Context;
-import android.database.ContentObserver;
 import android.database.Cursor;
 import android.database.DataSetObserver;
-import android.os.Handler;
-import android.provider.BaseColumns;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.webkit.DateSorter;
-import android.widget.ExpandableListAdapter;
+import android.widget.BaseExpandableListAdapter;
 import android.widget.ExpandableListView;
 import android.widget.TextView;
 
-import java.util.Vector;
-
 /**
  * ExpandableListAdapter which separates data into categories based on date.
  * Used for History and Downloads.
  */
-public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
+public class DateSortedExpandableListAdapter extends BaseExpandableListAdapter {
     // Array for each of our bins.  Each entry represents how many items are
     // in that bin.
     private int mItemMap[];
     // This is our GroupCount.  We will have at most DateSorter.DAY_COUNT
     // bins, less if the user has no items in one or more bins.
     private int mNumberOfBins;
-    private Vector<DataSetObserver> mObservers;
     private Cursor mCursor;
     private DateSorter mDateSorter;
     private int mDateIndex;
     private int mIdIndex;
     private Context mContext;
 
-    private class ChangeObserver extends ContentObserver {
-        public ChangeObserver(Handler handler) {
-            super(handler);
+    boolean mDataValid;
+
+    DataSetObserver mDataSetObserver = new DataSetObserver() {
+        @Override
+        public void onChanged() {
+            mDataValid = true;
+            notifyDataSetChanged();
         }
 
         @Override
-        public boolean deliverSelfNotifications() {
-            return true;
+        public void onInvalidated() {
+            mDataValid = false;
+            notifyDataSetInvalidated();
         }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            refreshData();
-        }
-    }
-
-    public DateSortedExpandableListAdapter(Context context, Cursor cursor,
-            int dateIndex, Handler handler) {
+    };
+    
+    public DateSortedExpandableListAdapter(Context context, int dateIndex) {
         mContext = context;
         mDateSorter = new DateSorter(context);
-        mObservers = new Vector<DataSetObserver>();
-        mCursor = cursor;
-        mIdIndex = cursor.getColumnIndexOrThrow(BaseColumns._ID);
-        cursor.registerContentObserver(new ChangeObserver(handler));
         mDateIndex = dateIndex;
-        buildMap();
+        mDataValid = false;
+        mIdIndex = -1;
     }
 
     /**
@@ -122,6 +112,7 @@
      * @return corresponding byte array from the Cursor.
      */
     /* package */ byte[] getBlob(int cursorIndex) {
+        if (!mDataValid) return null; 
         return mCursor.getBlob(cursorIndex);
     }
 
@@ -138,6 +129,7 @@
      * @return corresponding integer from the Cursor.
      */
     /* package */ int getInt(int cursorIndex) {
+        if (!mDataValid) return 0; 
         return mCursor.getInt(cursorIndex);
     }
 
@@ -146,6 +138,7 @@
      * already been moved to the correct position.
      */
     /* package */ long getLong(int cursorIndex) {
+        if (!mDataValid) return 0; 
         return mCursor.getLong(cursorIndex);
     }
 
@@ -158,6 +151,7 @@
      * @return corresponding String from the Cursor.
      */
     /* package */ String getString(int cursorIndex) {
+        if (!mDataValid) return null; 
         return mCursor.getString(cursorIndex);
     }
 
@@ -166,6 +160,7 @@
      * @param childId ID of the child view in question.
      * @return int Group position of the containing group.
     /* package */ int groupFromChildId(long childId) {
+        if (!mDataValid) return -1; 
         int group = -1;
         for (mCursor.moveToFirst(); !mCursor.isAfterLast();
                 mCursor.moveToNext()) {
@@ -173,11 +168,15 @@
                 int bin = mDateSorter.getIndex(getLong(mDateIndex));
                 // bin is the same as the group if the number of bins is the
                 // same as DateSorter
-                if (mDateSorter.DAY_COUNT == mNumberOfBins) return bin;
+                if (DateSorter.DAY_COUNT == mNumberOfBins) {
+                    return bin;
+                }
                 // There are some empty bins.  Find the corresponding group.
                 group = 0;
                 for (int i = 0; i < bin; i++) {
-                    if (mItemMap[i] != 0) group++;
+                    if (mItemMap[i] != 0) {
+                        group++;
+                    }
                 }
                 break;
             }
@@ -193,6 +192,7 @@
      * @return The corresponding bin that holds that group.
      */
     private int groupPositionToBin(int groupPosition) {
+        if (!mDataValid) return -1; 
         if (groupPosition < 0 || groupPosition >= DateSorter.DAY_COUNT) {
             throw new AssertionError("group position out of range");
         }
@@ -241,7 +241,9 @@
      */
     /* package */ boolean moveCursorToChildPosition(int groupPosition,
             int childPosition) {
-        if (mCursor.isClosed()) return false;
+        if (!mDataValid || mCursor.isClosed()) {
+            return false;
+        }
         groupPosition = groupPositionToBin(groupPosition);
         int index = childPosition;
         for (int i = 0; i < groupPosition; i++) {
@@ -250,19 +252,34 @@
         return mCursor.moveToPosition(index);
     }
 
-    /* package */ void refreshData() {
-        if (mCursor.isClosed()) {
+    public void changeCursor(Cursor cursor) {
+        if (cursor == mCursor) {
             return;
         }
-        mCursor.requery();
-        buildMap();
-        for (DataSetObserver o : mObservers) {
-            o.onChanged();
+        if (mCursor != null) {
+            mCursor.unregisterDataSetObserver(mDataSetObserver);
+            mCursor.close();
+        }
+        mCursor = cursor;
+        if (cursor != null) {
+            cursor.registerDataSetObserver(mDataSetObserver);
+            mIdIndex = cursor.getColumnIndexOrThrow("_id");
+            mDataValid = true;
+            buildMap();
+            // notify the observers about the new cursor
+            notifyDataSetChanged();
+        } else {
+            mIdIndex = -1;
+            mDataValid = false;
+            // notify the observers about the lack of a data set
+            notifyDataSetInvalidated();
         }
     }
 
+    @Override
     public View getGroupView(int groupPosition, boolean isExpanded,
             View convertView, ViewGroup parent) {
+        if (!mDataValid) throw new IllegalStateException("Data is not valid"); 
         TextView item;
         if (null == convertView || !(convertView instanceof TextView)) {
             LayoutInflater factory = LayoutInflater.from(mContext);
@@ -275,73 +292,87 @@
         return item;
     }
 
+    @Override
     public View getChildView(int groupPosition, int childPosition,
             boolean isLastChild, View convertView, ViewGroup parent) {
+        if (!mDataValid) throw new IllegalStateException("Data is not valid"); 
         return null;
     }
 
+    @Override
     public boolean areAllItemsEnabled() {
         return true;
     }
 
+    @Override
     public boolean isChildSelectable(int groupPosition, int childPosition) {
         return true;
     }
 
+    @Override
     public int getGroupCount() {
+        if (!mDataValid) return 0;
         return mNumberOfBins;
     }
 
+    @Override
     public int getChildrenCount(int groupPosition) {
+        if (!mDataValid) return 0;
         return mItemMap[groupPositionToBin(groupPosition)];
     }
 
+    @Override
     public Object getGroup(int groupPosition) {
         return null;
     }
 
+    @Override
     public Object getChild(int groupPosition, int childPosition) {
         return null;
     }
 
+    @Override
     public long getGroupId(int groupPosition) {
+        if (!mDataValid) return 0; 
         return groupPosition;
     }
 
+    @Override
     public long getChildId(int groupPosition, int childPosition) {
+        if (!mDataValid) return 0; 
         if (moveCursorToChildPosition(groupPosition, childPosition)) {
             return getLong(mIdIndex);
         }
         return 0;
     }
 
+    @Override
     public boolean hasStableIds() {
         return true;
     }
 
-    public void registerDataSetObserver(DataSetObserver observer) {
-        mObservers.add(observer);
-    }
-
-    public void unregisterDataSetObserver(DataSetObserver observer) {
-        mObservers.remove(observer);
-    }
-
+    @Override
     public void onGroupExpanded(int groupPosition) {
     }
 
+    @Override
     public void onGroupCollapsed(int groupPosition) {
     }
 
+    @Override
     public long getCombinedChildId(long groupId, long childId) {
+        if (!mDataValid) return 0; 
         return childId;
     }
 
+    @Override
     public long getCombinedGroupId(long groupId) {
+        if (!mDataValid) return 0; 
         return groupId;
     }
 
+    @Override
     public boolean isEmpty() {
-        return mCursor.isClosed() || mCursor.getCount() == 0;
+        return !mDataValid || mCursor == null || mCursor.isClosed() || mCursor.getCount() == 0;
     }
 }
diff --git a/src/com/android/browser/DownloadTouchIcon.java b/src/com/android/browser/DownloadTouchIcon.java
index 2816f58..1442683 100644
--- a/src/com/android/browser/DownloadTouchIcon.java
+++ b/src/com/android/browser/DownloadTouchIcon.java
@@ -16,6 +16,13 @@
 
 package com.android.browser;
 
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.params.HttpClientParams;
+import org.apache.http.conn.params.ConnRouteParams;
+
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.ContentUris;
@@ -23,21 +30,14 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.net.http.AndroidHttpClient;
 import android.net.Proxy;
+import android.net.http.AndroidHttpClient;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Message;
-import android.provider.Browser;
+import android.provider.BrowserContract;
 import android.webkit.WebView;
 
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.params.HttpClientParams;
-import org.apache.http.conn.params.ConnRouteParams;
-
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -102,7 +102,7 @@
     public Void doInBackground(String... values) {
         if (mContentResolver != null) {
             mCursor = Bookmarks.queryBookmarksForUrl(mContentResolver,
-                    mOriginalUrl, mUrl, true);
+                    mOriginalUrl, mUrl);
         }
 
         boolean inBookmarksDatabase = mCursor != null && mCursor.getCount() > 0;
@@ -180,13 +180,13 @@
         final ByteArrayOutputStream os = new ByteArrayOutputStream();
         icon.compress(Bitmap.CompressFormat.PNG, 100, os);
         ContentValues values = new ContentValues();
-        values.put(Browser.BookmarkColumns.TOUCH_ICON,
+        values.put(BrowserContract.Bookmarks.TOUCH_ICON,
                 os.toByteArray());
 
         if (mCursor.moveToFirst()) {
             do {
                 mContentResolver.update(ContentUris.withAppendedId(
-                        Browser.BOOKMARKS_URI, mCursor.getInt(0)),
+                        BrowserContract.Bookmarks.CONTENT_URI, mCursor.getLong(0)),
                         values, null, null);
             } while (mCursor.moveToNext());
         }
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 9abf32f..e7309a6 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -16,13 +16,16 @@
 
 package com.android.browser;
 
+import com.android.browser.TabControl.TabChangeListener;
+import com.android.common.speech.LoggingEvents;
+
 import android.app.AlertDialog;
 import android.app.SearchManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.DialogInterface;
-import android.content.Intent;
 import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
@@ -34,14 +37,15 @@
 import android.os.Message;
 import android.os.SystemClock;
 import android.provider.Browser;
+import android.provider.BrowserContract.History;
 import android.speech.RecognizerResultsIntent;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.ViewStub;
-import android.view.View.OnClickListener;
 import android.webkit.ConsoleMessage;
 import android.webkit.CookieSyncManager;
 import android.webkit.DownloadListener;
@@ -63,10 +67,6 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import com.android.browser.TabControl.TabChangeListener;
-import com.android.common.speech.LoggingEvents;
-
-import java.io.File;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -992,26 +992,12 @@
                             .replace("_", "\\_");
                     Cursor c = null;
                     try {
-                        final ContentResolver cr
-                                = mActivity.getContentResolver();
-                        url = "%" + url;
-                        String [] selArgs = new String[] { url };
-                        String where = Browser.BookmarkColumns.URL
-                                + " LIKE ? ESCAPE '\\' AND "
-                                + Browser.BookmarkColumns.BOOKMARK + " = 0";
-                        c = cr.query(Browser.BOOKMARKS_URI, new String[]
-                                { Browser.BookmarkColumns._ID }, where, selArgs,
-                                null);
-                        if (c.moveToFirst()) {
-                            // Current implementation of database only has one
-                            // entry per url.
-                            ContentValues map = new ContentValues();
-                            map.put(Browser.BookmarkColumns.TITLE, title);
-                            String[] projection = new String[]
-                                    { Integer.valueOf(c.getInt(0)).toString() };
-                            cr.update(Browser.BOOKMARKS_URI, map, "_id = ?",
-                                    projection);
-                        }
+                        final ContentResolver cr = mActivity.getContentResolver();
+                        String selection = History.URL + " LIKE ? ESCAPE '\\'";
+                        String [] selectionArgs = new String[] { "%" + url };
+                        ContentValues values = new ContentValues();
+                        values.put(History.TITLE, title);
+                        cr.update(History.CONTENT_URI, values, selection, selectionArgs);
                     } catch (IllegalStateException e) {
                         Log.e(LOGTAG, "Tab onReceived title", e);
                     } catch (SQLiteException ex) {
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index 0aae911..ab24c03 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -51,9 +51,8 @@
     static final String TABLE_SEARCHES = "searches";
     static final String TABLE_SYNC_STATE = "syncstate";
 
-    static final String HISTORY_JOIN_BOOKMARKS =
-            "history LEFT OUTER JOIN bookmarks ON (history.url = bookmarks.url)";
-
+    static final String DEFAULT_HISTORY_SORT = History.DATE_LAST_VISITED + " DESC";
+    
     static final int BOOKMARKS = 1000;
     static final int BOOKMARKS_ID = 1001;
     static final int BOOKMARKS_FOLDER = 1002;
@@ -96,17 +95,13 @@
         matcher.addURI(BrowserContract.AUTHORITY, "syncstate", SYNCSTATE);
         matcher.addURI(BrowserContract.AUTHORITY, "syncstate/#", SYNCSTATE_ID);
 
-        // Common BookmarkColumns
-        HashMap<String, String> bookmarksColumns = new HashMap();
-        bookmarksColumns.put(Bookmarks.TITLE, Bookmarks.TITLE);
-        bookmarksColumns.put(Bookmarks.URL, Bookmarks.URL);
-        bookmarksColumns.put(Bookmarks.FAVICON, Bookmarks.FAVICON);
-        bookmarksColumns.put(Bookmarks.THUMBNAIL, Bookmarks.THUMBNAIL);
-        bookmarksColumns.put(Bookmarks.TOUCH_ICON, Bookmarks.TOUCH_ICON);
-
         // Bookmarks
         HashMap<String, String> map = BOOKMARKS_PROJECTION_MAP;
-        map.putAll(bookmarksColumns);
+        map.put(Bookmarks.TITLE, Bookmarks.TITLE);
+        map.put(Bookmarks.URL, Bookmarks.URL);
+        map.put(Bookmarks.FAVICON, Bookmarks.FAVICON);
+        map.put(Bookmarks.THUMBNAIL, Bookmarks.THUMBNAIL);
+        map.put(Bookmarks.TOUCH_ICON, Bookmarks.TOUCH_ICON);
         map.put(Bookmarks._ID, TABLE_BOOKMARKS + "._id AS _id");
         map.put(Bookmarks.IS_FOLDER, Bookmarks.IS_FOLDER);
         map.put(Bookmarks.PARENT, Bookmarks.PARENT);
@@ -130,11 +125,16 @@
 
         // History
         map = HISTORY_PROJECTION_MAP;
-        map.putAll(bookmarksColumns);
-        map.put(History._ID, TABLE_HISTORY + "._id AS _id");
+        map.put(History._ID, qualifyColumn(TABLE_HISTORY, History._ID));
+        map.put(History.TITLE, Bookmarks.TITLE);
+        map.put(History.URL, Bookmarks.URL);
+        map.put(History.FAVICON, Bookmarks.FAVICON);
+        map.put(History.THUMBNAIL, Bookmarks.THUMBNAIL);
+        map.put(History.TOUCH_ICON, Bookmarks.TOUCH_ICON);
         map.put(History.DATE_CREATED, History.DATE_CREATED);
         map.put(History.DATE_LAST_VISITED, History.DATE_LAST_VISITED);
         map.put(History.VISITS, History.VISITS);
+        map.put(History.USER_ENTERED, History.USER_ENTERED);
 
         // Sync state
         map = SYNC_STATE_PROJECTION_MAP;
@@ -144,12 +144,16 @@
         map.put(SyncState.DATA, SyncState.DATA);
     }
 
+    static final String qualifyColumn(String table, String column) {
+        return table + "." + column + " AS " + column;
+    }
+    
     DatabaseHelper mOpenHelper;
     SyncStateContentProviderHelper mSyncHelper = new SyncStateContentProviderHelper();
 
     final class DatabaseHelper extends SQLiteOpenHelper {
         static final String DATABASE_NAME = "browser2.db";
-        static final int DATABASE_VERSION = 11;
+        static final int DATABASE_VERSION = 15;
         public DatabaseHelper(Context context) {
             super(context, DATABASE_NAME, null, DATABASE_VERSION);
         }
@@ -186,10 +190,15 @@
 
             db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
                     History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    History.TITLE + " TEXT," +
                     History.URL + " TEXT NOT NULL," +
+                    History.FAVICON + " BLOB," +
+                    History.THUMBNAIL + " BLOB," +
+                    History.TOUCH_ICON + " BLOB," +
                     History.DATE_CREATED + " INTEGER," +
                     History.DATE_LAST_VISITED + " INTEGER," +
-                    History.VISITS + " INTEGER NOT NULL DEFAULT 0" +
+                    History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
+                    History.USER_ENTERED + " INTEGER" +
                     ");");
 
             db.execSQL("CREATE TABLE " + TABLE_SEARCHES + " (" +
@@ -482,8 +491,11 @@
                 // fall through
             }
             case HISTORY: {
+                if (sortOrder == null) {
+                    sortOrder = DEFAULT_HISTORY_SORT;
+                }
                 qb.setProjectionMap(HISTORY_PROJECTION_MAP);
-                qb.setTables(HISTORY_JOIN_BOOKMARKS);
+                qb.setTables(TABLE_HISTORY);
                 break;
             }