AddBookmarkPage.java revision b1402a5c3e1617867c58d32d1fe9782cf6de423f
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 com.android.browser.provider.BrowserProvider2; 20 21import android.app.Activity; 22import android.app.LoaderManager; 23import android.content.ContentResolver; 24import android.content.ContentUris; 25import android.content.ContentValues; 26import android.content.Context; 27import android.content.CursorLoader; 28import android.content.Intent; 29import android.content.Loader; 30import android.content.SharedPreferences; 31import android.content.res.Resources; 32import android.database.Cursor; 33import android.graphics.Bitmap; 34import android.net.ParseException; 35import android.net.Uri; 36import android.net.WebAddress; 37import android.os.Bundle; 38import android.os.Handler; 39import android.os.Message; 40import android.preference.PreferenceManager; 41import android.provider.BrowserContract; 42import android.text.TextUtils; 43import android.view.KeyEvent; 44import android.view.LayoutInflater; 45import android.view.View; 46import android.view.ViewGroup; 47import android.view.Window; 48import android.view.WindowManager; 49import android.view.inputmethod.EditorInfo; 50import android.view.inputmethod.InputMethodManager; 51import android.widget.AdapterView; 52import android.widget.CursorAdapter; 53import android.widget.EditText; 54import android.widget.ListView; 55import android.widget.TextView; 56import android.widget.Toast; 57 58import java.net.URI; 59import java.net.URISyntaxException; 60import java.util.ArrayList; 61 62public class AddBookmarkPage extends Activity 63 implements View.OnClickListener, TextView.OnEditorActionListener, 64 AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor> { 65 66 private final String LOGTAG = "Bookmarks"; 67 68 // IDs for the CursorLoaders that are used. 69 private final int LOADER_ID_FOLDER_CONTENTS = 0; 70 private final int LOADER_ID_ALL_FOLDERS = 1; 71 72 private EditText mTitle; 73 private EditText mAddress; 74 private TextView mButton; 75 private View mCancelButton; 76 private boolean mEditingExisting; 77 private Bundle mMap; 78 private String mTouchIconUrl; 79 private Bitmap mThumbnail; 80 private String mOriginalUrl; 81 private TextView mFolder; 82 private View mDefaultView; 83 private View mFolderSelector; 84 private EditText mFolderNamer; 85 private View mAddNewFolder; 86 private long mCurrentFolder = 0; 87 private FolderAdapter mAdapter; 88 private ArrayList<Folder> mPaths; 89 private TextView mPath; 90 91 private static class Folder { 92 String Name; 93 long Id; 94 Folder(String name, long id) { 95 Name = name; 96 Id = id; 97 } 98 } 99 100 // Message IDs 101 private static final int SAVE_BOOKMARK = 100; 102 103 private Handler mHandler; 104 105 private InputMethodManager getInputMethodManager() { 106 return (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); 107 } 108 109 @Override 110 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 111 if (v == mFolderNamer) { 112 if (v.getText().length() > 0) { 113 if (actionId == EditorInfo.IME_NULL) { 114 // Only want to do this once. 115 if (event.getAction() == KeyEvent.ACTION_UP) { 116 completeFolderNaming(); 117 } 118 } 119 } 120 // Steal the key press; otherwise a newline will be added 121 return true; 122 } 123 return false; 124 } 125 126 @Override 127 public void onClick(View v) { 128 if (v == mButton) { 129 if (mFolderSelector.getVisibility() == View.VISIBLE) { 130 // We are showing the folder selector. 131 if (mFolderNamer.getVisibility() == View.VISIBLE) { 132 completeFolderNaming(); 133 } else { 134 // User has selected a folder. Go back to the opening page 135 mFolderSelector.setVisibility(View.GONE); 136 mDefaultView.setVisibility(View.VISIBLE); 137 setTitle(R.string.bookmark_this_page); 138 } 139 } else if (save()) { 140 finish(); 141 } 142 } else if (v == mCancelButton) { 143 if (mFolderNamer.getVisibility() == View.VISIBLE) { 144 mFolderNamer.setVisibility(View.GONE); 145 mAddNewFolder.setVisibility(View.VISIBLE); 146 } else { 147 finish(); 148 } 149 } else if (v == mFolder) { 150 switchToFolderSelector(); 151 } else if (v == mAddNewFolder) { 152 mFolderNamer.setVisibility(View.VISIBLE); 153 mFolderNamer.setText(R.string.new_folder); 154 mFolderNamer.requestFocus(); 155 mAddNewFolder.setVisibility(View.GONE); 156 getInputMethodManager().showSoftInput(mFolderNamer, 157 InputMethodManager.SHOW_IMPLICIT); 158 } 159 } 160 161 private void completeFolderNaming() { 162 if (!TextUtils.isEmpty(mFolderNamer.getText())) { 163 String name = mFolderNamer.getText().toString(); 164 long id = addFolderToCurrent(mFolderNamer.getText().toString()); 165 descendInto(name, id); 166 mFolderNamer.setVisibility(View.GONE); 167 mAddNewFolder.setVisibility(View.VISIBLE); 168 getInputMethodManager().hideSoftInputFromWindow( 169 mFolderNamer.getWindowToken(), 0); 170 } 171 } 172 173 private long addFolderToCurrent(String name) { 174 // Add the folder to the database 175 ContentValues values = new ContentValues(); 176 values.put(BrowserContract.Bookmarks.TITLE, 177 name); 178 values.put(BrowserContract.Bookmarks.IS_FOLDER, 1); 179 values.put(BrowserContract.Bookmarks.PARENT, 180 mCurrentFolder); 181 Uri uri = getContentResolver().insert( 182 BrowserContract.Bookmarks.CONTENT_URI, values); 183 if (uri != null) { 184 return ContentUris.parseId(uri); 185 } else { 186 return -1; 187 } 188 } 189 190 private void switchToFolderSelector() { 191 mDefaultView.setVisibility(View.GONE); 192 mFolderSelector.setVisibility(View.VISIBLE); 193 setTitle(R.string.containing_folder); 194 } 195 196 private void descendInto(String foldername, long id) { 197 if (id != -1) { 198 mCurrentFolder = id; 199 mPaths.add(new Folder(foldername, id)); 200 updatePathString(); 201 getLoaderManager().restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this); 202 } 203 } 204 205 @Override 206 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 207 String[] projection; 208 switch (id) { 209 case LOADER_ID_ALL_FOLDERS: 210 projection = new String[] { 211 BrowserContract.Bookmarks._ID, 212 BrowserContract.Bookmarks.PARENT, 213 BrowserContract.Bookmarks.TITLE, 214 BrowserContract.Bookmarks.IS_FOLDER 215 }; 216 return new CursorLoader(this, 217 BrowserContract.Bookmarks.CONTENT_URI, 218 projection, 219 BrowserContract.Bookmarks.IS_FOLDER + " != 0", 220 null, 221 null); 222 case LOADER_ID_FOLDER_CONTENTS: 223 projection = new String[] { 224 BrowserContract.Bookmarks._ID, 225 BrowserContract.Bookmarks.TITLE, 226 BrowserContract.Bookmarks.IS_FOLDER 227 }; 228 return new CursorLoader(this, 229 BrowserContract.Bookmarks.buildFolderUri( 230 mCurrentFolder), 231 projection, 232 BrowserContract.Bookmarks.IS_FOLDER + " != 0", 233 null, 234 null); 235 default: 236 throw new AssertionError("Asking for nonexistant loader!"); 237 } 238 } 239 240 @Override 241 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { 242 switch (loader.getId()) { 243 case LOADER_ID_FOLDER_CONTENTS: 244 mAdapter.changeCursor(cursor); 245 break; 246 case LOADER_ID_ALL_FOLDERS: 247 long parent = mCurrentFolder; 248 int idIndex = cursor.getColumnIndexOrThrow( 249 BrowserContract.Bookmarks._ID); 250 int titleIndex = cursor.getColumnIndexOrThrow( 251 BrowserContract.Bookmarks.TITLE); 252 int parentIndex = cursor.getColumnIndexOrThrow( 253 BrowserContract.Bookmarks.PARENT); 254 while ((parent != BrowserProvider2.FIXED_ID_ROOT) && 255 (parent != 0)) { 256 // First, find the folder corresponding to the current 257 // folder 258 if (!cursor.moveToFirst()) { 259 throw new AssertionError("No folders in the database!"); 260 } 261 long folder; 262 do { 263 folder = cursor.getLong(idIndex); 264 } while (folder != parent && cursor.moveToNext()); 265 if (cursor.isAfterLast()) { 266 throw new AssertionError("Folder(id=" + parent 267 + ") holding this bookmark does not exist!"); 268 } 269 String name = cursor.getString(titleIndex); 270 mPaths.add(1, new Folder(name, parent)); 271 parent = cursor.getLong(parentIndex); 272 } 273 getLoaderManager().stopLoader(LOADER_ID_ALL_FOLDERS); 274 updatePathString(); 275 break; 276 default: 277 break; 278 } 279 } 280 281 /** 282 * Update the TextViews in both modes to display the full path of the 283 * current location to insert. 284 */ 285 private void updatePathString() { 286 String path = mPaths.get(0).Name; 287 int size = mPaths.size(); 288 for (int i = 1; i < size; i++) { 289 path += " / " + mPaths.get(i).Name; 290 } 291 mPath.setText(path); 292 mFolder.setText(path); 293 } 294 295 @Override 296 public void onItemClick(AdapterView<?> parent, View view, int position, 297 long id) { 298 TextView tv = (TextView) view.findViewById(android.R.id.text1); 299 // Switch to the folder that was clicked on. 300 descendInto(tv.getText().toString(), id); 301 } 302 303 /** 304 * Shows a list of names of folders. 305 */ 306 private class FolderAdapter extends CursorAdapter { 307 public FolderAdapter(Context context) { 308 super(context, null); 309 } 310 311 @Override 312 public void bindView(View view, Context context, Cursor cursor) { 313 ((TextView) view.findViewById(android.R.id.text1)).setText( 314 cursor.getString(cursor.getColumnIndexOrThrow( 315 BrowserContract.Bookmarks.TITLE))); 316 } 317 318 @Override 319 public View newView(Context context, Cursor cursor, ViewGroup parent) { 320 View view = LayoutInflater.from(context).inflate( 321 R.layout.folder_list_item, null); 322 view.setBackgroundDrawable(context.getResources(). 323 getDrawable(android.R.drawable.list_selector_background)); 324 return view; 325 } 326 } 327 328 protected void onCreate(Bundle icicle) { 329 super.onCreate(icicle); 330 requestWindowFeature(Window.FEATURE_LEFT_ICON); 331 332 mMap = getIntent().getExtras(); 333 334 setContentView(R.layout.browser_add_bookmark); 335 336 setTitle(R.string.bookmark_this_page); 337 Window window = getWindow(); 338 window.setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.ic_list_bookmark); 339 340 String title = null; 341 String url = null; 342 343 if (mMap != null) { 344 Bundle b = mMap.getBundle("bookmark"); 345 if (b != null) { 346 mMap = b; 347 mEditingExisting = true; 348 setTitle(R.string.edit_bookmark); 349 } else { 350 int gravity = mMap.getInt("gravity", -1); 351 if (gravity != -1) { 352 WindowManager.LayoutParams l = window.getAttributes(); 353 l.gravity = gravity; 354 window.setAttributes(l); 355 } 356 } 357 title = mMap.getString("title"); 358 url = mOriginalUrl = mMap.getString("url"); 359 mTouchIconUrl = mMap.getString("touch_icon_url"); 360 mThumbnail = (Bitmap) mMap.getParcelable("thumbnail"); 361 mCurrentFolder = mMap.getLong(BrowserContract.Bookmarks.PARENT, -1); 362 } 363 if (mCurrentFolder == -1) { 364 mCurrentFolder = getBookmarksBarId(this); 365 } 366 367 mTitle = (EditText) findViewById(R.id.title); 368 mTitle.setText(title); 369 370 mAddress = (EditText) findViewById(R.id.address); 371 mAddress.setText(url); 372 373 mButton = (TextView) findViewById(R.id.OK); 374 mButton.setOnClickListener(this); 375 376 mCancelButton = findViewById(R.id.cancel); 377 mCancelButton.setOnClickListener(this); 378 379 mFolder = (TextView) findViewById(R.id.folder); 380 mFolder.setOnClickListener(this); 381 382 mDefaultView = findViewById(R.id.default_view); 383 mFolderSelector = findViewById(R.id.folder_selector); 384 385 mFolderNamer = (EditText) findViewById(R.id.folder_namer); 386 mFolderNamer.setOnEditorActionListener(this); 387 388 mAddNewFolder = findViewById(R.id.add_new_folder); 389 mAddNewFolder.setOnClickListener(this); 390 391 mPath = (TextView) findViewById(R.id.path); 392 ListView list = (ListView) findViewById(R.id.list); 393 394 mPaths = new ArrayList<Folder>(); 395 mPaths.add(0, new Folder(getString(R.string.bookmarks), BrowserProvider2.FIXED_ID_ROOT)); 396 mAdapter = new FolderAdapter(this); 397 list.setAdapter(mAdapter); 398 list.setOnItemClickListener(this); 399 LoaderManager manager = getLoaderManager(); 400 if (mCurrentFolder != BrowserProvider2.FIXED_ID_ROOT) { 401 // Find all the folders 402 manager.initLoader(LOADER_ID_ALL_FOLDERS, null, this); 403 } 404 manager.initLoader(LOADER_ID_FOLDER_CONTENTS, null, this); 405 406 407 if (!window.getDecorView().isInTouchMode()) { 408 mButton.requestFocus(); 409 } 410 } 411 412 // FIXME: Use a CursorLoader 413 private long getBookmarksBarId(Context context) { 414 SharedPreferences prefs 415 = PreferenceManager.getDefaultSharedPreferences(context); 416 String accountName = 417 prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null); 418 String accountType = 419 prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null); 420 if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) { 421 return BrowserProvider2.FIXED_ID_ROOT; 422 } 423 Cursor cursor = null; 424 try { 425 cursor = context.getContentResolver().query( 426 BrowserContract.Bookmarks.CONTENT_URI, 427 new String[] { BrowserContract.Bookmarks._ID }, 428 BrowserContract.ChromeSyncColumns.SERVER_UNIQUE + "=? AND " 429 + BrowserContract.Bookmarks.ACCOUNT_NAME + "=? AND " 430 + BrowserContract.Bookmarks.ACCOUNT_TYPE + "=?", 431 new String[] { 432 BrowserContract.ChromeSyncColumns 433 .FOLDER_NAME_BOOKMARKS_BAR, 434 accountName, 435 accountType }, 436 null); 437 if (cursor != null && cursor.moveToFirst()) { 438 return cursor.getLong(0); 439 } 440 } finally { 441 if (cursor != null) cursor.close(); 442 } 443 return BrowserProvider2.FIXED_ID_ROOT; 444 } 445 446 @Override 447 public boolean dispatchKeyEvent(KeyEvent event) { 448 if (mFolderSelector.getVisibility() == View.VISIBLE 449 && KeyEvent.KEYCODE_BACK == event.getKeyCode()) { 450 if (KeyEvent.ACTION_UP == event.getAction()) { 451 if (mFolderNamer.getVisibility() == View.VISIBLE) { 452 mFolderNamer.setVisibility(View.GONE); 453 mAddNewFolder.setVisibility(View.VISIBLE); 454 getInputMethodManager().hideSoftInputFromWindow( 455 mFolderNamer.getWindowToken(), 0); 456 } else { 457 int size = mPaths.size(); 458 if (1 == size) { 459 // We have reached the top level 460 finish(); 461 } else { 462 // Go up a level 463 mPaths.remove(size - 1); 464 mCurrentFolder = mPaths.get(size - 2).Id; 465 updatePathString(); 466 getLoaderManager().restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this); 467 } 468 } 469 } 470 return true; 471 } 472 return super.dispatchKeyEvent(event); 473 } 474 475 /** 476 * Runnable to save a bookmark, so it can be performed in its own thread. 477 */ 478 private class SaveBookmarkRunnable implements Runnable { 479 // FIXME: This should be an async task. 480 private Message mMessage; 481 private Context mContext; 482 public SaveBookmarkRunnable(Context ctx, Message msg) { 483 mContext = ctx; 484 mMessage = msg; 485 } 486 public void run() { 487 // Unbundle bookmark data. 488 Bundle bundle = mMessage.getData(); 489 String title = bundle.getString("title"); 490 String url = bundle.getString("url"); 491 boolean invalidateThumbnail = bundle.getBoolean( 492 "invalidateThumbnail"); 493 Bitmap thumbnail = invalidateThumbnail ? null 494 : (Bitmap) bundle.getParcelable("thumbnail"); 495 String touchIconUrl = bundle.getString("touchIconUrl"); 496 497 // Save to the bookmarks DB. 498 try { 499 final ContentResolver cr = getContentResolver(); 500 Bookmarks.addBookmark(AddBookmarkPage.this, false, url, 501 title, thumbnail, true, mCurrentFolder); 502 if (touchIconUrl != null) { 503 new DownloadTouchIcon(mContext, cr, url).execute(mTouchIconUrl); 504 } 505 mMessage.arg1 = 1; 506 } catch (IllegalStateException e) { 507 mMessage.arg1 = 0; 508 } 509 mMessage.sendToTarget(); 510 } 511 } 512 513 private void createHandler() { 514 if (mHandler == null) { 515 mHandler = new Handler() { 516 @Override 517 public void handleMessage(Message msg) { 518 switch (msg.what) { 519 case SAVE_BOOKMARK: 520 if (1 == msg.arg1) { 521 Toast.makeText(AddBookmarkPage.this, R.string.bookmark_saved, 522 Toast.LENGTH_LONG).show(); 523 } else { 524 Toast.makeText(AddBookmarkPage.this, R.string.bookmark_not_saved, 525 Toast.LENGTH_LONG).show(); 526 } 527 break; 528 } 529 } 530 }; 531 } 532 } 533 534 /** 535 * Parse the data entered in the dialog and post a message to update the bookmarks database. 536 */ 537 boolean save() { 538 createHandler(); 539 540 String title = mTitle.getText().toString().trim(); 541 String unfilteredUrl; 542 unfilteredUrl = BrowserActivity.fixUrl(mAddress.getText().toString()); 543 544 boolean emptyTitle = title.length() == 0; 545 boolean emptyUrl = unfilteredUrl.trim().length() == 0; 546 Resources r = getResources(); 547 if (emptyTitle || emptyUrl) { 548 if (emptyTitle) { 549 mTitle.setError(r.getText(R.string.bookmark_needs_title)); 550 } 551 if (emptyUrl) { 552 mAddress.setError(r.getText(R.string.bookmark_needs_url)); 553 } 554 return false; 555 556 } 557 String url = unfilteredUrl.trim(); 558 try { 559 // We allow bookmarks with a javascript: scheme, but these will in most cases 560 // fail URI parsing, so don't try it if that's the kind of bookmark we have. 561 562 if (!url.toLowerCase().startsWith("javascript:")) { 563 URI uriObj = new URI(url); 564 String scheme = uriObj.getScheme(); 565 if (!Bookmarks.urlHasAcceptableScheme(url)) { 566 // If the scheme was non-null, let the user know that we 567 // can't save their bookmark. If it was null, we'll assume 568 // they meant http when we parse it in the WebAddress class. 569 if (scheme != null) { 570 mAddress.setError(r.getText(R.string.bookmark_cannot_save_url)); 571 return false; 572 } 573 WebAddress address; 574 try { 575 address = new WebAddress(unfilteredUrl); 576 } catch (ParseException e) { 577 throw new URISyntaxException("", ""); 578 } 579 if (address.mHost.length() == 0) { 580 throw new URISyntaxException("", ""); 581 } 582 url = address.toString(); 583 } 584 } 585 } catch (URISyntaxException e) { 586 mAddress.setError(r.getText(R.string.bookmark_url_not_valid)); 587 return false; 588 } 589 590 if (mEditingExisting) { 591 mMap.putString("title", title); 592 mMap.putString("url", url); 593 mMap.putBoolean("invalidateThumbnail", !url.equals(mOriginalUrl)); 594 // FIXME: This does not work yet 595 mMap.putLong(BrowserContract.Bookmarks.PARENT, mCurrentFolder); 596 setResult(RESULT_OK, (new Intent()).setAction( 597 getIntent().toString()).putExtras(mMap)); 598 } else { 599 // Post a message to write to the DB. 600 Bundle bundle = new Bundle(); 601 bundle.putString("title", title); 602 bundle.putString("url", url); 603 bundle.putParcelable("thumbnail", mThumbnail); 604 bundle.putBoolean("invalidateThumbnail", !url.equals(mOriginalUrl)); 605 bundle.putString("touchIconUrl", mTouchIconUrl); 606 Message msg = Message.obtain(mHandler, SAVE_BOOKMARK); 607 msg.setData(bundle); 608 // Start a new thread so as to not slow down the UI 609 Thread t = new Thread(new SaveBookmarkRunnable(getApplicationContext(), msg)); 610 t.start(); 611 setResult(RESULT_OK); 612 LogTag.logBookmarkAdded(url, "bookmarkview"); 613 } 614 return true; 615 } 616 617} 618