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