New bookmark list widget

  Bug: 3144077 and 2988059
  This replaces the old stack based widget with a list based one. It
  also updates whenever the bookmarks changes rather than periodically

Change-Id: Ie37978918bab441bf31a6131360e308cd2bc0f4b
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 89e93dc..8e6934c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -200,19 +200,22 @@
                   android:configChanges="orientation|keyboardHidden" android:windowSoftInputMode="stateHidden">
         </activity>
 
+        <!-- Bookmark list homescreen widget -->
         <receiver
-            android:name=".widget.BookmarkStackWidgetProvider"
+            android:name=".widget.BookmarkListWidgetProvider"
             android:label="@string/bookmarks">
             <intent-filter>
                 <action
                     android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+                <action
+                    android:name="com.android.browser.BOOKMARK_APPWIDGET_UPDATE" />
             </intent-filter>
             <meta-data
                 android:name="android.appwidget.provider"
-                android:resource="@xml/bookmarkstackwidget" />
+                android:resource="@xml/bookmarklistwidget_info" />
         </receiver>
         <service
-            android:name=".widget.BookmarkStackWidgetService"
+            android:name=".widget.BookmarkListWidgetService"
             android:exported="true" />
 
         <!-- Makes .BrowserActivity the search target for any activity in Browser -->
diff --git a/res/layout/bookmarklistwidget.xml b/res/layout/bookmarklistwidget.xml
new file mode 100644
index 0000000..ffaa0c3
--- /dev/null
+++ b/res/layout/bookmarklistwidget.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="#00000000"
+    android:focusable="true"
+    android:clickable="true">
+    <LinearLayout
+        android:id="@+id/header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:padding="8dip"
+        android:background="#383847">
+        <ImageView
+            android:id="@+id/logo"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:src="@drawable/ic_launcher_browser" />
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:text="@string/tab_bookmarks"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="@color/white"
+            android:paddingLeft="8dip" />
+    </LinearLayout>
+    <ListView
+        android:id="@+id/bookmarks_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#2b2b3c"
+        android:cacheColorHint="#2b2b3c"
+        android:dividerHeight="0dp"
+        android:divider="#00000000" />
+</LinearLayout>
diff --git a/res/layout/bookmarkstackwidget_item.xml b/res/layout/bookmarklistwidget_item.xml
similarity index 66%
rename from res/layout/bookmarkstackwidget_item.xml
rename to res/layout/bookmarklistwidget_item.xml
index e7992f7..2257a26 100644
--- a/res/layout/bookmarkstackwidget_item.xml
+++ b/res/layout/bookmarklistwidget_item.xml
@@ -14,34 +14,30 @@
      limitations under the License.
 -->
 
-<RelativeLayout
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/stack_item"
+    android:id="@+id/list_item"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:padding="16dip">
+    android:orientation="horizontal"
+    android:padding="8dip"
+    android:background="#383847">
     <ImageView
         android:id="@+id/thumb"
         android:src="@drawable/browser_thumbnail"
-        android:layout_height="match_parent"
-        android:layout_width="match_parent"
-        android:scaleType="fitXY"
-        android:layout_centerInParent="true" />
+        android:layout_height="24dp"
+        android:layout_width="24dp"
+        android:layout_gravity="center_vertical"
+        android:scaleType="fitXY"/>
     <TextView
         android:id="@+id/label"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
         android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textStyle="bold"
         android:textColor="@color/white"
+        android:paddingLeft="8dip"
         android:maxLines="1"
-        android:paddingLeft="2dip"
-        android:paddingRight="2dip"
-        android:paddingTop="0dip"
-        android:paddingBottom="0dip"
         android:scrollHorizontally="true"
