BrowserBookmarksPage.java revision bf1d10af39eb23d68db69a8eda9e12e62f1a9682
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.content.res.Configuration;
37import android.content.res.Resources;
38import android.database.Cursor;
39import android.graphics.Bitmap;
40import android.graphics.BitmapFactory;
41import android.net.Uri;
42import android.os.AsyncTask;
43import android.os.Bundle;
44import android.preference.PreferenceManager;
45import android.provider.BrowserContract;
46import android.provider.BrowserContract.Accounts;
47import android.provider.BrowserContract.ChromeSyncColumns;
48import android.text.TextUtils;
49import android.view.ContextMenu;
50import android.view.ContextMenu.ContextMenuInfo;
51import android.view.LayoutInflater;
52import android.view.Menu;
53import android.view.MenuInflater;
54import android.view.MenuItem;
55import android.view.View;
56import android.view.ViewGroup;
57import android.webkit.WebIconDatabase.IconListener;
58import android.widget.Adapter;
59import android.widget.AdapterView;
60import android.widget.AdapterView.OnItemClickListener;
61import android.widget.AdapterView.OnItemSelectedListener;
62import android.widget.GridView;
63import android.widget.ListView;
64import android.widget.PopupMenu.OnMenuItemClickListener;
65import android.widget.Toast;
66
67interface BookmarksPageCallbacks {
68    // Return true if handled
69    boolean onBookmarkSelected(Cursor c, boolean isFolder);
70    // Return true if handled
71    boolean onOpenInNewWindow(Cursor c);
72    void onFolderChanged(int level, Uri uri);
73}
74
75/**
76 *  View showing the user's bookmarks in the browser.
77 */
78public class BrowserBookmarksPage extends Fragment implements View.OnCreateContextMenuListener,
79        LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, IconListener,
80        OnItemSelectedListener, BreadCrumbView.Controller, OnMenuItemClickListener {
81
82    static final String LOGTAG = "browser";
83
84    static final int LOADER_BOOKMARKS = 1;
85    static final int LOADER_ACCOUNTS_THEN_BOOKMARKS = 2;
86
87    static final String EXTRA_DISABLE_WINDOW = "disable_new_window";
88
89    static final String ACCOUNT_NAME_UNSYNCED = "Unsynced";
90
91    public static final String PREF_ACCOUNT_TYPE = "acct_type";
92    public static final String PREF_ACCOUNT_NAME = "acct_name";
93
94    static final String DEFAULT_ACCOUNT = "local";
95    static final int VIEW_THUMBNAILS = 1;
96    static final int VIEW_LIST = 2;
97    static final String PREF_SELECTED_VIEW = "bookmarks_view";
98
99    BookmarksPageCallbacks mCallbacks;
100    GridView mGrid;
101    ListView mList;
102    BrowserBookmarksAdapter mAdapter;
103    boolean mDisableNewWindow;
104    boolean mCanceled = false;
105    boolean mEnableContextMenu = true;
106    boolean mShowRootFolder = false;
107    View mEmptyView;
108    int mCurrentView;
109    View mHeader;
110    ViewGroup mHeaderContainer;
111    BreadCrumbView mCrumbs;
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 onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
343        inflater.inflate(R.menu.bookmark, menu);
344    }
345
346    @Override
347    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
348        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
349        Cursor cursor = mAdapter.getItem(info.position);
350        if (!canEdit(cursor)) {
351            return;
352        }
353        boolean isFolder
354                = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
355
356        final Activity activity = getActivity();
357        MenuInflater inflater = activity.getMenuInflater();
358        inflater.inflate(R.menu.bookmarkscontext, menu);
359        if (isFolder) {
360            menu.setGroupVisible(R.id.FOLDER_CONTEXT_MENU, true);
361        } else {
362            menu.setGroupVisible(R.id.BOOKMARK_CONTEXT_MENU, true);
363            if (mDisableNewWindow) {
364                menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
365            }
366        }
367        BookmarkItem header = new BookmarkItem(activity);
368        populateBookmarkItem(cursor, header, isFolder);
369        menu.setHeaderView(header);
370
371        int count = menu.size();
372        for (int i = 0; i < count; i++) {
373            menu.getItem(i).setOnMenuItemClickListener(mContextItemClickListener);
374        }
375    }
376
377    boolean canEdit(Cursor c) {
378        String unique = c.getString(BookmarksLoader.COLUMN_INDEX_SERVER_UNIQUE);
379        return !ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS.equals(unique);
380    }
381
382    private void populateBookmarkItem(Cursor cursor, BookmarkItem item, boolean isFolder) {
383        item.setName(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
384        if (isFolder) {
385            item.setUrl(null);
386            Bitmap bitmap =
387                BitmapFactory.decodeResource(getResources(), R.drawable.ic_folder_bookmark_widget_holo_dark);
388            item.setFavicon(bitmap);
389            new LookupBookmarkCount(getActivity(), item)
390                    .execute(cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
391        } else {
392            String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
393            item.setUrl(url);
394            Bitmap bitmap = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
395            if (bitmap == null) {
396                bitmap = CombinedBookmarkHistoryView.getIconListenerSet().getFavicon(url);
397            }
398            item.setFavicon(bitmap);
399        }
400    }
401
402    /**
403     *  Create a new BrowserBookmarksPage.
404     */
405    @Override
406    public void onCreate(Bundle icicle) {
407        super.onCreate(icicle);
408
409        setHasOptionsMenu(true);
410
411        Bundle args = getArguments();
412        mDisableNewWindow = args == null ? false : args.getBoolean(EXTRA_DISABLE_WINDOW, false);
413    }
414
415    @Override
416    public View onCreateView(LayoutInflater inflater, ViewGroup container,
417            Bundle savedInstanceState) {
418        Context context = getActivity();
419
420        View root = inflater.inflate(R.layout.bookmarks, container, false);
421        mEmptyView = root.findViewById(android.R.id.empty);
422
423        mGrid = (GridView) root.findViewById(R.id.grid);
424        mGrid.setOnItemClickListener(this);
425        mGrid.setColumnWidth(Controller.getDesiredThumbnailWidth(getActivity()));
426        mList = (ListView) root.findViewById(R.id.list);
427        mList.setOnItemClickListener(this);
428        setEnableContextMenu(mEnableContextMenu);
429
430        // Prep the header
431        ViewGroup hc = mHeaderContainer;
432        if (hc == null) {
433            hc = (ViewGroup) root.findViewById(R.id.header_container);
434            hc.setVisibility(View.VISIBLE);
435        }
436        mHeader = inflater.inflate(R.layout.bookmarks_header, hc, false);
437        hc.addView(mHeader);
438        mCrumbs = (BreadCrumbView) mHeader.findViewById(R.id.crumbs);
439        mCrumbs.setController(this);
440        mCrumbs.setUseBackButton(mCrumbBackButton);
441        mCrumbs.setMaxVisible(mCrumbMaxVisible);
442        mCrumbs.setVisibility(mCrumbVisibility);
443        String name = getString(R.string.bookmarks);
444        mCrumbs.pushView(name, false, BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
445        if (mCallbacks != null) {
446            mCallbacks.onFolderChanged(1, BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
447        }
448
449        // Start the loaders
450        LoaderManager lm = getLoaderManager();
451        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
452        mCurrentView =
453            prefs.getInt(PREF_SELECTED_VIEW, BrowserBookmarksPage.VIEW_THUMBNAILS);
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            Cursor c = mAdapter.getItem(position);
570            boolean isFolder = c.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) == 1;
571            if (isFolder) {
572                long id = c.getLong(BookmarksLoader.COLUMN_INDEX_ID);
573                new OpenAllInTabsTask(id).execute();
574            } else {
575                mCallbacks.onOpenInNewWindow(c);
576            }
577        }
578    }
579
580    class OpenAllInTabsTask extends AsyncTask<Void, Void, Cursor> {
581        long mFolderId;
582        public OpenAllInTabsTask(long id) {
583            mFolderId = id;
584        }
585
586        @Override
587        protected Cursor doInBackground(Void... params) {
588            Context c = getActivity();
589            if (c == null) return null;
590            return c.getContentResolver().query(BookmarkUtils.getBookmarksUri(c),
591                    BookmarksLoader.PROJECTION, BrowserContract.Bookmarks.PARENT + "=?",
592                    new String[] { Long.toString(mFolderId) }, null);
593        }
594
595        @Override
596        protected void onPostExecute(Cursor result) {
597            if (mCallbacks != null) {
598                while (result.moveToNext()) {
599                    mCallbacks.onOpenInNewWindow(result);
600                }
601            }
602        }
603
604    }
605
606    private void editBookmark(int position) {
607        Intent intent = new Intent(getActivity(), AddBookmarkPage.class);
608        Cursor cursor = mAdapter.getItem(position);
609        Bundle item = new Bundle();
610        item.putString(BrowserContract.Bookmarks.TITLE,
611                cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
612        item.putString(BrowserContract.Bookmarks.URL,
613                cursor.getString(BookmarksLoader.COLUMN_INDEX_URL));
614        byte[] data = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_FAVICON);
615        if (data != null) {
616            item.putParcelable(BrowserContract.Bookmarks.FAVICON,
617                    BitmapFactory.decodeByteArray(data, 0, data.length));
618        }
619        item.putLong(BrowserContract.Bookmarks._ID,
620                cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
621        item.putLong(BrowserContract.Bookmarks.PARENT,
622                cursor.getLong(BookmarksLoader.COLUMN_INDEX_PARENT));
623        intent.putExtra(AddBookmarkPage.EXTRA_EDIT_BOOKMARK, item);
624        intent.putExtra(AddBookmarkPage.EXTRA_IS_FOLDER,
625                cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) == 1);
626        startActivity(intent);
627    }
628
629    private void displayRemoveBookmarkDialog(final int position) {
630        // Put up a dialog asking if the user really wants to
631        // delete the bookmark
632        Cursor cursor = mAdapter.getItem(position);
633        long id = cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID);
634        String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
635        Context context = getActivity();
636        BookmarkUtils.displayRemoveBookmarkDialog(id, title, context, null);
637    }
638
639    private String getUrl(int position) {
640        return getUrl(mAdapter.getItem(position));
641    }
642
643    /* package */ static String getUrl(Cursor c) {
644        return c.getString(BookmarksLoader.COLUMN_INDEX_URL);
645    }
646
647    private void copy(CharSequence text) {
648        ClipboardManager cm = (ClipboardManager) getActivity().getSystemService(
649                Context.CLIPBOARD_SERVICE);
650        cm.setPrimaryClip(ClipData.newRawUri(null, Uri.parse(text.toString())));
651    }
652
653    @Override
654    public boolean onOptionsItemSelected(MenuItem item) {
655        switch (item.getItemId()) {
656        case R.id.thumbnail_view:
657            selectView(VIEW_THUMBNAILS);
658            return true;
659        case R.id.list_view:
660            selectView(VIEW_LIST);
661            return true;
662        }
663        return super.onOptionsItemSelected(item);
664    }
665
666    @Override
667    public void onConfigurationChanged(Configuration newConfig) {
668        super.onConfigurationChanged(newConfig);
669        Resources res = getActivity().getResources();
670        int horizontalSpacing = (int) res.getDimension(R.dimen.combo_horizontalSpacing);
671        mGrid.setHorizontalSpacing(horizontalSpacing);
672    }
673
674    @Override
675    public void onPrepareOptionsMenu(Menu menu) {
676        super.onPrepareOptionsMenu(menu);
677        menu.findItem(R.id.list_view).setVisible(mCurrentView != VIEW_LIST);
678        menu.findItem(R.id.thumbnail_view).setVisible(mCurrentView != VIEW_THUMBNAILS);
679    }
680
681    void selectView(int view) {
682        if (view == mCurrentView) {
683            return;
684        }
685        mCurrentView = view;
686        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
687        Editor edit = prefs.edit();
688        edit.putInt(PREF_SELECTED_VIEW, mCurrentView);
689        edit.apply();
690        if (mEmptyView.getVisibility() == View.VISIBLE) {
691            return;
692        }
693        setupBookmarkView();
694    }
695
696    private void setupBookmarkView() {
697        mAdapter.selectView(mCurrentView);
698        switch (mCurrentView) {
699        case VIEW_THUMBNAILS:
700            mList.setAdapter(null);
701            mGrid.setAdapter(mAdapter);
702            mGrid.setVisibility(View.VISIBLE);
703            mList.setVisibility(View.GONE);
704            break;
705        case VIEW_LIST:
706            mGrid.setAdapter(null);
707            mList.setAdapter(mAdapter);
708            mGrid.setVisibility(View.GONE);
709            mList.setVisibility(View.VISIBLE);
710            break;
711        }
712    }
713
714    /**
715     * BreadCrumb controller callback
716     */
717    @Override
718    public void onTop(int level, Object data) {
719        Uri uri = (Uri) data;
720        if (uri == null) {
721            // top level
722            uri = BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER;
723        }
724        loadFolder(uri);
725    }
726
727    /**
728     * @param uri
729     */
730    private void loadFolder(Uri uri) {
731        LoaderManager manager = getLoaderManager();
732        BookmarksLoader loader =
733                (BookmarksLoader) ((Loader<?>) manager.getLoader(LOADER_BOOKMARKS));
734        loader.setUri(uri);
735        loader.forceLoad();
736        if (mCallbacks != null) {
737            mCallbacks.onFolderChanged(mCrumbs.getTopLevel(), uri);
738        }
739    }
740
741    @Override
742    public boolean onMenuItemClick(MenuItem item) {
743        switch (item.getItemId()) {
744        case R.id.list_view:
745            selectView(BrowserBookmarksPage.VIEW_LIST);
746            return true;
747        case R.id.thumbnail_view:
748            selectView(BrowserBookmarksPage.VIEW_THUMBNAILS);
749            return true;
750        }
751        return false;
752    }
753
754    public boolean onBackPressed() {
755        if (canGoBack()) {
756            mCrumbs.popView();
757            return true;
758        }
759        return false;
760    }
761
762    private boolean canGoBack() {
763        Crumb c = mCrumbs.getTopCrumb();
764        return c != null && c.canGoBack;
765    }
766
767    public void setCallbackListener(BookmarksPageCallbacks callbackListener) {
768        mCallbacks = callbackListener;
769    }
770
771    public void setEnableContextMenu(boolean enable) {
772        mEnableContextMenu = enable;
773        if (mGrid != null) {
774            if (mEnableContextMenu) {
775                registerForContextMenu(mGrid);
776            } else {
777                unregisterForContextMenu(mGrid);
778                mGrid.setLongClickable(false);
779            }
780        }
781        if (mList != null) {
782            if (mEnableContextMenu) {
783                registerForContextMenu(mList);
784            } else {
785                unregisterForContextMenu(mList);
786                mList.setLongClickable(false);
787            }
788        }
789    }
790
791    private static class LookupBookmarkCount extends AsyncTask<Long, Void, Integer> {
792        Context mContext;
793        BookmarkItem mHeader;
794
795        public LookupBookmarkCount(Context context, BookmarkItem header) {
796            mContext = context;
797            mHeader = header;
798        }
799
800        @Override
801        protected Integer doInBackground(Long... params) {
802            if (params.length != 1) {
803                throw new IllegalArgumentException("Missing folder id!");
804            }
805            Uri uri = BookmarkUtils.getBookmarksUri(mContext);
806            Cursor c = mContext.getContentResolver().query(uri,
807                    null, BrowserContract.Bookmarks.PARENT + "=?",
808                    new String[] {params[0].toString()}, null);
809            return c.getCount();
810        }
811
812        @Override
813        protected void onPostExecute(Integer result) {
814            if (result > 0) {
815                mHeader.setUrl(mContext.getString(R.string.contextheader_folder_bookmarkcount,
816                        result));
817            } else if (result == 0) {
818                mHeader.setUrl(mContext.getString(R.string.contextheader_folder_empty));
819            }
820        }
821    }
822
823    public void setBreadCrumbVisibility(int visibility) {
824        mCrumbVisibility = visibility;
825        if (mCrumbs != null) {
826            mCrumbs.setVisibility(mCrumbVisibility);
827        }
828    }
829
830    public void setBreadCrumbUseBackButton(boolean use) {
831        mCrumbBackButton = use;
832        if (mCrumbs != null) {
833            mCrumbs.setUseBackButton(mCrumbBackButton);
834        }
835    }
836
837    public void setBreadCrumbMaxVisible(int max) {
838        mCrumbMaxVisible = max;
839        if (mCrumbs != null) {
840            mCrumbs.setMaxVisible(mCrumbMaxVisible);
841        }
842    }
843}
844