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