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