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