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