BrowserBookmarksPage.java revision 1cebb445779da9ca597621c79b020d6e5ea54fb2
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 com.android.browser.BreadCrumbView.Crumb;
20
21import android.app.Activity;
22import android.app.AlertDialog;
23import android.app.Fragment;
24import android.app.LoaderManager;
25import android.content.ClipData;
26import android.content.ClipboardManager;
27import android.content.ContentResolver;
28import android.content.ContentUris;
29import android.content.Context;
30import android.content.CursorLoader;
31import android.content.DialogInterface;
32import android.content.Intent;
33import android.content.Loader;
34import android.content.SharedPreferences;
35import android.content.SharedPreferences.Editor;
36import android.database.Cursor;
37import android.graphics.Bitmap;
38import android.graphics.BitmapFactory;
39import android.net.Uri;
40import android.os.AsyncTask;
41import android.os.Bundle;
42import android.preference.PreferenceManager;
43import android.provider.BrowserContract;
44import android.provider.BrowserContract.Accounts;
45import android.provider.BrowserContract.ChromeSyncColumns;
46import android.text.TextUtils;
47import android.view.ContextMenu;
48import android.view.ContextMenu.ContextMenuInfo;
49import android.view.LayoutInflater;
50import android.view.MenuInflater;
51import android.view.MenuItem;
52import android.view.View;
53import android.view.View.OnClickListener;
54import android.view.ViewGroup;
55import android.webkit.WebIconDatabase.IconListener;
56import android.widget.Adapter;
57import android.widget.AdapterView;
58import android.widget.AdapterView.OnItemClickListener;
59import android.widget.AdapterView.OnItemSelectedListener;
60import android.widget.GridView;
61import android.widget.ListView;
62import android.widget.PopupMenu.OnMenuItemClickListener;
63import android.widget.TextView;
64import android.widget.Toast;
65
66interface BookmarksPageCallbacks {
67    // Return true if handled
68    boolean onBookmarkSelected(Cursor c, boolean isFolder);
69    // Return true if handled
70    boolean onOpenInNewWindow(Cursor c);
71    void onFolderChanged(int level, Uri uri);
72}
73
74/**
75 *  View showing the user's bookmarks in the browser.
76 */
77public class BrowserBookmarksPage extends Fragment implements View.OnCreateContextMenuListener,
78        LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, IconListener,
79        OnItemSelectedListener, BreadCrumbView.Controller, OnClickListener, OnMenuItemClickListener {
80
81    static final String LOGTAG = "browser";
82
83    static final int LOADER_BOOKMARKS = 1;
84    static final int LOADER_ACCOUNTS_THEN_BOOKMARKS = 2;
85
86    static final String EXTRA_DISABLE_WINDOW = "disable_new_window";
87
88    static final String ACCOUNT_NAME_UNSYNCED = "Unsynced";
89
90    public static final String PREF_ACCOUNT_TYPE = "acct_type";
91    public static final String PREF_ACCOUNT_NAME = "acct_name";
92
93    static final String DEFAULT_ACCOUNT = "local";
94    static final int VIEW_THUMBNAILS = 1;
95    static final int VIEW_LIST = 2;
96    static final String PREF_SELECTED_VIEW = "bookmarks_view";
97
98    BookmarksPageCallbacks mCallbacks;
99    GridView mGrid;
100    ListView mList;
101    BrowserBookmarksAdapter mAdapter;
102    boolean mDisableNewWindow;
103    boolean mCanceled = false;
104    boolean mEnableContextMenu = true;
105    boolean mShowRootFolder = false;
106    View mEmptyView;
107    int mCurrentView;
108    View mHeader;
109    ViewGroup mHeaderContainer;
110    BreadCrumbView mCrumbs;
111    TextView mSelectBookmarkView;
112    int mCrumbVisibility = View.VISIBLE;
113    int mCrumbMaxVisible = -1;
114    boolean mCrumbBackButton = false;
115
116    static BrowserBookmarksPage newInstance(BookmarksPageCallbacks cb,
117            Bundle args, ViewGroup headerContainer) {
118        BrowserBookmarksPage bbp = new BrowserBookmarksPage();
119        bbp.mCallbacks = cb;
120        bbp.mHeaderContainer = headerContainer;
121        bbp.setArguments(args);
122        return bbp;
123    }
124
125    @Override
126    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
127        switch (id) {
128            case LOADER_BOOKMARKS: {
129                String accountType = null;
130                String accountName = null;
131                if (args != null) {
132                    accountType = args.getString(BookmarksLoader.ARG_ACCOUNT_TYPE);
133                    accountName = args.getString(BookmarksLoader.ARG_ACCOUNT_NAME);
134                }
135                BookmarksLoader bl = new BookmarksLoader(getActivity(), accountType, accountName);
136                if (mCrumbs != null) {
137                    Uri uri = (Uri) mCrumbs.getTopData();
138                    if (uri != null) {
139                        bl.setUri(uri);
140                    }
141                }
142                return bl;
143            }
144            case LOADER_ACCOUNTS_THEN_BOOKMARKS: {
145                return new CursorLoader(getActivity(), Accounts.CONTENT_URI,
146                        new String[] { Accounts.ACCOUNT_TYPE, Accounts.ACCOUNT_NAME }, null, null,
147                        null);
148            }
149        }
150        throw new UnsupportedOperationException("Unknown loader id " + id);
151    }
152
153    @Override
154    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
155        switch (loader.getId()) {
156            case LOADER_BOOKMARKS: {
157                // Set the visibility of the empty vs. content views
158                if (cursor == null || cursor.getCount() == 0) {
159                    mEmptyView.setVisibility(View.VISIBLE);
160                    mGrid.setVisibility(View.GONE);
161                    mList.setVisibility(View.GONE);
162                } else {
163                    mEmptyView.setVisibility(View.GONE);
164                    setupBookmarkView();
165                }
166
167                // Give the new data to the adapter
168                mAdapter.changeCursor(cursor);
169                break;
170            }
171
172            case LOADER_ACCOUNTS_THEN_BOOKMARKS: {
173                SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
174                        getActivity());
175                String storedAccountType = prefs.getString(PREF_ACCOUNT_TYPE, null);
176                String storedAccountName = prefs.getString(PREF_ACCOUNT_NAME, null);
177                String accountType =
178                        TextUtils.isEmpty(storedAccountType) ? DEFAULT_ACCOUNT : storedAccountType;
179                String accountName =
180                        TextUtils.isEmpty(storedAccountName) ? DEFAULT_ACCOUNT : storedAccountName;
181
182                Bundle args = null;
183                if (cursor == null || !cursor.moveToFirst()) {
184                    // No accounts, set the prefs to the default
185                    accountType = DEFAULT_ACCOUNT;
186                    accountName = DEFAULT_ACCOUNT;
187                } else {
188                    int accountPosition = -1;
189
190                    if (!DEFAULT_ACCOUNT.equals(accountType) &&
191                            !DEFAULT_ACCOUNT.equals(accountName)) {
192                        // Check to see if the account in prefs still exists
193                        cursor.moveToFirst();
194                        do {
195                            if (accountType.equals(cursor.getString(0))
196                                    && accountName.equals(cursor.getString(1))) {
197                                accountPosition = cursor.getPosition();
198                                break;
199                            }
200                        } while (cursor.moveToNext());
201                    }
202
203                    if (accountPosition == -1) {
204                        if (!(DEFAULT_ACCOUNT.equals(accountType)
205                                && DEFAULT_ACCOUNT.equals(accountName))) {
206                            // No account is set in prefs and there is at least one,
207                            // so pick the first one as the default
208                            cursor.moveToFirst();
209                            accountType = cursor.getString(0);
210                            accountName = cursor.getString(1);
211                        }
212                    }
213
214                    args = new Bundle();
215                    args.putString(BookmarksLoader.ARG_ACCOUNT_TYPE, accountType);
216                    args.putString(BookmarksLoader.ARG_ACCOUNT_NAME, accountName);
217                }
218
219                // The stored account name wasn't found, update the stored account with a valid one
220                if (!accountType.equals(storedAccountType)
221                        || !accountName.equals(storedAccountName)) {
222                    prefs.edit()
223                            .putString(PREF_ACCOUNT_TYPE, accountType)
224                            .putString(PREF_ACCOUNT_NAME, accountName)
225                            .apply();
226                }
227                getLoaderManager().initLoader(LOADER_BOOKMARKS, args, this);
228
229                break;
230            }
231        }
232    }
233
234    long getFolderId() {
235        LoaderManager manager = getLoaderManager();
236        BookmarksLoader loader =
237                (BookmarksLoader) ((Loader<?>)manager.getLoader(LOADER_BOOKMARKS));
238
239        Uri uri = loader.getUri();
240        if (uri != null) {
241            try {
242                return ContentUris.parseId(uri);
243            } catch (NumberFormatException nfx) {
244                return -1;
245            }
246        }
247        return -1;
248    }
249
250    @Override
251    public boolean onContextItemSelected(MenuItem item) {
252        final Activity activity = getActivity();
253        // It is possible that the view has been canceled when we get to
254        // this point as back has a higher priority
255        if (mCanceled) {
256            return false;
257        }
258        AdapterView.AdapterContextMenuInfo i =
259            (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
260        // If we have no menu info, we can't tell which item was selected.
261        if (i == null) {
262            return false;
263        }
264
265        switch (item.getItemId()) {
266        case R.id.open_context_menu_id:
267            loadUrl(i.position);
268            break;
269        case R.id.edit_context_menu_id:
270            editBookmark(i.position);
271            break;
272        case R.id.shortcut_context_menu_id:
273            Cursor c = mAdapter.getItem(i.position);
274            activity.sendBroadcast(createShortcutIntent(getActivity(), c));
275            break;
276        case R.id.delete_context_menu_id:
277            displayRemoveBookmarkDialog(i.position);
278            break;
279        case R.id.new_window_context_menu_id:
280            openInNewWindow(i.position);
281            break;
282        case R.id.share_link_context_menu_id: {
283            Cursor cursor = mAdapter.getItem(i.position);
284            Controller.sharePage(activity,
285                    cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE),
286                    cursor.getString(BookmarksLoader.COLUMN_INDEX_URL),
287                    getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON),
288                    getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_THUMBNAIL));
289            break;
290        }
291        case R.id.copy_url_context_menu_id:
292            copy(getUrl(i.position));
293            break;
294        case R.id.homepage_context_menu_id: {
295            BrowserSettings.getInstance().setHomePage(activity, getUrl(i.position));
296            Toast.makeText(activity, R.string.homepage_set, Toast.LENGTH_LONG).show();
297            break;
298        }
299        // Only for the Most visited page
300        case R.id.save_to_bookmarks_menu_id: {
301            Cursor cursor = mAdapter.getItem(i.position);
302            String name = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
303            String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
304            // If the site is bookmarked, the item becomes remove from
305            // bookmarks.
306            Bookmarks.removeFromBookmarks(activity, activity.getContentResolver(), url, name);
307            break;
308        }
309        default:
310            return super.onContextItemSelected(item);
311        }
312        return true;
313    }
314
315    static Bitmap getBitmap(Cursor cursor, int columnIndex) {
316        byte[] data = cursor.getBlob(columnIndex);
317        if (data == null) {
318            return null;
319        }
320        return BitmapFactory.decodeByteArray(data, 0, data.length);
321    }
322
323    private MenuItem.OnMenuItemClickListener mContextItemClickListener =
324            new MenuItem.OnMenuItemClickListener() {
325        @Override
326        public boolean onMenuItemClick(MenuItem item) {
327            return onContextItemSelected(item);
328        }
329    };
330
331    @Override
332    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
333        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
334        Cursor cursor = mAdapter.getItem(info.position);
335        if (!canEdit(cursor)) {
336            return;
337        }
338        boolean isFolder
339                = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
340
341        final Activity activity = getActivity();
342        MenuInflater inflater = activity.getMenuInflater();
343        inflater.inflate(R.menu.bookmarkscontext, menu);
344        if (isFolder) {
345            menu.setGroupVisible(R.id.FOLDER_CONTEXT_MENU, true);
346        } else {
347            menu.setGroupVisible(R.id.BOOKMARK_CONTEXT_MENU, true);
348            if (mDisableNewWindow) {
349                menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
350            }
351        }
352        BookmarkItem header = new BookmarkItem(activity);
353        populateBookmarkItem(cursor, header, isFolder);
354        menu.setHeaderView(header);
355
356        int count = menu.size();
357        for (int i = 0; i < count; i++) {
358            menu.getItem(i).setOnMenuItemClickListener(mContextItemClickListener);
359        }
360    }
361
362    boolean canEdit(Cursor c) {
363        String unique = c.getString(BookmarksLoader.COLUMN_INDEX_SERVER_UNIQUE);
364        return !ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS.equals(unique);
365    }
366
367    private void populateBookmarkItem(Cursor cursor, BookmarkItem item, boolean isFolder) {
368        item.setName(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
369        if (isFolder) {
370            item.setUrl(null);
371            Bitmap bitmap =
372                BitmapFactory.decodeResource(getResources(), R.drawable.ic_folder);
373            item.setFavicon(bitmap);
374            new LookupBookmarkCount(getActivity(), item)
375                    .execute(cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
376        } else {
377            String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
378            item.setUrl(url);
379            Bitmap bitmap = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
380            if (bitmap == null) {
381                bitmap = CombinedBookmarkHistoryView.getIconListenerSet().getFavicon(url);
382            }
383            item.setFavicon(bitmap);
384        }
385    }
386
387    /**
388     *  Create a new BrowserBookmarksPage.
389     */
390    @Override
391    public void onCreate(Bundle icicle) {
392        super.onCreate(icicle);
393
394        Bundle args = getArguments();
395        mDisableNewWindow = args == null ? false : args.getBoolean(EXTRA_DISABLE_WINDOW, false);
396    }
397
398    @Override
399    public View onCreateView(LayoutInflater inflater, ViewGroup container,
400            Bundle savedInstanceState) {
401        Context context = getActivity();
402
403        View root = inflater.inflate(R.layout.bookmarks, container, false);
404        mEmptyView = root.findViewById(android.R.id.empty);
405
406        mGrid = (GridView) root.findViewById(R.id.grid);
407        mGrid.setOnItemClickListener(this);
408        mGrid.setColumnWidth(Controller.getDesiredThumbnailWidth(getActivity()));
409        mList = (ListView) root.findViewById(R.id.list);
410        mList.setOnItemClickListener(this);
411        setEnableContextMenu(mEnableContextMenu);
412
413        // Prep the header
414        ViewGroup hc = mHeaderContainer;
415        if (hc == null) {
416            hc = (ViewGroup) root.findViewById(R.id.header_container);
417            hc.setVisibility(View.VISIBLE);
418        }
419        mHeader = inflater.inflate(R.layout.bookmarks_header, hc, false);
420        hc.addView(mHeader);
421        mCrumbs = (BreadCrumbView) mHeader.findViewById(R.id.crumbs);
422        mCrumbs.setController(this);
423        mCrumbs.setUseBackButton(mCrumbBackButton);
424        mCrumbs.setMaxVisible(mCrumbMaxVisible);
425        mCrumbs.setVisibility(mCrumbVisibility);
426        String name = getString(R.string.bookmarks);
427        mCrumbs.pushView(name, false, BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
428        if (mCallbacks != null) {
429            mCallbacks.onFolderChanged(1, BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
430        }
431        mSelectBookmarkView = (TextView) mHeader.findViewById(R.id.select_bookmark_view);
432        mSelectBookmarkView.setOnClickListener(this);
433
434        // Start the loaders
435        LoaderManager lm = getLoaderManager();
436        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
437        mCurrentView =
438            prefs.getInt(PREF_SELECTED_VIEW, BrowserBookmarksPage.VIEW_THUMBNAILS);
439        if (mCurrentView == BrowserBookmarksPage.VIEW_THUMBNAILS) {
440            mSelectBookmarkView.setText(R.string.bookmark_list_view);
441        } else {
442            mSelectBookmarkView.setText(R.string.bookmark_thumbnail_view);
443        }
444        mAdapter = new BrowserBookmarksAdapter(getActivity(), mCurrentView);
445        String accountType = prefs.getString(PREF_ACCOUNT_TYPE, DEFAULT_ACCOUNT);
446        String accountName = prefs.getString(PREF_ACCOUNT_NAME, DEFAULT_ACCOUNT);
447        if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
448            // There is an account set, load up that one
449            Bundle args = null;
450            if (!DEFAULT_ACCOUNT.equals(accountType) && !DEFAULT_ACCOUNT.equals(accountName)) {
451                args = new Bundle();
452                args.putString(BookmarksLoader.ARG_ACCOUNT_TYPE, accountType);
453                args.putString(BookmarksLoader.ARG_ACCOUNT_NAME, accountName);
454            }
455            lm.restartLoader(LOADER_BOOKMARKS, args, this);
456        } else {
457            // No account set, load the account list first
458            lm.restartLoader(LOADER_ACCOUNTS_THEN_BOOKMARKS, null, this);
459        }
460
461        // Add our own listener in case there are favicons that have yet to be loaded.
462        CombinedBookmarkHistoryView.getIconListenerSet().addListener(this);
463
464        return root;
465    }
466
467    @Override
468    public void onDestroyView() {
469        super.onDestroyView();
470        if (mHeaderContainer != null) {
471            mHeaderContainer.removeView(mHeader);
472        }
473        mCrumbs.setController(null);
474        mCrumbs = null;
475    }
476
477    @Override
478    public void onReceivedIcon(String url, Bitmap icon) {
479        // A new favicon has been loaded, so let anything attached to the adapter know about it
480        // so new icons will be loaded.
481        mAdapter.notifyDataSetChanged();
482    }
483
484    @Override
485    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
486        // It is possible that the view has been canceled when we get to
487        // this point as back has a higher priority
488        if (mCanceled) {
489            android.util.Log.e(LOGTAG, "item clicked when dismissing");
490            return;
491        }
492
493        Cursor cursor = mAdapter.getItem(position);
494        boolean isFolder = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
495        if (mCallbacks != null &&
496                mCallbacks.onBookmarkSelected(cursor, isFolder)) {
497            return;
498        }
499
500        if (isFolder) {
501            String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
502            Uri uri = ContentUris.withAppendedId(
503                    BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, id);
504            if (mCrumbs != null) {
505                // update crumbs
506                mCrumbs.pushView(title, uri);
507            }
508            loadFolder(uri);
509        }
510    }
511
512    @Override
513    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
514        Adapter adapter = parent.getAdapter();
515        String accountType = "com.google";
516        String accountName = adapter.getItem(position).toString();
517
518        Bundle args = null;
519        if (ACCOUNT_NAME_UNSYNCED.equals(accountName)) {
520            accountType = DEFAULT_ACCOUNT;
521            accountName = DEFAULT_ACCOUNT;
522        } else {
523            args = new Bundle();
524            args.putString(BookmarksLoader.ARG_ACCOUNT_TYPE, accountType);
525            args.putString(BookmarksLoader.ARG_ACCOUNT_NAME, accountName);
526        }
527
528        // Remember the selection for later
529        PreferenceManager.getDefaultSharedPreferences(getActivity()).edit()
530                .putString(PREF_ACCOUNT_TYPE, accountType)
531                .putString(PREF_ACCOUNT_NAME, accountName)
532                .apply();
533
534        getLoaderManager().restartLoader(LOADER_BOOKMARKS, args, this);
535    }
536
537    @Override
538    public void onNothingSelected(AdapterView<?> parent) {
539        // Do nothing
540    }
541
542    /* package */ static Intent createShortcutIntent(Context context, Cursor cursor) {
543        String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
544        String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
545        Bitmap touchIcon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_TOUCH_ICON);
546        Bitmap favicon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
547        return BookmarkUtils.createAddToHomeIntent(context, url, title, touchIcon, favicon);
548    }
549
550    private void loadUrl(int position) {
551        if (mCallbacks != null) {
552            mCallbacks.onBookmarkSelected(mAdapter.getItem(position), false);
553        }
554    }
555
556    private void openInNewWindow(int position) {
557        if (mCallbacks != null) {
558            mCallbacks.onOpenInNewWindow(mAdapter.getItem(position));
559        }
560    }
561
562    private void editBookmark(int position) {
563        Intent intent = new Intent(getActivity(), AddBookmarkPage.class);
564        Cursor cursor = mAdapter.getItem(position);
565        Bundle item = new Bundle();
566        item.putString(BrowserContract.Bookmarks.TITLE,
567                cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
568        item.putString(BrowserContract.Bookmarks.URL,
569                cursor.getString(BookmarksLoader.COLUMN_INDEX_URL));
570        byte[] data = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_FAVICON);
571        if (data != null) {
572            item.putParcelable(BrowserContract.Bookmarks.FAVICON,
573                    BitmapFactory.decodeByteArray(data, 0, data.length));
574        }
575        item.putLong(BrowserContract.Bookmarks._ID,
576                cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
577        item.putLong(BrowserContract.Bookmarks.PARENT,
578                cursor.getLong(BookmarksLoader.COLUMN_INDEX_PARENT));
579        intent.putExtra(AddBookmarkPage.EXTRA_EDIT_BOOKMARK, item);
580        intent.putExtra(AddBookmarkPage.EXTRA_IS_FOLDER,
581                cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) == 1);
582        startActivity(intent);
583    }
584
585    private void displayRemoveBookmarkDialog(final int position) {
586        // Put up a dialog asking if the user really wants to
587        // delete the bookmark
588        Cursor cursor = mAdapter.getItem(position);
589        Context context = getActivity();
590        final ContentResolver resolver = context.getContentResolver();
591        final Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI,
592                cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
593
594        new AlertDialog.Builder(context)
595                .setTitle(R.string.delete_bookmark)
596                .setIcon(android.R.drawable.ic_dialog_alert)
597                .setMessage(context.getString(R.string.delete_bookmark_warning,
598                        cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE)))
599                .setPositiveButton(R.string.ok,
600                        new DialogInterface.OnClickListener() {
601                            @Override
602                            public void onClick(DialogInterface dialog, int whichButton) {
603                                resolver.delete(uri, null, null);
604                            }
605                        })
606                .setNegativeButton(R.string.cancel, null)
607                .show();
608    }
609
610    private String getUrl(int position) {
611        return getUrl(mAdapter.getItem(position));
612    }
613
614    /* package */ static String getUrl(Cursor c) {
615        return c.getString(BookmarksLoader.COLUMN_INDEX_URL);
616    }
617
618    private void copy(CharSequence text) {
619        ClipboardManager cm = (ClipboardManager) getActivity().getSystemService(
620                Context.CLIPBOARD_SERVICE);
621        cm.setPrimaryClip(ClipData.newRawUri(null, null, Uri.parse(text.toString())));
622    }
623
624    void selectView(int view) {
625        if (view == mCurrentView) {
626            return;
627        }
628        mCurrentView = view;
629        if (mCurrentView == BrowserBookmarksPage.VIEW_THUMBNAILS) {
630            mSelectBookmarkView.setText(R.string.bookmark_list_view);
631        } else {
632            mSelectBookmarkView.setText(R.string.bookmark_thumbnail_view);
633        }
634        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
635        Editor edit = prefs.edit();
636        edit.putInt(PREF_SELECTED_VIEW, mCurrentView);
637        edit.apply();
638        if (mEmptyView.getVisibility() == View.VISIBLE) {
639            return;
640        }
641        setupBookmarkView();
642    }
643
644    private void setupBookmarkView() {
645        mAdapter.selectView(mCurrentView);
646        switch (mCurrentView) {
647        case VIEW_THUMBNAILS:
648            mList.setAdapter(null);
649            mGrid.setAdapter(mAdapter);
650            mGrid.setVisibility(View.VISIBLE);
651            mList.setVisibility(View.GONE);
652            break;
653        case VIEW_LIST:
654            mGrid.setAdapter(null);
655            mList.setAdapter(mAdapter);
656            mGrid.setVisibility(View.GONE);
657            mList.setVisibility(View.VISIBLE);
658            break;
659        }
660    }
661
662    /**
663     * BreadCrumb controller callback
664     */
665    @Override
666    public void onTop(int level, Object data) {
667        Uri uri = (Uri) data;
668        if (uri == null) {
669            // top level
670            uri = BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER;
671        }
672        loadFolder(uri);
673    }
674
675    /**
676     * @param uri
677     */
678    private void loadFolder(Uri uri) {
679        LoaderManager manager = getLoaderManager();
680        BookmarksLoader loader =
681                (BookmarksLoader) ((Loader<?>) manager.getLoader(LOADER_BOOKMARKS));
682        loader.setUri(uri);
683        loader.forceLoad();
684        if (mCallbacks != null) {
685            mCallbacks.onFolderChanged(mCrumbs.getTopLevel(), uri);
686        }
687    }
688
689    @Override
690    public void onClick(View view) {
691        if (mSelectBookmarkView == view) {
692            selectView(mCurrentView == BrowserBookmarksPage.VIEW_LIST
693                    ? BrowserBookmarksPage.VIEW_THUMBNAILS
694                    : BrowserBookmarksPage.VIEW_LIST);
695        }
696    }
697
698    @Override
699    public boolean onMenuItemClick(MenuItem item) {
700        switch (item.getItemId()) {
701        case R.id.list_view:
702            selectView(BrowserBookmarksPage.VIEW_LIST);
703            return true;
704        case R.id.thumbnail_view:
705            selectView(BrowserBookmarksPage.VIEW_THUMBNAILS);
706            return true;
707        }
708        return false;
709    }
710
711    public boolean onBackPressed() {
712        if (canGoBack()) {
713            mCrumbs.popView();
714            return true;
715        }
716        return false;
717    }
718
719    private boolean canGoBack() {
720        Crumb c = mCrumbs.getTopCrumb();
721        return c != null && c.canGoBack;
722    }
723
724    public void setCallbackListener(BookmarksPageCallbacks callbackListener) {
725        mCallbacks = callbackListener;
726    }
727
728    public void setEnableContextMenu(boolean enable) {
729        mEnableContextMenu = enable;
730        if (mGrid != null) {
731            if (mEnableContextMenu) {
732                registerForContextMenu(mGrid);
733            } else {
734                unregisterForContextMenu(mGrid);
735                mGrid.setLongClickable(false);
736            }
737        }
738        if (mList != null) {
739            if (mEnableContextMenu) {
740                registerForContextMenu(mList);
741            } else {
742                unregisterForContextMenu(mList);
743                mList.setLongClickable(false);
744            }
745        }
746    }
747
748    private static class LookupBookmarkCount extends AsyncTask<Long, Void, Integer> {
749        Context mContext;
750        BookmarkItem mHeader;
751
752        public LookupBookmarkCount(Context context, BookmarkItem header) {
753            mContext = context;
754            mHeader = header;
755        }
756
757        @Override
758        protected Integer doInBackground(Long... params) {
759            if (params.length != 1) {
760                throw new IllegalArgumentException("Missing folder id!");
761            }
762            Uri uri = BookmarkUtils.getBookmarksUri(mContext);
763            Cursor c = mContext.getContentResolver().query(uri,
764                    null, BrowserContract.Bookmarks.PARENT + "=?",
765                    new String[] {params[0].toString()}, null);
766            return c.getCount();
767        }
768
769        @Override
770        protected void onPostExecute(Integer result) {
771            if (result > 0) {
772                mHeader.setUrl(mContext.getString(R.string.contextheader_folder_bookmarkcount,
773                        result));
774            } else if (result == 0) {
775                mHeader.setUrl(mContext.getString(R.string.contextheader_folder_empty));
776            }
777        }
778    }
779
780    public void setBreadCrumbVisibility(int visibility) {
781        mCrumbVisibility = visibility;
782        if (mCrumbs != null) {
783            mCrumbs.setVisibility(mCrumbVisibility);
784        }
785    }
786
787    public void setBreadCrumbUseBackButton(boolean use) {
788        mCrumbBackButton = use;
789        if (mCrumbs != null) {
790            mCrumbs.setUseBackButton(mCrumbBackButton);
791        }
792    }
793
794    public void setBreadCrumbMaxVisible(int max) {
795        mCrumbMaxVisible = max;
796        if (mCrumbs != null) {
797            mCrumbs.setMaxVisible(mCrumbMaxVisible);
798        }
799    }
800}
801