Get the widget off of startservice

 Bug: 3379120
 Now with drastically lower memory usage as well!

Change-Id: Iff16d2acceba3c8a983a51ec03a31a39ac6a12ed
diff --git a/src/com/android/browser/AccountsChangedReceiver.java b/src/com/android/browser/AccountsChangedReceiver.java
index 92d6ad0..a34180a 100644
--- a/src/com/android/browser/AccountsChangedReceiver.java
+++ b/src/com/android/browser/AccountsChangedReceiver.java
@@ -16,6 +16,8 @@
 
 package com.android.browser;
 
+import com.android.browser.widget.BookmarkThumbnailWidgetProvider;
+
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.content.BroadcastReceiver;
@@ -55,6 +57,7 @@
             ContentResolver.setSyncAutomatically(a, BrowserContract.AUTHORITY, false);
             ContentResolver.setIsSyncable(a, BrowserContract.AUTHORITY, 0);
         }
+        BookmarkThumbnailWidgetProvider.refreshWidgets(context, true);
     }
 
 }
diff --git a/src/com/android/browser/preferences/GeneralPreferencesFragment.java b/src/com/android/browser/preferences/GeneralPreferencesFragment.java
index b6228dc..33bfd9c 100644
--- a/src/com/android/browser/preferences/GeneralPreferencesFragment.java
+++ b/src/com/android/browser/preferences/GeneralPreferencesFragment.java
@@ -21,6 +21,7 @@
 import com.android.browser.BrowserPreferencesPage;
 import com.android.browser.BrowserSettings;
 import com.android.browser.R;
+import com.android.browser.widget.BookmarkThumbnailWidgetProvider;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
@@ -125,6 +126,7 @@
             if (BrowserBookmarksPage.PREF_ACCOUNT_NAME.equals(key)
                     || BrowserBookmarksPage.PREF_ACCOUNT_TYPE.equals(key)) {
                 refreshUi();
+                BookmarkThumbnailWidgetProvider.refreshWidgets(getActivity(), true);
             }
         }
 
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index a51635c..74cb773 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -19,6 +19,7 @@
 import com.android.browser.BookmarkUtils;
 import com.android.browser.BrowserBookmarksPage;
 import com.android.browser.R;
+import com.android.browser.widget.BookmarkThumbnailWidgetProvider;
 import com.android.common.content.SyncStateContentProviderHelper;
 
 import android.accounts.Account;
@@ -35,6 +36,7 @@
 import android.database.AbstractCursor;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
+import android.database.MatrixCursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
@@ -106,6 +108,7 @@
     static final int BOOKMARKS_FOLDER = 1002;
     static final int BOOKMARKS_FOLDER_ID = 1003;
     static final int BOOKMARKS_SUGGESTIONS = 1004;
+    static final int BOOKMARKS_DEFAULT_FOLDER_ID = 1005;
 
     static final int HISTORY = 2000;
     static final int HISTORY_ID = 2001;
@@ -159,6 +162,7 @@
         matcher.addURI(authority, "bookmarks/#", BOOKMARKS_ID);
         matcher.addURI(authority, "bookmarks/folder", BOOKMARKS_FOLDER);
         matcher.addURI(authority, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
+        matcher.addURI(authority, "bookmarks/folder/id", BOOKMARKS_DEFAULT_FOLDER_ID);
         matcher.addURI(authority,
                 SearchManager.SUGGEST_URI_PATH_QUERY,
                 BOOKMARKS_SUGGESTIONS);
@@ -758,6 +762,15 @@
                 return cursor;
             }
 
+            case BOOKMARKS_DEFAULT_FOLDER_ID: {
+                String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
+                String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
+                long id = queryDefaultFolderId(accountName, accountType);
+                MatrixCursor c = new MatrixCursor(new String[] {Bookmarks._ID});
+                c.newRow().add(id);
+                return c;
+            }
+
             case BOOKMARKS_SUGGESTIONS: {
                 return doSuggestQuery(selection, selectionArgs, limit);
             }
@@ -973,6 +986,9 @@
                 selectionArgs = (String[]) withAccount[1];
                 int deleted = deleteBookmarks(selection, selectionArgs, callerIsSyncAdapter);
                 pruneImages();
+                if (deleted > 0) {
+                    BookmarkThumbnailWidgetProvider.refreshWidgets(getContext());
+                }
                 return deleted;
             }
 
