BrowserBookmarksPage.java revision 71e76c71d8fb70433a60521dfdbcd4e7a8762ef8
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    public void onLoaderReset(Loader<Cursor> loader) {
235        onLoadFinished(loader, null);
236        switch (loader.getId()) {
237            case LOADER_BOOKMARKS: {
238                onLoadFinished(loader, null);
239                break;
240            }
241        }
242    }
243
244    long getFolderId() {
245        LoaderManager manager = getLoaderManager();
246        BookmarksLoader loader =
247                (BookmarksLoader) ((Loader<?>)manager.getLoader(LOADER_BOOKMARKS));
248
249        Uri uri = loader.getUri();
250        if (uri != null) {
251            try {
252                return ContentUris.parseId(uri);
253            } catch (NumberFormatException nfx) {
254                return -1;
255            }
256        }
257        return -1;
258    }
259
260    @Override
261    public boolean onContextItemSelected(MenuItem item) {
262        final Activity activity = getActivity();
263        // It is possible that the view has been canceled when we get to
264        // this point as back has a higher priority
265        if (mCanceled) {
266            return false;
267        }
268        AdapterView.AdapterContextMenuInfo i =
269            (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
270        // If we have no menu info, we can't tell which item was selected.
271        if (i == null) {
272            return false;
273        }
274
275        switch (item.getItemId()) {
276        case R.id.open_context_menu_id:
277            loadUrl(i.position);
278            break;
279        case R.id.edit_context_menu_id:
280            editBookmark(i.position);
281            break;
282        case R.id.shortcut_context_menu_id:
283            Cursor c = mAdapter.getItem(i.position);
284            activity.sendBroadcast(createShortcutIntent(getActivity(), c));
285            break;
286        case R.id.delete_context_menu_id:
287            displayRemoveBookmarkDialog(i.position);
288            break;
289        case R.id.new_window_context_menu_id:
290            openInNewWindow(i.position);
291            break;
292        case R.id.share_link_context_menu_id: {
293            Cursor cursor = mAdapter.getItem(i.position);
294            Controller.sharePage(activity,
295                    cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE),
296                    cursor.getString(BookmarksLoader.COLUMN_INDEX_URL),
297                    getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON),
298                    getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_THUMBNAIL));
299            break;
300        }
301        case R.id.copy_url_context_menu_id:
302            copy(getUrl(i.position));
303            break;
304        case R.id.homepage_context_menu_id: {
305            BrowserSettings.getInstance().setHomePage(activity, getUrl(i.position));
306            Toast.makeText(activity, R.string.homepage_set, Toast.LENGTH_LONG).show();
307            break;
308        }
309        // Only for the Most visited page
310        case R.id.save_to_bookmarks_menu_id: {
311            Cursor cursor = mAdapter.getItem(i.position);
312            String name = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
313            String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
314            // If the site is bookmarked, the item becomes remove from
315            // bookmarks.
316            Bookmarks.removeFromBookmarks(activity, activity.getContentResolver(), url, name);
317            break;
318        }
319        default:
320            return super.onContextItemSelected(item);
321        }
322        return true;
323    }
324
325    static Bitmap getBitmap(Cursor cursor, int columnIndex) {
326        byte[] data = cursor.getBlob(columnIndex);
327        if (data == null) {
328            return null;
329        }
330        return BitmapFactory.decodeByteArray(data, 0, data.length);
331    }
332
333    private MenuItem.OnMenuItemClickListener mContextItemClickListener =
334            new MenuItem.OnMenuItemClickListener() {
335        @Override
336        public boolean onMenuItemClick(MenuItem item) {
337            return onContextItemSelected(item);
338        }
339    };
340
341    @Override
342    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
343        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
344        Cursor cursor = mAdapter.getItem(info.position);
345        if (!canEdit(cursor)) {
346            return;
347        }
348        boolean isFolder
349                = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
350
351        final Activity activity = getActivity();
352        MenuInflater inflater = activity.getMenuInflater();
353        inflater.inflate(R.menu.bookmarkscontext, menu);
354        if (isFolder) {
355            menu.setGroupVisible(R.id.FOLDER_CONTEXT_MENU, true);
356        } else {
357            menu.setGroupVisible(R.id.BOOKMARK_CONTEXT_MENU, true);
358            if (mDisableNewWindow) {
359                menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
360            }
361        }
362        BookmarkItem header = new BookmarkItem(activity);
363        populateBookmarkItem(cursor, header, isFolder);
364        menu.setHeaderView(header);
365
366        int count = menu.size();
367        for (int i = 0; i < count; i++) {
368            menu.getItem(i).setOnMenuItemClickListener(mContextItemClickListener);
369        }
370    }
371
372    boolean canEdit(Cursor c) {
373        String unique = c.getString(BookmarksLoader.COLUMN_INDEX_SERVER_UNIQUE);
374        return !ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS.equals(unique);
375    }
376
377    private void populateBookmarkItem(Cursor cursor, BookmarkItem item, boolean isFolder) {
378        item.setName(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
379        if (isFolder) {
380            item.setUrl(null);
381            Bitmap bitmap =
382                BitmapFactory.decodeResource(getResources(), R.drawable.ic_folder_bookmark_widget_holo_dark);
383            item.setFavicon(bitmap);
384            new LookupBookmarkCount(getActivity(), item)
385                    .execute(cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
386        } else {
387            String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
388            item.setUrl(url);
389            Bitmap bitmap = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
390            if (bitmap == null) {
391                bitmap = CombinedBookmarkHistoryView.getIconListenerSet().getFavicon(url);
392            }
393            item.setFavicon(bitmap);
394        }
395    }
396
397    /**
398     *  Create a new BrowserBookmarksPage.
399     */
400    @Override
401    public void onCreate(Bundle icicle) {
402        super.onCreate(icicle);
403
404        Bundle args = getArguments();
405        mDisableNewWindow = args == null ? false : args.getBoolean(EXTRA_DISABLE_WINDOW, false);
406    }
407
408    @Override
409    public View onCreateView(LayoutInflater inflater, ViewGroup container,
410            Bundle savedInstanceState) {
411        Context context = getActivity();
412
413        View root = inflater.inflate(R.layout.bookmarks, container, false);
414        mEmptyView = root.findViewById(android.R.id.empty);
415
416        mGrid = (GridView) root.findViewById(R.id.grid);
417        mGrid.setOnItemClickListener(this);
418        mGrid.setColumnWidth(Controller.getDesiredThumbnailWidth(getActivity()));
419        mList = (ListView) root.findViewById(R.id.list);
420        mList.setOnItemClickListener(this);
421        setEnableContextMenu(mEnableContextMenu);
422
423        // Prep the header
424        ViewGroup hc = mHeaderContainer;
425        if (hc == null) {
426            hc = (ViewGroup) root.findViewById(R.id.header_container);
427            hc.setVisibility(View.VISIBLE);
428        }
429        mHeader = inflater.inflate(R.layout.bookmarks_header, hc, false);
430        hc.addView(mHeader);
431        mCrumbs = (BreadCrumbView) mHeader.findViewById(R.id.crumbs);
432        mCrumbs.setController(this);
433        mCrumbs.setUseBackButton(mCrumbBackButton);
434        mCrumbs.setMaxVisible(mCrumbMaxVisible);
435        mCrumbs.setVisibility(mCrumbVisibility);
436        String name = getString(R.string.bookmarks);
437        mCrumbs.pushView(name, false, BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
438        if (mCallbacks != null) {
439            mCallbacks.onFolderChanged(1, BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
440        }
441        mSelectBookmarkView = (TextView) mHeader.findViewById(R.id.select_bookmark_view);
442        mSelectBookmarkView.setOnClickListener(this);
443
444        // Start the loaders
445        LoaderManager lm = getLoaderManager();
446        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
447        mCurrentView =
448            prefs.getInt(PREF_SELECTED_VIEW, BrowserBookmarksPage.VIEW_THUMBNAILS);
449        if (mCurrentView == BrowserBookmarksPage.VIEW_THUMBNAILS) {
450            mSelectBookmarkView.setText(R.string.bookmark_list_view);
451        } else {
452            mSelectBookmarkView.setText(R.string.bookmark_thumbnail_view);
453        }
454        mAdapter = new BrowserBookmarksAdapter(getActivity(), mCurrentView);
455        String accountType = prefs.getString(PREF_ACCOUNT_TYPE, DEFAULT_ACCOUNT);
456        String accountName = prefs.getString(PREF_ACCOUNT_NAME, DEFAULT_ACCOUNT);
457        if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
458            // There is an account set, load up that one
459            Bundle args = null;
460            if (!DEFAULT_ACCOUNT.equals(accountType) && !DEFAULT_ACCOUNT.equals(accountName)) {
461                args = new Bundle();
462                args.putString(BookmarksLoader.ARG_ACCOUNT_TYPE, accountType);
463                args.putString(BookmarksLoader.ARG_ACCOUNT_NAME, accountName);
464            }
465            lm.restartLoader(LOADER_BOOKMARKS, args, this);
466        } else {
467            // No account set, load the account list first
468            lm.restartLoader(LOADER_ACCOUNTS_THEN_BOOKMARKS, null, this);
469        }
470
471        // Add our own listener in case there are favicons that have yet to be loaded.
472        CombinedBookmarkHistoryView.getIconListenerSet().addListener(this);
473
474        return root;
475    }
476
477    @Override
478    public void onDestroyView() {
479        super.onDestroyView();
480        if (mHeaderContainer != null) {
481            mHeaderContainer.removeView(mHeader);
482        }
483        mCrumbs.setController(null);
484        mCrumbs = null;
485        getLoaderManager().destroyLoader(LOADER_BOOKMARKS);
486    }
487
488    @Override
489    public void onReceivedIcon(String url, Bitmap icon) {
490        // A new favicon has been loaded, so let anything attached to the adapter know about it
491        // so new icons will be loaded.
492        mAdapter.notifyDataSetChanged();
493    }
494
495    @Override
496    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
497        // It is possible that the view has been canceled when we get to
498        // this point as back has a higher priority
499        if (mCanceled) {
500            android.util.Log.e(LOGTAG, "item clicked when dismissing");
501            return;
502        }
503
504        Cursor cursor = mAdapter.getItem(position);
505        boolean isFolder = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
506        if (mCallbacks != null &&
507                mCallbacks.onBookmarkSelected(cursor, isFolder)) {
508            return;
509        }
510
511        if (isFolder) {
512            String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
513            Uri uri = ContentUris.withAppendedId(
514                    BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, id);
515            if (mCrumbs != null) {
516                // update crumbs
517                mCrumbs.pushView(title, uri);
518            }
519            loadFolder(uri);
520        }
521    }
522
523    @Override
524    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
525        Adapter adapter = parent.getAdapter();
526        String accountType = "com.google";
527        String accountName = adapter.getItem(position).toString();
528
529        Bundle args = null;
530        if (ACCOUNT_NAME_UNSYNCED.equals(accountName)) {
531            accountType = DEFAULT_ACCOUNT;
532            accountName = DEFAULT_ACCOUNT;
533        } else {
534            args = new Bundle();
535            args.putString(BookmarksLoader.ARG_ACCOUNT_TYPE, accountType);
536            args.putString(BookmarksLoader.ARG_ACCOUNT_NAME, accountName);
537        }
538
539        // Remember the selection for later
540        PreferenceManager.getDefaultSharedPreferences(getActivity()).edit()
541                .putString(PREF_ACCOUNT_TYPE, accountType)
542                .putString(PREF_ACCOUNT_NAME, accountName)
543                .apply();
544
545        getLoaderManager().restartLoader(LOADER_BOOKMARKS, args, this);
546    }
547
548    @Override
549    public void onNothingSelected(AdapterView<?> parent) {
550        // Do nothing
551    }
552
553    /* package */ static Intent createShortcutIntent(Context context, Cursor cursor) {
554        String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
555        String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
556        Bitmap touchIcon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_TOUCH_ICON);
557        Bitmap favicon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
558        return BookmarkUtils.createAddToHomeIntent(context, url, title, touchIcon, favicon);
559    }
560
561    private void loadUrl(int position) {
562        if (mCallbacks != null) {
563            mCallbacks.onBookmarkSelected(mAdapter.getItem(position), false);
564        }
565    }
566
567    private void openInNewWindow(int position) {
568        if (mCallbacks != null) {
569            mCallbacks.onOpenInNewWindow(mAdapter.getItem(position));
570        }
571    }
572
573    private void editBookmark(int position) {
574        Intent intent = new Intent(getActivity(), AddBookmarkPage.class);
575        Cursor cursor = mAdapter.getItem(position);
576        Bundle item = new Bundle();
577        item.putString(BrowserContract.Bookmarks.TITLE,
578                cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
579        item.putString(BrowserContract.Bookmarks.URL,
580                cursor.getString(BookmarksLoader.COLUMN_INDEX_URL));
581        byte[] data = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_FAVICON);
582        if (data != null) {
583            item.putParcelable(BrowserContract.Bookmarks.FAVICON,
584                    BitmapFactory.decodeByteArray(data, 0, data.length));
585        }
586        item.putLong(BrowserContract.Bookmarks._ID,
587                cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
588        item.putLong(BrowserContract.Bookmarks.PARENT,
589                cursor.getLong(BookmarksLoader.COLUMN_INDEX_PARENT));
590        intent.putExtra(AddBookmarkPage.EXTRA_EDIT_BOOKMARK, item);
591        intent.putExtra(AddBookmarkPage.EXTRA_IS_FOLDER,
592                cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) == 1);
593        startActivity(intent);
594    }
595
596    private void displayRemoveBookmarkDialog(final int position) {
597        // Put up a dialog asking if the user really wants to
598        // delete the bookmark
599        Cursor cursor = mAdapter.getItem(position);
600        Context context = getActivity();
601        final ContentResolver resolver = context.getContentResolver();
602        final Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI,
603                cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
604
605        new AlertDialog.Builder(context)
606                .setTitle(R.string.delete_bookmark)
607                .setIcon(android.R.drawable.ic_dialog_alert)
608                .setMessage(context.getString(R.string.delete_bookmark_warning,
609                        cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE)))
610                .setPositiveButton(R.string.ok,
611                        new DialogInterface.OnClickListener() {
612                            @Override
613                            public void onClick(DialogInterface dialog, int whichButton) {
614                                resolver.delete(uri, null, null);
615                            }
616                        })
617                .setNegativeButton(R.string.cancel, null)
618                .show();
619    }
620
621    private String getUrl(int position) {
622        return getUrl(mAdapter.getItem(position));
623    }
624
625    /* package */ static String getUrl(Cursor c) {
626        return c.getString(BookmarksLoader.COLUMN_INDEX_URL);
627    }
628
629    private void copy(CharSequence text) {
630        ClipboardManager cm = (ClipboardManager) getActivity().getSystemService(
631                Context.CLIPBOARD_SERVICE);
632        cm.setPrimaryClip(ClipData.newRawUri(null, null, Uri.parse(text.toString())));
633    }
634
635    void selectView(int view) {
636        if (view == mCurrentView) {
637            return;
638        }
639        mCurrentView = view;
640        if (mCurrentView == BrowserBookmarksPage.VIEW_THUMBNAILS) {
641            mSelectBookmarkView.setText(R.string.bookmark_list_view);
642        } else {
643            mSelectBookmarkView.setText(R.string.bookmark_thumbnail_view);
644        }
645        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
646        Editor edit = prefs.edit();
647        edit.putInt(PREF_SELECTED_VIEW, mCurrentView);
648        edit.apply();
649        if (mEmptyView.getVisibility() == View.VISIBLE) {
650            return;
651        }
652        setupBookmarkView();
653    }
654
655    private void setupBookmarkView() {
656        mAdapter.selectView(mCurrentView);
657        switch (mCurrentView) {
658        case VIEW_THUMBNAILS:
659            mList.setAdapter(null);
660            mGrid.setAdapter(mAdapter);
661            mGrid.setVisibility(View.VISIBLE);
662            mList.setVisibility(View.GONE);
663            break;
664        case VIEW_LIST:
665            mGrid.setAdapter(null);
666            mList.setAdapter(mAdapter);
667            mGrid.setVisibility(View.GONE);
668            mList.setVisibility(View.VISIBLE);
669            break;
670        }
671    }
672
673    /**
674     * BreadCrumb controller callback
675     */
676    @Override
677    public void onTop(int level, Object data) {
678        Uri uri = (Uri) data;
679        if (uri == null) {
680            // top level
681            uri = BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER;
682        }
683        loadFolder(uri);
684    }
685
686    /**
687     * @param uri
688     */
689    private void loadFolder(Uri uri) {
690        LoaderManager manager = getLoaderManager();
691        BookmarksLoader loader =
692                (BookmarksLoader) ((Loader<?>) manager.getLoader(LOADER_BOOKMARKS));
693        loader.setUri(uri);
694        loader.forceLoad();
695        if (mCallbacks != null) {
696            mCallbacks.onFolderChanged(mCrumbs.getTopLevel(), uri);
697        }
698    }
699
700    @Override
701    public void onClick(View view) {
702        if (mSelectBookmarkView == view) {
703            selectView(mCurrentView == BrowserBookmarksPage.VIEW_LIST
704                    ? BrowserBookmarksPage.VIEW_THUMBNAILS
705                    : BrowserBookmarksPage.VIEW_LIST);
706        }
707    }
708
709    @Override
710    public boolean onMenuItemClick(MenuItem item) {
711        switch (item.getItemId()) {
712        case R.id.list_view:
713            selectView(BrowserBookmarksPage.VIEW_LIST);
714            return true;
715        case R.id.thumbnail_view:
716            selectView(BrowserBookmarksPage.VIEW_THUMBNAILS);
717            return true;
718        }
719        return false;
720    }
721
722    public boolean onBackPressed() {
723        if (canGoBack()) {
724            mCrumbs.popView();
725            return true;
726        }
727        return false;
728    }
729
730    private boolean canGoBack() {
731        Crumb c = mCrumbs.getTopCrumb();
732        return c != null && c.canGoBack;
733    }
734
735    public void setCallbackListener(BookmarksPageCallbacks callbackListener) {
736        mCallbacks = callbackListener;
737    }
738
739    public void setEnableContextMenu(boolean enable) {
740        mEnableContextMenu = enable;
741        if (mGrid != null) {
742            if (mEnableContextMenu) {
743                registerForContextMenu(mGrid);
744            } else {
745                unregisterForContextMenu(mGrid);
746                mGrid.setLongClickable(false);
747            }
748        }
749        if (mList != null) {
750            if (mEnableContextMenu) {
751                registerForContextMenu(mList);
752            } else {
753                unregisterForContextMenu(mList);
754                mList.setLongClickable(false);
755            }
756        }
757    }
758
759    private static class LookupBookmarkCount extends AsyncTask<Long, Void, Integer> {
760        Context mContext;
761        BookmarkItem mHeader;
762
763        public LookupBookmarkCount(Context context, BookmarkItem header) {
764            mContext = context;
765            mHeader = header;
766        }
767
768        @Override
769        protected Integer doInBackground(Long... params) {
770            if (params.length != 1) {
771                throw new IllegalArgumentException("Missing folder id!");
772            }
773            Uri uri = BookmarkUtils.getBookmarksUri(mContext);
774            Cursor c = mContext.getContentResolver().query(uri,
775                    null, BrowserContract.Bookmarks.PARENT + "=?",
776                    new String[] {params[0].toString()}, null);
777            return c.getCount();
778        }
779
780        @Override
781        protected void onPostExecute(Integer result) {
782            if (result > 0) {
783                mHeader.setUrl(mContext.getString(R.string.contextheader_folder_bookmarkcount,
784                        result));
785            } else if (result == 0) {
786                mHeader.setUrl(mContext.getString(R.string.contextheader_folder_empty));
787            }
788        }
789    }
790
791    public void setBreadCrumbVisibility(int visibility) {
792        mCrumbVisibility = visibility;
793        if (mCrumbs != null) {
794            mCrumbs.setVisibility(mCrumbVisibility);
795        }
796    }
797
798    public void setBreadCrumbUseBackButton(boolean use) {
799        mCrumbBackButton = use;
800        if (mCrumbs != null) {
801            mCrumbs.setUseBackButton(mCrumbBackButton);
802        }
803    }
804
805    public void setBreadCrumbMaxVisible(int max) {
806        mCrumbMaxVisible = max;
807        if (mCrumbs != null) {
808            mCrumbs.setMaxVisible(mCrumbMaxVisible);
809        }
810    }
811}
812