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