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