BrowserBookmarksPage.java revision c6bc160151e0e0e86a914a8d20433bb694d7dc57
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.graphics.Bitmap; 24import android.graphics.BitmapFactory; 25import android.graphics.Canvas; 26import android.graphics.Color; 27import android.graphics.Paint; 28import android.graphics.RectF; 29import android.net.Uri; 30import android.os.Bundle; 31import android.os.Handler; 32import android.os.Message; 33import android.os.ServiceManager; 34import android.provider.Browser; 35import android.text.IClipboard; 36import android.util.Log; 37import android.view.ContextMenu; 38import android.view.KeyEvent; 39import android.view.Menu; 40import android.view.MenuInflater; 41import android.view.MenuItem; 42import android.view.View; 43import android.view.ViewGroup; 44import android.view.ContextMenu.ContextMenuInfo; 45import android.widget.AdapterView; 46import android.widget.ListView; 47 48/** 49 * View showing the user's bookmarks in the browser. 50 */ 51public class BrowserBookmarksPage extends Activity implements 52 View.OnCreateContextMenuListener { 53 54 private BrowserBookmarksAdapter mBookmarksAdapter; 55 private static final int BOOKMARKS_SAVE = 1; 56 private boolean mMaxTabsOpen; 57 private BookmarkItem mContextHeader; 58 private AddNewBookmark mAddHeader; 59 private boolean mCanceled = false; 60 private boolean mCreateShortcut; 61 // XXX: There is no public string defining this intent so if Home changes 62 // the value, we have to update this string. 63 private static final String INSTALL_SHORTCUT = 64 "com.android.launcher.action.INSTALL_SHORTCUT"; 65 66 private final static String LOGTAG = "browser"; 67 68 69 @Override 70 public boolean onContextItemSelected(MenuItem item) { 71 // It is possible that the view has been canceled when we get to 72 // this point as back has a higher priority 73 if (mCanceled) { 74 return true; 75 } 76 AdapterView.AdapterContextMenuInfo i = 77 (AdapterView.AdapterContextMenuInfo)item.getMenuInfo(); 78 // If we have no menu info, we can't tell which item was selected. 79 if (i == null) { 80 return true; 81 } 82 83 switch (item.getItemId()) { 84 case R.id.new_context_menu_id: 85 saveCurrentPage(); 86 break; 87 case R.id.open_context_menu_id: 88 loadUrl(i.position); 89 break; 90 case R.id.edit_context_menu_id: 91 editBookmark(i.position); 92 break; 93 case R.id.shortcut_context_menu_id: 94 final Intent send = createShortcutIntent(getUrl(i.position), 95 getBookmarkTitle(i.position), getFavicon(i.position)); 96 send.setAction(INSTALL_SHORTCUT); 97 sendBroadcast(send); 98 break; 99 case R.id.delete_context_menu_id: 100 displayRemoveBookmarkDialog(i.position); 101 break; 102 case R.id.new_window_context_menu_id: 103 openInNewWindow(i.position); 104 break; 105 case R.id.send_context_menu_id: 106 Browser.sendString(BrowserBookmarksPage.this, getUrl(i.position)); 107 break; 108 case R.id.copy_url_context_menu_id: 109 copy(getUrl(i.position)); 110 111 default: 112 return super.onContextItemSelected(item); 113 } 114 return true; 115 } 116 117 @Override 118 public void onCreateContextMenu(ContextMenu menu, View v, 119 ContextMenuInfo menuInfo) { 120 AdapterView.AdapterContextMenuInfo i = 121 (AdapterView.AdapterContextMenuInfo) menuInfo; 122 123 MenuInflater inflater = getMenuInflater(); 124 inflater.inflate(R.menu.bookmarkscontext, menu); 125 126 if (0 == i.position) { 127 menu.setGroupVisible(R.id.CONTEXT_MENU, false); 128 if (mAddHeader == null) { 129 mAddHeader = new AddNewBookmark(BrowserBookmarksPage.this); 130 } else if (mAddHeader.getParent() != null) { 131 ((ViewGroup) mAddHeader.getParent()). 132 removeView(mAddHeader); 133 } 134 ((AddNewBookmark) i.targetView).copyTo(mAddHeader); 135 menu.setHeaderView(mAddHeader); 136 return; 137 } 138 menu.setGroupVisible(R.id.ADD_MENU, false); 139 BookmarkItem b = (BookmarkItem) i.targetView; 140 if (mContextHeader == null) { 141 mContextHeader = new BookmarkItem(BrowserBookmarksPage.this); 142 } else if (mContextHeader.getParent() != null) { 143 ((ViewGroup) mContextHeader.getParent()). 144 removeView(mContextHeader); 145 } 146 b.copyTo(mContextHeader); 147 menu.setHeaderView(mContextHeader); 148 149 if (mMaxTabsOpen) { 150 menu.findItem(R.id.new_window_context_menu_id).setVisible( 151 false); 152 } 153 } 154 155 /** 156 * Create a new BrowserBookmarksPage. 157 */ 158 @Override 159 protected void onCreate(Bundle icicle) { 160 super.onCreate(icicle); 161 162 setContentView(R.layout.browser_bookmarks_page); 163 setTitle(R.string.browser_bookmarks_page_bookmarks_text); 164 165 if (Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction())) { 166 mCreateShortcut = true; 167 } 168 169 mBookmarksAdapter = new BrowserBookmarksAdapter(this, 170 getIntent().getStringExtra("url"), mCreateShortcut); 171 mMaxTabsOpen = getIntent().getBooleanExtra("maxTabsOpen", false); 172 173 ListView listView = (ListView) findViewById(R.id.list); 174 listView.setAdapter(mBookmarksAdapter); 175 listView.setDrawSelectorOnTop(false); 176 listView.setVerticalScrollBarEnabled(true); 177 listView.setOnItemClickListener(mListener); 178 179 if (!mCreateShortcut) { 180 listView.setOnCreateContextMenuListener(this); 181 } 182 } 183 184 private static final int SAVE_CURRENT_PAGE = 1000; 185 private final Handler mHandler = new Handler() { 186 @Override 187 public void handleMessage(Message msg) { 188 if (msg.what == SAVE_CURRENT_PAGE) { 189 saveCurrentPage(); 190 } 191 } 192 }; 193 194 private AdapterView.OnItemClickListener mListener = new AdapterView.OnItemClickListener() { 195 public void onItemClick(AdapterView parent, View v, int position, long id) { 196 // It is possible that the view has been canceled when we get to 197 // this point as back has a higher priority 198 if (mCanceled) { 199 android.util.Log.e("browser", "item clicked when dismising"); 200 return; 201 } 202 if (!mCreateShortcut) { 203 if (0 == position) { 204 // XXX: Work-around for a framework issue. 205 mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE); 206 } else { 207 loadUrl(position); 208 } 209 } else { 210 final Intent intent = createShortcutIntent(getUrl(position), 211 getBookmarkTitle(position), getFavicon(position)); 212 setResultToParent(RESULT_OK, intent); 213 finish(); 214 } 215 } 216 }; 217 218 private Intent createShortcutIntent(String url, String title, 219 Bitmap favicon) { 220 final Intent i = new Intent(); 221 final Intent shortcutIntent = new Intent(Intent.ACTION_VIEW, 222 Uri.parse(url)); 223 long urlHash = url.hashCode(); 224 long uniqueId = (urlHash << 32) | shortcutIntent.hashCode(); 225 shortcutIntent.putExtra(Browser.EXTRA_APPLICATION_ID, 226 Long.toString(uniqueId)); 227 i.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); 228 i.putExtra(Intent.EXTRA_SHORTCUT_NAME, title); 229 if (favicon == null) { 230 i.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, 231 Intent.ShortcutIconResource.fromContext( 232 BrowserBookmarksPage.this, 233 R.drawable.ic_launcher_shortcut_browser_bookmark)); 234 } else { 235 Bitmap icon = BitmapFactory.decodeResource(getResources(), 236 R.drawable.ic_launcher_shortcut_browser_bookmark); 237 238 // Make a copy of the regular icon so we can modify the pixels. 239 Bitmap copy = icon.copy(Bitmap.Config.ARGB_8888, true); 240 Canvas canvas = new Canvas(copy); 241 242 // Make a Paint for the white background rectangle and for 243 // filtering the favicon. 244 Paint p = new Paint(Paint.ANTI_ALIAS_FLAG 245 | Paint.FILTER_BITMAP_FLAG); 246 p.setStyle(Paint.Style.FILL_AND_STROKE); 247 p.setColor(Color.WHITE); 248 249 // Create a rectangle that is slightly wider than the favicon 250 final float iconSize = 16; // 16x16 favicon 251 final float padding = 2; // white padding around icon 252 final float rectSize = iconSize + 2 * padding; 253 final float y = icon.getHeight() - rectSize; 254 RectF r = new RectF(0, y, rectSize, y + rectSize); 255 256 // Draw a white rounded rectangle behind the favicon 257 canvas.drawRoundRect(r, 2, 2, p); 258 259 // Draw the favicon in the same rectangle as the rounded rectangle 260 // but inset by the padding (results in a 16x16 favicon). 261 r.inset(padding, padding); 262 canvas.drawBitmap(favicon, null, r, p); 263 i.putExtra(Intent.EXTRA_SHORTCUT_ICON, copy); 264 } 265 // Do not allow duplicate items 266 i.putExtra("duplicate", false); 267 return i; 268 } 269 270 private void saveCurrentPage() { 271 Intent i = new Intent(BrowserBookmarksPage.this, 272 AddBookmarkPage.class); 273 i.putExtras(getIntent()); 274 startActivityForResult(i, BOOKMARKS_SAVE); 275 } 276 277 private void loadUrl(int position) { 278 Intent intent = (new Intent()).setAction(getUrl(position)); 279 setResultToParent(RESULT_OK, intent); 280 finish(); 281 } 282 283 @Override 284 public boolean onCreateOptionsMenu(Menu menu) { 285 boolean result = super.onCreateOptionsMenu(menu); 286 if (!mCreateShortcut) { 287 MenuInflater inflater = getMenuInflater(); 288 inflater.inflate(R.menu.bookmarks, menu); 289 return true; 290 } 291 return result; 292 } 293 294 @Override 295 public boolean onOptionsItemSelected(MenuItem item) { 296 switch (item.getItemId()) { 297 case R.id.new_context_menu_id: 298 saveCurrentPage(); 299 break; 300 301 default: 302 return super.onOptionsItemSelected(item); 303 } 304 return true; 305 } 306 307 private void openInNewWindow(int position) { 308 Bundle b = new Bundle(); 309 b.putBoolean("new_window", true); 310 setResultToParent(RESULT_OK, 311 (new Intent()).setAction(getUrl(position)).putExtras(b)); 312 313 finish(); 314 } 315 316 317 private void editBookmark(int position) { 318 Intent intent = new Intent(BrowserBookmarksPage.this, 319 AddBookmarkPage.class); 320 intent.putExtra("bookmark", getRow(position)); 321 startActivityForResult(intent, BOOKMARKS_SAVE); 322 } 323 324 @Override 325 protected void onActivityResult(int requestCode, int resultCode, 326 Intent data) { 327 switch(requestCode) { 328 case BOOKMARKS_SAVE: 329 if (resultCode == RESULT_OK) { 330 Bundle extras; 331 if (data != null && (extras = data.getExtras()) != null) { 332 // If there are extras, then we need to save 333 // the edited bookmark. This is done in updateRow() 334 String title = extras.getString("title"); 335 String url = extras.getString("url"); 336 if (title != null && url != null) { 337 mBookmarksAdapter.updateRow(extras); 338 } 339 } else { 340 // extras == null then a new bookmark was added to 341 // the database. 342 refreshList(); 343 } 344 } 345 break; 346 default: 347 break; 348 } 349 } 350 351 private void displayRemoveBookmarkDialog(int position) { 352 // Put up a dialog asking if the user really wants to 353 // delete the bookmark 354 final int deletePos = position; 355 new AlertDialog.Builder(this) 356 .setTitle(R.string.delete_bookmark) 357 .setIcon(android.R.drawable.ic_dialog_alert) 358 .setMessage(getText(R.string.delete_bookmark_warning).toString().replace( 359 "%s", getBookmarkTitle(deletePos))) 360 .setPositiveButton(R.string.ok, 361 new DialogInterface.OnClickListener() { 362 public void onClick(DialogInterface dialog, int whichButton) { 363 deleteBookmark(deletePos); 364 } 365 }) 366 .setNegativeButton(R.string.cancel, null) 367 .show(); 368 } 369 370 /** 371 * Refresh the shown list after the database has changed. 372 */ 373 public void refreshList() { 374 mBookmarksAdapter.refreshList(); 375 } 376 377 /** 378 * Return a hashmap representing the currently highlighted row. 379 */ 380 public Bundle getRow(int position) { 381 return mBookmarksAdapter.getRow(position); 382 } 383 384 /** 385 * Return the url of the currently highlighted row. 386 */ 387 public String getUrl(int position) { 388 return mBookmarksAdapter.getUrl(position); 389 } 390 391 /** 392 * Return the favicon of the currently highlighted row. 393 */ 394 public Bitmap getFavicon(int position) { 395 return mBookmarksAdapter.getFavicon(position); 396 } 397 398 private void copy(CharSequence text) { 399 try { 400 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard")); 401 if (clip != null) { 402 clip.setClipboardText(text); 403 } 404 } catch (android.os.RemoteException e) { 405 Log.e(LOGTAG, "Copy failed", e); 406 } 407 } 408 409 public String getBookmarkTitle(int position) { 410 return mBookmarksAdapter.getTitle(position); 411 } 412 413 /** 414 * Delete the currently highlighted row. 415 */ 416 public void deleteBookmark(int position) { 417 mBookmarksAdapter.deleteRow(position); 418 } 419 420 public boolean dispatchKeyEvent(KeyEvent event) { 421 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.isDown()) { 422 setResultToParent(RESULT_CANCELED, null); 423 mCanceled = true; 424 } 425 return super.dispatchKeyEvent(event); 426 } 427 428 // This Activity is generally a sub-Activity of CombinedHistoryActivity. In 429 // that situation, we need to pass our result code up to our parent. 430 // However, if someone calls this Activity directly, then this has no 431 // parent, and it needs to set it on itself. 432 private void setResultToParent(int resultCode, Intent data) { 433 Activity a = getParent() == null ? this : getParent(); 434 a.setResult(resultCode, data); 435 } 436} 437