BrowserBookmarksPage.java revision bef7a759aa57894124c67ff4912ea595cdc92ce1
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.app.Fragment;
22import android.app.LoaderManager;
23import android.content.ClipData;
24import android.content.ClipboardManager;
25import android.content.ContentUris;
26import android.content.ContentValues;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.content.Loader;
31import android.database.Cursor;
32import android.graphics.Bitmap;
33import android.graphics.BitmapFactory;
34import android.net.Uri;
35import android.os.Bundle;
36import android.provider.Browser;
37import android.provider.BrowserContract;
38import android.util.Pair;
39import android.view.ContextMenu;
40import android.view.ContextMenu.ContextMenuInfo;
41import android.view.LayoutInflater;
42import android.view.MenuInflater;
43import android.view.MenuItem;
44import android.view.View;
45import android.view.View.OnClickListener;
46import android.view.ViewGroup;
47import android.webkit.WebIconDatabase.IconListener;
48import android.widget.AdapterView;
49import android.widget.AdapterView.OnItemClickListener;
50import android.widget.Button;
51import android.widget.GridView;
52import android.widget.Toast;
53
54import java.util.Stack;
55
56/**
57 *  View showing the user's bookmarks in the browser.
58 */
59public class BrowserBookmarksPage extends Fragment implements View.OnCreateContextMenuListener,
60        LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, IconListener, OnClickListener {
61
62    static final int BOOKMARKS_SAVE = 1;
63    static final String LOGTAG = "browser";
64
65    static final int LOADER_BOOKMARKS = 1;
66
67    static final String EXTRA_SHORTCUT = "create_shortcut";
68    static final String EXTRA_DISABLE_WINDOW = "disable_new_window";
69
70    BookmarksHistoryCallbacks mCallbacks;
71    GridView mGrid;
72    BrowserBookmarksAdapter mAdapter;
73    boolean mDisableNewWindow;
74    BookmarkItem mContextHeader;
75    boolean mCanceled = false;
76    boolean mCreateShortcut;
77    View mEmptyView;
78    View mContentView;
79    Stack<Pair<String, Uri>> mFolderStack = new Stack<Pair<String, Uri>>();
80    Button mUpButton;
81
82    @Override
83    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
84        switch (id) {
85            case LOADER_BOOKMARKS: {
86                int rootFolder = 0;
87                if (args != null) {
88                    args.getInt(BookmarksLoader.ARG_ROOT_FOLDER, 0);
89                }
90                return new BookmarksLoader(getActivity(), rootFolder);
91            }
92        }
93        throw new UnsupportedOperationException("Unknown loader id " + id);
94    }
95
96    @Override
97    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
98        // Set the visibility of the empty vs. content views
99        if (data == null || data.getCount() == 0) {
100            mEmptyView.setVisibility(View.VISIBLE);
101            mContentView.setVisibility(View.GONE);
102        } else {
103            mEmptyView.setVisibility(View.GONE);
104            mContentView.setVisibility(View.VISIBLE);
105        }
106
107        // Fill in the "up" button if needed
108        BookmarksLoader bl = (BookmarksLoader) loader;
109        boolean rootFolder =
110                (BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER.equals(bl.getUri()));
111        if (rootFolder) {
112            mUpButton.setText(R.string.defaultBookmarksUpButton);
113            mUpButton.setEnabled(false);
114        } else {
115            mUpButton.setText(mFolderStack.peek().first);
116            mUpButton.setEnabled(true);
117        }
118
119        // Give the new data to the adapter
120        mAdapter.changeCursor(data);
121    }
122
123    @Override
124    public void onClick(View view) {
125        if (view == mUpButton) {
126            Pair<String, Uri> pair = mFolderStack.pop();
127            BookmarksLoader loader =
128                    (BookmarksLoader) ((Loader) getLoaderManager().getLoader(LOADER_BOOKMARKS));
129            loader.setUri(pair.second);
130            loader.forceLoad();
131        }
132    }
133
134    @Override
135    public boolean onContextItemSelected(MenuItem item) {
136        final Activity activity = getActivity();
137        // It is possible that the view has been canceled when we get to
138        // this point as back has a higher priority
139        if (mCanceled) {
140            return true;
141        }
142        AdapterView.AdapterContextMenuInfo i =
143            (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
144        // If we have no menu info, we can't tell which item was selected.
145        if (i == null) {
146            return true;
147        }
148
149        switch (item.getItemId()) {
150        case R.id.open_context_menu_id:
151            loadUrl(i.position);
152            break;
153        case R.id.edit_context_menu_id:
154            editBookmark(i.position);
155            break;
156        case R.id.shortcut_context_menu_id:
157            activity.sendBroadcast(createShortcutIntent(i.position));
158            break;
159        case R.id.delete_context_menu_id:
160            displayRemoveBookmarkDialog(i.position);
161            break;
162        case R.id.new_window_context_menu_id:
163            openInNewWindow(i.position);
164            break;
165        case R.id.share_link_context_menu_id: {
166            Cursor cursor = (Cursor) mAdapter.getItem(i.position);
167            BrowserActivity.sharePage(activity,
168                    cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE),
169                    cursor.getString(BookmarksLoader.COLUMN_INDEX_URL),
170                    getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON),
171                    getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_THUMBNAIL));
172            break;
173        }
174        case R.id.copy_url_context_menu_id:
175            copy(getUrl(i.position));
176            break;
177        case R.id.homepage_context_menu_id: {
178            BrowserSettings.getInstance().setHomePage(activity, getUrl(i.position));
179            Toast.makeText(activity, R.string.homepage_set, Toast.LENGTH_LONG).show();
180            break;
181        }
182        // Only for the Most visited page
183        case R.id.save_to_bookmarks_menu_id: {
184            Cursor cursor = (Cursor) mAdapter.getItem(i.position);
185            String name = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
186            String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
187            // If the site is bookmarked, the item becomes remove from
188            // bookmarks.
189            Bookmarks.removeFromBookmarks(activity, activity.getContentResolver(), url, name);
190            break;
191        }
192        default:
193            return super.onContextItemSelected(item);
194        }
195        return true;
196    }
197
198    Bitmap getBitmap(Cursor cursor, int columnIndex) {
199        byte[] data = cursor.getBlob(columnIndex);
200        if (data == null) {
201            return null;
202        }
203        return BitmapFactory.decodeByteArray(data, 0, data.length);
204    }
205
206    @Override
207    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
208        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
209
210        final Activity activity = getActivity();
211        MenuInflater inflater = activity.getMenuInflater();
212        inflater.inflate(R.menu.bookmarkscontext, menu);
213
214        if (mDisableNewWindow) {
215            menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
216        }
217
218        if (mContextHeader == null) {
219            mContextHeader = new BookmarkItem(activity);
220        } else if (mContextHeader.getParent() != null) {
221            ((ViewGroup) mContextHeader.getParent()).removeView(mContextHeader);
222        }
223
224        populateBookmarkItem(mAdapter, mContextHeader, info.position);
225
226        menu.setHeaderView(mContextHeader);
227    }
228
229    private void populateBookmarkItem(BrowserBookmarksAdapter adapter, BookmarkItem item,
230            int position) {
231        Cursor cursor = (Cursor) mAdapter.getItem(position);
232        String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
233        item.setUrl(url);
234        item.setName(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
235        Bitmap bitmap = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
236        if (bitmap == null) {
237            bitmap = CombinedBookmarkHistoryActivity.getIconListenerSet().getFavicon(url);
238        }
239        item.setFavicon(bitmap);
240    }
241
242    /**
243     *  Create a new BrowserBookmarksPage.
244     */
245    @Override
246    public void onCreate(Bundle icicle) {
247        super.onCreate(icicle);
248
249        Bundle args = getArguments();
250        mCreateShortcut = args == null ? false : args.getBoolean("create_shortcut", false);
251        mDisableNewWindow = args == null ? false : args.getBoolean("disable_new_window", false);
252    }
253
254    @Override
255    public void onAttach(Activity activity) {
256        super.onAttach(activity);
257        mCallbacks = (BookmarksHistoryCallbacks) activity;
258    }
259
260    @Override
261    public View onCreateView(LayoutInflater inflater, ViewGroup container,
262            Bundle savedInstanceState) {
263        View root = inflater.inflate(R.layout.bookmarks, container, false);
264        mEmptyView = root.findViewById(android.R.id.empty);
265        mContentView = root.findViewById(android.R.id.content);
266
267        mGrid = (GridView) root.findViewById(R.id.grid);
268        mGrid.setOnItemClickListener(this);
269        mGrid.setColumnWidth(BrowserActivity.getDesiredThumbnailWidth(getActivity()));
270        if (!mCreateShortcut) {
271            mGrid.setOnCreateContextMenuListener(this);
272        }
273
274        mUpButton = (Button) root.findViewById(R.id.up);
275        mUpButton.setEnabled(false);
276        mUpButton.setOnClickListener(this);
277
278        mAdapter = new BrowserBookmarksAdapter(getActivity());
279        mGrid.setAdapter(mAdapter);
280
281        // Start the loader for the bookmark data
282        getLoaderManager().initLoader(LOADER_BOOKMARKS, null, this);
283
284        // Add our own listener in case there are favicons that have yet to be loaded.
285        CombinedBookmarkHistoryActivity.getIconListenerSet().addListener(this);
286
287        return root;
288    }
289
290    @Override
291    public void onReceivedIcon(String url, Bitmap icon) {
292        // A new favicon has been loaded, so let anything attached to the adapter know about it
293        // so new icons will be loaded.
294        mAdapter.notifyDataSetChanged();
295    }
296
297    @Override
298    public void onItemClick(AdapterView parent, View v, int position, long id) {
299        // It is possible that the view has been canceled when we get to
300        // this point as back has a higher priority
301        if (mCanceled) {
302            android.util.Log.e(LOGTAG, "item clicked when dismissing");
303            return;
304        }
305        if (mCreateShortcut) {
306            Intent intent = createShortcutIntent(position);
307            // the activity handles the intent in startActivityFromFragment
308            startActivity(intent);
309            return;
310        }
311
312        Cursor cursor = (Cursor) mAdapter.getItem(position);
313        boolean isFolder = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
314        if (!isFolder) {
315            mCallbacks.onUrlSelected(getUrl(position), false);
316        } else {
317            String title;
318            if (mFolderStack.size() != 0) {
319                title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
320            } else {
321                // TODO localize
322                title = "Bookmarks";
323            }
324            LoaderManager manager = getLoaderManager();
325            BookmarksLoader loader =
326                    (BookmarksLoader) ((Loader) manager.getLoader(LOADER_BOOKMARKS));
327            mFolderStack.push(new Pair(title, loader.getUri()));
328            Uri uri = ContentUris.withAppendedId(
329                    BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, id);
330            loader.setUri(uri);
331            loader.forceLoad();
332        }
333    }
334
335    private Intent createShortcutIntent(int position) {
336        Cursor cursor = (Cursor) mAdapter.getItem(position);
337        String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
338        String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
339        Bitmap touchIcon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_TOUCH_ICON);
340        Bitmap favicon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
341        return BookmarkUtils.createAddToHomeIntent(getActivity(), url, title, touchIcon, favicon);
342    }
343
344    private void loadUrl(int position) {
345        mCallbacks.onUrlSelected(getUrl(position), false);
346    }
347
348    private void openInNewWindow(int position) {
349        mCallbacks.onUrlSelected(getUrl(position), true);
350    }
351
352    private void editBookmark(int position) {
353        Intent intent = new Intent(getActivity(), AddBookmarkPage.class);
354        Cursor cursor = (Cursor) mAdapter.getItem(position);
355        Bundle item = new Bundle();
356        item.putString(BrowserContract.Bookmarks.TITLE,
357                cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
358        item.putString(BrowserContract.Bookmarks.URL,
359                cursor.getString(BookmarksLoader.COLUMN_INDEX_URL));
360        byte[] data = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_FAVICON);
361        if (data != null) {
362            item.putParcelable(BrowserContract.Bookmarks.FAVICON,
363                    BitmapFactory.decodeByteArray(data, 0, data.length));
364        }
365        item.putInt("id", cursor.getInt(BookmarksLoader.COLUMN_INDEX_ID));
366        intent.putExtra("bookmark", item);
367        startActivityForResult(intent, BOOKMARKS_SAVE);
368    }
369
370    @Override
371    public void onActivityResult(int requestCode, int resultCode, Intent data) {
372        switch(requestCode) {
373            case BOOKMARKS_SAVE:
374                if (resultCode == Activity.RESULT_OK) {
375                    Bundle extras;
376                    if (data != null && (extras = data.getExtras()) != null) {
377                        // If there are extras, then we need to save
378                        // the edited bookmark. This is done in updateRow()
379                        String title = extras.getString("title");
380                        String url = extras.getString("url");
381                        if (title != null && url != null) {
382                            updateRow(extras);
383                        }
384                    }
385                }
386                break;
387        }
388    }
389
390    /**
391     *  Update a row in the database with new information.
392     *  Requeries the database if the information has changed.
393     *  @param map  Bundle storing id, title and url of new information
394     */
395    public void updateRow(Bundle map) {
396
397        // Find the record
398        int id = map.getInt("id");
399        int position = -1;
400        Cursor cursor = mAdapter.getCursor();
401        for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
402            if (cursor.getInt(BookmarksLoader.COLUMN_INDEX_ID) == id) {
403                position = cursor.getPosition();
404                break;
405            }
406        }
407        if (position < 0) {
408            return;
409        }
410
411        cursor.moveToPosition(position);
412        ContentValues values = new ContentValues();
413        String title = map.getString(BrowserContract.Bookmarks.TITLE);
414        if (!title.equals(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE))) {
415            values.put(BrowserContract.Bookmarks.TITLE, title);
416        }
417        String url = map.getString(BrowserContract.Bookmarks.URL);
418        if (!url.equals(cursor.getString(BookmarksLoader.COLUMN_INDEX_URL))) {
419            values.put(BrowserContract.Bookmarks.URL, url);
420        }
421
422        if (map.getBoolean("invalidateThumbnail") == true) {
423            values.putNull(BrowserContract.Bookmarks.THUMBNAIL);
424        }
425
426        if (values.size() > 0) {
427            getActivity().getContentResolver().update(
428                    ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id),
429                    values, null, null);
430        }
431        updateView();
432    }
433
434    private void displayRemoveBookmarkDialog(final int position) {
435        // Put up a dialog asking if the user really wants to
436        // delete the bookmark
437        Cursor cursor = (Cursor) mAdapter.getItem(position);
438        Context context = getActivity();
439        new AlertDialog.Builder(context)
440                .setTitle(R.string.delete_bookmark)
441                .setIcon(android.R.drawable.ic_dialog_alert)
442                .setMessage(context.getText(R.string.delete_bookmark_warning).toString().replace(
443                        "%s", cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE)))
444                .setPositiveButton(R.string.ok,
445                        new DialogInterface.OnClickListener() {
446                            public void onClick(DialogInterface dialog, int whichButton) {
447                                deleteBookmark(position);
448                            }
449                        })
450                .setNegativeButton(R.string.cancel, null)
451                .show();
452    }
453
454    private String getUrl(int position) {
455        Cursor cursor = (Cursor) mAdapter.getItem(position);
456        return cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
457    }
458
459    private void copy(CharSequence text) {
460        ClipboardManager cm = (ClipboardManager) getActivity().getSystemService(
461                Context.CLIPBOARD_SERVICE);
462        cm.setPrimaryClip(ClipData.newRawUri(null, null, Uri.parse(text.toString())));
463    }
464
465    /**
466     *  Delete the currently highlighted row.
467     */
468    public void deleteBookmark(int position) {
469        Cursor cursor = (Cursor) mAdapter.getItem(position);
470        String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
471        String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
472        Bookmarks.removeFromBookmarks(null, getActivity().getContentResolver(), url, title);
473        updateView();
474    }
475
476    private void updateView() {
477        BookmarksLoader loader =
478            (BookmarksLoader) (Loader)(getLoaderManager().getLoader(LOADER_BOOKMARKS));
479        loader.forceLoad();
480    }
481
482}
483