AddBookmarkPage.java revision ddf4a474080aa126dead6dc590caa6a7c3059eea
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.addbookmark.FolderSpinner; 20import com.android.browser.addbookmark.FolderSpinnerAdapter; 21 22import android.app.Activity; 23import android.app.LoaderManager; 24import android.app.LoaderManager.LoaderCallbacks; 25import android.content.AsyncTaskLoader; 26import android.content.ContentResolver; 27import android.content.ContentUris; 28import android.content.ContentValues; 29import android.content.Context; 30import android.content.CursorLoader; 31import android.content.Loader; 32import android.content.res.Resources; 33import android.database.Cursor; 34import android.graphics.Bitmap; 35import android.graphics.drawable.Drawable; 36import android.net.ParseException; 37import android.net.Uri; 38import android.net.WebAddress; 39import android.os.AsyncTask; 40import android.os.Bundle; 41import android.os.Handler; 42import android.os.Message; 43import android.provider.BrowserContract; 44import android.provider.BrowserContract.Accounts; 45import android.text.TextUtils; 46import android.util.AttributeSet; 47import android.view.KeyEvent; 48import android.view.LayoutInflater; 49import android.view.View; 50import android.view.ViewGroup; 51import android.view.Window; 52import android.view.WindowManager; 53import android.view.inputmethod.EditorInfo; 54import android.view.inputmethod.InputMethodManager; 55import android.widget.AdapterView; 56import android.widget.AdapterView.OnItemSelectedListener; 57import android.widget.ArrayAdapter; 58import android.widget.CursorAdapter; 59import android.widget.EditText; 60import android.widget.ListView; 61import android.widget.Spinner; 62import android.widget.TextView; 63import android.widget.Toast; 64 65import java.net.URI; 66import java.net.URISyntaxException; 67 68public class AddBookmarkPage extends Activity 69 implements View.OnClickListener, TextView.OnEditorActionListener, 70 AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor>, 71 BreadCrumbView.Controller, FolderSpinner.OnSetSelectionListener, 72 OnItemSelectedListener { 73 74 public static final long DEFAULT_FOLDER_ID = -1; 75 public static final String TOUCH_ICON_URL = "touch_icon_url"; 76 // Place on an edited bookmark to remove the saved thumbnail 77 public static final String REMOVE_THUMBNAIL = "remove_thumbnail"; 78 public static final String USER_AGENT = "user_agent"; 79 public static final String CHECK_FOR_DUPE = "check_for_dupe"; 80 81 /* package */ static final String EXTRA_EDIT_BOOKMARK = "bookmark"; 82 /* package */ static final String EXTRA_IS_FOLDER = "is_folder"; 83 84 private static final int MAX_CRUMBS_SHOWN = 2; 85 86 private final String LOGTAG = "Bookmarks"; 87 88 // IDs for the CursorLoaders that are used. 89 private final int LOADER_ID_ACCOUNTS = 0; 90 private final int LOADER_ID_FOLDER_CONTENTS = 1; 91 private final int LOADER_ID_EDIT_INFO = 2; 92 93 private EditText mTitle; 94 private EditText mAddress; 95 private TextView mButton; 96 private View mCancelButton; 97 private boolean mEditingExisting; 98 private boolean mEditingFolder; 99 private Bundle mMap; 100 private String mTouchIconUrl; 101 private String mOriginalUrl; 102 private FolderSpinner mFolder; 103 private View mDefaultView; 104 private View mFolderSelector; 105 private EditText mFolderNamer; 106 private View mFolderCancel; 107 private boolean mIsFolderNamerShowing; 108 private View mFolderNamerHolder; 109 private View mAddNewFolder; 110 private View mAddSeparator; 111 private long mCurrentFolder; 112 private FolderAdapter mAdapter; 113 private BreadCrumbView mCrumbs; 114 private TextView mFakeTitle; 115 private View mCrumbHolder; 116 private CustomListView mListView; 117 private boolean mSaveToHomeScreen; 118 private long mRootFolder; 119 private TextView mTopLevelLabel; 120 private Drawable mHeaderIcon; 121 private View mRemoveLink; 122 private View mFakeTitleHolder; 123 private FolderSpinnerAdapter mFolderAdapter; 124 private Spinner mAccountSpinner; 125 private ArrayAdapter<BookmarkAccount> mAccountAdapter; 126 127 private static class Folder { 128 String Name; 129 long Id; 130 Folder(String name, long id) { 131 Name = name; 132 Id = id; 133 } 134 } 135 136 // Message IDs 137 private static final int SAVE_BOOKMARK = 100; 138 private static final int TOUCH_ICON_DOWNLOADED = 101; 139 private static final int BOOKMARK_DELETED = 102; 140 141 private Handler mHandler; 142 143 private InputMethodManager getInputMethodManager() { 144 return (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); 145 } 146 147 private Uri getUriForFolder(long folder) { 148 return BrowserContract.Bookmarks.buildFolderUri(folder); 149 } 150 151 @Override 152 public void onTop(BreadCrumbView view, int level, Object data) { 153 if (null == data) return; 154 Folder folderData = (Folder) data; 155 long folder = folderData.Id; 156 LoaderManager manager = getLoaderManager(); 157 CursorLoader loader = (CursorLoader) ((Loader<?>) manager.getLoader( 158 LOADER_ID_FOLDER_CONTENTS)); 159 loader.setUri(getUriForFolder(folder)); 160 loader.forceLoad(); 161 if (mIsFolderNamerShowing) { 162 completeOrCancelFolderNaming(true); 163 } 164 setShowBookmarkIcon(level == 1); 165 } 166 167 /** 168 * Show or hide the icon for bookmarks next to "Bookmarks" in the crumb view. 169 * @param show True if the icon should visible, false otherwise. 170 */ 171 private void setShowBookmarkIcon(boolean show) { 172 Drawable drawable = show ? mHeaderIcon: null; 173 mTopLevelLabel.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null); 174 } 175 176 @Override 177 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 178 if (v == mFolderNamer) { 179 if (v.getText().length() > 0) { 180 if (actionId == EditorInfo.IME_NULL) { 181 // Only want to do this once. 182 if (event.getAction() == KeyEvent.ACTION_UP) { 183 completeOrCancelFolderNaming(false); 184 } 185 } 186 } 187 // Steal the key press; otherwise a newline will be added 188 return true; 189 } 190 return false; 191 } 192 193 private void switchToDefaultView(boolean changedFolder) { 194 mFolderSelector.setVisibility(View.GONE); 195 mDefaultView.setVisibility(View.VISIBLE); 196 mCrumbHolder.setVisibility(View.GONE); 197 mFakeTitleHolder.setVisibility(View.VISIBLE); 198 if (changedFolder) { 199 Object data = mCrumbs.getTopData(); 200 if (data != null) { 201 Folder folder = (Folder) data; 202 mCurrentFolder = folder.Id; 203 if (mCurrentFolder == mRootFolder) { 204 // The Spinner changed to show "Other folder ..." Change 205 // it back to "Bookmarks", which is position 0 if we are 206 // editing a folder, 1 otherwise. 207 mFolder.setSelectionIgnoringSelectionChange(mEditingFolder ? 0 : 1); 208 } else { 209 mFolderAdapter.setOtherFolderDisplayText(folder.Name); 210 } 211 } 212 } else { 213 // The user canceled selecting a folder. Revert back to the earlier 214 // selection. 215 if (mSaveToHomeScreen) { 216 mFolder.setSelectionIgnoringSelectionChange(0); 217 } else { 218 if (mCurrentFolder == mRootFolder) { 219 mFolder.setSelectionIgnoringSelectionChange(mEditingFolder ? 0 : 1); 220 } else { 221 Object data = mCrumbs.getTopData(); 222 if (data != null && ((Folder) data).Id == mCurrentFolder) { 223 // We are showing the correct folder hierarchy. The 224 // folder selector will say "Other folder..." Change it 225 // to say the name of the folder once again. 226 mFolderAdapter.setOtherFolderDisplayText(((Folder) data).Name); 227 } else { 228 // We are not showing the correct folder hierarchy. 229 // Clear the Crumbs and find the proper folder 230 setupTopCrumb(); 231 LoaderManager manager = getLoaderManager(); 232 manager.restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this); 233 234 } 235 } 236 } 237 } 238 } 239 240 @Override 241 public void onClick(View v) { 242 if (v == mButton) { 243 if (mFolderSelector.getVisibility() == View.VISIBLE) { 244 // We are showing the folder selector. 245 if (mIsFolderNamerShowing) { 246 completeOrCancelFolderNaming(false); 247 } else { 248 // User has selected a folder. Go back to the opening page 249 mSaveToHomeScreen = false; 250 switchToDefaultView(true); 251 } 252 } else if (save()) { 253 finish(); 254 } 255 } else if (v == mCancelButton) { 256 if (mIsFolderNamerShowing) { 257 completeOrCancelFolderNaming(true); 258 } else if (mFolderSelector.getVisibility() == View.VISIBLE) { 259 switchToDefaultView(false); 260 } else { 261 finish(); 262 } 263 } else if (v == mFolderCancel) { 264 completeOrCancelFolderNaming(true); 265 } else if (v == mAddNewFolder) { 266 setShowFolderNamer(true); 267 mFolderNamer.setText(R.string.new_folder); 268 mFolderNamer.requestFocus(); 269 mAddNewFolder.setVisibility(View.GONE); 270 mAddSeparator.setVisibility(View.GONE); 271 InputMethodManager imm = getInputMethodManager(); 272 // Set the InputMethodManager to focus on the ListView so that it 273 // can transfer the focus to mFolderNamer. 274 imm.focusIn(mListView); 275 imm.showSoftInput(mFolderNamer, InputMethodManager.SHOW_IMPLICIT); 276 } else if (v == mRemoveLink) { 277 if (!mEditingExisting) { 278 throw new AssertionError("Remove button should not be shown for" 279 + " new bookmarks"); 280 } 281 long id = mMap.getLong(BrowserContract.Bookmarks._ID); 282 createHandler(); 283 Message msg = Message.obtain(mHandler, BOOKMARK_DELETED); 284 BookmarkUtils.displayRemoveBookmarkDialog(id, 285 mTitle.getText().toString(), this, msg); 286 } 287 } 288 289 // FolderSpinner.OnSetSelectionListener 290 291 @Override 292 public void onSetSelection(long id) { 293 int intId = (int) id; 294 switch (intId) { 295 case FolderSpinnerAdapter.ROOT_FOLDER: 296 mCurrentFolder = mRootFolder; 297 mSaveToHomeScreen = false; 298 break; 299 case FolderSpinnerAdapter.HOME_SCREEN: 300 // Create a short cut to the home screen 301 mSaveToHomeScreen = true; 302 break; 303 case FolderSpinnerAdapter.OTHER_FOLDER: 304 switchToFolderSelector(); 305 break; 306 case FolderSpinnerAdapter.RECENT_FOLDER: 307 mCurrentFolder = mFolderAdapter.recentFolderId(); 308 mSaveToHomeScreen = false; 309 // In case the user decides to select OTHER_FOLDER 310 // and choose a different one, so that we will start from 311 // the correct place. 312 LoaderManager manager = getLoaderManager(); 313 manager.restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this); 314 break; 315 default: 316 break; 317 } 318 } 319 320 /** 321 * Finish naming a folder, and close the IME 322 * @param cancel If true, the new folder is not created. If false, the new 323 * folder is created and the user is taken inside it. 324 */ 325 private void completeOrCancelFolderNaming(boolean cancel) { 326 if (!cancel && !TextUtils.isEmpty(mFolderNamer.getText())) { 327 String name = mFolderNamer.getText().toString(); 328 long id = addFolderToCurrent(mFolderNamer.getText().toString()); 329 descendInto(name, id); 330 } 331 setShowFolderNamer(false); 332 mAddNewFolder.setVisibility(View.VISIBLE); 333 mAddSeparator.setVisibility(View.VISIBLE); 334 getInputMethodManager().hideSoftInputFromWindow( 335 mListView.getWindowToken(), 0); 336 } 337 338 private long addFolderToCurrent(String name) { 339 // Add the folder to the database 340 ContentValues values = new ContentValues(); 341 values.put(BrowserContract.Bookmarks.TITLE, 342 name); 343 values.put(BrowserContract.Bookmarks.IS_FOLDER, 1); 344 String[] accountInfo = getAccountNameAndType(); 345 if (accountInfo != null) { 346 values.put(BrowserContract.Bookmarks.ACCOUNT_TYPE, accountInfo[1]); 347 values.put(BrowserContract.Bookmarks.ACCOUNT_NAME, accountInfo[0]); 348 } 349 long currentFolder; 350 Object data = mCrumbs.getTopData(); 351 if (data != null) { 352 currentFolder = ((Folder) data).Id; 353 } else { 354 currentFolder = mRootFolder; 355 } 356 values.put(BrowserContract.Bookmarks.PARENT, currentFolder); 357 Uri uri = getContentResolver().insert( 358 BrowserContract.Bookmarks.CONTENT_URI, values); 359 if (uri != null) { 360 return ContentUris.parseId(uri); 361 } else { 362 return -1; 363 } 364 } 365 366 private void switchToFolderSelector() { 367 // Set the list to the top in case it is scrolled. 368 mListView.setSelection(0); 369 mDefaultView.setVisibility(View.GONE); 370 mFolderSelector.setVisibility(View.VISIBLE); 371 mCrumbHolder.setVisibility(View.VISIBLE); 372 mFakeTitleHolder.setVisibility(View.GONE); 373 mAddNewFolder.setVisibility(View.VISIBLE); 374 mAddSeparator.setVisibility(View.VISIBLE); 375 } 376 377 private void descendInto(String foldername, long id) { 378 if (id != DEFAULT_FOLDER_ID) { 379 mCrumbs.pushView(foldername, new Folder(foldername, id)); 380 mCrumbs.notifyController(); 381 } 382 } 383 384 private LoaderCallbacks<EditBookmarkInfo> mEditInfoLoaderCallbacks = 385 new LoaderCallbacks<EditBookmarkInfo>() { 386 387 @Override 388 public void onLoaderReset(Loader<EditBookmarkInfo> loader) { 389 // Don't care 390 } 391 392 @Override 393 public void onLoadFinished(Loader<EditBookmarkInfo> loader, 394 EditBookmarkInfo info) { 395 boolean setAccount = false; 396 if (info.id != -1) { 397 mEditingExisting = true; 398 showRemoveButton(); 399 mFakeTitle.setText(R.string.edit_bookmark); 400 mTitle.setText(info.title); 401 mFolderAdapter.setOtherFolderDisplayText(info.parentTitle); 402 mMap.putLong(BrowserContract.Bookmarks._ID, info.id); 403 setAccount = true; 404 setAccount(info.accountName, info.accountType); 405 mCurrentFolder = info.parentId; 406 onCurrentFolderFound(); 407 } 408 if (info.lastUsedId != -1 && info.lastUsedId != info.id) { 409 if (setAccount && info.lastUsedId != mRootFolder 410 && TextUtils.equals(info.lastUsedAccountName, info.accountName) 411 && TextUtils.equals(info.lastUsedAccountType, info.accountType)) { 412 mFolderAdapter.addRecentFolder(info.lastUsedId, info.lastUsedTitle); 413 } else if (!setAccount) { 414 setAccount = true; 415 setAccount(info.lastUsedAccountName, info.lastUsedAccountType); 416 if (info.lastUsedId != mRootFolder) { 417 mFolderAdapter.addRecentFolder(info.lastUsedId, 418 info.lastUsedTitle); 419 } 420 } 421 } 422 if (!setAccount) { 423 mAccountSpinner.setSelection(0); 424 } 425 } 426 427 @Override 428 public Loader<EditBookmarkInfo> onCreateLoader(int id, Bundle args) { 429 return new EditBookmarkInfoLoader(AddBookmarkPage.this, mMap); 430 } 431 }; 432 433 void setAccount(String accountName, String accountType) { 434 for (int i = 0; i < mAccountAdapter.getCount(); i++) { 435 BookmarkAccount account = mAccountAdapter.getItem(i); 436 if (TextUtils.equals(account.accountName, accountName) 437 && TextUtils.equals(account.accountType, accountType)) { 438 onRootFolderFound(account.rootFolderId); 439 mAccountSpinner.setSelection(i); 440 return; 441 } 442 } 443 } 444 445 @Override 446 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 447 String[] projection; 448 switch (id) { 449 case LOADER_ID_ACCOUNTS: 450 return new AccountsLoader(this); 451 case LOADER_ID_FOLDER_CONTENTS: 452 projection = new String[] { 453 BrowserContract.Bookmarks._ID, 454 BrowserContract.Bookmarks.TITLE, 455 BrowserContract.Bookmarks.IS_FOLDER 456 }; 457 String where = BrowserContract.Bookmarks.IS_FOLDER + " != 0"; 458 if (mEditingFolder) { 459 where += " AND " + BrowserContract.Bookmarks._ID + " != " 460 + mMap.getLong(BrowserContract.Bookmarks._ID); 461 } 462 return new CursorLoader(this, 463 getUriForFolder(mCurrentFolder), 464 projection, 465 where, 466 null, 467 BrowserContract.Bookmarks._ID + " ASC"); 468 default: 469 throw new AssertionError("Asking for nonexistant loader!"); 470 } 471 } 472 473 @Override 474 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { 475 switch (loader.getId()) { 476 case LOADER_ID_ACCOUNTS: 477 mAccountAdapter.clear(); 478 while (cursor.moveToNext()) { 479 mAccountAdapter.add(new BookmarkAccount(this, cursor)); 480 } 481 getLoaderManager().destroyLoader(LOADER_ID_ACCOUNTS); 482 getLoaderManager().restartLoader(LOADER_ID_EDIT_INFO, null, 483 mEditInfoLoaderCallbacks); 484 break; 485 case LOADER_ID_FOLDER_CONTENTS: 486 mAdapter.changeCursor(cursor); 487 break; 488 } 489 } 490 491 public void onLoaderReset(Loader<Cursor> loader) { 492 switch (loader.getId()) { 493 case LOADER_ID_FOLDER_CONTENTS: 494 mAdapter.changeCursor(null); 495 break; 496 } 497 } 498 499 /** 500 * Move cursor to the position that has folderToFind as its "_id". 501 * @param cursor Cursor containing folders in the bookmarks database 502 * @param folderToFind "_id" of the folder to move to. 503 * @param idIndex Index in cursor of "_id" 504 * @throws AssertionError if cursor is empty or there is no row with folderToFind 505 * as its "_id". 506 */ 507 void moveCursorToFolder(Cursor cursor, long folderToFind, int idIndex) 508 throws AssertionError { 509 if (!cursor.moveToFirst()) { 510 throw new AssertionError("No folders in the database!"); 511 } 512 long folder; 513 do { 514 folder = cursor.getLong(idIndex); 515 } while (folder != folderToFind && cursor.moveToNext()); 516 if (cursor.isAfterLast()) { 517 throw new AssertionError("Folder(id=" + folderToFind 518 + ") holding this bookmark does not exist!"); 519 } 520 } 521 522 @Override 523 public void onItemClick(AdapterView<?> parent, View view, int position, 524 long id) { 525 TextView tv = (TextView) view.findViewById(android.R.id.text1); 526 // Switch to the folder that was clicked on. 527 descendInto(tv.getText().toString(), id); 528 } 529 530 private void setShowFolderNamer(boolean show) { 531 if (show != mIsFolderNamerShowing) { 532 mIsFolderNamerShowing = show; 533 if (show) { 534 // Set the selection to the folder namer so it will be in 535 // view. 536 mListView.addFooterView(mFolderNamerHolder); 537 } else { 538 mListView.removeFooterView(mFolderNamerHolder); 539 } 540 // Refresh the list. 541 mListView.setAdapter(mAdapter); 542 if (show) { 543 mListView.setSelection(mListView.getCount() - 1); 544 } 545 } 546 } 547 548 /** 549 * Shows a list of names of folders. 550 */ 551 private class FolderAdapter extends CursorAdapter { 552 public FolderAdapter(Context context) { 553 super(context, null); 554 } 555 556 @Override 557 public void bindView(View view, Context context, Cursor cursor) { 558 ((TextView) view.findViewById(android.R.id.text1)).setText( 559 cursor.getString(cursor.getColumnIndexOrThrow( 560 BrowserContract.Bookmarks.TITLE))); 561 } 562 563 @Override 564 public View newView(Context context, Cursor cursor, ViewGroup parent) { 565 View view = LayoutInflater.from(context).inflate( 566 R.layout.folder_list_item, null); 567 view.setBackgroundDrawable(context.getResources(). 568 getDrawable(android.R.drawable.list_selector_background)); 569 return view; 570 } 571 572 @Override 573 public boolean isEmpty() { 574 // Do not show the empty view if the user is creating a new folder. 575 return super.isEmpty() && !mIsFolderNamerShowing; 576 } 577 } 578 579 @Override 580 protected void onCreate(Bundle icicle) { 581 super.onCreate(icicle); 582 requestWindowFeature(Window.FEATURE_NO_TITLE); 583 584 mMap = getIntent().getExtras(); 585 586 setContentView(R.layout.browser_add_bookmark); 587 588 Window window = getWindow(); 589 590 String title = null; 591 String url = null; 592 593 mFakeTitle = (TextView) findViewById(R.id.fake_title); 594 595 if (mMap != null) { 596 Bundle b = mMap.getBundle(EXTRA_EDIT_BOOKMARK); 597 if (b != null) { 598 mEditingFolder = mMap.getBoolean(EXTRA_IS_FOLDER, false); 599 mMap = b; 600 mEditingExisting = true; 601 mFakeTitle.setText(R.string.edit_bookmark); 602 if (mEditingFolder) { 603 findViewById(R.id.row_address).setVisibility(View.GONE); 604 } else { 605 showRemoveButton(); 606 } 607 } else { 608 int gravity = mMap.getInt("gravity", -1); 609 if (gravity != -1) { 610 WindowManager.LayoutParams l = window.getAttributes(); 611 l.gravity = gravity; 612 window.setAttributes(l); 613 } 614 } 615 title = mMap.getString(BrowserContract.Bookmarks.TITLE); 616 url = mOriginalUrl = mMap.getString(BrowserContract.Bookmarks.URL); 617 mTouchIconUrl = mMap.getString(TOUCH_ICON_URL); 618 mCurrentFolder = mMap.getLong(BrowserContract.Bookmarks.PARENT, DEFAULT_FOLDER_ID); 619 } 620 621 mTitle = (EditText) findViewById(R.id.title); 622 mTitle.setText(title); 623 624 mAddress = (EditText) findViewById(R.id.address); 625 mAddress.setText(url); 626 627 mButton = (TextView) findViewById(R.id.OK); 628 mButton.setOnClickListener(this); 629 630 mCancelButton = findViewById(R.id.cancel); 631 mCancelButton.setOnClickListener(this); 632 633 mFolder = (FolderSpinner) findViewById(R.id.folder); 634 mFolderAdapter = new FolderSpinnerAdapter(this, !mEditingFolder); 635 mFolder.setAdapter(mFolderAdapter); 636 mFolder.setOnSetSelectionListener(this); 637 638 mDefaultView = findViewById(R.id.default_view); 639 mFolderSelector = findViewById(R.id.folder_selector); 640 641 mFolderNamerHolder = getLayoutInflater().inflate(R.layout.new_folder_layout, null); 642 mFolderNamer = (EditText) mFolderNamerHolder.findViewById(R.id.folder_namer); 643 mFolderNamer.setOnEditorActionListener(this); 644 mFolderCancel = mFolderNamerHolder.findViewById(R.id.close); 645 mFolderCancel.setOnClickListener(this); 646 647 mAddNewFolder = findViewById(R.id.add_new_folder); 648 mAddNewFolder.setOnClickListener(this); 649 mAddSeparator = findViewById(R.id.add_divider); 650 651 mCrumbs = (BreadCrumbView) findViewById(R.id.crumbs); 652 mCrumbs.setUseBackButton(true); 653 mCrumbs.setController(this); 654 mHeaderIcon = getResources().getDrawable(R.drawable.ic_folder_holo_dark); 655 mCrumbHolder = findViewById(R.id.crumb_holder); 656 mCrumbs.setMaxVisible(MAX_CRUMBS_SHOWN); 657 658 mAdapter = new FolderAdapter(this); 659 mListView = (CustomListView) findViewById(R.id.list); 660 View empty = findViewById(R.id.empty); 661 mListView.setEmptyView(empty); 662 mListView.setAdapter(mAdapter); 663 mListView.setOnItemClickListener(this); 664 mListView.addEditText(mFolderNamer); 665 666 mAccountAdapter = new ArrayAdapter<BookmarkAccount>(this, 667 android.R.layout.simple_spinner_item); 668 mAccountAdapter.setDropDownViewResource( 669 android.R.layout.simple_spinner_dropdown_item); 670 mAccountSpinner = (Spinner) findViewById(R.id.accounts); 671 mAccountSpinner.setAdapter(mAccountAdapter); 672 mAccountSpinner.setOnItemSelectedListener(this); 673 674 675 mFakeTitleHolder = findViewById(R.id.title_holder); 676 677 if (!window.getDecorView().isInTouchMode()) { 678 mButton.requestFocus(); 679 } 680 681 getLoaderManager().restartLoader(LOADER_ID_ACCOUNTS, null, this); 682 } 683 684 private void showRemoveButton() { 685 findViewById(R.id.remove_divider).setVisibility(View.VISIBLE); 686 mRemoveLink = findViewById(R.id.remove); 687 mRemoveLink.setVisibility(View.VISIBLE); 688 mRemoveLink.setOnClickListener(this); 689 } 690 691 // Called once we have determined which folder is the root folder 692 private void onRootFolderFound(long root) { 693 mRootFolder = root; 694 mCurrentFolder = mRootFolder; 695 setupTopCrumb(); 696 onCurrentFolderFound(); 697 } 698 699 private void setupTopCrumb() { 700 mCrumbs.clear(); 701 String name = getString(R.string.bookmarks); 702 mTopLevelLabel = (TextView) mCrumbs.pushView(name, false, 703 new Folder(name, mRootFolder)); 704 // To better match the other folders. 705 mTopLevelLabel.setCompoundDrawablePadding(6); 706 } 707 708 private void onCurrentFolderFound() { 709 LoaderManager manager = getLoaderManager(); 710 if (mCurrentFolder != mRootFolder) { 711 // Since we're not in the root folder, change the selection to other 712 // folder now. The text will get changed once we select the correct 713 // folder. 714 mFolder.setSelectionIgnoringSelectionChange(mEditingFolder ? 1 : 2); 715 } else { 716 setShowBookmarkIcon(true); 717 if (!mEditingFolder) { 718 // Initially the "Bookmarks" folder should be showing, rather than 719 // the home screen. In the editing folder case, home screen is not 720 // an option, so "Bookmarks" folder is already at the top. 721 mFolder.setSelectionIgnoringSelectionChange(FolderSpinnerAdapter.ROOT_FOLDER); 722 } 723 } 724 // Find the contents of the current folder 725 manager.restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this); 726} 727 /** 728 * Get the account name and type of the currently synced account. 729 * @return null if no account name or type. Otherwise, the result will be 730 * an array of two Strings, the accountName and accountType, respectively. 731 */ 732 private String[] getAccountNameAndType() { 733 BookmarkAccount account = (BookmarkAccount) mAccountSpinner.getSelectedItem(); 734 if (account == null) { 735 return null; 736 } 737 return new String[] { account.accountName, account.accountType }; 738 } 739 740 /** 741 * Runnable to save a bookmark, so it can be performed in its own thread. 742 */ 743 private class SaveBookmarkRunnable implements Runnable { 744 // FIXME: This should be an async task. 745 private Message mMessage; 746 private Context mContext; 747 public SaveBookmarkRunnable(Context ctx, Message msg) { 748 mContext = ctx; 749 mMessage = msg; 750 } 751 public void run() { 752 // Unbundle bookmark data. 753 Bundle bundle = mMessage.getData(); 754 String title = bundle.getString(BrowserContract.Bookmarks.TITLE); 755 String url = bundle.getString(BrowserContract.Bookmarks.URL); 756 boolean invalidateThumbnail = bundle.getBoolean(REMOVE_THUMBNAIL); 757 Bitmap thumbnail = invalidateThumbnail ? null 758 : (Bitmap) bundle.getParcelable(BrowserContract.Bookmarks.THUMBNAIL); 759 String touchIconUrl = bundle.getString(TOUCH_ICON_URL); 760 761 // Save to the bookmarks DB. 762 try { 763 final ContentResolver cr = getContentResolver(); 764 Bookmarks.addBookmark(AddBookmarkPage.this, false, url, 765 title, thumbnail, true, mCurrentFolder); 766 if (touchIconUrl != null) { 767 new DownloadTouchIcon(mContext, cr, url).execute(mTouchIconUrl); 768 } 769 mMessage.arg1 = 1; 770 } catch (IllegalStateException e) { 771 mMessage.arg1 = 0; 772 } 773 mMessage.sendToTarget(); 774 } 775 } 776 777 private static class UpdateBookmarkTask extends AsyncTask<ContentValues, Void, Void> { 778 Context mContext; 779 Long mId; 780 781 public UpdateBookmarkTask(Context context, long id) { 782 mContext = context; 783 mId = id; 784 } 785 786 @Override 787 protected Void doInBackground(ContentValues... params) { 788 if (params.length != 1) { 789 throw new IllegalArgumentException("No ContentValues provided!"); 790 } 791 Uri uri = ContentUris.withAppendedId(BookmarkUtils.getBookmarksUri(mContext), mId); 792 mContext.getContentResolver().update( 793 uri, 794 params[0], null, null); 795 return null; 796 } 797 } 798 799 private void createHandler() { 800 if (mHandler == null) { 801 mHandler = new Handler() { 802 @Override 803 public void handleMessage(Message msg) { 804 switch (msg.what) { 805 case SAVE_BOOKMARK: 806 if (1 == msg.arg1) { 807 Toast.makeText(AddBookmarkPage.this, R.string.bookmark_saved, 808 Toast.LENGTH_LONG).show(); 809 } else { 810 Toast.makeText(AddBookmarkPage.this, R.string.bookmark_not_saved, 811 Toast.LENGTH_LONG).show(); 812 } 813 break; 814 case TOUCH_ICON_DOWNLOADED: 815 Bundle b = msg.getData(); 816 sendBroadcast(BookmarkUtils.createAddToHomeIntent( 817 AddBookmarkPage.this, 818 b.getString(BrowserContract.Bookmarks.URL), 819 b.getString(BrowserContract.Bookmarks.TITLE), 820 (Bitmap) b.getParcelable(BrowserContract.Bookmarks.TOUCH_ICON), 821 (Bitmap) b.getParcelable(BrowserContract.Bookmarks.FAVICON))); 822 break; 823 case BOOKMARK_DELETED: 824 finish(); 825 break; 826 } 827 } 828 }; 829 } 830 } 831 832 /** 833 * Parse the data entered in the dialog and post a message to update the bookmarks database. 834 */ 835 boolean save() { 836 createHandler(); 837 838 String title = mTitle.getText().toString().trim(); 839 String unfilteredUrl; 840 unfilteredUrl = UrlUtils.fixUrl(mAddress.getText().toString()); 841 842 boolean emptyTitle = title.length() == 0; 843 boolean emptyUrl = unfilteredUrl.trim().length() == 0; 844 Resources r = getResources(); 845 if (emptyTitle || (emptyUrl && !mEditingFolder)) { 846 if (emptyTitle) { 847 mTitle.setError(r.getText(R.string.bookmark_needs_title)); 848 } 849 if (emptyUrl) { 850 mAddress.setError(r.getText(R.string.bookmark_needs_url)); 851 } 852 return false; 853 854 } 855 String url = unfilteredUrl.trim(); 856 if (!mEditingFolder) { 857 try { 858 // We allow bookmarks with a javascript: scheme, but these will in most cases 859 // fail URI parsing, so don't try it if that's the kind of bookmark we have. 860 861 if (!url.toLowerCase().startsWith("javascript:")) { 862 URI uriObj = new URI(url); 863 String scheme = uriObj.getScheme(); 864 if (!Bookmarks.urlHasAcceptableScheme(url)) { 865 // If the scheme was non-null, let the user know that we 866 // can't save their bookmark. If it was null, we'll assume 867 // they meant http when we parse it in the WebAddress class. 868 if (scheme != null) { 869 mAddress.setError(r.getText(R.string.bookmark_cannot_save_url)); 870 return false; 871 } 872 WebAddress address; 873 try { 874 address = new WebAddress(unfilteredUrl); 875 } catch (ParseException e) { 876 throw new URISyntaxException("", ""); 877 } 878 if (address.getHost().length() == 0) { 879 throw new URISyntaxException("", ""); 880 } 881 url = address.toString(); 882 } 883 } 884 } catch (URISyntaxException e) { 885 mAddress.setError(r.getText(R.string.bookmark_url_not_valid)); 886 return false; 887 } 888 } 889 890 if (mSaveToHomeScreen) { 891 mEditingExisting = false; 892 } 893 894 boolean urlUnmodified = url.equals(mOriginalUrl); 895 896 if (mEditingExisting) { 897 Long id = mMap.getLong(BrowserContract.Bookmarks._ID); 898 ContentValues values = new ContentValues(); 899 values.put(BrowserContract.Bookmarks.TITLE, title); 900 values.put(BrowserContract.Bookmarks.PARENT, mCurrentFolder); 901 if (!mEditingFolder) { 902 values.put(BrowserContract.Bookmarks.URL, url); 903 if (!urlUnmodified) { 904 values.putNull(BrowserContract.Bookmarks.THUMBNAIL); 905 } 906 } 907 if (values.size() > 0) { 908 new UpdateBookmarkTask(getApplicationContext(), id).execute(values); 909 } 910 setResult(RESULT_OK); 911 } else { 912 Bitmap thumbnail; 913 Bitmap favicon; 914 if (urlUnmodified) { 915 thumbnail = (Bitmap) mMap.getParcelable( 916 BrowserContract.Bookmarks.THUMBNAIL); 917 favicon = (Bitmap) mMap.getParcelable( 918 BrowserContract.Bookmarks.FAVICON); 919 } else { 920 thumbnail = null; 921 favicon = null; 922 } 923 924 Bundle bundle = new Bundle(); 925 bundle.putString(BrowserContract.Bookmarks.TITLE, title); 926 bundle.putString(BrowserContract.Bookmarks.URL, url); 927 bundle.putParcelable(BrowserContract.Bookmarks.FAVICON, favicon); 928 929 if (mSaveToHomeScreen) { 930 if (mTouchIconUrl != null && urlUnmodified) { 931 Message msg = Message.obtain(mHandler, 932 TOUCH_ICON_DOWNLOADED); 933 msg.setData(bundle); 934 DownloadTouchIcon icon = new DownloadTouchIcon(this, msg, 935 mMap.getString(USER_AGENT)); 936 icon.execute(mTouchIconUrl); 937 } else { 938 sendBroadcast(BookmarkUtils.createAddToHomeIntent(this, url, 939 title, null /*touchIcon*/, favicon)); 940 } 941 } else { 942 bundle.putParcelable(BrowserContract.Bookmarks.THUMBNAIL, thumbnail); 943 bundle.putBoolean(REMOVE_THUMBNAIL, !urlUnmodified); 944 bundle.putString(TOUCH_ICON_URL, mTouchIconUrl); 945 // Post a message to write to the DB. 946 Message msg = Message.obtain(mHandler, SAVE_BOOKMARK); 947 msg.setData(bundle); 948 // Start a new thread so as to not slow down the UI 949 Thread t = new Thread(new SaveBookmarkRunnable(getApplicationContext(), msg)); 950 t.start(); 951 } 952 setResult(RESULT_OK); 953 LogTag.logBookmarkAdded(url, "bookmarkview"); 954 } 955 return true; 956 } 957 958 @Override 959 public void onItemSelected(AdapterView<?> parent, View view, int position, 960 long id) { 961 if (mAccountSpinner == parent) { 962 long root = mAccountAdapter.getItem(position).rootFolderId; 963 if (root != mRootFolder) { 964 onRootFolderFound(root); 965 } 966 } 967 } 968 969 @Override 970 public void onNothingSelected(AdapterView<?> parent) { 971 // Don't care 972 } 973 974 /* 975 * Class used as a proxy for the InputMethodManager to get to mFolderNamer 976 */ 977 public static class CustomListView extends ListView { 978 private EditText mEditText; 979 980 public void addEditText(EditText editText) { 981 mEditText = editText; 982 } 983 984 public CustomListView(Context context) { 985 super(context); 986 } 987 988 public CustomListView(Context context, AttributeSet attrs) { 989 super(context, attrs); 990 } 991 992 public CustomListView(Context context, AttributeSet attrs, int defStyle) { 993 super(context, attrs, defStyle); 994 } 995 996 @Override 997 public boolean checkInputConnectionProxy(View view) { 998 return view == mEditText; 999 } 1000 } 1001 1002 static class AccountsLoader extends CursorLoader { 1003 1004 static final String[] PROJECTION = new String[] { 1005 Accounts.ACCOUNT_NAME, 1006 Accounts.ACCOUNT_TYPE, 1007 Accounts.ROOT_ID, 1008 }; 1009 1010 static final int COLUMN_INDEX_ACCOUNT_NAME = 0; 1011 static final int COLUMN_INDEX_ACCOUNT_TYPE = 1; 1012 static final int COLUMN_INDEX_ROOT_ID = 2; 1013 1014 public AccountsLoader(Context context) { 1015 super(context, Accounts.CONTENT_URI, PROJECTION, null, null, 1016 Accounts.ACCOUNT_NAME + " ASC"); 1017 } 1018 1019 } 1020 1021 static class BookmarkAccount { 1022 1023 private String mLabel; 1024 String accountName, accountType; 1025 long rootFolderId; 1026 1027 public BookmarkAccount(Context context, Cursor cursor) { 1028 accountName = cursor.getString( 1029 AccountsLoader.COLUMN_INDEX_ACCOUNT_NAME); 1030 accountType = cursor.getString( 1031 AccountsLoader.COLUMN_INDEX_ACCOUNT_TYPE); 1032 rootFolderId = cursor.getLong( 1033 AccountsLoader.COLUMN_INDEX_ROOT_ID); 1034 mLabel = accountName; 1035 if (TextUtils.isEmpty(mLabel)) { 1036 mLabel = context.getString(R.string.local_bookmarks); 1037 } 1038 } 1039 1040 @Override 1041 public String toString() { 1042 return mLabel; 1043 } 1044 } 1045 1046 static class EditBookmarkInfo { 1047 long id = -1; 1048 long parentId = -1; 1049 String parentTitle; 1050 String title; 1051 String accountName; 1052 String accountType; 1053 1054 long lastUsedId = -1; 1055 String lastUsedTitle; 1056 String lastUsedAccountName; 1057 String lastUsedAccountType; 1058 } 1059 1060 static class EditBookmarkInfoLoader extends AsyncTaskLoader<EditBookmarkInfo> { 1061 1062 private Context mContext; 1063 private Bundle mMap; 1064 1065 public EditBookmarkInfoLoader(Context context, Bundle bundle) { 1066 super(context); 1067 mContext = context; 1068 mMap = bundle; 1069 } 1070 1071 @Override 1072 public EditBookmarkInfo loadInBackground() { 1073 final ContentResolver cr = mContext.getContentResolver(); 1074 EditBookmarkInfo info = new EditBookmarkInfo(); 1075 Cursor c = null; 1076 1077 try { 1078 // First, let's lookup the bookmark (check for dupes, get needed info) 1079 String url = mMap.getString(BrowserContract.Bookmarks.URL); 1080 info.id = mMap.getLong(BrowserContract.Bookmarks._ID, -1); 1081 boolean checkForDupe = mMap.getBoolean(CHECK_FOR_DUPE); 1082 if (checkForDupe && info.id == -1 && !TextUtils.isEmpty(url)) { 1083 c = cr.query(BrowserContract.Bookmarks.CONTENT_URI, 1084 new String[] { BrowserContract.Bookmarks._ID}, 1085 BrowserContract.Bookmarks.URL + "=?", 1086 new String[] { url }, null); 1087 if (c.getCount() == 1 && c.moveToFirst()) { 1088 info.id = c.getLong(0); 1089 } 1090 c.close(); 1091 } 1092 if (info.id != -1) { 1093 c = cr.query(ContentUris.withAppendedId( 1094 BrowserContract.Bookmarks.CONTENT_URI, info.id), 1095 new String[] { 1096 BrowserContract.Bookmarks.PARENT, 1097 BrowserContract.Bookmarks.ACCOUNT_NAME, 1098 BrowserContract.Bookmarks.ACCOUNT_TYPE, 1099 BrowserContract.Bookmarks.TITLE}, 1100 null, null, null); 1101 if (c.moveToFirst()) { 1102 info.parentId = c.getLong(0); 1103 info.accountName = c.getString(1); 1104 info.accountType = c.getString(2); 1105 info.title = c.getString(3); 1106 } 1107 c.close(); 1108 c = cr.query(ContentUris.withAppendedId( 1109 BrowserContract.Bookmarks.CONTENT_URI, info.parentId), 1110 new String[] { 1111 BrowserContract.Bookmarks.TITLE,}, 1112 null, null, null); 1113 if (c.moveToFirst()) { 1114 info.parentTitle = c.getString(0); 1115 } 1116 c.close(); 1117 } 1118 1119 // Figure out the last used folder/account 1120 c = cr.query(BrowserContract.Bookmarks.CONTENT_URI, 1121 new String[] { 1122 BrowserContract.Bookmarks.PARENT, 1123 }, null, null, 1124 BrowserContract.Bookmarks.DATE_MODIFIED + " DESC LIMIT 1"); 1125 if (c.moveToFirst()) { 1126 long parent = c.getLong(0); 1127 c.close(); 1128 c = cr.query(BrowserContract.Bookmarks.CONTENT_URI, 1129 new String[] { 1130 BrowserContract.Bookmarks.TITLE, 1131 BrowserContract.Bookmarks.ACCOUNT_NAME, 1132 BrowserContract.Bookmarks.ACCOUNT_TYPE}, 1133 BrowserContract.Bookmarks._ID + "=?", new String[] { 1134 Long.toString(parent)}, null); 1135 if (c.moveToFirst()) { 1136 info.lastUsedId = parent; 1137 info.lastUsedTitle = c.getString(0); 1138 info.lastUsedAccountName = c.getString(1); 1139 info.lastUsedAccountType = c.getString(2); 1140 } 1141 c.close(); 1142 } 1143 } finally { 1144 if (c != null) { 1145 c.close(); 1146 } 1147 } 1148 1149 return info; 1150 } 1151 1152 @Override 1153 protected void onStartLoading() { 1154 forceLoad(); 1155 } 1156 1157 } 1158 1159} 1160