1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.browser;
18
19import android.app.Activity;
20import android.app.Fragment;
21import android.app.LoaderManager;
22import android.content.ClipData;
23import android.content.ClipboardManager;
24import android.content.ContentUris;
25import android.content.Context;
26import android.content.CursorLoader;
27import android.content.Intent;
28import android.content.Loader;
29import android.content.SharedPreferences;
30import android.content.res.Configuration;
31import android.content.res.Resources;
32import android.database.Cursor;
33import android.graphics.Bitmap;
34import android.graphics.BitmapFactory;
35import android.graphics.BitmapFactory.Options;
36import android.net.Uri;
37import android.os.AsyncTask;
38import android.os.Bundle;
39import android.provider.BrowserContract;
40import android.provider.BrowserContract.Accounts;
41import android.view.ContextMenu;
42import android.view.ContextMenu.ContextMenuInfo;
43import android.view.LayoutInflater;
44import android.view.MenuInflater;
45import android.view.MenuItem;
46import android.view.View;
47import android.view.ViewGroup;
48import android.widget.ExpandableListView;
49import android.widget.ExpandableListView.OnChildClickListener;
50import android.widget.Toast;
51
52import com.android.browser.provider.BrowserProvider2;
53import com.android.browser.view.BookmarkExpandableView;
54import com.android.browser.view.BookmarkExpandableView.BookmarkContextMenuInfo;
55
56import org.json.JSONException;
57import org.json.JSONObject;
58
59import java.util.HashMap;
60
61interface BookmarksPageCallbacks {
62    // Return true if handled
63    boolean onBookmarkSelected(Cursor c, boolean isFolder);
64    // Return true if handled
65    boolean onOpenInNewWindow(String... urls);
66}
67
68/**
69 *  View showing the user's bookmarks in the browser.
70 */
71public class BrowserBookmarksPage extends Fragment implements View.OnCreateContextMenuListener,
72        LoaderManager.LoaderCallbacks<Cursor>, BreadCrumbView.Controller,
73        OnChildClickListener {
74
75    public static class ExtraDragState {
76        public int childPosition;
77        public int groupPosition;
78    }
79
80    static final String LOGTAG = "browser";
81
82    static final int LOADER_ACCOUNTS = 1;
83    static final int LOADER_BOOKMARKS = 100;
84
85    static final String EXTRA_DISABLE_WINDOW = "disable_new_window";
86    static final String PREF_GROUP_STATE = "bbp_group_state";
87
88    static final String ACCOUNT_TYPE = "account_type";
89    static final String ACCOUNT_NAME = "account_name";
90
91    BookmarksPageCallbacks mCallbacks;
92    View mRoot;
93    BookmarkExpandableView mGrid;
94    boolean mDisableNewWindow;
95    boolean mEnableContextMenu = true;
96    View mEmptyView;
97    View mHeader;
98    HashMap<Integer, BrowserBookmarksAdapter> mBookmarkAdapters = new HashMap<Integer, BrowserBookmarksAdapter>();
99    JSONObject mState;
100
101    @Override
102    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
103        if (id == LOADER_ACCOUNTS) {
104            return new AccountsLoader(getActivity());
105        } else if (id >= LOADER_BOOKMARKS) {
106            String accountType = args.getString(ACCOUNT_TYPE);
107            String accountName = args.getString(ACCOUNT_NAME);
108            BookmarksLoader bl = new BookmarksLoader(getActivity(),
109                    accountType, accountName);
110            return bl;
111        } else {
112            throw new UnsupportedOperationException("Unknown loader id " + id);
113        }
114    }
115
116    @Override
117    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
118        if (loader.getId() == LOADER_ACCOUNTS) {
119            LoaderManager lm = getLoaderManager();
120            int id = LOADER_BOOKMARKS;
121            while (cursor.moveToNext()) {
122                String accountName = cursor.getString(0);
123                String accountType = cursor.getString(1);
124                Bundle args = new Bundle();
125                args.putString(ACCOUNT_NAME, accountName);
126                args.putString(ACCOUNT_TYPE, accountType);
127                BrowserBookmarksAdapter adapter = new BrowserBookmarksAdapter(
128                        getActivity());
129                mBookmarkAdapters.put(id, adapter);
130                boolean expand = true;
131                try {
132                    expand = mState.getBoolean(accountName != null ? accountName
133                            : BookmarkExpandableView.LOCAL_ACCOUNT_NAME);
134                } catch (JSONException e) {} // no state for accountName
135                mGrid.addAccount(accountName, adapter, expand);
136                lm.restartLoader(id, args, this);
137                id++;
138            }
139            // TODO: Figure out what a reload of these means
140            // Currently, a reload is triggered whenever bookmarks change
141            // This is less than ideal
142            // It also causes UI flickering as a new adapter is created
143            // instead of re-using an existing one when the account_name is the
144            // same.
145            // For now, this is a one-shot load
146            getLoaderManager().destroyLoader(LOADER_ACCOUNTS);
147        } else if (loader.getId() >= LOADER_BOOKMARKS) {
148            BrowserBookmarksAdapter adapter = mBookmarkAdapters.get(loader.getId());
149            adapter.changeCursor(cursor);
150        }
151    }
152
153    @Override
154    public void onLoaderReset(Loader<Cursor> loader) {
155        if (loader.getId() >= LOADER_BOOKMARKS) {
156            BrowserBookmarksAdapter adapter = mBookmarkAdapters.get(loader.getId());
157            adapter.changeCursor(null);
158        }
159    }
160
161    @Override
162    public boolean onContextItemSelected(MenuItem item) {
163        if (!(item.getMenuInfo() instanceof BookmarkContextMenuInfo)) {
164            return false;
165        }
166        BookmarkContextMenuInfo i = (BookmarkContextMenuInfo) item.getMenuInfo();
167        // If we have no menu info, we can't tell which item was selected.
168        if (i == null) {
169            return false;
170        }
171
172        if (handleContextItem(item.getItemId(), i.groupPosition, i.childPosition)) {
173            return true;
174        }
175        return super.onContextItemSelected(item);
176    }
177
178    public boolean handleContextItem(int itemId, int groupPosition,
179            int childPosition) {
180        final Activity activity = getActivity();
181        BrowserBookmarksAdapter adapter = getChildAdapter(groupPosition);
182
183        switch (itemId) {
184        case R.id.open_context_menu_id:
185            loadUrl(adapter, childPosition);
186            break;
187        case R.id.edit_context_menu_id:
188            editBookmark(adapter, childPosition);
189            break;
190        case R.id.shortcut_context_menu_id:
191            Cursor c = adapter.getItem(childPosition);
192            activity.sendBroadcast(createShortcutIntent(getActivity(), c));
193            break;
194        case R.id.delete_context_menu_id:
195            displayRemoveBookmarkDialog(adapter, childPosition);
196            break;
197        case R.id.new_window_context_menu_id:
198            openInNewWindow(adapter, childPosition);
199            break;
200        case R.id.share_link_context_menu_id: {
201            Cursor cursor = adapter.getItem(childPosition);
202            Controller.sharePage(activity,
203                    cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE),
204                    cursor.getString(BookmarksLoader.COLUMN_INDEX_URL),
205                    getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON),
206                    getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_THUMBNAIL));
207            break;
208        }
209        case R.id.copy_url_context_menu_id:
210            copy(getUrl(adapter, childPosition));
211            break;
212        case R.id.homepage_context_menu_id: {
213            BrowserSettings.getInstance().setHomePage(getUrl(adapter, childPosition));
214            Toast.makeText(activity, R.string.homepage_set, Toast.LENGTH_LONG).show();
215            break;
216        }
217        // Only for the Most visited page
218        case R.id.save_to_bookmarks_menu_id: {
219            Cursor cursor = adapter.getItem(childPosition);
220            String name = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
221            String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
222            // If the site is bookmarked, the item becomes remove from
223            // bookmarks.
224            Bookmarks.removeFromBookmarks(activity, activity.getContentResolver(), url, name);
225            break;
226        }
227        default:
228            return false;
229        }
230        return true;
231    }
232
233    static Bitmap getBitmap(Cursor cursor, int columnIndex) {
234        return getBitmap(cursor, columnIndex, null);
235    }
236
237    static ThreadLocal<Options> sOptions = new ThreadLocal<Options>() {
238        @Override
239        protected Options initialValue() {
240            return new Options();
241        };
242    };
243    static Bitmap getBitmap(Cursor cursor, int columnIndex, Bitmap inBitmap) {
244        byte[] data = cursor.getBlob(columnIndex);
245        if (data == null) {
246            return null;
247        }
248        Options opts = sOptions.get();
249        opts.inBitmap = inBitmap;
250        opts.inSampleSize = 1;
251        opts.inScaled = false;
252        try {
253            return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
254        } catch (IllegalArgumentException ex) {
255            // Failed to re-use bitmap, create a new one
256            return BitmapFactory.decodeByteArray(data, 0, data.length);
257        }
258    }
259
260    private MenuItem.OnMenuItemClickListener mContextItemClickListener =
261            new MenuItem.OnMenuItemClickListener() {
262        @Override
263        public boolean onMenuItemClick(MenuItem item) {
264            return onContextItemSelected(item);
265        }
266    };
267
268    @Override
269    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
270        BookmarkContextMenuInfo info = (BookmarkContextMenuInfo) menuInfo;
271        BrowserBookmarksAdapter adapter = getChildAdapter(info.groupPosition);
272        Cursor cursor = adapter.getItem(info.childPosition);
273        if (!canEdit(cursor)) {
274            return;
275        }
276        boolean isFolder
277                = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
278
279        final Activity activity = getActivity();
280        MenuInflater inflater = activity.getMenuInflater();
281        inflater.inflate(R.menu.bookmarkscontext, menu);
282        if (isFolder) {
283            menu.setGroupVisible(R.id.FOLDER_CONTEXT_MENU, true);
284        } else {
285            menu.setGroupVisible(R.id.BOOKMARK_CONTEXT_MENU, true);
286            if (mDisableNewWindow) {
287                menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
288            }
289        }
290        BookmarkItem header = new BookmarkItem(activity);
291        header.setEnableScrolling(true);
292        populateBookmarkItem(cursor, header, isFolder);
293        menu.setHeaderView(header);
294
295        int count = menu.size();
296        for (int i = 0; i < count; i++) {
297            menu.getItem(i).setOnMenuItemClickListener(mContextItemClickListener);
298        }
299    }
300
301    boolean canEdit(Cursor c) {
302        int type = c.getInt(BookmarksLoader.COLUMN_INDEX_TYPE);
303        return type == BrowserContract.Bookmarks.BOOKMARK_TYPE_BOOKMARK
304                || type == BrowserContract.Bookmarks.BOOKMARK_TYPE_FOLDER;
305    }
306
307    private void populateBookmarkItem(Cursor cursor, BookmarkItem item, boolean isFolder) {
308        item.setName(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
309        if (isFolder) {
310            item.setUrl(null);
311            Bitmap bitmap =
312                BitmapFactory.decodeResource(getResources(), R.drawable.ic_folder_holo_dark);
313            item.setFavicon(bitmap);
314            new LookupBookmarkCount(getActivity(), item)
315                    .execute(cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
316        } else {
317            String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
318            item.setUrl(url);
319            Bitmap bitmap = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
320            item.setFavicon(bitmap);
321        }
322    }
323
324    /**
325     *  Create a new BrowserBookmarksPage.
326     */
327    @Override
328    public void onCreate(Bundle icicle) {
329        super.onCreate(icicle);
330        SharedPreferences prefs = BrowserSettings.getInstance().getPreferences();
331        try {
332            mState = new JSONObject(prefs.getString(PREF_GROUP_STATE, "{}"));
333        } catch (JSONException e) {
334            // Parse failed, clear preference and start with empty state
335            prefs.edit().remove(PREF_GROUP_STATE).apply();
336            mState = new JSONObject();
337        }
338        Bundle args = getArguments();
339        mDisableNewWindow = args == null ? false : args.getBoolean(EXTRA_DISABLE_WINDOW, false);
340        setHasOptionsMenu(true);
341        if (mCallbacks == null && getActivity() instanceof CombinedBookmarksCallbacks) {
342            mCallbacks = new CombinedBookmarksCallbackWrapper(
343                    (CombinedBookmarksCallbacks) getActivity());
344        }
345    }
346
347    @Override
348    public void onPause() {
349        super.onPause();
350        try {
351            mState = mGrid.saveGroupState();
352            // Save state
353            SharedPreferences prefs = BrowserSettings.getInstance().getPreferences();
354            prefs.edit()
355                    .putString(PREF_GROUP_STATE, mState.toString())
356                    .apply();
357        } catch (JSONException e) {
358            // Not critical, ignore
359        }
360    }
361
362    private static class CombinedBookmarksCallbackWrapper
363            implements BookmarksPageCallbacks {
364
365        private CombinedBookmarksCallbacks mCombinedCallback;
366
367        private CombinedBookmarksCallbackWrapper(CombinedBookmarksCallbacks cb) {
368            mCombinedCallback = cb;
369        }
370
371        @Override
372        public boolean onOpenInNewWindow(String... urls) {
373            mCombinedCallback.openInNewTab(urls);
374            return true;
375        }
376
377        @Override
378        public boolean onBookmarkSelected(Cursor c, boolean isFolder) {
379            if (isFolder) {
380                return false;
381            }
382            mCombinedCallback.openUrl(BrowserBookmarksPage.getUrl(c));
383            return true;
384        }
385    };
386
387    @Override
388    public View onCreateView(LayoutInflater inflater, ViewGroup container,
389            Bundle savedInstanceState) {
390        mRoot = inflater.inflate(R.layout.bookmarks, container, false);
391        mEmptyView = mRoot.findViewById(android.R.id.empty);
392
393        mGrid = (BookmarkExpandableView) mRoot.findViewById(R.id.grid);
394        mGrid.setOnChildClickListener(this);
395        mGrid.setColumnWidthFromLayout(R.layout.bookmark_thumbnail);
396        mGrid.setBreadcrumbController(this);
397        setEnableContextMenu(mEnableContextMenu);
398
399        // Start the loaders
400        LoaderManager lm = getLoaderManager();
401        lm.restartLoader(LOADER_ACCOUNTS, null, this);
402
403        return mRoot;
404    }
405
406    @Override
407    public void onDestroyView() {
408        super.onDestroyView();
409        mGrid.setBreadcrumbController(null);
410        mGrid.clearAccounts();
411        LoaderManager lm = getLoaderManager();
412        lm.destroyLoader(LOADER_ACCOUNTS);
413        for (int id : mBookmarkAdapters.keySet()) {
414            lm.destroyLoader(id);
415        }
416        mBookmarkAdapters.clear();
417    }
418
419    private BrowserBookmarksAdapter getChildAdapter(int groupPosition) {
420        return mGrid.getChildAdapter(groupPosition);
421    }
422
423    private BreadCrumbView getBreadCrumbs(int groupPosition) {
424        return mGrid.getBreadCrumbs(groupPosition);
425    }
426
427    @Override
428    public boolean onChildClick(ExpandableListView parent, View v,
429            int groupPosition, int childPosition, long id) {
430        BrowserBookmarksAdapter adapter = getChildAdapter(groupPosition);
431        Cursor cursor = adapter.getItem(childPosition);
432        boolean isFolder = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
433        if (mCallbacks != null &&
434                mCallbacks.onBookmarkSelected(cursor, isFolder)) {
435            return true;
436        }
437
438        if (isFolder) {
439            String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
440            Uri uri = ContentUris.withAppendedId(
441                    BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, id);
442            BreadCrumbView crumbs = getBreadCrumbs(groupPosition);
443            if (crumbs != null) {
444                // update crumbs
445                crumbs.pushView(title, uri);
446                crumbs.setVisibility(View.VISIBLE);
447            }
448            loadFolder(groupPosition, uri);
449        }
450        return true;
451    }
452
453    /* package */ static Intent createShortcutIntent(Context context, Cursor cursor) {
454        String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
455        String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
456        Bitmap touchIcon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_TOUCH_ICON);
457        Bitmap favicon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
458        return BookmarkUtils.createAddToHomeIntent(context, url, title, touchIcon, favicon);
459    }
460
461    private void loadUrl(BrowserBookmarksAdapter adapter, int position) {
462        if (mCallbacks != null && adapter != null) {
463            mCallbacks.onBookmarkSelected(adapter.getItem(position), false);
464        }
465    }
466
467    private void openInNewWindow(BrowserBookmarksAdapter adapter, int position) {
468        if (mCallbacks != null) {
469            Cursor c = adapter.getItem(position);
470            boolean isFolder = c.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) == 1;
471            if (isFolder) {
472                long id = c.getLong(BookmarksLoader.COLUMN_INDEX_ID);
473                new OpenAllInTabsTask(id).execute();
474            } else {
475                mCallbacks.onOpenInNewWindow(BrowserBookmarksPage.getUrl(c));
476            }
477        }
478    }
479
480    class OpenAllInTabsTask extends AsyncTask<Void, Void, Cursor> {
481        long mFolderId;
482        public OpenAllInTabsTask(long id) {
483            mFolderId = id;
484        }
485
486        @Override
487        protected Cursor doInBackground(Void... params) {
488            Context c = getActivity();
489            if (c == null) return null;
490            return c.getContentResolver().query(BookmarkUtils.getBookmarksUri(c),
491                    BookmarksLoader.PROJECTION, BrowserContract.Bookmarks.PARENT + "=?",
492                    new String[] { Long.toString(mFolderId) }, null);
493        }
494
495        @Override
496        protected void onPostExecute(Cursor result) {
497            if (mCallbacks != null && result.getCount() > 0) {
498                String[] urls = new String[result.getCount()];
499                int i = 0;
500                while (result.moveToNext()) {
501                    urls[i++] = BrowserBookmarksPage.getUrl(result);
502                }
503                mCallbacks.onOpenInNewWindow(urls);
504            }
505        }
506
507    }
508
509    private void editBookmark(BrowserBookmarksAdapter adapter, int position) {
510        Intent intent = new Intent(getActivity(), AddBookmarkPage.class);
511        Cursor cursor = adapter.getItem(position);
512        Bundle item = new Bundle();
513        item.putString(BrowserContract.Bookmarks.TITLE,
514                cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
515        item.putString(BrowserContract.Bookmarks.URL,
516                cursor.getString(BookmarksLoader.COLUMN_INDEX_URL));
517        byte[] data = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_FAVICON);
518        if (data != null) {
519            item.putParcelable(BrowserContract.Bookmarks.FAVICON,
520                    BitmapFactory.decodeByteArray(data, 0, data.length));
521        }
522        item.putLong(BrowserContract.Bookmarks._ID,
523                cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
524        item.putLong(BrowserContract.Bookmarks.PARENT,
525                cursor.getLong(BookmarksLoader.COLUMN_INDEX_PARENT));
526        intent.putExtra(AddBookmarkPage.EXTRA_EDIT_BOOKMARK, item);
527        intent.putExtra(AddBookmarkPage.EXTRA_IS_FOLDER,
528                cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) == 1);
529        startActivity(intent);
530    }
531
532    private void displayRemoveBookmarkDialog(BrowserBookmarksAdapter adapter,
533            int position) {
534        // Put up a dialog asking if the user really wants to
535        // delete the bookmark
536        Cursor cursor = adapter.getItem(position);
537        long id = cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID);
538        String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
539        Context context = getActivity();
540        BookmarkUtils.displayRemoveBookmarkDialog(id, title, context, null);
541    }
542
543    private String getUrl(BrowserBookmarksAdapter adapter, int position) {
544        return getUrl(adapter.getItem(position));
545    }
546
547    /* package */ static String getUrl(Cursor c) {
548        return c.getString(BookmarksLoader.COLUMN_INDEX_URL);
549    }
550
551    private void copy(CharSequence text) {
552        ClipboardManager cm = (ClipboardManager) getActivity().getSystemService(
553                Context.CLIPBOARD_SERVICE);
554        cm.setPrimaryClip(ClipData.newRawUri(null, Uri.parse(text.toString())));
555    }
556
557    @Override
558    public void onConfigurationChanged(Configuration newConfig) {
559        super.onConfigurationChanged(newConfig);
560        Resources res = getActivity().getResources();
561        mGrid.setColumnWidthFromLayout(R.layout.bookmark_thumbnail);
562        int paddingTop = (int) res.getDimension(R.dimen.combo_paddingTop);
563        mRoot.setPadding(0, paddingTop, 0, 0);
564        getActivity().invalidateOptionsMenu();
565    }
566
567    /**
568     * BreadCrumb controller callback
569     */
570    @Override
571    public void onTop(BreadCrumbView view, int level, Object data) {
572        int groupPosition = (Integer) view.getTag(R.id.group_position);
573        Uri uri = (Uri) data;
574        if (uri == null) {
575            // top level
576            uri = BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER;
577        }
578        loadFolder(groupPosition, uri);
579        if (level <= 1) {
580            view.setVisibility(View.GONE);
581        } else {
582            view.setVisibility(View.VISIBLE);
583        }
584    }
585
586    /**
587     * @param uri
588     */
589    private void loadFolder(int groupPosition, Uri uri) {
590        LoaderManager manager = getLoaderManager();
591        // This assumes groups are ordered the same as loaders
592        BookmarksLoader loader = (BookmarksLoader) ((Loader<?>)
593                manager.getLoader(LOADER_BOOKMARKS + groupPosition));
594        loader.setUri(uri);
595        loader.forceLoad();
596    }
597
598    public void setCallbackListener(BookmarksPageCallbacks callbackListener) {
599        mCallbacks = callbackListener;
600    }
601
602    public void setEnableContextMenu(boolean enable) {
603        mEnableContextMenu = enable;
604        if (mGrid != null) {
605            if (mEnableContextMenu) {
606                registerForContextMenu(mGrid);
607            } else {
608                unregisterForContextMenu(mGrid);
609                mGrid.setLongClickable(false);
610            }
611        }
612    }
613
614    private static class LookupBookmarkCount extends AsyncTask<Long, Void, Integer> {
615        Context mContext;
616        BookmarkItem mHeader;
617
618        public LookupBookmarkCount(Context context, BookmarkItem header) {
619            mContext = context.getApplicationContext();
620            mHeader = header;
621        }
622
623        @Override
624        protected Integer doInBackground(Long... params) {
625            if (params.length != 1) {
626                throw new IllegalArgumentException("Missing folder id!");
627            }
628            Uri uri = BookmarkUtils.getBookmarksUri(mContext);
629            Cursor c = mContext.getContentResolver().query(uri,
630                    null, BrowserContract.Bookmarks.PARENT + "=?",
631                    new String[] {params[0].toString()}, null);
632            return c.getCount();
633        }
634
635        @Override
636        protected void onPostExecute(Integer result) {
637            if (result > 0) {
638                mHeader.setUrl(mContext.getString(R.string.contextheader_folder_bookmarkcount,
639                        result));
640            } else if (result == 0) {
641                mHeader.setUrl(mContext.getString(R.string.contextheader_folder_empty));
642            }
643        }
644    }
645
646    static class AccountsLoader extends CursorLoader {
647
648        static String[] ACCOUNTS_PROJECTION = new String[] {
649            Accounts.ACCOUNT_NAME,
650            Accounts.ACCOUNT_TYPE
651        };
652
653        public AccountsLoader(Context context) {
654            super(context, Accounts.CONTENT_URI
655                    .buildUpon()
656                    .appendQueryParameter(BrowserProvider2.PARAM_ALLOW_EMPTY_ACCOUNTS, "false")
657                    .build(),
658                    ACCOUNTS_PROJECTION, null, null, null);
659        }
660
661    }
662}
663