BrowserBookmarksPage.java revision f94abcf44fc1611f76e55461f48220e621fc31b7
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    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        return getBitmap(cursor, columnIndex, null);
240    }
241
242    static ThreadLocal<Options> sOptions = new ThreadLocal<Options>() {
243        @Override
244        protected Options initialValue() {
245            return new Options();
246        };
247    };
248    static Bitmap getBitmap(Cursor cursor, int columnIndex, Bitmap inBitmap) {
249        byte[] data = cursor.getBlob(columnIndex);
250        if (data == null) {
251            return null;
252        }
253        Options opts = sOptions.get();
254        opts.inBitmap = inBitmap;
255        opts.inSampleSize = 1;
256        opts.inScaled = false;
257        try {
258            return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
259        } catch (IllegalArgumentException ex) {
260            // Failed to re-use bitmap, create a new one
261            return BitmapFactory.decodeByteArray(data, 0, data.length);
262        }
263    }
264
265    private MenuItem.OnMenuItemClickListener mContextItemClickListener =
266            new MenuItem.OnMenuItemClickListener() {
267        @Override
268        public boolean onMenuItemClick(MenuItem item) {
269            return onContextItemSelected(item);
270        }
271    };
272
273    @Override
274    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
275        BookmarkContextMenuInfo info = (BookmarkContextMenuInfo) menuInfo;
276        BrowserBookmarksAdapter adapter = getChildAdapter(info.groupPosition);
277        Cursor cursor = adapter.getItem(info.childPosition);
278        if (!canEdit(cursor)) {
279            return;
280        }
281        boolean isFolder
282                = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
283
284        final Activity activity = getActivity();
285        MenuInflater inflater = activity.getMenuInflater();
286        inflater.inflate(R.menu.bookmarkscontext, menu);
287        if (isFolder) {
288            menu.setGroupVisible(R.id.FOLDER_CONTEXT_MENU, true);
289        } else {
290            menu.setGroupVisible(R.id.BOOKMARK_CONTEXT_MENU, true);
291            if (mDisableNewWindow) {
292                menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
293            }
294        }
295        BookmarkItem header = new BookmarkItem(activity);
296        header.setEnableScrolling(true);
297        populateBookmarkItem(cursor, header, isFolder);
298        menu.setHeaderView(header);
299
300        int count = menu.size();
301        for (int i = 0; i < count; i++) {
302            menu.getItem(i).setOnMenuItemClickListener(mContextItemClickListener);
303        }
304    }
305
306    boolean canEdit(Cursor c) {
307        int type = c.getInt(BookmarksLoader.COLUMN_INDEX_TYPE);
308        return type == BrowserContract.Bookmarks.BOOKMARK_TYPE_BOOKMARK
309                || type == BrowserContract.Bookmarks.BOOKMARK_TYPE_FOLDER;
310    }
311
312    private void populateBookmarkItem(Cursor cursor, BookmarkItem item, boolean isFolder) {
313        item.setName(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
314        if (isFolder) {
315            item.setUrl(null);
316            Bitmap bitmap =
317                BitmapFactory.decodeResource(getResources(), R.drawable.ic_folder_holo_dark);
318            item.setFavicon(bitmap);
319            new LookupBookmarkCount(getActivity(), item)
320                    .execute(cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
321        } else {
322            String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
323            item.setUrl(url);
324            Bitmap bitmap = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
325            item.setFavicon(bitmap);
326        }
327    }
328
329    /**
330     *  Create a new BrowserBookmarksPage.
331     */
332    @Override
333    public void onCreate(Bundle icicle) {
334        super.onCreate(icicle);
335        SharedPreferences prefs = BrowserSettings.getInstance().getPreferences();
336        try {
337            mState = new JSONObject(prefs.getString(PREF_GROUP_STATE, "{}"));
338        } catch (JSONException e) {
339            // Parse failed, clear preference and start with empty state
340            prefs.edit().remove(PREF_GROUP_STATE).apply();
341            mState = new JSONObject();
342        }
343        Bundle args = getArguments();
344        mDisableNewWindow = args == null ? false : args.getBoolean(EXTRA_DISABLE_WINDOW, false);
345        setHasOptionsMenu(true);
346        if (mCallbacks == null && getActivity() instanceof CombinedBookmarksCallbacks) {
347            mCallbacks = new CombinedBookmarksCallbackWrapper(
348                    (CombinedBookmarksCallbacks) getActivity());
349        }
350    }
351
352    @Override
353    public void onPause() {
354        super.onPause();
355        try {
356            mState = mGrid.saveGroupState();
357            // Save state
358            SharedPreferences prefs = BrowserSettings.getInstance().getPreferences();
359            prefs.edit()
360                    .putString(PREF_GROUP_STATE, mState.toString())
361                    .apply();
362        } catch (JSONException e) {
363            // Not critical, ignore
364        }
365    }
366
367    private static class CombinedBookmarksCallbackWrapper
368            implements BookmarksPageCallbacks {
369
370        private CombinedBookmarksCallbacks mCombinedCallback;
371
372        private CombinedBookmarksCallbackWrapper(CombinedBookmarksCallbacks cb) {
373            mCombinedCallback = cb;
374        }
375
376        @Override
377        public boolean onOpenInNewWindow(String... urls) {
378            mCombinedCallback.openInNewTab(urls);
379            return true;
380        }
381
382        @Override
383        public boolean onBookmarkSelected(Cursor c, boolean isFolder) {
384            if (isFolder) {
385                return false;
386            }
387            mCombinedCallback.openUrl(BrowserBookmarksPage.getUrl(c));
388            return true;
389        }
390    };
391
392    @Override
393    public View onCreateView(LayoutInflater inflater, ViewGroup container,
394            Bundle savedInstanceState) {
395        mRoot = inflater.inflate(R.layout.bookmarks, container, false);
396        mEmptyView = mRoot.findViewById(android.R.id.empty);
397
398        mGrid = (BookmarkExpandableView) mRoot.findViewById(R.id.grid);
399        mGrid.setOnChildClickListener(this);
400        mGrid.setColumnWidthFromLayout(R.layout.bookmark_thumbnail);
401        mGrid.setBreadcrumbController(this);
402        setEnableContextMenu(mEnableContextMenu);
403        mDragHandler = new BookmarkDragHandler(getActivity(), mDragController,
404                mGrid.getDragAdapter());
405
406        // Start the loaders
407        LoaderManager lm = getLoaderManager();
408        lm.restartLoader(LOADER_ACCOUNTS, null, this);
409
410        return mRoot;
411    }
412
413    @Override
414    public void onDestroyView() {
415        super.onDestroyView();
416        mGrid.setBreadcrumbController(null);
417        mGrid.clearAccounts();
418        LoaderManager lm = getLoaderManager();
419        lm.destroyLoader(LOADER_ACCOUNTS);
420        for (int id : mBookmarkAdapters.keySet()) {
421            lm.destroyLoader(id);
422        }
423        mBookmarkAdapters.clear();
424    }
425
426    private BrowserBookmarksAdapter getChildAdapter(int groupPosition) {
427        return mGrid.getChildAdapter(groupPosition);
428    }
429
430    private BreadCrumbView getBreadCrumbs(int groupPosition) {
431        return mGrid.getBreadCrumbs(groupPosition);
432    }
433
434    @Override
435    public boolean onChildClick(ExpandableListView parent, View v,
436            int groupPosition, int childPosition, long id) {
437        BrowserBookmarksAdapter adapter = getChildAdapter(groupPosition);
438        Cursor cursor = adapter.getItem(childPosition);
439        boolean isFolder = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
440        if (mCallbacks != null &&
441                mCallbacks.onBookmarkSelected(cursor, isFolder)) {
442            return true;
443        }
444
445        if (isFolder) {
446            String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
447            Uri uri = ContentUris.withAppendedId(
448                    BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, id);
449            BreadCrumbView crumbs = getBreadCrumbs(groupPosition);
450            if (crumbs != null) {
451                // update crumbs
452                crumbs.pushView(title, uri);
453                crumbs.setVisibility(View.VISIBLE);
454            }
455            loadFolder(groupPosition, uri);
456        }
457        return true;
458    }
459
460    /* package */ static Intent createShortcutIntent(Context context, Cursor cursor) {
461        String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
462        String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
463        Bitmap touchIcon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_TOUCH_ICON);
464        Bitmap favicon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
465        return BookmarkUtils.createAddToHomeIntent(context, url, title, touchIcon, favicon);
466    }
467
468    private void loadUrl(BrowserBookmarksAdapter adapter, int position) {
469        if (mCallbacks != null && adapter != null) {
470            mCallbacks.onBookmarkSelected(adapter.getItem(position), false);
471        }
472    }
473
474    private void openInNewWindow(BrowserBookmarksAdapter adapter, int position) {
475        if (mCallbacks != null) {
476            Cursor c = adapter.getItem(position);
477            boolean isFolder = c.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) == 1;
478            if (isFolder) {
479                long id = c.getLong(BookmarksLoader.COLUMN_INDEX_ID);
480                new OpenAllInTabsTask(id).execute();
481            } else {
482                mCallbacks.onOpenInNewWindow(BrowserBookmarksPage.getUrl(c));
483            }
484        }
485    }
486
487    class OpenAllInTabsTask extends AsyncTask<Void, Void, Cursor> {
488        long mFolderId;
489        public OpenAllInTabsTask(long id) {
490            mFolderId = id;
491        }
492
493        @Override
494        protected Cursor doInBackground(Void... params) {
495            Context c = getActivity();
496            if (c == null) return null;
497            return c.getContentResolver().query(BookmarkUtils.getBookmarksUri(c),
498                    BookmarksLoader.PROJECTION, BrowserContract.Bookmarks.PARENT + "=?",
499                    new String[] { Long.toString(mFolderId) }, null);
500        }
501
502        @Override
503        protected void onPostExecute(Cursor result) {
504            if (mCallbacks != null && result.getCount() > 0) {
505                String[] urls = new String[result.getCount()];
506                int i = 0;
507                while (result.moveToNext()) {
508                    urls[i++] = BrowserBookmarksPage.getUrl(result);
509                }
510                mCallbacks.onOpenInNewWindow(urls);
511            }
512        }
513
514    }
515
516    private void editBookmark(BrowserBookmarksAdapter adapter, int position) {
517        Intent intent = new Intent(getActivity(), AddBookmarkPage.class);
518        Cursor cursor = adapter.getItem(position);
519        Bundle item = new Bundle();
520        item.putString(BrowserContract.Bookmarks.TITLE,
521                cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
522        item.putString(BrowserContract.Bookmarks.URL,
523                cursor.getString(BookmarksLoader.COLUMN_INDEX_URL));
524        byte[] data = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_FAVICON);
525        if (data != null) {
526            item.putParcelable(BrowserContract.Bookmarks.FAVICON,
527                    BitmapFactory.decodeByteArray(data, 0, data.length));
528        }
529        item.putLong(BrowserContract.Bookmarks._ID,
530                cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
531        item.putLong(BrowserContract.Bookmarks.PARENT,
532                cursor.getLong(BookmarksLoader.COLUMN_INDEX_PARENT));
533        intent.putExtra(AddBookmarkPage.EXTRA_EDIT_BOOKMARK, item);
534        intent.putExtra(AddBookmarkPage.EXTRA_IS_FOLDER,
535                cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) == 1);
536        startActivity(intent);
537    }
538
539    private void displayRemoveBookmarkDialog(BrowserBookmarksAdapter adapter,
540            int position) {
541        // Put up a dialog asking if the user really wants to
542        // delete the bookmark
543        Cursor cursor = adapter.getItem(position);
544        long id = cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID);
545        String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
546        Context context = getActivity();
547        BookmarkUtils.displayRemoveBookmarkDialog(id, title, context, null);
548    }
549
550    private String getUrl(BrowserBookmarksAdapter adapter, int position) {
551        return getUrl(adapter.getItem(position));
552    }
553
554    /* package */ static String getUrl(Cursor c) {
555        return c.getString(BookmarksLoader.COLUMN_INDEX_URL);
556    }
557
558    private void copy(CharSequence text) {
559        ClipboardManager cm = (ClipboardManager) getActivity().getSystemService(
560                Context.CLIPBOARD_SERVICE);
561        cm.setPrimaryClip(ClipData.newRawUri(null, Uri.parse(text.toString())));
562    }
563
564    @Override
565    public void onConfigurationChanged(Configuration newConfig) {
566        super.onConfigurationChanged(newConfig);
567        Resources res = getActivity().getResources();
568        mGrid.setColumnWidthFromLayout(R.layout.bookmark_thumbnail);
569        int paddingTop = (int) res.getDimension(R.dimen.combo_paddingTop);
570        mRoot.setPadding(0, paddingTop, 0, 0);
571        getActivity().invalidateOptionsMenu();
572    }
573
574    /**
575     * BreadCrumb controller callback
576     */
577    @Override
578    public void onTop(BreadCrumbView view, int level, Object data) {
579        int groupPosition = (Integer) view.getTag(R.id.group_position);
580        Uri uri = (Uri) data;
581        if (uri == null) {
582            // top level
583            uri = BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER;
584        }
585        loadFolder(groupPosition, uri);
586        if (level <= 1) {
587            view.setVisibility(View.GONE);
588        } else {
589            view.setVisibility(View.VISIBLE);
590        }
591    }
592
593    /**
594     * @param uri
595     */
596    private void loadFolder(int groupPosition, Uri uri) {
597        LoaderManager manager = getLoaderManager();
598        // This assumes groups are ordered the same as loaders
599        BookmarksLoader loader = (BookmarksLoader) ((Loader<?>)
600                manager.getLoader(LOADER_BOOKMARKS + groupPosition));
601        loader.setUri(uri);
602        loader.forceLoad();
603    }
604
605    public void setCallbackListener(BookmarksPageCallbacks callbackListener) {
606        mCallbacks = callbackListener;
607    }
608
609    public void setEnableContextMenu(boolean enable) {
610        mEnableContextMenu = enable;
611        if (mGrid != null) {
612            if (mEnableContextMenu) {
613                registerForContextMenu(mGrid);
614            } else {
615                unregisterForContextMenu(mGrid);
616                mGrid.setLongClickable(false);
617            }
618        }
619    }
620
621    private BookmarkDragController mDragController = new BookmarkDragController() {
622
623        @Override
624        public boolean startDrag(Cursor item) {
625            return canEdit(item);
626        }
627
628        @Override
629        public ViewGroup getActionModeView(ActionMode mode,
630                BookmarkDragState state) {
631            LayoutInflater inflater = LayoutInflater.from(getActivity());
632            LinearLayout view = (LinearLayout) inflater.inflate(
633                    R.layout.bookmarks_drag_actionmode, null);
634            view.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
635            ExtraDragState extraState = (ExtraDragState) state.extraState;
636            BrowserBookmarksAdapter adapter = getChildAdapter(extraState.groupPosition);
637            Cursor c = adapter.getItem(extraState.childPosition);
638            boolean isFolder = c.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
639            if (isFolder) {
640                view.findViewById(R.id.open_context_menu_id).setVisibility(View.GONE);
641                ImageView iv = (ImageView) view.findViewById(
642                        R.id.new_window_context_menu_id);
643                iv.setImageResource(R.drawable.ic_windows_holo_dark);
644            }
645            return view;
646        }
647
648        @Override
649        public void actionItemClicked(View v, BookmarkDragState state) {
650            if (v.getId() == R.id.info) {
651                mGrid.showContextMenuForState(state);
652            } else {
653                ExtraDragState extraState = (ExtraDragState) state.extraState;
654                handleContextItem(v.getId(), extraState.groupPosition,
655                        extraState.childPosition);
656            }
657        }
658    };
659
660    private static class LookupBookmarkCount extends AsyncTask<Long, Void, Integer> {
661        Context mContext;
662        BookmarkItem mHeader;
663
664        public LookupBookmarkCount(Context context, BookmarkItem header) {
665            mContext = context.getApplicationContext();
666            mHeader = header;
667        }
668
669        @Override
670        protected Integer doInBackground(Long... params) {
671            if (params.length != 1) {
672                throw new IllegalArgumentException("Missing folder id!");
673            }
674            Uri uri = BookmarkUtils.getBookmarksUri(mContext);
675            Cursor c = mContext.getContentResolver().query(uri,
676                    null, BrowserContract.Bookmarks.PARENT + "=?",
677                    new String[] {params[0].toString()}, null);
678            return c.getCount();
679        }
680
681        @Override
682        protected void onPostExecute(Integer result) {
683            if (result > 0) {
684                mHeader.setUrl(mContext.getString(R.string.contextheader_folder_bookmarkcount,
685                        result));
686            } else if (result == 0) {
687                mHeader.setUrl(mContext.getString(R.string.contextheader_folder_empty));
688            }
689        }
690    }
691
692    static class AccountsLoader extends CursorLoader {
693
694        static String[] ACCOUNTS_PROJECTION = new String[] {
695            Accounts.ACCOUNT_NAME,
696            Accounts.ACCOUNT_TYPE
697        };
698
699        public AccountsLoader(Context context) {
700            super(context, Accounts.CONTENT_URI
701                    .buildUpon()
702                    .appendQueryParameter(BrowserProvider2.PARAM_ALLOW_EMPTY_ACCOUNTS, "false")
703                    .build(),
704                    ACCOUNTS_PROJECTION, null, null, null);
705        }
706
707    }
708}
709