-        android:ellipsize="marquee"
-        android:layout_alignBottom="@id/thumb"
-        android:layout_alignLeft="@id/thumb"
-        android:layout_alignRight="@id/thumb" />
-</RelativeLayout>
+        android:ellipsize="marquee"/>
+</LinearLayout>
diff --git a/res/layout/bookmarkstackwidget.xml b/res/layout/bookmarkstackwidget.xml
deleted file mode 100644
index c8c88c4..0000000
--- a/res/layout/bookmarkstackwidget.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <StackView
-        android:id="@+id/stackwidget_stack"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:paddingTop="16dip"
-        android:paddingBottom="8dip"
-        android:paddingLeft="8dip"
-        android:paddingRight="8dip"
-        android:background="#00000000"
-        android:cacheColorHint="#00000000"
-        android:autoStart="true" />
-    <ImageView
-        android:id="@+id/logo"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentTop="true"
-        android:layout_centerHorizontal="true"
-        android:src="@drawable/ic_launcher_shortcut_browser_bookmark" />
-</RelativeLayout>
diff --git a/res/xml/bookmarkstackwidget.xml b/res/xml/bookmarklistwidget_info.xml
similarity index 77%
rename from res/xml/bookmarkstackwidget.xml
rename to res/xml/bookmarklistwidget_info.xml
index 56e7351..0baee5e 100644
--- a/res/xml/bookmarkstackwidget.xml
+++ b/res/xml/bookmarklistwidget_info.xml
@@ -14,12 +14,12 @@
      limitations under the License.
 -->
 
-<!-- 2x2 Widget displaying the user's bookmarks as thumbnails. -->
+<!-- 3x3 Widget displaying the user's bookmarks as a list with favicons. -->
 <appwidget-provider
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:minWidth="146dip"
-    android:minHeight="146dip"
-    android:updatePeriodMillis="3600000"
+    android:minWidth="219dip"
+    android:minHeight="219dip"
+    android:updatePeriodMillis="0"
     android:previewImage="@drawable/preview"
-    android:initialLayout="@layout/bookmarkstackwidget">
+    android:initialLayout="@layout/bookmarklistwidget">
 </appwidget-provider>
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index c909d70..37b8eef 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -94,6 +94,7 @@
 
     public static final long FIXED_ID_ROOT = 1;
 
+    // BookmarkListWidgetService.ORDER_BY_CLAUSE has a copy of this default sort order
     static final String DEFAULT_BOOKMARKS_SORT_ORDER = "position ASC, _id ASC";
 
     static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