@@ -1054,7 +1070,7 @@
     }
 
     long queryDefaultFolderId(String accountName, String accountType) {
-        if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
+        if (!isNullAccount(accountName) && !isNullAccount(accountType)) {
             final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
             Cursor c = db.query(TABLE_BOOKMARKS, new String[] { Bookmarks._ID },
                     ChromeSyncColumns.SERVER_UNIQUE + " = ?" +
@@ -1134,6 +1150,7 @@
                 }
 
                 id = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
+                BookmarkThumbnailWidgetProvider.refreshWidgets(getContext());
                 break;
             }
 
@@ -1302,6 +1319,9 @@
                 int updated = updateBookmarksInTransaction(values, selection, selectionArgs,
                         callerIsSyncAdapter);
                 pruneImages();
+                if (updated > 0) {
+                    BookmarkThumbnailWidgetProvider.refreshWidgets(getContext());
+                }
                 return updated;
             }
 
@@ -1342,6 +1362,9 @@
                     db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, values);
                     count = 1;
                 }
+                if (count > 0) {
+                    BookmarkThumbnailWidgetProvider.refreshWidgets(getContext());
+                }
                 return count;
             }
 
diff --git a/src/com/android/browser/widget/BookmarkThumbnailWidgetProvider.java b/src/com/android/browser/widget/BookmarkThumbnailWidgetProvider.java
index b991abd..48d7123 100644
--- a/src/com/android/browser/widget/BookmarkThumbnailWidgetProvider.java
+++ b/src/com/android/browser/widget/BookmarkThumbnailWidgetProvider.java
@@ -32,12 +32,9 @@
  * Widget that shows a preview of the user's bookmarks.
  */
 public class BookmarkThumbnailWidgetProvider extends AppWidgetProvider {
-    static final String ACTION_BOOKMARK_APPWIDGET_UPDATE =
+    public static final String ACTION_BOOKMARK_APPWIDGET_UPDATE =
         "com.android.browser.BOOKMARK_APPWIDGET_UPDATE";
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
     public void onReceive(Context context, Intent intent) {
         // Handle bookmark-specific updates ourselves because they might be
@@ -58,23 +55,27 @@
     }
 
     @Override
-    public void onEnabled(Context context) {
-        // Start the backing service
-        context.startService(new Intent(context, BookmarkThumbnailWidgetService.class));
+    public void onDeleted(Context context, int[] appWidgetIds) {
+        super.onDeleted(context, appWidgetIds);
+        for (int widgetId : appWidgetIds) {
+            BookmarkThumbnailWidgetService.deleteWidgetState(context, widgetId);
+        }
+        removeOrphanedFiles(context);
     }
 
     @Override
     public void onDisabled(Context context) {
-        // Stop the backing service
-        context.stopService(new Intent(context, BookmarkThumbnailWidgetService.class));
+        super.onDisabled(context);
+        removeOrphanedFiles(context);
     }
 
-    @Override
-    public void onDeleted(Context context, int[] appWidgetIds) {
-        super.onDeleted(context, appWidgetIds);
-        context.startService(new Intent(BookmarkThumbnailWidgetService.ACTION_REMOVE_FACTORIES,
-                null, context, BookmarkThumbnailWidgetService.class)
-                .putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds));
+    /**
+     *  Checks for any state files that may have not received onDeleted
+     */
+    void removeOrphanedFiles(Context context) {
+        AppWidgetManager wm = AppWidgetManager.getInstance(context);
+        int[] ids = wm.getAppWidgetIds(getComponentName(context));
+        BookmarkThumbnailWidgetService.removeOrphanedStates(context, ids);
     }
 
     private void performUpdate(Context context,
@@ -92,9 +93,9 @@
             views.setOnClickPendingIntent(R.id.app_shortcut, launchBrowser);
             views.setRemoteAdapter(appWidgetId, R.id.bookmarks_list, updateIntent);
             appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.bookmarks_list);
-            Intent ic = new Intent(context, BookmarkThumbnailWidgetService.class);
+            Intent ic = new Intent(context, BookmarkWidgetProxy.class);
             views.setPendingIntentTemplate(R.id.bookmarks_list,
-                    PendingIntent.getService(context, 0, ic,
+                    PendingIntent.getBroadcast(context, 0, ic,
                     PendingIntent.FLAG_UPDATE_CURRENT));
             appWidgetManager.updateAppWidget(appWidgetId, views);
         }
@@ -107,4 +108,29 @@
     static ComponentName getComponentName(Context context) {
         return new ComponentName(context, BookmarkThumbnailWidgetProvider.class);
     }
+
+    public static void refreshWidgets(Context context) {
+        refreshWidgets(context, false);
+    }
+
+    public static void refreshWidgets(Context context, boolean zeroState) {
+        if (zeroState) {
+            final Context appContext = context.getApplicationContext();
+            new Thread() {
+                @Override
+                public void run() {
+                    AppWidgetManager wm = AppWidgetManager.getInstance(appContext);
+                    int[] ids = wm.getAppWidgetIds(getComponentName(appContext));
+                    for (int id : ids) {
+                        BookmarkThumbnailWidgetService.clearWidgetState(appContext, id);
+                    }
+                    refreshWidgets(appContext, false);
+                }
+            }.start();
+        } else {
+            context.sendBroadcast(new Intent(
+                    BookmarkThumbnailWidgetProvider.ACTION_BOOKMARK_APPWIDGET_UPDATE,
+                    null, context, BookmarkThumbnailWidgetProvider.class));
+        }
+    }
 }
diff --git a/src/com/android/browser/widget/BookmarkThumbnailWidgetService.java b/src/com/android/browser/widget/BookmarkThumbnailWidgetService.java
index e525159..675cdd9 100644
--- a/src/com/android/browser/widget/BookmarkThumbnailWidgetService.java
+++ b/src/com/android/browser/widget/BookmarkThumbnailWidgetService.java
@@ -16,8 +16,8 @@
 
 package com.android.browser.widget;
 
+import com.android.browser.BookmarkUtils;
 import com.android.browser.BrowserActivity;
-import com.android.browser.BrowserBookmarksPage;
 import com.android.browser.R;
 
 import android.appwidget.AppWidgetManager;
@@ -25,17 +25,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.database.ContentObserver;
 import android.database.Cursor;
+import android.database.MergeCursor;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapFactory.Options;
 import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.preference.PreferenceManager;
+import android.os.Binder;
 import android.provider.BrowserContract;
 import android.provider.BrowserContract.Bookmarks;
 import android.text.TextUtils;
@@ -43,120 +40,38 @@
 import android.widget.RemoteViews;
 import android.widget.RemoteViewsService;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Stack;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class BookmarkThumbnailWidgetService extends RemoteViewsService {
 
     static final String TAG = "BookmarkThumbnailWidgetService";
-    static final boolean USE_FOLDERS = true;
-
-    static final String ACTION_REMOVE_FACTORIES
-            = "com.android.browser.widget.REMOVE_FACTORIES";
     static final String ACTION_CHANGE_FOLDER
             = "com.android.browser.widget.CHANGE_FOLDER";
 
+    static final String STATE_CURRENT_FOLDER = "current_folder";
+    static final String STATE_ROOT_FOLDER = "root_folder";
+
     private static final String[] PROJECTION = new String[] {
             BrowserContract.Bookmarks._ID,
             BrowserContract.Bookmarks.TITLE,
             BrowserContract.Bookmarks.URL,
             BrowserContract.Bookmarks.FAVICON,
             BrowserContract.Bookmarks.IS_FOLDER,
-            BrowserContract.Bookmarks.TOUCH_ICON,
             BrowserContract.Bookmarks.POSITION, /* needed for order by */
-            BrowserContract.Bookmarks.THUMBNAIL};
+            BrowserContract.Bookmarks.THUMBNAIL,
+            BrowserContract.Bookmarks.PARENT};
     private static final int BOOKMARK_INDEX_ID = 0;
     private static final int BOOKMARK_INDEX_TITLE = 1;
     private static final int BOOKMARK_INDEX_URL = 2;
     private static final int BOOKMARK_INDEX_FAVICON = 3;
     private static final int BOOKMARK_INDEX_IS_FOLDER = 4;
-    private static final int BOOKMARK_INDEX_TOUCH_ICON = 5;
-    private static final int BOOKMARK_INDEX_THUMBNAIL = 7;
-
-    // The service will likely be destroyed at any time, so we need to keep references to the
-    // factories across services connections.
-    private static final Map<Integer, BookmarkFactory> mFactories =
-            new HashMap<Integer, BookmarkFactory>();
-    private Handler mUiHandler;
-    private BookmarksObserver mBookmarksObserver;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mUiHandler = new Handler();
-        mBookmarksObserver = new BookmarksObserver(mUiHandler);
-        getContentResolver().registerContentObserver(
-                BrowserContract.Bookmarks.CONTENT_URI, true, mBookmarksObserver);
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        String action = intent.getAction();
-        if (Intent.ACTION_VIEW.equals(action)) {
-            if (intent.getData() == null) {
-                startActivity(new Intent(BrowserActivity.ACTION_SHOW_BROWSER, null,
-                        this, BrowserActivity.class)
-                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-            } else {
-                Intent view = new Intent(intent);
-                view.setComponent(null);
-                startActivity(view);
-            }
-        } else if (ACTION_REMOVE_FACTORIES.equals(action)) {
-            int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
-            if (ids != null) {
-                for (int id : ids) {
-                    BookmarkFactory bf = mFactories.remove(id);
-                    if (bf != null) {
-                        // Workaround a known framework bug
-                        // onDestroy is currently never called
-                        bf.onDestroy();
-                    }
-                }
-            }
-        } else if (ACTION_CHANGE_FOLDER.equals(action)) {
-            int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
-            long folderId = intent.getLongExtra(Bookmarks._ID, -1);
-            BookmarkFactory fac = mFactories.get(widgetId);
-            if (fac != null && folderId >= 0) {
-                fac.changeFolder(folderId);
-            } else {
-                // This a workaround to the issue when the Browser process crashes, after which
-                // mFactories is not populated (due to onBind() not being called).  Calling
-                // notifyDataSetChanged() will trigger a connection to be made.
-                AppWidgetManager.getInstance(getApplicationContext())
-                    .notifyAppWidgetViewDataChanged(widgetId, R.id.bookmarks_list);
-            }
-        }
-        return START_STICKY;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        getContentResolver().unregisterContentObserver(mBookmarksObserver);
-    }
-
-    private class BookmarksObserver extends ContentObserver {
-        public BookmarksObserver(Handler handler) {
-            super(handler);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-
-            // Update all the bookmark widgets
-            if (mFactories != null) {
-                for (BookmarkFactory fac : mFactories.values()) {
-                    fac.loadData();
-                }
-            }
-        }
-    }
+    private static final int BOOKMARK_INDEX_THUMBNAIL = 6;
+    private static final int BOOKMARK_INDEX_PARENT_ID = 7;
 
     @Override
     public RemoteViewsFactory onGetViewFactory(Intent intent) {
@@ -164,64 +79,125 @@
         if (widgetId < 0) {
             Log.w(TAG, "Missing EXTRA_APPWIDGET_ID!");
             return null;
-        } else {
-            BookmarkFactory fac = mFactories.get(widgetId);
-            if (fac == null) {
-                fac = new BookmarkFactory(getApplicationContext(), widgetId);
+        }
+        return new BookmarkFactory(getApplicationContext(), widgetId);
+    }
+
+    static SharedPreferences getWidgetState(Context context, int widgetId) {
+        return context.getSharedPreferences(
+                String.format("widgetState-%d", widgetId),
+                Context.MODE_PRIVATE);
+    }
+
+    static void deleteWidgetState(Context context, int widgetId) {
+        File file = context.getSharedPrefsFile(
+                String.format("widgetState-%d", widgetId));
+        if (file.exists()) {
+            if (!file.delete()) {
+                file.deleteOnExit();
             }
-            mFactories.put(widgetId, fac);
-            return fac;
         }
     }
 
-    private static class Breadcrumb {
-        long mId;
-        String mTitle;
-        public Breadcrumb(long id, String title) {
-            mId = id;
-            mTitle = title;
+    static void changeFolder(Context context, Intent intent) {
+        int wid = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+        long fid = intent.getLongExtra(Bookmarks._ID, -1);
+        if (wid >= 0 && fid >= 0) {
+            SharedPreferences prefs = getWidgetState(context, wid);
+            prefs.edit().putLong(STATE_CURRENT_FOLDER, fid).commit();
+            AppWidgetManager.getInstance(context)
+                    .notifyAppWidgetViewDataChanged(wid, R.id.bookmarks_list);
         }
     }
 
-    static class BookmarkFactory implements RemoteViewsService.RemoteViewsFactory,
-            OnSharedPreferenceChangeListener {
-        private List<RenderResult> mBookmarks;
+    static void clearWidgetState(Context context, int widgetId) {
+        SharedPreferences pref = getWidgetState(context, widgetId);
+        pref.edit()
+            .remove(STATE_CURRENT_FOLDER)
+            .remove(STATE_ROOT_FOLDER)
+            .commit();
+    }
+
+    /**
+     *  Checks for any state files that may have not received onDeleted
+     */
+    static void removeOrphanedStates(Context context, int[] widgetIds) {
+        File prefsDirectory = context.getSharedPrefsFile("null").getParentFile();
+        File[] widgetStates = prefsDirectory.listFiles(new StateFilter(widgetIds));
+        for (File f : widgetStates) {
+            Log.w(TAG, "Found orphaned state: " + f.getName());
+            if (!f.delete()) {
+                f.deleteOnExit();
+            }
+        }
+    }
+
+    static class StateFilter implements FilenameFilter {
+
+        static final Pattern sStatePattern = Pattern.compile("widgetState-(\\d+)\\.xml");
+        HashSet<Integer> mWidgetIds;
+
+        StateFilter(int[] ids) {
+            mWidgetIds = new HashSet<Integer>();
+            for (int id : ids) {
+                mWidgetIds.add(id);
+            }
+        }
+
+        @Override
+        public boolean accept(File dir, String filename) {
+            Matcher m = sStatePattern.matcher(filename);
+            if (m.matches()) {
+                int id = Integer.parseInt(m.group(1));
+                if (!mWidgetIds.contains(id)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+    }
+
+    static class BookmarkFactory implements RemoteViewsService.RemoteViewsFactory {
+        private Cursor mBookmarks;
         private Context mContext;
         private int mWidgetId;
-        private String mAccountType;
-        private String mAccountName;
-        private Stack<Breadcrumb> mBreadcrumbs;
-        private LoadBookmarksTask mLoadTask;
+        private long mCurrentFolder = -1;
+        private long mRootFolder = -1;
+        private SharedPreferences mPreferences = null;
 
         public BookmarkFactory(Context context, int widgetId) {
-            mBreadcrumbs = new Stack<Breadcrumb>();
             mContext = context;
             mWidgetId = widgetId;
         }
 
-        void changeFolder(long folderId) {
-            if (mBookmarks == null) return;
-
-            if (!mBreadcrumbs.empty() && mBreadcrumbs.peek().mId == folderId) {
-                mBreadcrumbs.pop();
-                loadData();
-                return;
+        void syncState() {
+            if (mPreferences == null) {
+                mPreferences = getWidgetState(mContext, mWidgetId);
             }
-
-            for (RenderResult res : mBookmarks) {
-                if (res.mId == folderId) {
-                    mBreadcrumbs.push(new Breadcrumb(res.mId, res.mTitle));
-                    loadData();
-                    break;
-                }
+            long currentFolder = mPreferences.getLong(STATE_CURRENT_FOLDER, -1);
+            mRootFolder = mPreferences.getLong(STATE_ROOT_FOLDER, -1);
+            if (currentFolder != mCurrentFolder) {
+                resetBookmarks();
+                mCurrentFolder = currentFolder;
             }
         }
 
+        void saveState() {
+            if (mPreferences == null) {
+                mPreferences = getWidgetState(mContext, mWidgetId);
+            }
+            mPreferences.edit()
+                .putLong(STATE_CURRENT_FOLDER, mCurrentFolder)
+                .putLong(STATE_ROOT_FOLDER, mRootFolder)
+                .commit();
+        }
+
         @Override
         public int getCount() {
             if (mBookmarks == null)
                 return 0;
-            return mBookmarks.size();
+            return mBookmarks.getCount();
         }
 
         @Override
@@ -231,44 +207,33 @@
 
         @Override
         public RemoteViews getLoadingView() {
-            return null;
+            return new RemoteViews(
+                    mContext.getPackageName(), R.layout.bookmarkthumbnailwidget_item);
         }
 
         @Override
         public RemoteViews getViewAt(int position) {
-            if (position < 0 || position >= getCount()) {
+            if (!mBookmarks.moveToPosition(position)) {
                 return null;
             }
 
-            RenderResult res = mBookmarks.get(position);
-            Breadcrumb folder = mBreadcrumbs.empty() ? null : mBreadcrumbs.peek();
+            long id = mBookmarks.getLong(BOOKMARK_INDEX_ID);
+            String title = mBookmarks.getString(BOOKMARK_INDEX_TITLE);
+            String url = mBookmarks.getString(BOOKMARK_INDEX_URL);
+            boolean isFolder = mBookmarks.getInt(BOOKMARK_INDEX_IS_FOLDER) != 0;
 
             RemoteViews views = new RemoteViews(
                     mContext.getPackageName(), R.layout.bookmarkthumbnailwidget_item);
-            Intent fillin;
-            if (res.mIsFolder) {
-                long nfi = res.mId;
-                fillin = new Intent(ACTION_CHANGE_FOLDER, null,
-                        mContext, BookmarkThumbnailWidgetService.class)
-                        .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
-                        .putExtra(Bookmarks._ID, nfi);
-            } else {
-                fillin = new Intent(Intent.ACTION_VIEW)
-                        .addCategory(Intent.CATEGORY_BROWSABLE);
-                if (!TextUtils.isEmpty(res.mUrl)) {
-                    fillin.setData(Uri.parse(res.mUrl));
-                }
-            }
-            views.setOnClickFillInIntent(R.id.list_item, fillin);
             // Set the title of the bookmark. Use the url as a backup.
-            String displayTitle = res.mTitle;
+            String displayTitle = title;
             if (TextUtils.isEmpty(displayTitle)) {
                 // The browser always requires a title for bookmarks, but jic...
-                displayTitle = res.mUrl;
+                displayTitle = url;
             }
             views.setTextViewText(R.id.label, displayTitle);
-            if (res.mIsFolder) {
-                if (folder != null && res.mId == folder.mId) {
+            if (isFolder) {
+                if (id == mCurrentFolder) {
+                    id = mBookmarks.getLong(BOOKMARK_INDEX_PARENT_ID);
                     views.setImageViewResource(R.id.thumb, R.drawable.thumb_bookmark_widget_folder_back_holo);
                 } else {
                     views.setImageViewResource(R.id.thumb, R.drawable.thumb_bookmark_widget_folder_holo);
@@ -276,20 +241,45 @@
                 views.setImageViewResource(R.id.favicon, R.drawable.ic_bookmark_widget_bookmark_holo_dark);
                 views.setDrawableParameters(R.id.thumb, true, 0, -1, null, -1);
             } else {
+                // RemoteViews require a valid bitmap config
+                Options options = new Options();
+                options.inPreferredConfig = Config.ARGB_8888;
+                Bitmap thumbnail = null, favicon = null;
+                byte[] blob = mBookmarks.getBlob(BOOKMARK_INDEX_THUMBNAIL);
                 views.setDrawableParameters(R.id.thumb, true, 255, -1, null, -1);
-                if (res.mThumbnail != null) {
-                    views.setImageViewBitmap(R.id.thumb, res.mThumbnail);
+                if (blob != null && blob.length > 0) {
+                    thumbnail = BitmapFactory.decodeByteArray(
+                            blob, 0, blob.length, options);
+                    views.setImageViewBitmap(R.id.thumb, thumbnail);
                 } else {
                     views.setImageViewResource(R.id.thumb,
                             R.drawable.browser_thumbnail);
                 }
-                if (res.mIcon != null) {
-                    views.setImageViewBitmap(R.id.favicon, res.mIcon);
+                blob = mBookmarks.getBlob(BOOKMARK_INDEX_FAVICON);
+                if (blob != null && blob.length > 0) {
+                    favicon = BitmapFactory.decodeByteArray(
+                            blob, 0, blob.length, options);
+                    views.setImageViewBitmap(R.id.favicon, favicon);
                 } else {
                     views.setImageViewResource(R.id.favicon,
                             R.drawable.app_web_browser_sm);
                 }
             }
+            Intent fillin;
+            if (isFolder) {
+                fillin = new Intent(ACTION_CHANGE_FOLDER)
+                        .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
+                        .putExtra(Bookmarks._ID, id);
+            } else {
+                if (!TextUtils.isEmpty(url)) {
+                    fillin = new Intent(Intent.ACTION_VIEW)
+                            .addCategory(Intent.CATEGORY_BROWSABLE)
+                            .setData(Uri.parse(url));
+                } else {
+                    fillin = new Intent(BrowserActivity.ACTION_SHOW_BROWSER);
+                }
+            }
+            views.setOnClickFillInIntent(R.id.list_item, fillin);
             return views;
         }
 
@@ -305,187 +295,70 @@
 
         @Override
         public void onCreate() {
-            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
-            mAccountType = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null);
-            mAccountName = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
-            prefs.registerOnSharedPreferenceChangeListener(this);
-            loadData();
         }
 
         @Override
         public void onDestroy() {
-            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
-            prefs.unregisterOnSharedPreferenceChangeListener(this);
-
-            // Workaround known framework bug
-            // This class currently leaks, so free as much memory as we can
-            recycleBitmaps();
-            mBookmarks.clear();
-            mBreadcrumbs.clear();
-            if (mLoadTask != null) {
-                mLoadTask.cancel(false);
-                mLoadTask = null;
+            if (mBookmarks != null) {
+                mBookmarks.close();
+                mBookmarks = null;
             }
+            deleteWidgetState(mContext, mWidgetId);
         }
 
         @Override
         public void onDataSetChanged() {
+            long token = Binder.clearCallingIdentity();
+            syncState();
+            if (mRootFolder < 0 || mCurrentFolder < 0) {
+                // Our state has been zero'd, reset (account change most likely)
+                mRootFolder = getRootFolder();
+                mCurrentFolder = mRootFolder;
+                saveState();
+            }
+            loadBookmarks();
+            Binder.restoreCallingIdentity(token);
         }
 
-        void loadData() {
-            if (mLoadTask != null) {
-                mLoadTask.cancel(false);
-            }
-            mLoadTask = new LoadBookmarksTask();
-            mLoadTask.execute();
-        }
-
-        class LoadBookmarksTask extends AsyncTask<Void, Void, List<RenderResult>> {
-            private Breadcrumb mFolder;
-
-            @Override
-            protected void onPreExecute() {
-                mFolder = mBreadcrumbs.empty() ? null : mBreadcrumbs.peek();
-            }
-
-            @Override
-            protected List<RenderResult> doInBackground(Void... params) {
-                return loadBookmarks(mFolder);
-            }
-
-            @Override
-            protected void onPostExecute(List<RenderResult> result) {
-                if (!isCancelled() && result != null) {
-                    recycleBitmaps();
-                    mBookmarks = result;
-                    AppWidgetManager.getInstance(mContext)
-                            .notifyAppWidgetViewDataChanged(mWidgetId, R.id.bookmarks_list);
-                }
-            }
-        }
-
-        List<RenderResult> loadBookmarks(Breadcrumb folder) {
-            String where = null;
-            Uri uri;
-            if (USE_FOLDERS) {
-                uri = BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER;
-                if (folder != null) {
-                    uri = ContentUris.withAppendedId(uri, folder.mId);
-                }
-            } else {
-                uri = BrowserContract.Bookmarks.CONTENT_URI;
-                where = Bookmarks.IS_FOLDER + " == 0";
-            }
-            uri = uri.buildUpon()
-                    .appendQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE, mAccountType)
-                    .appendQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME, mAccountName)
-                    .build();
-            Cursor c = null;
-            try {
-                c = mContext.getContentResolver().query(uri, PROJECTION,
-                        where, null, null);
-                if (c != null) {
-                    ArrayList<RenderResult> bookmarks
-                            = new ArrayList<RenderResult>(c.getCount() + 1);
-                    if (folder != null) {
-                        RenderResult res = new RenderResult(
-                                folder.mId, folder.mTitle, null);
-                        res.mIsFolder = true;
-                        bookmarks.add(res);
-                    }
-                    while (c.moveToNext()) {
-                        long id = c.getLong(BOOKMARK_INDEX_ID);
-                        String title = c.getString(BOOKMARK_INDEX_TITLE);
-                        String url = c.getString(BOOKMARK_INDEX_URL);
-                        RenderResult res = new RenderResult(id, title, url);
-                        res.mIsFolder = c.getInt(BOOKMARK_INDEX_IS_FOLDER) != 0;
-                        if (!res.mIsFolder) {
-                            // RemoteViews require a valid bitmap config
-                            Options options = new Options();
-                            options.inPreferredConfig = Config.ARGB_8888;
-                            Bitmap thumbnail = null, favicon = null;
-                            byte[] blob = c.getBlob(BOOKMARK_INDEX_THUMBNAIL);
-                            if (blob != null && blob.length > 0) {
-                                thumbnail = BitmapFactory.decodeByteArray(
-                                        blob, 0, blob.length, options);
-                            }
-                            blob = c.getBlob(BOOKMARK_INDEX_FAVICON);
-                            if (blob != null && blob.length > 0) {
-                                favicon = BitmapFactory.decodeByteArray(
-                                        blob, 0, blob.length, options);
-                            }
-                            res.mThumbnail = thumbnail;
-                            res.mIcon = favicon;
-                        }
-                        bookmarks.add(res);
-                    }
-                    if (bookmarks.size() == 0) {
-                        RenderResult res = new RenderResult(0, "", "");
-                        Bitmap thumbnail = BitmapFactory.decodeResource(
-                                mContext.getResources(),
-                                R.drawable.thumbnail_bookmarks_widget_no_bookmark_holo);
-                        Bitmap favicon = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
-                        res.mThumbnail = thumbnail;
-                        res.mIcon = favicon;
-                        for (int i = 0; i < 6; i++) {
-                            bookmarks.add(res);
-                        }
-                    }
-                    return bookmarks;
-                }
-            } catch (IllegalStateException e) {
-                Log.e(TAG, "update bookmark widget", e);
-            } finally {
-                if (c != null) {
-                    c.close();
-                }
-            }
-            return null;
-        }
-
-        private void recycleBitmaps() {
-            // Do a bit of house cleaning for the system
+        private void resetBookmarks() {
             if (mBookmarks != null) {
-                for (RenderResult res : mBookmarks) {
-                    if (res.mThumbnail != null) {
-                        res.mThumbnail.recycle();
-                        res.mThumbnail = null;
-                    }
-                }
+                mBookmarks.close();
+                mBookmarks = null;
             }
         }
 
-        @Override
-        public void onSharedPreferenceChanged(
-                SharedPreferences prefs, String key) {
-            if (BrowserBookmarksPage.PREF_ACCOUNT_TYPE.equals(key)) {
-                mAccountType = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null);
-                mBreadcrumbs.clear();
-                loadData();
-            }
-            if (BrowserBookmarksPage.PREF_ACCOUNT_NAME.equals(key)) {
-                mAccountName = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
-                mBreadcrumbs.clear();
-                loadData();
+        long getRootFolder() {
+            Uri uri = Uri.withAppendedPath(
+                    BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, "id");
+            uri = BookmarkUtils.addAccountInfo(mContext, uri.buildUpon()).build();
+            Cursor c = mContext.getContentResolver().query(
+                    uri, null, null, null, null);
+            try {
+                c.moveToFirst();
+                return c.getLong(0);
+            } finally {
+                c.close();
             }
         }
-    }
 
-    // Class containing the rendering information for a specific bookmark.
-    private static class RenderResult {
-        final String mTitle;
-        final String mUrl;
-        Bitmap mThumbnail;
-        Bitmap mIcon;
-        boolean mIsFolder;
-        long mId;
+        void loadBookmarks() {
+            resetBookmarks();
 
-        RenderResult(long id, String title, String url) {
-            mId = id;
-            mTitle = title;
-            mUrl = url;
+            Uri uri = ContentUris.withAppendedId(
+                    BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER,
+                    mCurrentFolder);
+            uri = BookmarkUtils.addAccountInfo(mContext, uri.buildUpon()).build();
+            mBookmarks = mContext.getContentResolver().query(uri, PROJECTION,
+                    null, null, null);
+            if (mCurrentFolder != mRootFolder) {
+                uri = ContentUris.withAppendedId(
+                        BrowserContract.Bookmarks.CONTENT_URI,
+                        mCurrentFolder);
+                Cursor c = mContext.getContentResolver().query(uri, PROJECTION,
+                        null, null, null);
+                mBookmarks = new MergeCursor(new Cursor[] { c, mBookmarks });
+            }
         }
-
     }
 
 }
diff --git a/src/com/android/browser/widget/BookmarkWidgetProxy.java b/src/com/android/browser/widget/BookmarkWidgetProxy.java
new file mode 100644
index 0000000..8ab57fc
--- /dev/null
+++ b/src/com/android/browser/widget/BookmarkWidgetProxy.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.browser.widget;
+
+import com.android.browser.BrowserActivity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class BookmarkWidgetProxy extends BroadcastReceiver {
+
+    private static final String TAG = "BookmarkWidgetProxy";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (BookmarkThumbnailWidgetService.ACTION_CHANGE_FOLDER.equals(intent.getAction())) {
+            BookmarkThumbnailWidgetService.changeFolder(context, intent);
+        } else if (BrowserActivity.ACTION_SHOW_BROWSER.equals(intent.getAction())) {
+            startActivity(context,
+                    new Intent(BrowserActivity.ACTION_SHOW_BROWSER,
+                    null, context, BrowserActivity.class));
+        } else {
+            Intent view = new Intent(intent);
+            view.setComponent(null);
+            startActivity(context, view);
+        }
+    }
+
+    void startActivity(Context context, Intent intent) {
+        try {
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            context.startActivity(intent);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to start intent activity", e);
+        }
+    }
+}