BrowserBookmarksPage.java revision fe25199a6f975c67d28afcc1de56b0f987b66cd8
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.browser;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.content.DialogInterface;
22import android.content.Intent;
23import android.content.SharedPreferences;
24import android.content.SharedPreferences.Editor;
25import android.graphics.Bitmap;
26import android.os.AsyncTask;
27import android.os.Bundle;
28import android.os.Handler;
29import android.os.Message;
30import android.os.ServiceManager;
31import android.provider.Browser;
32import android.text.IClipboard;
33import android.util.Log;
34import android.view.ContextMenu;
35import android.view.Menu;
36import android.view.MenuInflater;
37import android.view.MenuItem;
38import android.view.View;
39import android.view.ViewGroup;
40import android.view.ContextMenu.ContextMenuInfo;
41import android.webkit.WebIconDatabase.IconListener;
42import android.widget.AdapterView;
43import android.widget.GridView;
44import android.widget.ListView;
45import android.widget.Toast;
46import android.widget.AdapterView.OnItemClickListener;
47
48/*package*/ enum BookmarkViewMode { NONE, GRID, LIST }
49/**
50 *  View showing the user's bookmarks in the browser.
51 */
52public class BrowserBookmarksPage extends Activity implements
53        View.OnCreateContextMenuListener {
54
55    private BookmarkViewMode        mViewMode = BookmarkViewMode.NONE;
56    private GridView                mGridPage;
57    private ListView                mVerticalList;
58    private BrowserBookmarksAdapter mBookmarksAdapter;
59    private static final int        BOOKMARKS_SAVE = 1;
60    private boolean                 mDisableNewWindow;
61    private BookmarkItem            mContextHeader;
62    private AddNewBookmark          mAddHeader;
63    private boolean                 mCanceled = false;
64    private boolean                 mCreateShortcut;
65    private boolean                 mMostVisited;
66    private View                    mEmptyView;
67    private int                     mIconSize;
68
69    private final static String LOGTAG = "browser";
70    private final static String PREF_BOOKMARK_VIEW_MODE = "pref_bookmark_view_mode";
71    private final static String PREF_MOST_VISITED_VIEW_MODE = "pref_most_visited_view_mode";
72
73    @Override
74    public boolean onContextItemSelected(MenuItem item) {
75        // It is possible that the view has been canceled when we get to
76        // this point as back has a higher priority
77        if (mCanceled) {
78            return true;
79        }
80        AdapterView.AdapterContextMenuInfo i =
81            (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
82        // If we have no menu info, we can't tell which item was selected.
83        if (i == null) {
84            return true;
85        }
86
87        switch (item.getItemId()) {
88        case R.id.new_context_menu_id:
89            saveCurrentPage();
90            break;
91        case R.id.open_context_menu_id:
92            loadUrl(i.position);
93            break;
94        case R.id.edit_context_menu_id:
95            editBookmark(i.position);
96            break;
97        case R.id.shortcut_context_menu_id:
98            sendBroadcast(createShortcutIntent(i.position));
99            break;
100        case R.id.delete_context_menu_id:
101            if (mMostVisited) {
102                Browser.deleteFromHistory(getContentResolver(),
103                        getUrl(i.position));
104                refreshList();
105            } else {
106                displayRemoveBookmarkDialog(i.position);
107            }
108            break;
109        case R.id.new_window_context_menu_id:
110            openInNewWindow(i.position);
111            break;
112        case R.id.share_link_context_menu_id:
113            BrowserActivity.sharePage(BrowserBookmarksPage.this,
114                    mBookmarksAdapter.getTitle(i.position), getUrl(i.position),
115                    getFavicon(i.position),
116                    mBookmarksAdapter.getScreenshot(i.position));
117            break;
118        case R.id.copy_url_context_menu_id:
119            copy(getUrl(i.position));
120            break;
121        case R.id.homepage_context_menu_id:
122            BrowserSettings.getInstance().setHomePage(this,
123                    getUrl(i.position));
124            Toast.makeText(this, R.string.homepage_set,
125                    Toast.LENGTH_LONG).show();
126            break;
127        // Only for the Most visited page
128        case R.id.save_to_bookmarks_menu_id:
129            boolean isBookmark;
130            String name;
131            String url;
132            if (mViewMode == BookmarkViewMode.GRID) {
133                isBookmark = mBookmarksAdapter.getIsBookmark(i.position);
134                name = mBookmarksAdapter.getTitle(i.position);
135                url = mBookmarksAdapter.getUrl(i.position);
136            } else {
137                HistoryItem historyItem = ((HistoryItem) i.targetView);
138                isBookmark = historyItem.isBookmark();
139                name = historyItem.getName();
140                url = historyItem.getUrl();
141            }
142            // If the site is bookmarked, the item becomes remove from
143            // bookmarks.
144            if (isBookmark) {
145                Bookmarks.removeFromBookmarks(this, getContentResolver(), url, name);
146            } else {
147                Browser.saveBookmark(this, name, url);
148            }
149            break;
150        default:
151            return super.onContextItemSelected(item);
152        }
153        return true;
154    }
155
156    @Override
157    public void onCreateContextMenu(ContextMenu menu, View v,
158                ContextMenuInfo menuInfo) {
159            AdapterView.AdapterContextMenuInfo i =
160                    (AdapterView.AdapterContextMenuInfo) menuInfo;
161
162            MenuInflater inflater = getMenuInflater();
163            if (mMostVisited) {
164                inflater.inflate(R.menu.historycontext, menu);
165            } else {
166                inflater.inflate(R.menu.bookmarkscontext, menu);
167            }
168
169            if (0 == i.position && !mMostVisited) {
170                menu.setGroupVisible(R.id.CONTEXT_MENU, false);
171                if (mAddHeader == null) {
172                    mAddHeader = new AddNewBookmark(BrowserBookmarksPage.this);
173                } else if (mAddHeader.getParent() != null) {
174                    ((ViewGroup) mAddHeader.getParent()).
175                            removeView(mAddHeader);
176                }
177                mAddHeader.setUrl(getIntent().getStringExtra("url"));
178                menu.setHeaderView(mAddHeader);
179                return;
180            }
181            if (mMostVisited) {
182                if ((mViewMode == BookmarkViewMode.LIST
183                        && ((HistoryItem) i.targetView).isBookmark())
184                        || mBookmarksAdapter.getIsBookmark(i.position)) {
185                    MenuItem item = menu.findItem(
186                            R.id.save_to_bookmarks_menu_id);
187                    item.setTitle(R.string.remove_from_bookmarks);
188                }
189            } else {
190                // The historycontext menu has no ADD_MENU group.
191                menu.setGroupVisible(R.id.ADD_MENU, false);
192            }
193            if (mDisableNewWindow) {
194                menu.findItem(R.id.new_window_context_menu_id).setVisible(
195                        false);
196            }
197            if (mContextHeader == null) {
198                mContextHeader = new BookmarkItem(BrowserBookmarksPage.this);
199            } else if (mContextHeader.getParent() != null) {
200                ((ViewGroup) mContextHeader.getParent()).
201                        removeView(mContextHeader);
202            }
203            if (mViewMode == BookmarkViewMode.GRID) {
204                mBookmarksAdapter.populateBookmarkItem(mContextHeader,
205                        i.position);
206            } else {
207                BookmarkItem b = (BookmarkItem) i.targetView;
208                b.copyTo(mContextHeader);
209            }
210            menu.setHeaderView(mContextHeader);
211        }
212
213    /**
214     *  Create a new BrowserBookmarksPage.
215     */
216    @Override
217    protected void onCreate(Bundle icicle) {
218        super.onCreate(icicle);
219
220        // Grab the app icon size as a resource.
221        mIconSize = getResources().getDimensionPixelSize(
222                android.R.dimen.app_icon_size);
223
224        Intent intent = getIntent();
225        if (Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) {
226            mCreateShortcut = true;
227        }
228        mDisableNewWindow = intent.getBooleanExtra("disable_new_window",
229                false);
230        mMostVisited = intent.getBooleanExtra("mostVisited", false);
231
232        if (mCreateShortcut) {
233            setTitle(R.string.browser_bookmarks_page_bookmarks_text);
234        }
235
236        setContentView(R.layout.empty_history);
237        mEmptyView = findViewById(R.id.empty_view);
238        mEmptyView.setVisibility(View.GONE);
239
240        SharedPreferences p = getPreferences(MODE_PRIVATE);
241
242        // See if the user has set a preference for the view mode of their
243        // bookmarks. Otherwise default to grid mode.
244        BookmarkViewMode preference = BookmarkViewMode.NONE;
245        if (mMostVisited) {
246            // For the most visited page, only use list mode.
247            preference = BookmarkViewMode.LIST;
248        } else {
249            preference = BookmarkViewMode.values()[p.getInt(
250                    PREF_BOOKMARK_VIEW_MODE, BookmarkViewMode.GRID.ordinal())];
251        }
252        switchViewMode(preference);
253
254        final boolean createShortcut = mCreateShortcut;
255        final boolean mostVisited = mMostVisited;
256        final String url = intent.getStringExtra("url");
257        final String title = intent.getStringExtra("title");
258        final Bitmap thumbnail =
259                (Bitmap) intent.getParcelableExtra("thumbnail");
260        new AsyncTask<Void, Void, Void>() {
261            @Override
262            protected Void doInBackground(Void... unused) {
263                BrowserBookmarksAdapter adapter =
264                    new BrowserBookmarksAdapter(
265                            BrowserBookmarksPage.this,
266                            url,
267                            title,
268                            thumbnail,
269                            createShortcut,
270                            mostVisited);
271                mHandler.obtainMessage(ADAPTER_CREATED, adapter).sendToTarget();
272                return null;
273            }
274        }.execute();
275    }
276
277    @Override
278    protected void onDestroy() {
279        mHandler.removeCallbacksAndMessages(null);
280        super.onDestroy();
281    }
282
283    /**
284     *  Set the ContentView to be either the grid of thumbnails or the vertical
285     *  list.
286     */
287    private void switchViewMode(BookmarkViewMode viewMode) {
288        if (mViewMode == viewMode) {
289            return;
290        }
291
292        mViewMode = viewMode;
293
294        // Update the preferences to make the new view mode sticky.
295        Editor ed = getPreferences(MODE_PRIVATE).edit();
296        if (mMostVisited) {
297            ed.putInt(PREF_MOST_VISITED_VIEW_MODE, mViewMode.ordinal());
298        } else {
299            ed.putInt(PREF_BOOKMARK_VIEW_MODE, mViewMode.ordinal());
300        }
301        ed.commit();
302
303        if (mBookmarksAdapter != null) {
304            mBookmarksAdapter.switchViewMode(viewMode);
305        }
306        if (mViewMode == BookmarkViewMode.GRID) {
307            if (mGridPage == null) {
308                mGridPage = new GridView(this);
309                if (mBookmarksAdapter != null) {
310                    mGridPage.setAdapter(mBookmarksAdapter);
311                }
312                mGridPage.setOnItemClickListener(mListener);
313                mGridPage.setNumColumns(GridView.AUTO_FIT);
314                mGridPage.setColumnWidth(
315                        BrowserActivity.getDesiredThumbnailWidth(this));
316                mGridPage.setFocusable(true);
317                mGridPage.setFocusableInTouchMode(true);
318                mGridPage.setSelector(android.R.drawable.gallery_thumb);
319                float density = getResources().getDisplayMetrics().density;
320                mGridPage.setVerticalSpacing((int) (14 * density));
321                mGridPage.setHorizontalSpacing((int) (8 * density));
322                mGridPage.setStretchMode(GridView.STRETCH_SPACING);
323                mGridPage.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
324                mGridPage.setDrawSelectorOnTop(true);
325                if (mMostVisited) {
326                    mGridPage.setEmptyView(mEmptyView);
327                }
328                if (!mCreateShortcut) {
329                    mGridPage.setOnCreateContextMenuListener(this);
330                }
331            }
332            addContentView(mGridPage, FULL_SCREEN_PARAMS);
333            if (mVerticalList != null) {
334                ViewGroup parent = (ViewGroup) mVerticalList.getParent();
335                if (parent != null) {
336                    parent.removeView(mVerticalList);
337                }
338            }
339        } else {
340            if (null == mVerticalList) {
341                ListView listView = new ListView(this);
342                if (mBookmarksAdapter != null) {
343                    listView.setAdapter(mBookmarksAdapter);
344                }
345                listView.setDrawSelectorOnTop(false);
346                listView.setVerticalScrollBarEnabled(true);
347                listView.setOnItemClickListener(mListListener);
348                if (mMostVisited) {
349                    listView.setEmptyView(mEmptyView);
350                }
351                if (!mCreateShortcut) {
352                    listView.setOnCreateContextMenuListener(this);
353                }
354                mVerticalList = listView;
355            }
356            addContentView(mVerticalList, FULL_SCREEN_PARAMS);
357            if (mGridPage != null) {
358                ViewGroup parent = (ViewGroup) mGridPage.getParent();
359                if (parent != null) {
360                    parent.removeView(mGridPage);
361                }
362            }
363        }
364    }
365
366    private static final ViewGroup.LayoutParams FULL_SCREEN_PARAMS
367            = new ViewGroup.LayoutParams(
368            ViewGroup.LayoutParams.MATCH_PARENT,
369            ViewGroup.LayoutParams.MATCH_PARENT);
370
371    private static final int SAVE_CURRENT_PAGE = 1000;
372    private static final int ADAPTER_CREATED = 1001;
373    private final Handler mHandler = new Handler() {
374        @Override
375        public void handleMessage(Message msg) {
376            switch (msg.what) {
377                case SAVE_CURRENT_PAGE:
378                    saveCurrentPage();
379                    break;
380                case ADAPTER_CREATED:
381                    mBookmarksAdapter = (BrowserBookmarksAdapter) msg.obj;
382                    mBookmarksAdapter.switchViewMode(mViewMode);
383                    if (mGridPage != null) {
384                        mGridPage.setAdapter(mBookmarksAdapter);
385                    }
386                    if (mVerticalList != null) {
387                        mVerticalList.setAdapter(mBookmarksAdapter);
388                    }
389                    // Add our own listener in case there are favicons that
390                    // have yet to be loaded.
391                    if (mMostVisited) {
392                        IconListener listener = new IconListener() {
393                            public void onReceivedIcon(String url,
394                                    Bitmap icon) {
395                                if (mGridPage != null) {
396                                    mGridPage.setAdapter(mBookmarksAdapter);
397                                }
398                                if (mVerticalList != null) {
399                                    mVerticalList.setAdapter(mBookmarksAdapter);
400                                }
401                            }
402                        };
403                        CombinedBookmarkHistoryActivity.getIconListenerSet()
404                                .addListener(listener);
405                    }
406                    break;
407            }
408        }
409    };
410
411    private OnItemClickListener mListener = new OnItemClickListener() {
412        public void onItemClick(AdapterView parent, View v, int position, long id) {
413            // It is possible that the view has been canceled when we get to
414            // this point as back has a higher priority
415            if (mCanceled) {
416                android.util.Log.e(LOGTAG, "item clicked when dismissing");
417                return;
418            }
419            if (!mCreateShortcut) {
420                if (0 == position && !mMostVisited) {
421                    // XXX: Work-around for a framework issue.
422                    mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE);
423                } else {
424                    loadUrl(position);
425                }
426            } else {
427                setResultToParent(RESULT_OK, createShortcutIntent(position));
428                finish();
429            }
430        }
431    };
432
433    private OnItemClickListener mListListener = new OnItemClickListener() {
434        public void onItemClick(AdapterView parent, View v, int position, long id) {
435            // It is possible that the view has been canceled when we get to
436            // this point as back has a higher priority
437            if (mCanceled) {
438                android.util.Log.e(LOGTAG, "item clicked when dismissing");
439                return;
440            }
441            if (!mCreateShortcut) {
442                if (0 == position && !mMostVisited) {
443                    // XXX: Work-around for a framework issue.
444                    mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE);
445                } else {
446                    loadUrl(position);
447                }
448            } else {
449                setResultToParent(RESULT_OK, createShortcutIntent(position));
450                finish();
451            }
452        }
453    };
454
455    private Intent createShortcutIntent(int position) {
456        String url = getUrl(position);
457        String title = getBookmarkTitle(position);
458        Bitmap touchIcon = getTouchIcon(position);
459        Bitmap favicon = getFavicon(position);
460        return BookmarkUtils.createAddToHomeIntent(this, url, title, touchIcon, favicon);
461    }
462
463    private void saveCurrentPage() {
464        Intent i = new Intent(BrowserBookmarksPage.this,
465                AddBookmarkPage.class);
466        i.putExtras(getIntent());
467        startActivityForResult(i, BOOKMARKS_SAVE);
468    }
469
470    private void loadUrl(int position) {
471        Intent intent = (new Intent()).setAction(getUrl(position));
472        setResultToParent(RESULT_OK, intent);
473        finish();
474    }
475
476    @Override
477    public boolean onCreateOptionsMenu(Menu menu) {
478        boolean result = super.onCreateOptionsMenu(menu);
479        if (!mCreateShortcut && !mMostVisited) {
480            MenuInflater inflater = getMenuInflater();
481            inflater.inflate(R.menu.bookmarks, menu);
482            return true;
483        }
484        return result;
485    }
486
487    @Override
488    public boolean onPrepareOptionsMenu(Menu menu) {
489        boolean result = super.onPrepareOptionsMenu(menu);
490        if (mCreateShortcut || mMostVisited || mBookmarksAdapter == null
491                || mBookmarksAdapter.getCount() == 0) {
492            // No need to show the menu if there are no items.
493            return result;
494        }
495        MenuItem switchItem = menu.findItem(R.id.switch_mode_menu_id);
496        int titleResId;
497        int iconResId;
498        if (mViewMode == BookmarkViewMode.GRID) {
499            titleResId = R.string.switch_to_list;
500            iconResId = R.drawable.ic_menu_list;
501        } else {
502            titleResId = R.string.switch_to_thumbnails;
503            iconResId = R.drawable.ic_menu_thumbnail;
504        }
505        switchItem.setTitle(titleResId);
506        switchItem.setIcon(iconResId);
507        return true;
508    }
509
510    @Override
511    public boolean onOptionsItemSelected(MenuItem item) {
512        switch (item.getItemId()) {
513        case R.id.new_context_menu_id:
514            saveCurrentPage();
515            break;
516
517        case R.id.switch_mode_menu_id:
518            if (mViewMode == BookmarkViewMode.GRID) {
519                switchViewMode(BookmarkViewMode.LIST);
520            } else {
521                switchViewMode(BookmarkViewMode.GRID);
522            }
523            break;
524
525        default:
526            return super.onOptionsItemSelected(item);
527        }
528        return true;
529    }
530
531    private void openInNewWindow(int position) {
532        Bundle b = new Bundle();
533        b.putBoolean("new_window", true);
534        setResultToParent(RESULT_OK,
535                (new Intent()).setAction(getUrl(position)).putExtras(b));
536
537        finish();
538    }
539
540
541    private void editBookmark(int position) {
542        Intent intent = new Intent(BrowserBookmarksPage.this,
543            AddBookmarkPage.class);
544        intent.putExtra("bookmark", getRow(position));
545        startActivityForResult(intent, BOOKMARKS_SAVE);
546    }
547
548    @Override
549    protected void onActivityResult(int requestCode, int resultCode,
550                                    Intent data) {
551        switch(requestCode) {
552            case BOOKMARKS_SAVE:
553                if (resultCode == RESULT_OK) {
554                    Bundle extras;
555                    if (data != null && (extras = data.getExtras()) != null) {
556                        // If there are extras, then we need to save
557                        // the edited bookmark. This is done in updateRow()
558                        String title = extras.getString("title");
559                        String url = extras.getString("url");
560                        if (title != null && url != null) {
561                            mBookmarksAdapter.updateRow(extras);
562                        }
563                    } else {
564                        // extras == null then a new bookmark was added to
565                        // the database.
566                        refreshList();
567                    }
568                }
569                break;
570            default:
571                break;
572        }
573    }
574
575    private void displayRemoveBookmarkDialog(int position) {
576        // Put up a dialog asking if the user really wants to
577        // delete the bookmark
578        final int deletePos = position;
579        new AlertDialog.Builder(this)
580                .setTitle(R.string.delete_bookmark)
581                .setIcon(android.R.drawable.ic_dialog_alert)
582                .setMessage(getText(R.string.delete_bookmark_warning).toString().replace(
583                        "%s", getBookmarkTitle(deletePos)))
584                .setPositiveButton(R.string.ok,
585                        new DialogInterface.OnClickListener() {
586                            public void onClick(DialogInterface dialog, int whichButton) {
587                                deleteBookmark(deletePos);
588                            }
589                        })
590                .setNegativeButton(R.string.cancel, null)
591                .show();
592    }
593
594    /**
595     *  Refresh the shown list after the database has changed.
596     */
597    private void refreshList() {
598        if (mBookmarksAdapter == null) return;
599        mBookmarksAdapter.refreshList();
600    }
601
602    /**
603     *  Return a hashmap representing the currently highlighted row.
604     */
605    public Bundle getRow(int position) {
606        return mBookmarksAdapter == null ? null
607                : mBookmarksAdapter.getRow(position);
608    }
609
610    /**
611     *  Return the url of the currently highlighted row.
612     */
613    public String getUrl(int position) {
614        return mBookmarksAdapter == null ? null
615                : mBookmarksAdapter.getUrl(position);
616    }
617
618    /**
619     * Return the favicon of the currently highlighted row.
620     */
621    public Bitmap getFavicon(int position) {
622        return mBookmarksAdapter == null ? null
623                : mBookmarksAdapter.getFavicon(position);
624    }
625
626    private Bitmap getTouchIcon(int position) {
627        return mBookmarksAdapter == null ? null
628                : mBookmarksAdapter.getTouchIcon(position);
629    }
630
631    private void copy(CharSequence text) {
632        try {
633            IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
634            if (clip != null) {
635                clip.setClipboardText(text);
636            }
637        } catch (android.os.RemoteException e) {
638            Log.e(LOGTAG, "Copy failed", e);
639        }
640    }
641
642    public String getBookmarkTitle(int position) {
643        return mBookmarksAdapter == null ? null
644                : mBookmarksAdapter.getTitle(position);
645    }
646
647    /**
648     *  Delete the currently highlighted row.
649     */
650    public void deleteBookmark(int position) {
651        if (mBookmarksAdapter == null) return;
652        mBookmarksAdapter.deleteRow(position);
653    }
654
655    @Override
656    public void onBackPressed() {
657        setResultToParent(RESULT_CANCELED, null);
658        mCanceled = true;
659        super.onBackPressed();
660    }
661
662    // This Activity is generally a sub-Activity of
663    // CombinedBookmarkHistoryActivity. In that situation, we need to pass our
664    // result code up to our parent. However, if someone calls this Activity
665    // directly, then this has no parent, and it needs to set it on itself.
666    private void setResultToParent(int resultCode, Intent data) {
667        Activity parent = getParent();
668        if (parent == null) {
669            setResult(resultCode, data);
670        } else {
671            ((CombinedBookmarkHistoryActivity) parent).setResultFromChild(
672                    resultCode, data);
673        }
674    }
675
676}
677