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