1df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck/*
2df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck * Copyright (C) 2010 The Android Open Source Project
3df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck *
4df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck * Licensed under the Apache License, Version 2.0 (the "License");
5df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck * you may not use this file except in compliance with the License.
6df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck * You may obtain a copy of the License at
7df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck *
8df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck *      http://www.apache.org/licenses/LICENSE-2.0
9df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck *
10df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck * Unless required by applicable law or agreed to in writing, software
11df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck * distributed under the License is distributed on an "AS IS" BASIS,
12df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck * See the License for the specific language governing permissions and
14df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck * limitations under the License.
15df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck */
16df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
17df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckpackage com.android.browser.widget;
18df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
19df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.appwidget.AppWidgetManager;
206eb77c79f9a88ea0e76eb07ea0412bbd2446a110John Reckimport android.content.ContentUris;
21df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.content.Context;
22df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.content.Intent;
236eb77c79f9a88ea0e76eb07ea0412bbd2446a110John Reckimport android.content.SharedPreferences;
24df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.database.Cursor;
2599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reckimport android.database.MergeCursor;
26df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.graphics.Bitmap;
27df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.graphics.Bitmap.Config;
28df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.graphics.BitmapFactory;
29df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.graphics.BitmapFactory.Options;
30df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.net.Uri;
3199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reckimport android.os.Binder;
32df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.provider.BrowserContract;
33df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.provider.BrowserContract.Bookmarks;
34df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.text.TextUtils;
35df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.util.Log;
36df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.widget.RemoteViews;
37df63d2ff2e9881706eef78796d05078d0ffb7f60John Reckimport android.widget.RemoteViewsService;
38df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
399b8cd1e564984874f2a6f5cc9bdc68cda8aa15ceJohn Reckimport com.android.browser.BrowserActivity;
409b8cd1e564984874f2a6f5cc9bdc68cda8aa15ceJohn Reckimport com.android.browser.R;
419b8cd1e564984874f2a6f5cc9bdc68cda8aa15ceJohn Reckimport com.android.browser.provider.BrowserProvider2;
429b8cd1e564984874f2a6f5cc9bdc68cda8aa15ceJohn Reck
4399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reckimport java.io.File;
4499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reckimport java.io.FilenameFilter;
4599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reckimport java.util.HashSet;
4699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reckimport java.util.regex.Matcher;
4799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reckimport java.util.regex.Pattern;
48df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
491537b774a62042a8b86100e1e6b0988bed70c385John Reckpublic class BookmarkThumbnailWidgetService extends RemoteViewsService {
50df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
511537b774a62042a8b86100e1e6b0988bed70c385John Reck    static final String TAG = "BookmarkThumbnailWidgetService";
52df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck    static final String ACTION_CHANGE_FOLDER
53df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            = "com.android.browser.widget.CHANGE_FOLDER";
54df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
5599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    static final String STATE_CURRENT_FOLDER = "current_folder";
5699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    static final String STATE_ROOT_FOLDER = "root_folder";
5799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck
58df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck    private static final String[] PROJECTION = new String[] {
59df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            BrowserContract.Bookmarks._ID,
60df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            BrowserContract.Bookmarks.TITLE,
61df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            BrowserContract.Bookmarks.URL,
62df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            BrowserContract.Bookmarks.FAVICON,
63df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            BrowserContract.Bookmarks.IS_FOLDER,
641537b774a62042a8b86100e1e6b0988bed70c385John Reck            BrowserContract.Bookmarks.POSITION, /* needed for order by */
6599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            BrowserContract.Bookmarks.THUMBNAIL,
6699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            BrowserContract.Bookmarks.PARENT};
67023a60e37c0f3374aed57531ab11b7f20ece232bJohn Reck    private static final int BOOKMARK_INDEX_ID = 0;
68023a60e37c0f3374aed57531ab11b7f20ece232bJohn Reck    private static final int BOOKMARK_INDEX_TITLE = 1;
69023a60e37c0f3374aed57531ab11b7f20ece232bJohn Reck    private static final int BOOKMARK_INDEX_URL = 2;
70023a60e37c0f3374aed57531ab11b7f20ece232bJohn Reck    private static final int BOOKMARK_INDEX_FAVICON = 3;
71023a60e37c0f3374aed57531ab11b7f20ece232bJohn Reck    private static final int BOOKMARK_INDEX_IS_FOLDER = 4;
7299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    private static final int BOOKMARK_INDEX_THUMBNAIL = 6;
7399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    private static final int BOOKMARK_INDEX_PARENT_ID = 7;
74df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
75df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck    @Override
7699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    public RemoteViewsFactory onGetViewFactory(Intent intent) {
7799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
7899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        if (widgetId < 0) {
7999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            Log.w(TAG, "Missing EXTRA_APPWIDGET_ID!");
8099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            return null;
8199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        }
8299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        return new BookmarkFactory(getApplicationContext(), widgetId);
83df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck    }
84df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
8599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    static SharedPreferences getWidgetState(Context context, int widgetId) {
8699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        return context.getSharedPreferences(
8799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                String.format("widgetState-%d", widgetId),
8899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                Context.MODE_PRIVATE);
89df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck    }
90df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
9199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    static void deleteWidgetState(Context context, int widgetId) {
9299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        File file = context.getSharedPrefsFile(
9399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                String.format("widgetState-%d", widgetId));
9499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        if (file.exists()) {
9599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            if (!file.delete()) {
9699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                file.deleteOnExit();
9799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            }
9899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        }
99df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck    }
100df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
10199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    static void changeFolder(Context context, Intent intent) {
10299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        int wid = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
10399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        long fid = intent.getLongExtra(Bookmarks._ID, -1);
10499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        if (wid >= 0 && fid >= 0) {
10599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            SharedPreferences prefs = getWidgetState(context, wid);
10699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            prefs.edit().putLong(STATE_CURRENT_FOLDER, fid).commit();
10799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            AppWidgetManager.getInstance(context)
10899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                    .notifyAppWidgetViewDataChanged(wid, R.id.bookmarks_list);
109df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
11099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    }
111df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
1129b8cd1e564984874f2a6f5cc9bdc68cda8aa15ceJohn Reck    static void setupWidgetState(Context context, int widgetId, long rootFolder) {
11399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        SharedPreferences pref = getWidgetState(context, widgetId);
11499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        pref.edit()
1159b8cd1e564984874f2a6f5cc9bdc68cda8aa15ceJohn Reck            .putLong(STATE_CURRENT_FOLDER, rootFolder)
1169b8cd1e564984874f2a6f5cc9bdc68cda8aa15ceJohn Reck            .putLong(STATE_ROOT_FOLDER, rootFolder)
11732a915b47ece86b8fa6026e5917eaa9cf651d64bBen Murdoch            .apply();
11899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    }
119df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
12099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    /**
12199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck     *  Checks for any state files that may have not received onDeleted
12299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck     */
12399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    static void removeOrphanedStates(Context context, int[] widgetIds) {
12499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        File prefsDirectory = context.getSharedPrefsFile("null").getParentFile();
12599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        File[] widgetStates = prefsDirectory.listFiles(new StateFilter(widgetIds));
126728abd38d05820cf1a3329d7b5513cf92eef5644John Reck        if (widgetStates != null) {
127728abd38d05820cf1a3329d7b5513cf92eef5644John Reck            for (File f : widgetStates) {
128728abd38d05820cf1a3329d7b5513cf92eef5644John Reck                Log.w(TAG, "Found orphaned state: " + f.getName());
129728abd38d05820cf1a3329d7b5513cf92eef5644John Reck                if (!f.delete()) {
130728abd38d05820cf1a3329d7b5513cf92eef5644John Reck                    f.deleteOnExit();
131728abd38d05820cf1a3329d7b5513cf92eef5644John Reck                }
1327c11d28643f571669b59ef0d700392c45256ce97John Reck            }
133df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
134df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck    }
135df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
13699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    static class StateFilter implements FilenameFilter {
13799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck
13899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        static final Pattern sStatePattern = Pattern.compile("widgetState-(\\d+)\\.xml");
13999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        HashSet<Integer> mWidgetIds;
14099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck
14199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        StateFilter(int[] ids) {
14299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            mWidgetIds = new HashSet<Integer>();
14399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            for (int id : ids) {
14499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                mWidgetIds.add(id);
1457c11d28643f571669b59ef0d700392c45256ce97John Reck            }
146df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
147df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
14899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        @Override
14999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        public boolean accept(File dir, String filename) {
15099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            Matcher m = sStatePattern.matcher(filename);
15199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            if (m.matches()) {
15299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                int id = Integer.parseInt(m.group(1));
15399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                if (!mWidgetIds.contains(id)) {
15499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                    return true;
15599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                }
15699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            }
15799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            return false;
1586eb77c79f9a88ea0e76eb07ea0412bbd2446a110John Reck        }
15999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck
1606eb77c79f9a88ea0e76eb07ea0412bbd2446a110John Reck    }
1616eb77c79f9a88ea0e76eb07ea0412bbd2446a110John Reck
16299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck    static class BookmarkFactory implements RemoteViewsService.RemoteViewsFactory {
16399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        private Cursor mBookmarks;
164df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        private Context mContext;
165df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        private int mWidgetId;
16699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        private long mCurrentFolder = -1;
16799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        private long mRootFolder = -1;
16899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        private SharedPreferences mPreferences = null;
169df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
170df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        public BookmarkFactory(Context context, int widgetId) {
171914c5591baeb86bf30a5bc28930071442a822d60Ben Murdoch            mContext = context.getApplicationContext();
172df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            mWidgetId = widgetId;
173df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
174df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
17599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        void syncState() {
17699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            if (mPreferences == null) {
17799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                mPreferences = getWidgetState(mContext, mWidgetId);
17899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            }
17999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            long currentFolder = mPreferences.getLong(STATE_CURRENT_FOLDER, -1);
18099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            mRootFolder = mPreferences.getLong(STATE_ROOT_FOLDER, -1);
18199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            if (currentFolder != mCurrentFolder) {
18299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                resetBookmarks();
18399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                mCurrentFolder = currentFolder;
1846eb77c79f9a88ea0e76eb07ea0412bbd2446a110John Reck            }
18599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        }
1866eb77c79f9a88ea0e76eb07ea0412bbd2446a110John Reck
18799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        void saveState() {
18899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            if (mPreferences == null) {
18999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                mPreferences = getWidgetState(mContext, mWidgetId);
1906eb77c79f9a88ea0e76eb07ea0412bbd2446a110John Reck            }
19199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            mPreferences.edit()
19299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                .putLong(STATE_CURRENT_FOLDER, mCurrentFolder)
19399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                .putLong(STATE_ROOT_FOLDER, mRootFolder)
19499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                .commit();
195df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
196df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
197df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        @Override
198df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        public int getCount() {
199df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            if (mBookmarks == null)
200df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck                return 0;
20199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            return mBookmarks.getCount();
202df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
203df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
204df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        @Override
205df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        public long getItemId(int position) {
206df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            return position;
207df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
208df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
209df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        @Override
210df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        public RemoteViews getLoadingView() {
21199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            return new RemoteViews(
21299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                    mContext.getPackageName(), R.layout.bookmarkthumbnailwidget_item);
213df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
214df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
215df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        @Override
216df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        public RemoteViews getViewAt(int position) {
21799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            if (!mBookmarks.moveToPosition(position)) {
218df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck                return null;
219df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            }
220df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
22199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            long id = mBookmarks.getLong(BOOKMARK_INDEX_ID);
22299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            String title = mBookmarks.getString(BOOKMARK_INDEX_TITLE);
22399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            String url = mBookmarks.getString(BOOKMARK_INDEX_URL);
22499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            boolean isFolder = mBookmarks.getInt(BOOKMARK_INDEX_IS_FOLDER) != 0;
225df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
22623c61dd30cd2f4a96ab2b01a5878badc79380c6aJohn Reck            RemoteViews views;
22723c61dd30cd2f4a96ab2b01a5878badc79380c6aJohn Reck            // Two layouts are needed because of b/5387153
22823c61dd30cd2f4a96ab2b01a5878badc79380c6aJohn Reck            if (isFolder) {
22923c61dd30cd2f4a96ab2b01a5878badc79380c6aJohn Reck                views = new RemoteViews(mContext.getPackageName(),
23023c61dd30cd2f4a96ab2b01a5878badc79380c6aJohn Reck                        R.layout.bookmarkthumbnailwidget_item_folder);
23123c61dd30cd2f4a96ab2b01a5878badc79380c6aJohn Reck            } else {
23223c61dd30cd2f4a96ab2b01a5878badc79380c6aJohn Reck                views = new RemoteViews(mContext.getPackageName(),
23323c61dd30cd2f4a96ab2b01a5878badc79380c6aJohn Reck                        R.layout.bookmarkthumbnailwidget_item);
23423c61dd30cd2f4a96ab2b01a5878badc79380c6aJohn Reck            }
235df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            // Set the title of the bookmark. Use the url as a backup.
23699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            String displayTitle = title;
237df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            if (TextUtils.isEmpty(displayTitle)) {
238df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck                // The browser always requires a title for bookmarks, but jic...
23999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                displayTitle = url;
240df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            }
241df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            views.setTextViewText(R.id.label, displayTitle);
24299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            if (isFolder) {
24399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                if (id == mCurrentFolder) {
24499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                    id = mBookmarks.getLong(BOOKMARK_INDEX_PARENT_ID);
2451537b774a62042a8b86100e1e6b0988bed70c385John Reck                    views.setImageViewResource(R.id.thumb, R.drawable.thumb_bookmark_widget_folder_back_holo);
246df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck                } else {
2471537b774a62042a8b86100e1e6b0988bed70c385John Reck                    views.setImageViewResource(R.id.thumb, R.drawable.thumb_bookmark_widget_folder_holo);
248df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck                }
2491537b774a62042a8b86100e1e6b0988bed70c385John Reck                views.setImageViewResource(R.id.favicon, R.drawable.ic_bookmark_widget_bookmark_holo_dark);
250f95eb7410686ee1ea4b7f583f03b642c30551b79John Reck                views.setDrawableParameters(R.id.thumb, true, 0, -1, null, -1);
251df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            } else {
25299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                // RemoteViews require a valid bitmap config
25399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                Options options = new Options();
25499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                options.inPreferredConfig = Config.ARGB_8888;
25599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                Bitmap thumbnail = null, favicon = null;
25699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                byte[] blob = mBookmarks.getBlob(BOOKMARK_INDEX_THUMBNAIL);
257f95eb7410686ee1ea4b7f583f03b642c30551b79John Reck                views.setDrawableParameters(R.id.thumb, true, 255, -1, null, -1);
25899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                if (blob != null && blob.length > 0) {
25999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                    thumbnail = BitmapFactory.decodeByteArray(
26099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                            blob, 0, blob.length, options);
26199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                    views.setImageViewBitmap(R.id.thumb, thumbnail);
262df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck                } else {
263df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck                    views.setImageViewResource(R.id.thumb,
264df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck                            R.drawable.browser_thumbnail);
265df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck                }
26699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                blob = mBookmarks.getBlob(BOOKMARK_INDEX_FAVICON);
26799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                if (blob != null && blob.length > 0) {
26899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                    favicon = BitmapFactory.decodeByteArray(
26999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                            blob, 0, blob.length, options);
27099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                    views.setImageViewBitmap(R.id.favicon, favicon);
2711537b774a62042a8b86100e1e6b0988bed70c385John Reck                } else {
2721537b774a62042a8b86100e1e6b0988bed70c385John Reck                    views.setImageViewResource(R.id.favicon,
2731537b774a62042a8b86100e1e6b0988bed70c385John Reck                            R.drawable.app_web_browser_sm);
2741537b774a62042a8b86100e1e6b0988bed70c385John Reck                }
275df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            }
27699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            Intent fillin;
27799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            if (isFolder) {
27899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                fillin = new Intent(ACTION_CHANGE_FOLDER)
27999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                        .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
28099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                        .putExtra(Bookmarks._ID, id);
28199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            } else {
28299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                if (!TextUtils.isEmpty(url)) {
28399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                    fillin = new Intent(Intent.ACTION_VIEW)
28499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                            .addCategory(Intent.CATEGORY_BROWSABLE)
28599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                            .setData(Uri.parse(url));
28699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                } else {
28799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                    fillin = new Intent(BrowserActivity.ACTION_SHOW_BROWSER);
28899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                }
28999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            }
29099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            views.setOnClickFillInIntent(R.id.list_item, fillin);
291df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            return views;
292df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
293df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
294df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        @Override
295df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        public int getViewTypeCount() {
29623c61dd30cd2f4a96ab2b01a5878badc79380c6aJohn Reck            return 2;
297df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
298df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
299df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        @Override
300df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        public boolean hasStableIds() {
301df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck            return false;
302df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
303df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
304df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        @Override
305df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        public void onCreate() {
306df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
307df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
308df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        @Override
309df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        public void onDestroy() {
31099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            if (mBookmarks != null) {
31199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                mBookmarks.close();
31299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                mBookmarks = null;
31373eb105e5bb53356e171837bd6b13b29661806e3John Reck            }
31499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            deleteWidgetState(mContext, mWidgetId);
315df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
316df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
317df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        @Override
318df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        public void onDataSetChanged() {
31999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            long token = Binder.clearCallingIdentity();
32099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            syncState();
32199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            if (mRootFolder < 0 || mCurrentFolder < 0) {
3229b8cd1e564984874f2a6f5cc9bdc68cda8aa15ceJohn Reck                // This shouldn't happen, but JIC default to the local account
3239b8cd1e564984874f2a6f5cc9bdc68cda8aa15ceJohn Reck                mRootFolder = BrowserProvider2.FIXED_ID_ROOT;
32499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                mCurrentFolder = mRootFolder;
32599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                saveState();
3267c11d28643f571669b59ef0d700392c45256ce97John Reck            }
32799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            loadBookmarks();
32899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            Binder.restoreCallingIdentity(token);
329df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck        }
330df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
33199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        private void resetBookmarks() {
33299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            if (mBookmarks != null) {
33399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                mBookmarks.close();
33499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                mBookmarks = null;
3357c11d28643f571669b59ef0d700392c45256ce97John Reck            }
3367c11d28643f571669b59ef0d700392c45256ce97John Reck        }
3377c11d28643f571669b59ef0d700392c45256ce97John Reck
33899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck        void loadBookmarks() {
33999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            resetBookmarks();
34099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck
34199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            Uri uri = ContentUris.withAppendedId(
34299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                    BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER,
34399c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                    mCurrentFolder);
34499c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            mBookmarks = mContext.getContentResolver().query(uri, PROJECTION,
34599c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                    null, null, null);
34699c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck            if (mCurrentFolder != mRootFolder) {
34799c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                uri = ContentUris.withAppendedId(
34899c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                        BrowserContract.Bookmarks.CONTENT_URI,
34999c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                        mCurrentFolder);
35099c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                Cursor c = mContext.getContentResolver().query(uri, PROJECTION,
35199c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                        null, null, null);
35299c6d333106cc0fb3c028b5f8d1ff1f5cf726ca2John Reck                mBookmarks = new MergeCursor(new Cursor[] { c, mBookmarks });
3537c11d28643f571669b59ef0d700392c45256ce97John Reck            }
3547c11d28643f571669b59ef0d700392c45256ce97John Reck        }
355df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck    }
356df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck
357df63d2ff2e9881706eef78796d05078d0ffb7f60John Reck}
358