diff --git a/src/com/android/browser/widget/BookmarkListWidgetProvider.java b/src/com/android/browser/widget/BookmarkListWidgetProvider.java
new file mode 100644
index 0000000..04f7b07
--- /dev/null
+++ b/src/com/android/browser/widget/BookmarkListWidgetProvider.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2010 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 com.android.browser.R;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+/**
+ * Widget that shows a preview of the user's bookmarks.
+ */
+public class BookmarkListWidgetProvider extends AppWidgetProvider {
+    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
+        // coming in without extras, which AppWidgetProvider then blocks.
+        final String action = intent.getAction();
+        if (ACTION_BOOKMARK_APPWIDGET_UPDATE.equals(action)) {
+            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+            performUpdate(context, appWidgetManager,
+                    appWidgetManager.getAppWidgetIds(getComponentName(context)));
+        } else {
+            super.onReceive(context, intent);
+        }
+    }
+
+    @Override
+    public void onUpdate(Context context, AppWidgetManager mngr, int[] ids) {
+        performUpdate(context, mngr, ids);
+    }
+
+    @Override
+    public void onEnabled(Context context) {
+        // Start the backing service
+        context.startService(new Intent(context, BookmarkListWidgetService.class));
+    }
+
+    @Override
+    public void onDisabled(Context context) {
+        // Stop the backing service
+        context.stopService(new Intent(context, BookmarkListWidgetService.class));
+    }
+
+    @Override
+    public void onDeleted(Context context, int[] appWidgetIds) {
+        super.onDeleted(context, appWidgetIds);
+        context.startService(new Intent(BookmarkListWidgetService.ACTION_REMOVE_FACTORIES,
+                null, context, BookmarkListWidgetService.class)
+                .putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds));
+    }
+
+    private void performUpdate(Context context,
+            AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+        // Update the widgets
+        for (int appWidgetId : appWidgetIds) {
+            Intent updateIntent = new Intent(context, BookmarkListWidgetService.class);
+            updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+            updateIntent.setData(Uri.parse(updateIntent.toUri(Intent.URI_INTENT_SCHEME)));
+            RemoteViews views = new RemoteViews(context.getPackageName(),
+                    R.layout.bookmarklistwidget);
+            views.setRemoteAdapter(R.id.bookmarks_list, updateIntent);
+            appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.bookmarks_list);
+            Intent ic = new Intent(context, BookmarkListWidgetService.class);
+            views.setPendingIntentTemplate(R.id.bookmarks_list,
+                    PendingIntent.getService(context, 0, ic,
+                    PendingIntent.FLAG_UPDATE_CURRENT));
+            Intent launch = new Intent(context, BrowserActivity.class);
+            views.setOnClickPendingIntent(R.id.header, PendingIntent
+                    .getActivity(context, 0, launch, PendingIntent.FLAG_CANCEL_CURRENT));
+            appWidgetManager.updateAppWidget(appWidgetId, views);
+        }
+    }
+
+    /**
+     * Build {@link ComponentName} describing this specific
+     * {@link AppWidgetProvider}
+     */
+    static ComponentName getComponentName(Context context) {
+        return new ComponentName(context, BookmarkListWidgetProvider.class);
+    }
+}
diff --git a/src/com/android/browser/widget/BookmarkListWidgetService.java b/src/com/android/browser/widget/BookmarkListWidgetService.java
new file mode 100644
index 0000000..1b3b1f4
--- /dev/null
+++ b/src/com/android/browser/widget/BookmarkListWidgetService.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2010 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.R;
+import com.android.browser.provider.BrowserProvider2;
+
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.database.Cursor;
+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.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Bookmarks;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BookmarkListWidgetService extends RemoteViewsService {
+
+    static final String TAG = "BookmarkListWidgetService";
+    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";
+
+    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.PARENT};
+
+    // Ordering merged with DEFAULT_BOOKMARK_SORT_ORDER from BrowserProvider2
+    private static final String ORDER_BY_CLAUSE =
+        Bookmarks.IS_FOLDER + " DESC, position ASC, _id ASC";
+
+    private Map<Integer, BookmarkFactory> mFactories;
+    private Handler mUiHandler;
+    private HandlerThread mBackgroundThread;
+    private BookmarksObserver mBookmarksObserver;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mFactories = new HashMap<Integer, BookmarkFactory>();
+        mUiHandler = new Handler();
+        mBookmarksObserver = new BookmarksObserver(mUiHandler);
+        getContentResolver().registerContentObserver(
+                BrowserContract.AUTHORITY_URI, true, mBookmarksObserver);
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        String action = intent.getAction();
+        if (Intent.ACTION_VIEW.equals(action)) {
+            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) {
+                    mFactories.remove(id);
+                }
+            }
+        } 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.setFolder(folderId);
+                AppWidgetManager.getInstance(this).notifyAppWidgetViewDataChanged(widgetId, R.id.bookmarks_list);
+            }
+        }
+        return START_STICKY;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        getContentResolver().unregisterContentObserver(mBookmarksObserver);
+        mBackgroundThread.quit();
+    }
+
+    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
+            sendBroadcast(new Intent(
+                    BookmarkListWidgetProvider.ACTION_BOOKMARK_APPWIDGET_UPDATE,
+                    null, BookmarkListWidgetService.this,
+                    BookmarkListWidgetProvider.class));
+        }
+    }
+
+    @Override
+    public RemoteViewsFactory onGetViewFactory(Intent intent) {
+        int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+        if (widgetId < 0) {
+            Log.w(TAG, "Missing EXTRA_APPWIDGET_ID!");
+            return null;
+        } else {
+            BookmarkFactory fac = new BookmarkFactory(this, widgetId);
+            mFactories.put(widgetId, fac);
+            return fac;
+        }
+    }
+
+    static class BookmarkFactory implements RemoteViewsService.RemoteViewsFactory {
+        private List<RenderResult> mBookmarks;
+        private Context mContext;
+        private int mWidgetId;
+        private long mFolderId = BrowserProvider2.FIXED_ID_ROOT;
+
+        public BookmarkFactory(Context context, int widgetId) {
+            mContext = context;
+            mWidgetId = widgetId;
+        }
+
+        void setFolder(long folderId) {
+            mFolderId = folderId;
+        }
+
+        @Override
+        public int getCount() {
+            if (mBookmarks == null)
+                return 0;
+            return mBookmarks.size();
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public RemoteViews getLoadingView() {
+            return null;
+        }
+
+        @Override
+        public RemoteViews getViewAt(int position) {
+            if (position < 0 || position >= getCount()) {
+                return null;
+            }
+
+            RenderResult res = mBookmarks.get(position);
+
+            RemoteViews views = new RemoteViews(
+                    mContext.getPackageName(), R.layout.bookmarklistwidget_item);
+            Intent fillin;
+            if (res.mIsFolder) {
+                long nfi = res.mId;
+                if (nfi == mFolderId) nfi = res.mParentId;
+                fillin = new Intent(ACTION_CHANGE_FOLDER, null,
+                        mContext, BookmarkListWidgetService.class)
+                        .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
+                        .putExtra(Bookmarks._ID, nfi);
+            } else {
+                fillin = new Intent(Intent.ACTION_VIEW)
+                        .setData(Uri.parse(res.mUrl))
+                        .addCategory(Intent.CATEGORY_BROWSABLE);
+            }
+            views.setOnClickFillInIntent(R.id.list_item, fillin);
+            // Set the title of the bookmark. Use the url as a backup.
+            String displayTitle = res.mTitle;
+            if (TextUtils.isEmpty(displayTitle)) {
+                // The browser always requires a title for bookmarks, but jic...
+                displayTitle = res.mUrl;
+            }
+            views.setTextViewText(R.id.label, displayTitle);
+            views.setDrawableParameters(R.id.list_item, true, 0, -1, null, -1);
+            if (res.mIsFolder) {
+                if (res.mId == mFolderId) {
+                    views.setDrawableParameters(R.id.list_item, true, 140, -1, null, -1);
+                    views.setImageViewResource(R.id.thumb, R.drawable.ic_back_normal);
+                } else {
+                    views.setImageViewResource(R.id.thumb, R.drawable.ic_folder);
+                }
+            } else {
+                if (res.mBitmap != null) {
+                    views.setImageViewBitmap(R.id.thumb, res.mBitmap);
+                } else {
+                    views.setImageViewResource(R.id.thumb,
+                            R.drawable.browser_thumbnail);
+                }
+            }
+            return views;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return 1;
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return false;
+        }
+
+        @Override
+        public void onCreate() {
+            loadData();
+        }
+
+        @Override
+        public void onDestroy() {
+            recycleBitmaps();
+        }
+
+        @Override
+        public void onDataSetChanged() {
+            loadData();
+        }
+
+        void loadData() {
+            // Reset identity since this could be an IPC call
+            long token = Binder.clearCallingIdentity();
+            update();
+            Binder.restoreCallingIdentity(token);
+        }
+
+        void update() {
+            recycleBitmaps();
+            String where;
+            if (USE_FOLDERS) {
+                where = String.format("%s == %d", Bookmarks.PARENT, mFolderId);
+                if (mFolderId != BrowserProvider2.FIXED_ID_ROOT) {
+                    where = String.format("%s OR %s == %d", where,
+                        Bookmarks._ID, mFolderId);
+                }
+            } else {
+                where = Bookmarks.IS_FOLDER + " == 0";
+            }
+            Cursor c = null;
+            try {
+                c = mContext.getContentResolver().query(
+                        BrowserContract.Bookmarks.CONTENT_URI, PROJECTION,
+                        where, null, ORDER_BY_CLAUSE);
+                if (c != null) {
+                    mBookmarks = new ArrayList<RenderResult>(c.getCount());
+                    while (c.moveToNext()) {
+                        long id = c.getLong(0);
+                        String title = c.getString(1);
+                        String url = c.getString(2);
+                        RenderResult res = new RenderResult(id, title, url);
+                        byte[] blob = c.getBlob(3);
+                        if (blob != null) {
+                            // RemoteViews require a valid bitmap config
+                            Options options = new Options();
+                            options.inPreferredConfig = Config.ARGB_8888;
+                            res.mBitmap = BitmapFactory.decodeByteArray(
+                                    blob, 0, blob.length, options);
+                        }
+                        res.mIsFolder = c.getInt(4) != 0;
+                        res.mParentId = c.getLong(5);
+                        if (res.mId == mFolderId) {
+                            // Make sure this is first
+                            mBookmarks.add(0, res);
+                        } else {
+                            mBookmarks.add(res);
+                        }
+                    }
+                }
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "update bookmark widget", e);
+            } finally {
+                if (c != null) {
+                    c.close();
+                }
+            }
+        }
+
+        private void recycleBitmaps() {
+            // Do a bit of house cleaning for the system
+            if (mBookmarks != null) {
+                for (RenderResult res : mBookmarks) {
+                    if (res.mBitmap != null) {
+                        res.mBitmap.recycle();
+                        res.mBitmap = null;
+                    }
+                }
+            }
+        }
+    }
+
+    // Class containing the rendering information for a specific bookmark.
+    private static class RenderResult {
+        final String mTitle;
+        final String mUrl;
+        Bitmap mBitmap;
+        boolean mIsFolder;
+        long mParentId;
+        long mId;
+
+        RenderResult(long id, String title, String url) {
+            mId = id;
+            mTitle = title;
+            mUrl = url;
+        }
+
+    }
+
+}
diff --git a/src/com/android/browser/widget/BookmarkStackWidgetProvider.java b/src/com/android/browser/widget/BookmarkStackWidgetProvider.java
deleted file mode 100644
index 0684c61..0000000
--- a/src/com/android/browser/widget/BookmarkStackWidgetProvider.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2010 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 android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProvider;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * Widget that shows a preview of the user's bookmarks.
- */
-public class BookmarkStackWidgetProvider extends AppWidgetProvider {
-
-    @Override
-    public void onUpdate(Context context, AppWidgetManager mngr, int[] ids) {
-        context.startService(new Intent(BookmarkStackWidgetService.UPDATE, null,
-                    context, BookmarkStackWidgetService.class));
-    }
-
-    @Override
-    public void onEnabled(Context context) {
-        context.startService(new Intent(context, BookmarkStackWidgetService.class));
-    }
-
-    @Override
-    public void onDisabled(Context context) {
-        context.stopService(new Intent(context, BookmarkStackWidgetService.class));
-    }
-
-}
diff --git a/src/com/android/browser/widget/BookmarkStackWidgetService.java b/src/com/android/browser/widget/BookmarkStackWidgetService.java
deleted file mode 100644
index 83b07df..0000000
--- a/src/com/android/browser/widget/BookmarkStackWidgetService.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2010 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.R;
-
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetManager;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.BrowserContract;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.RemoteViewsService;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class BookmarkStackWidgetService extends RemoteViewsService {
-
-    private static final String LOGTAG = "browserwidget";
-
-    /** Force the bookmarks to be re-rendered. */
-    public static final String UPDATE = "com.android.browser.widget.UPDATE";
-
-    /** the adapter intent action */
-    public static final String ADAPTER = "com.android.browser.widget.ADAPTER";
-
-    private static final String[] PROJECTION = new String[] {
-        BrowserContract.Bookmarks._ID,
-        BrowserContract.Bookmarks.TITLE,
-        BrowserContract.Bookmarks.URL,
-        BrowserContract.Bookmarks.THUMBNAIL };
-
-    private static final String WHERE_CLAUSE = BrowserContract.Bookmarks.IS_FOLDER +
-            " == 0";
-
-    // No id specified.
-    private static final int NO_ID = -1;
-
-    private static final int MSG_UPDATE = 0;
-
-    List<RenderResult> mBookmarks;
-
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_UPDATE:
-                    updateWidget();
-                    break;
-                default:
-                    break;
-            }
-        }
-    };
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        if ((intent == null) || (intent.getAction() != null) && UPDATE.equals(intent.getAction())) {
-            mHandler.sendEmptyMessage(MSG_UPDATE);
-        }
-        return START_STICKY;
-    }
-
-    private void updateWidget() {
-        RemoteViews views = new RemoteViews(getPackageName(),
-                R.layout.bookmarkstackwidget);
-        Intent vi = new Intent(Intent.ACTION_VIEW);
-        vi.addCategory(Intent.CATEGORY_BROWSABLE);
-        views.setPendingIntentTemplate(R.id.stackwidget_stack,
-                PendingIntent.getActivity(BookmarkStackWidgetService.this, 0, vi,
-                        PendingIntent.FLAG_CANCEL_CURRENT));
-        Intent adapter = new Intent(BookmarkStackWidgetService.ADAPTER, null,
-                this, BookmarkStackWidgetService.class);
-        views.setRemoteAdapter(R.id.stackwidget_stack, adapter);
-        AppWidgetManager.getInstance(this).updateAppWidget(
-                new ComponentName(this, BookmarkStackWidgetProvider.class),
-                views);
-    }
-
-    @Override
-    public RemoteViewsFactory onGetViewFactory(Intent intent) {
-        return mViewFactory;
-    }
-
-    RemoteViewsService.RemoteViewsFactory mViewFactory = new RemoteViewsFactory () {
-
-        Intent mFillIntent;
-        
-        @Override
-        public int getCount() {
-            return mBookmarks.size();
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return position;
-        }
-
-        @Override
-        public RemoteViews getLoadingView() {
-            return null;
-        }
-
-        @Override
-        public RemoteViews getViewAt(int position) {
-            RenderResult res = mBookmarks.get(position);
-            RemoteViews views = new RemoteViews(getPackageName(),
-                    R.layout.bookmarkstackwidget_item);
-            mFillIntent.setData(Uri.parse(res.mUrl));
-            views.setOnClickFillInIntent(R.id.stack_item, mFillIntent);
-            // Set the title of the bookmark. Use the url as a backup.
-            String displayTitle = res.mTitle;
-            if (TextUtils.isEmpty(displayTitle)) {
-                displayTitle = res.mUrl;
-            }
-            views.setTextViewText(R.id.label, displayTitle);
-            if (res.mBitmap != null) {
-                views.setImageViewBitmap(R.id.thumb, res.mBitmap);
-                views.setViewVisibility(R.id.label, View.GONE);
-            }
-            return views;
-        }
-
-        @Override
-        public int getViewTypeCount() {
-            return 1;
-        }
-
-        @Override
-        public boolean hasStableIds() {
-            return false;
-        }
-
-        @Override
-        public void onCreate() {
-            mFillIntent = new Intent();
-            update();
-        }
-
-        @Override
-        public void onDestroy() {
-        }
-
-        public void update() {
-            mBookmarks = new ArrayList<RenderResult>();
-            // Look up all the bookmarks
-            Cursor c = null;
-            try {
-                c = getContentResolver().query(BrowserContract.Bookmarks.CONTENT_URI,
-                        PROJECTION, WHERE_CLAUSE, null, null);
-                if (c != null) {
-                    while (c.moveToNext()) {
-                        int id = c.getInt(0);
-                        String title = c.getString(1);
-                        String url = c.getString(2);
-                        RenderResult res = new RenderResult(id, title, url);
-                        byte[] blob = c.getBlob(3);
-                        if (blob != null) {
-                            res.mBitmap = BitmapFactory.decodeByteArray(blob, 0, blob.length);
-                        }
-                        mBookmarks.add(res);
-                    }
-                }
-            } catch (IllegalStateException e) {
-                Log.e(LOGTAG, "update bookmark widget", e);
-            } finally {
-                if (c != null) {
-                    c.close();
-                }
-            }
-        }
-
-        @Override
-        public void onDataSetChanged() {
-        }
-    };
-
-    // Class containing the rendering information for a specific bookmark.
-    private static class RenderResult {
-        final int mId;
-        final String mTitle;
-        final String mUrl;
-        Bitmap mBitmap;
-
-        RenderResult(int id, String title, String url) {
-            mId = id;
-            mTitle = title;
-            mUrl = url;
-        }
-
-    }
-
-}