Home | History | Annotate | Download | only in browser
      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 
     17 package com.android.browser;
     18 
     19 import com.android.browser.addbookmark.FolderSpinner;
     20 import com.android.browser.addbookmark.FolderSpinnerAdapter;
     21 
     22 import android.app.Activity;
     23 import android.app.LoaderManager;
     24 import android.app.LoaderManager.LoaderCallbacks;
     25 import android.content.AsyncTaskLoader;
     26 import android.content.ContentResolver;
     27 import android.content.ContentUris;
     28 import android.content.ContentValues;
     29 import android.content.Context;
     30 import android.content.CursorLoader;
     31 import android.content.Loader;
     32 import android.content.res.Resources;
     33 import android.database.Cursor;
     34 import android.graphics.Bitmap;
     35 import android.graphics.drawable.Drawable;
     36 import android.net.ParseException;
     37 import android.net.Uri;
     38 import android.net.WebAddress;
     39 import android.os.AsyncTask;
     40 import android.os.Bundle;
     41 import android.os.Handler;
     42 import android.os.Message;
     43 import android.provider.BrowserContract;
     44 import android.provider.BrowserContract.Accounts;
     45 import android.text.TextUtils;
     46 import android.util.AttributeSet;
     47 import android.view.KeyEvent;
     48 import android.view.LayoutInflater;
     49 import android.view.View;
     50 import android.view.ViewGroup;
     51 import android.view.Window;
     52 import android.view.WindowManager;
     53 import android.view.inputmethod.EditorInfo;
     54 import android.view.inputmethod.InputMethodManager;
     55 import android.widget.AdapterView;
     56 import android.widget.AdapterView.OnItemSelectedListener;
     57 import android.widget.ArrayAdapter;
     58 import android.widget.CursorAdapter;
     59 import android.widget.EditText;
     60 import android.widget.ListView;
     61 import android.widget.Spinner;
     62 import android.widget.TextView;
     63 import android.widget.Toast;
     64 
     65 import java.net.URI;
     66 import java.net.URISyntaxException;
     67 
     68 public 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         long currentFolder;
    345         Object data = mCrumbs.getTopData();
    346         if (data != null) {
    347             currentFolder = ((Folder) data).Id;
    348         } else {
    349             currentFolder = mRootFolder;
    350         }
    351         values.put(BrowserContract.Bookmarks.PARENT, currentFolder);
    352         Uri uri = getContentResolver().insert(
    353                 BrowserContract.Bookmarks.CONTENT_URI, values);
    354         if (uri != null) {
    355             return ContentUris.parseId(uri);
    356         } else {
    357             return -1;
    358         }
    359     }
    360 
    361     private void switchToFolderSelector() {
    362         // Set the list to the top in case it is scrolled.
    363         mListView.setSelection(0);
    364         mDefaultView.setVisibility(View.GONE);
    365         mFolderSelector.setVisibility(View.VISIBLE);
    366         mCrumbHolder.setVisibility(View.VISIBLE);
    367         mFakeTitleHolder.setVisibility(View.GONE);
    368         mAddNewFolder.setVisibility(View.VISIBLE);
    369         mAddSeparator.setVisibility(View.VISIBLE);
    370         getInputMethodManager().hideSoftInputFromWindow(
    371                 mListView.getWindowToken(), 0);
    372     }
    373 
    374     private void descendInto(String foldername, long id) {
    375         if (id != DEFAULT_FOLDER_ID) {
    376             mCrumbs.pushView(foldername, new Folder(foldername, id));
    377             mCrumbs.notifyController();
    378         }
    379     }
    380 
    381     private LoaderCallbacks<EditBookmarkInfo> mEditInfoLoaderCallbacks =
    382             new LoaderCallbacks<EditBookmarkInfo>() {
    383 
    384         @Override
    385         public void onLoaderReset(Loader<EditBookmarkInfo> loader) {
    386             // Don't care
    387         }
    388 
    389         @Override
    390         public void onLoadFinished(Loader<EditBookmarkInfo> loader,
    391                 EditBookmarkInfo info) {
    392             boolean setAccount = false;
    393             if (info.id != -1) {
    394                 mEditingExisting = true;
    395                 showRemoveButton();
    396                 mFakeTitle.setText(R.string.edit_bookmark);
    397                 mTitle.setText(info.title);
    398                 mFolderAdapter.setOtherFolderDisplayText(info.parentTitle);
    399                 mMap.putLong(BrowserContract.Bookmarks._ID, info.id);
    400                 setAccount = true;
    401                 setAccount(info.accountName, info.accountType);
    402                 mCurrentFolder = info.parentId;
    403                 onCurrentFolderFound();
    404             }
    405             // TODO: Detect if lastUsedId is a subfolder of info.id in the
    406             // editing folder case. For now, just don't show the last used
    407             // folder at all to prevent any chance of the user adding a parent
    408             // folder to a child folder
    409             if (info.lastUsedId != -1 && info.lastUsedId != info.id
    410                     && !mEditingFolder) {
    411                 if (setAccount && info.lastUsedId != mRootFolder
    412                         && TextUtils.equals(info.lastUsedAccountName, info.accountName)
    413                         && TextUtils.equals(info.lastUsedAccountType, info.accountType)) {
    414                     mFolderAdapter.addRecentFolder(info.lastUsedId, info.lastUsedTitle);
    415                 } else if (!setAccount) {
    416                     setAccount = true;
    417                     setAccount(info.lastUsedAccountName, info.lastUsedAccountType);
    418                     if (info.lastUsedId != mRootFolder) {
    419                         mFolderAdapter.addRecentFolder(info.lastUsedId,
    420                                 info.lastUsedTitle);
    421                     }
    422                 }
    423             }
    424             if (!setAccount) {
    425                 mAccountSpinner.setSelection(0);
    426             }
    427         }
    428 
    429         @Override
    430         public Loader<EditBookmarkInfo> onCreateLoader(int id, Bundle args) {
    431             return new EditBookmarkInfoLoader(AddBookmarkPage.this, mMap);
    432         }
    433     };
    434 
    435     void setAccount(String accountName, String accountType) {
    436         for (int i = 0; i < mAccountAdapter.getCount(); i++) {
    437             BookmarkAccount account = mAccountAdapter.getItem(i);
    438             if (TextUtils.equals(account.accountName, accountName)
    439                     && TextUtils.equals(account.accountType, accountType)) {
    440                 onRootFolderFound(account.rootFolderId);
    441                 mAccountSpinner.setSelection(i);
    442                 return;
    443             }
    444         }
    445     }
    446 
    447     @Override
    448     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    449         String[] projection;
    450         switch (id) {
    451             case LOADER_ID_ACCOUNTS:
    452                 return new AccountsLoader(this);
    453             case LOADER_ID_FOLDER_CONTENTS:
    454                 projection = new String[] {
    455                         BrowserContract.Bookmarks._ID,
    456                         BrowserContract.Bookmarks.TITLE,
    457                         BrowserContract.Bookmarks.IS_FOLDER
    458                 };
    459                 String where = BrowserContract.Bookmarks.IS_FOLDER + " != 0";
    460                 String whereArgs[] = null;
    461                 if (mEditingFolder) {
    462                     where += " AND " + BrowserContract.Bookmarks._ID + " != ?";
    463                     whereArgs = new String[] { Long.toString(mMap.getLong(
    464                             BrowserContract.Bookmarks._ID)) };
    465                 }
    466                 long currentFolder;
    467                 Object data = mCrumbs.getTopData();
    468                 if (data != null) {
    469                     currentFolder = ((Folder) data).Id;
    470                 } else {
    471                     currentFolder = mRootFolder;
    472                 }
    473                 return new CursorLoader(this,
    474                         getUriForFolder(currentFolder),
    475                         projection,
    476                         where,
    477                         whereArgs,
    478                         BrowserContract.Bookmarks._ID + " ASC");
    479             default:
    480                 throw new AssertionError("Asking for nonexistant loader!");
    481         }
    482     }
    483 
    484     @Override
    485     public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    486         switch (loader.getId()) {
    487             case LOADER_ID_ACCOUNTS:
    488                 mAccountAdapter.clear();
    489                 while (cursor.moveToNext()) {
    490                     mAccountAdapter.add(new BookmarkAccount(this, cursor));
    491                 }
    492                 getLoaderManager().destroyLoader(LOADER_ID_ACCOUNTS);
    493                 getLoaderManager().restartLoader(LOADER_ID_EDIT_INFO, null,
    494                         mEditInfoLoaderCallbacks);
    495                 break;
    496             case LOADER_ID_FOLDER_CONTENTS:
    497                 mAdapter.changeCursor(cursor);
    498                 break;
    499         }
    500     }
    501 
    502     public void onLoaderReset(Loader<Cursor> loader) {
    503         switch (loader.getId()) {
    504             case LOADER_ID_FOLDER_CONTENTS:
    505                 mAdapter.changeCursor(null);
    506                 break;
    507         }
    508     }
    509 
    510     /**
    511      * Move cursor to the position that has folderToFind as its "_id".
    512      * @param cursor Cursor containing folders in the bookmarks database
    513      * @param folderToFind "_id" of the folder to move to.
    514      * @param idIndex Index in cursor of "_id"
    515      * @throws AssertionError if cursor is empty or there is no row with folderToFind
    516      *      as its "_id".
    517      */
    518     void moveCursorToFolder(Cursor cursor, long folderToFind, int idIndex)
    519             throws AssertionError {
    520         if (!cursor.moveToFirst()) {
    521             throw new AssertionError("No folders in the database!");
    522         }
    523         long folder;
    524         do {
    525             folder = cursor.getLong(idIndex);
    526         } while (folder != folderToFind && cursor.moveToNext());
    527         if (cursor.isAfterLast()) {
    528             throw new AssertionError("Folder(id=" + folderToFind
    529                     + ") holding this bookmark does not exist!");
    530         }
    531     }
    532 
    533     @Override
    534     public void onItemClick(AdapterView<?> parent, View view, int position,
    535             long id) {
    536         TextView tv = (TextView) view.findViewById(android.R.id.text1);
    537         // Switch to the folder that was clicked on.
    538         descendInto(tv.getText().toString(), id);
    539     }
    540 
    541     private void setShowFolderNamer(boolean show) {
    542         if (show != mIsFolderNamerShowing) {
    543             mIsFolderNamerShowing = show;
    544             if (show) {
    545                 // Set the selection to the folder namer so it will be in
    546                 // view.
    547                 mListView.addFooterView(mFolderNamerHolder);
    548             } else {
    549                 mListView.removeFooterView(mFolderNamerHolder);
    550             }
    551             // Refresh the list.
    552             mListView.setAdapter(mAdapter);
    553             if (show) {
    554                 mListView.setSelection(mListView.getCount() - 1);
    555             }
    556         }
    557     }
    558 
    559     /**
    560      * Shows a list of names of folders.
    561      */
    562     private class FolderAdapter extends CursorAdapter {
    563         public FolderAdapter(Context context) {
    564             super(context, null);
    565         }
    566 
    567         @Override
    568         public void bindView(View view, Context context, Cursor cursor) {
    569             ((TextView) view.findViewById(android.R.id.text1)).setText(
    570                     cursor.getString(cursor.getColumnIndexOrThrow(
    571                     BrowserContract.Bookmarks.TITLE)));
    572         }
    573 
    574         @Override
    575         public View newView(Context context, Cursor cursor, ViewGroup parent) {
    576             View view = LayoutInflater.from(context).inflate(
    577                     R.layout.folder_list_item, null);
    578             view.setBackgroundDrawable(context.getResources().
    579                     getDrawable(android.R.drawable.list_selector_background));
    580             return view;
    581         }
    582 
    583         @Override
    584         public boolean isEmpty() {
    585             // Do not show the empty view if the user is creating a new folder.
    586             return super.isEmpty() && !mIsFolderNamerShowing;
    587         }
    588     }
    589 
    590     @Override
    591     protected void onCreate(Bundle icicle) {
    592         super.onCreate(icicle);
    593         requestWindowFeature(Window.FEATURE_NO_TITLE);
    594 
    595         mMap = getIntent().getExtras();
    596 
    597         setContentView(R.layout.browser_add_bookmark);
    598 
    599         Window window = getWindow();
    600 
    601         String title = null;
    602         String url = null;
    603 
    604         mFakeTitle = (TextView) findViewById(R.id.fake_title);
    605 
    606         if (mMap != null) {
    607             Bundle b = mMap.getBundle(EXTRA_EDIT_BOOKMARK);
    608             if (b != null) {
    609                 mEditingFolder = mMap.getBoolean(EXTRA_IS_FOLDER, false);
    610                 mMap = b;
    611                 mEditingExisting = true;
    612                 mFakeTitle.setText(R.string.edit_bookmark);
    613                 if (mEditingFolder) {
    614                     findViewById(R.id.row_address).setVisibility(View.GONE);
    615                 } else {
    616                     showRemoveButton();
    617                 }
    618             } else {
    619                 int gravity = mMap.getInt("gravity", -1);
    620                 if (gravity != -1) {
    621                     WindowManager.LayoutParams l = window.getAttributes();
    622                     l.gravity = gravity;
    623                     window.setAttributes(l);
    624                 }
    625             }
    626             title = mMap.getString(BrowserContract.Bookmarks.TITLE);
    627             url = mOriginalUrl = mMap.getString(BrowserContract.Bookmarks.URL);
    628             mTouchIconUrl = mMap.getString(TOUCH_ICON_URL);
    629             mCurrentFolder = mMap.getLong(BrowserContract.Bookmarks.PARENT, DEFAULT_FOLDER_ID);
    630         }
    631 
    632         mTitle = (EditText) findViewById(R.id.title);
    633         mTitle.setText(title);
    634 
    635         mAddress = (EditText) findViewById(R.id.address);
    636         mAddress.setText(url);
    637 
    638         mButton = (TextView) findViewById(R.id.OK);
    639         mButton.setOnClickListener(this);
    640 
    641         mCancelButton = findViewById(R.id.cancel);
    642         mCancelButton.setOnClickListener(this);
    643 
    644         mFolder = (FolderSpinner) findViewById(R.id.folder);
    645         mFolderAdapter = new FolderSpinnerAdapter(this, !mEditingFolder);
    646         mFolder.setAdapter(mFolderAdapter);
    647         mFolder.setOnSetSelectionListener(this);
    648 
    649         mDefaultView = findViewById(R.id.default_view);
    650         mFolderSelector = findViewById(R.id.folder_selector);
    651 
    652         mFolderNamerHolder = getLayoutInflater().inflate(R.layout.new_folder_layout, null);
    653         mFolderNamer = (EditText) mFolderNamerHolder.findViewById(R.id.folder_namer);
    654         mFolderNamer.setOnEditorActionListener(this);
    655         mFolderCancel = mFolderNamerHolder.findViewById(R.id.close);
    656         mFolderCancel.setOnClickListener(this);
    657 
    658         mAddNewFolder = findViewById(R.id.add_new_folder);
    659         mAddNewFolder.setOnClickListener(this);
    660         mAddSeparator = findViewById(R.id.add_divider);
    661 
    662         mCrumbs = (BreadCrumbView) findViewById(R.id.crumbs);
    663         mCrumbs.setUseBackButton(true);
    664         mCrumbs.setController(this);
    665         mHeaderIcon = getResources().getDrawable(R.drawable.ic_folder_holo_dark);
    666         mCrumbHolder = findViewById(R.id.crumb_holder);
    667         mCrumbs.setMaxVisible(MAX_CRUMBS_SHOWN);
    668 
    669         mAdapter = new FolderAdapter(this);
    670         mListView = (CustomListView) findViewById(R.id.list);
    671         View empty = findViewById(R.id.empty);
    672         mListView.setEmptyView(empty);
    673         mListView.setAdapter(mAdapter);
    674         mListView.setOnItemClickListener(this);
    675         mListView.addEditText(mFolderNamer);
    676 
    677         mAccountAdapter = new ArrayAdapter<BookmarkAccount>(this,
    678                 android.R.layout.simple_spinner_item);
    679         mAccountAdapter.setDropDownViewResource(
    680                 android.R.layout.simple_spinner_dropdown_item);
    681         mAccountSpinner = (Spinner) findViewById(R.id.accounts);
    682         mAccountSpinner.setAdapter(mAccountAdapter);
    683         mAccountSpinner.setOnItemSelectedListener(this);
    684 
    685 
    686         mFakeTitleHolder = findViewById(R.id.title_holder);
    687 
    688         if (!window.getDecorView().isInTouchMode()) {
    689             mButton.requestFocus();
    690         }
    691 
    692         getLoaderManager().restartLoader(LOADER_ID_ACCOUNTS, null, this);
    693     }
    694 
    695     private void showRemoveButton() {
    696         findViewById(R.id.remove_divider).setVisibility(View.VISIBLE);
    697         mRemoveLink = findViewById(R.id.remove);
    698         mRemoveLink.setVisibility(View.VISIBLE);
    699         mRemoveLink.setOnClickListener(this);
    700     }
    701 
    702     // Called once we have determined which folder is the root folder
    703     private void onRootFolderFound(long root) {
    704         mRootFolder = root;
    705         mCurrentFolder = mRootFolder;
    706         setupTopCrumb();
    707         onCurrentFolderFound();
    708     }
    709 
    710     private void setupTopCrumb() {
    711         mCrumbs.clear();
    712         String name = getString(R.string.bookmarks);
    713         mTopLevelLabel = (TextView) mCrumbs.pushView(name, false,
    714                 new Folder(name, mRootFolder));
    715         // To better match the other folders.
    716         mTopLevelLabel.setCompoundDrawablePadding(6);
    717     }
    718 
    719     private void onCurrentFolderFound() {
    720         LoaderManager manager = getLoaderManager();
    721         if (mCurrentFolder != mRootFolder) {
    722             // Since we're not in the root folder, change the selection to other
    723             // folder now.  The text will get changed once we select the correct
    724             // folder.
    725             mFolder.setSelectionIgnoringSelectionChange(mEditingFolder ? 1 : 2);
    726         } else {
    727             setShowBookmarkIcon(true);
    728             if (!mEditingFolder) {
    729                 // Initially the "Bookmarks" folder should be showing, rather than
    730                 // the home screen.  In the editing folder case, home screen is not
    731                 // an option, so "Bookmarks" folder is already at the top.
    732                 mFolder.setSelectionIgnoringSelectionChange(FolderSpinnerAdapter.ROOT_FOLDER);
    733             }
    734         }
    735         // Find the contents of the current folder
    736         manager.restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this);
    737     }
    738 
    739     /**
    740      * Runnable to save a bookmark, so it can be performed in its own thread.
    741      */
    742     private class SaveBookmarkRunnable implements Runnable {
    743         // FIXME: This should be an async task.
    744         private Message mMessage;
    745         private Context mContext;
    746         public SaveBookmarkRunnable(Context ctx, Message msg) {
    747             mContext = ctx.getApplicationContext();
    748             mMessage = msg;
    749         }
    750         public void run() {
    751             // Unbundle bookmark data.
    752             Bundle bundle = mMessage.getData();
    753             String title = bundle.getString(BrowserContract.Bookmarks.TITLE);
    754             String url = bundle.getString(BrowserContract.Bookmarks.URL);
    755             boolean invalidateThumbnail = bundle.getBoolean(REMOVE_THUMBNAIL);
    756             Bitmap thumbnail = invalidateThumbnail ? null
    757                     : (Bitmap) bundle.getParcelable(BrowserContract.Bookmarks.THUMBNAIL);
    758             String touchIconUrl = bundle.getString(TOUCH_ICON_URL);
    759 
    760             // Save to the bookmarks DB.
    761             try {
    762                 final ContentResolver cr = getContentResolver();
    763                 Bookmarks.addBookmark(AddBookmarkPage.this, false, url,
    764                         title, thumbnail, mCurrentFolder);
    765                 if (touchIconUrl != null) {
    766                     new DownloadTouchIcon(mContext, cr, url).execute(mTouchIconUrl);
    767                 }
    768                 mMessage.arg1 = 1;
    769             } catch (IllegalStateException e) {
    770                 mMessage.arg1 = 0;
    771             }
    772             mMessage.sendToTarget();
    773         }
    774     }
    775 
    776     private static class UpdateBookmarkTask extends AsyncTask<ContentValues, Void, Void> {
    777         Context mContext;
    778         Long mId;
    779 
    780         public UpdateBookmarkTask(Context context, long id) {
    781             mContext = context.getApplicationContext();
    782             mId = id;
    783         }
    784 
    785         @Override
    786         protected Void doInBackground(ContentValues... params) {
    787             if (params.length != 1) {
    788                 throw new IllegalArgumentException("No ContentValues provided!");
    789             }
    790             Uri uri = ContentUris.withAppendedId(BookmarkUtils.getBookmarksUri(mContext), mId);
    791             mContext.getContentResolver().update(
    792                     uri,
    793                     params[0], null, null);
    794             return null;
    795         }
    796     }
    797 
    798     private void createHandler() {
    799         if (mHandler == null) {
    800             mHandler = new Handler() {
    801                 @Override
    802                 public void handleMessage(Message msg) {
    803                     switch (msg.what) {
    804                         case SAVE_BOOKMARK:
    805                             if (1 == msg.arg1) {
    806                                 Toast.makeText(AddBookmarkPage.this, R.string.bookmark_saved,
    807                                         Toast.LENGTH_LONG).show();
    808                             } else {
    809                                 Toast.makeText(AddBookmarkPage.this, R.string.bookmark_not_saved,
    810                                         Toast.LENGTH_LONG).show();
    811                             }
    812                             break;
    813                         case TOUCH_ICON_DOWNLOADED:
    814                             Bundle b = msg.getData();
    815                             sendBroadcast(BookmarkUtils.createAddToHomeIntent(
    816                                     AddBookmarkPage.this,
    817                                     b.getString(BrowserContract.Bookmarks.URL),
    818                                     b.getString(BrowserContract.Bookmarks.TITLE),
    819                                     (Bitmap) b.getParcelable(BrowserContract.Bookmarks.TOUCH_ICON),
    820                                     (Bitmap) b.getParcelable(BrowserContract.Bookmarks.FAVICON)));
    821                             break;
    822                         case BOOKMARK_DELETED:
    823                             finish();
    824                             break;
    825                     }
    826                 }
    827             };
    828         }
    829     }
    830 
    831     /**
    832      * Parse the data entered in the dialog and post a message to update the bookmarks database.
    833      */
    834     boolean save() {
    835         createHandler();
    836 
    837         String title = mTitle.getText().toString().trim();
    838         String unfilteredUrl;
    839         unfilteredUrl = UrlUtils.fixUrl(mAddress.getText().toString());
    840 
    841         boolean emptyTitle = title.length() == 0;
    842         boolean emptyUrl = unfilteredUrl.trim().length() == 0;
    843         Resources r = getResources();
    844         if (emptyTitle || (emptyUrl && !mEditingFolder)) {
    845             if (emptyTitle) {
    846                 mTitle.setError(r.getText(R.string.bookmark_needs_title));
    847             }
    848             if (emptyUrl) {
    849                 mAddress.setError(r.getText(R.string.bookmark_needs_url));
    850             }
    851             return false;
    852 
    853         }
    854         String url = unfilteredUrl.trim();
    855         if (!mEditingFolder) {
    856             try {
    857                 // We allow bookmarks with a javascript: scheme, but these will in most cases
    858                 // fail URI parsing, so don't try it if that's the kind of bookmark we have.
    859 
    860                 if (!url.toLowerCase().startsWith("javascript:")) {
    861                     URI uriObj = new URI(url);
    862                     String scheme = uriObj.getScheme();
    863                     if (!Bookmarks.urlHasAcceptableScheme(url)) {
    864                         // If the scheme was non-null, let the user know that we
    865                         // can't save their bookmark. If it was null, we'll assume
    866                         // they meant http when we parse it in the WebAddress class.
    867                         if (scheme != null) {
    868                             mAddress.setError(r.getText(R.string.bookmark_cannot_save_url));
    869                             return false;
    870                         }
    871                         WebAddress address;
    872                         try {
    873                             address = new WebAddress(unfilteredUrl);
    874                         } catch (ParseException e) {
    875                             throw new URISyntaxException("", "");
    876                         }
    877                         if (address.getHost().length() == 0) {
    878                             throw new URISyntaxException("", "");
    879                         }
    880                         url = address.toString();
    881                     }
    882                 }
    883             } catch (URISyntaxException e) {
    884                 mAddress.setError(r.getText(R.string.bookmark_url_not_valid));
    885                 return false;
    886             }
    887         }
    888 
    889         if (mSaveToHomeScreen) {
    890             mEditingExisting = false;
    891         }
    892 
    893         boolean urlUnmodified = url.equals(mOriginalUrl);
    894 
    895         if (mEditingExisting) {
    896             Long id = mMap.getLong(BrowserContract.Bookmarks._ID);
    897             ContentValues values = new ContentValues();
    898             values.put(BrowserContract.Bookmarks.TITLE, title);
    899             values.put(BrowserContract.Bookmarks.PARENT, mCurrentFolder);
    900             if (!mEditingFolder) {
    901                 values.put(BrowserContract.Bookmarks.URL, url);
    902                 if (!urlUnmodified) {
    903                     values.putNull(BrowserContract.Bookmarks.THUMBNAIL);
    904                 }
    905             }
    906             if (values.size() > 0) {
    907                 new UpdateBookmarkTask(getApplicationContext(), id).execute(values);
    908             }
    909             setResult(RESULT_OK);
    910         } else {
    911             Bitmap thumbnail;
    912             Bitmap favicon;
    913             if (urlUnmodified) {
    914                 thumbnail = (Bitmap) mMap.getParcelable(
    915                         BrowserContract.Bookmarks.THUMBNAIL);
    916                 favicon = (Bitmap) mMap.getParcelable(
    917                         BrowserContract.Bookmarks.FAVICON);
    918             } else {
    919                 thumbnail = null;
    920                 favicon = null;
    921             }
    922 
    923             Bundle bundle = new Bundle();
    924             bundle.putString(BrowserContract.Bookmarks.TITLE, title);
    925             bundle.putString(BrowserContract.Bookmarks.URL, url);
    926             bundle.putParcelable(BrowserContract.Bookmarks.FAVICON, favicon);
    927 
    928             if (mSaveToHomeScreen) {
    929                 if (mTouchIconUrl != null && urlUnmodified) {
    930                     Message msg = Message.obtain(mHandler,
    931                             TOUCH_ICON_DOWNLOADED);
    932                     msg.setData(bundle);
    933                     DownloadTouchIcon icon = new DownloadTouchIcon(this, msg,
    934                             mMap.getString(USER_AGENT));
    935                     icon.execute(mTouchIconUrl);
    936                 } else {
    937                     sendBroadcast(BookmarkUtils.createAddToHomeIntent(this, url,
    938                             title, null /*touchIcon*/, favicon));
    939                 }
    940             } else {
    941                 bundle.putParcelable(BrowserContract.Bookmarks.THUMBNAIL, thumbnail);
    942                 bundle.putBoolean(REMOVE_THUMBNAIL, !urlUnmodified);
    943                 bundle.putString(TOUCH_ICON_URL, mTouchIconUrl);
    944                 // Post a message to write to the DB.
    945                 Message msg = Message.obtain(mHandler, SAVE_BOOKMARK);
    946                 msg.setData(bundle);
    947                 // Start a new thread so as to not slow down the UI
    948                 Thread t = new Thread(new SaveBookmarkRunnable(getApplicationContext(), msg));
    949                 t.start();
    950             }
    951             setResult(RESULT_OK);
    952             LogTag.logBookmarkAdded(url, "bookmarkview");
    953         }
    954         return true;
    955     }
    956 
    957     @Override
    958     public void onItemSelected(AdapterView<?> parent, View view, int position,
    959             long id) {
    960         if (mAccountSpinner == parent) {
    961             long root = mAccountAdapter.getItem(position).rootFolderId;
    962             if (root != mRootFolder) {
    963                 onRootFolderFound(root);
    964                 mFolderAdapter.clearRecentFolder();
    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, null);
   1016         }
   1017 
   1018     }
   1019 
   1020     public static class BookmarkAccount {
   1021 
   1022         private String mLabel;
   1023         String accountName, accountType;
   1024         public long rootFolderId;
   1025 
   1026         public BookmarkAccount(Context context, Cursor cursor) {
   1027             accountName = cursor.getString(
   1028                     AccountsLoader.COLUMN_INDEX_ACCOUNT_NAME);
   1029             accountType = cursor.getString(
   1030                     AccountsLoader.COLUMN_INDEX_ACCOUNT_TYPE);
   1031             rootFolderId = cursor.getLong(
   1032                     AccountsLoader.COLUMN_INDEX_ROOT_ID);
   1033             mLabel = accountName;
   1034             if (TextUtils.isEmpty(mLabel)) {
   1035                 mLabel = context.getString(R.string.local_bookmarks);
   1036             }
   1037         }
   1038 
   1039         @Override
   1040         public String toString() {
   1041             return mLabel;
   1042         }
   1043     }
   1044 
   1045     static class EditBookmarkInfo {
   1046         long id = -1;
   1047         long parentId = -1;
   1048         String parentTitle;
   1049         String title;
   1050         String accountName;
   1051         String accountType;
   1052 
   1053         long lastUsedId = -1;
   1054         String lastUsedTitle;
   1055         String lastUsedAccountName;
   1056         String lastUsedAccountType;
   1057     }
   1058 
   1059     static class EditBookmarkInfoLoader extends AsyncTaskLoader<EditBookmarkInfo> {
   1060 
   1061         private Context mContext;
   1062         private Bundle mMap;
   1063 
   1064         public EditBookmarkInfoLoader(Context context, Bundle bundle) {
   1065             super(context);
   1066             mContext = context.getApplicationContext();
   1067             mMap = bundle;
   1068         }
   1069 
   1070         @Override
   1071         public EditBookmarkInfo loadInBackground() {
   1072             final ContentResolver cr = mContext.getContentResolver();
   1073             EditBookmarkInfo info = new EditBookmarkInfo();
   1074             Cursor c = null;
   1075 
   1076             try {
   1077                 // First, let's lookup the bookmark (check for dupes, get needed info)
   1078                 String url = mMap.getString(BrowserContract.Bookmarks.URL);
   1079                 info.id = mMap.getLong(BrowserContract.Bookmarks._ID, -1);
   1080                 boolean checkForDupe = mMap.getBoolean(CHECK_FOR_DUPE);
   1081                 if (checkForDupe && info.id == -1 && !TextUtils.isEmpty(url)) {
   1082                     c = cr.query(BrowserContract.Bookmarks.CONTENT_URI,
   1083                             new String[] { BrowserContract.Bookmarks._ID},
   1084                             BrowserContract.Bookmarks.URL + "=?",
   1085                             new String[] { url }, null);
   1086                     if (c.getCount() == 1 && c.moveToFirst()) {
   1087                         info.id = c.getLong(0);
   1088                     }
   1089                     c.close();
   1090                 }
   1091                 if (info.id != -1) {
   1092                     c = cr.query(ContentUris.withAppendedId(
   1093                             BrowserContract.Bookmarks.CONTENT_URI, info.id),
   1094                             new String[] {
   1095                             BrowserContract.Bookmarks.PARENT,
   1096                             BrowserContract.Bookmarks.ACCOUNT_NAME,
   1097                             BrowserContract.Bookmarks.ACCOUNT_TYPE,
   1098                             BrowserContract.Bookmarks.TITLE},
   1099                             null, null, null);
   1100                     if (c.moveToFirst()) {
   1101                         info.parentId = c.getLong(0);
   1102                         info.accountName = c.getString(1);
   1103                         info.accountType = c.getString(2);
   1104                         info.title = c.getString(3);
   1105                     }
   1106                     c.close();
   1107                     c = cr.query(ContentUris.withAppendedId(
   1108                             BrowserContract.Bookmarks.CONTENT_URI, info.parentId),
   1109                             new String[] {
   1110                             BrowserContract.Bookmarks.TITLE,},
   1111                             null, null, null);
   1112                     if (c.moveToFirst()) {
   1113                         info.parentTitle = c.getString(0);
   1114                     }
   1115                     c.close();
   1116                 }
   1117 
   1118                 // Figure out the last used folder/account
   1119                 c = cr.query(BrowserContract.Bookmarks.CONTENT_URI,
   1120                         new String[] {
   1121                         BrowserContract.Bookmarks.PARENT,
   1122                         }, null, null,
   1123                         BrowserContract.Bookmarks.DATE_MODIFIED + " DESC LIMIT 1");
   1124                 if (c.moveToFirst()) {
   1125                     long parent = c.getLong(0);
   1126                     c.close();
   1127                     c = cr.query(BrowserContract.Bookmarks.CONTENT_URI,
   1128                             new String[] {
   1129                             BrowserContract.Bookmarks.TITLE,
   1130                             BrowserContract.Bookmarks.ACCOUNT_NAME,
   1131                             BrowserContract.Bookmarks.ACCOUNT_TYPE},
   1132                             BrowserContract.Bookmarks._ID + "=?", new String[] {
   1133                             Long.toString(parent)}, null);
   1134                     if (c.moveToFirst()) {
   1135                         info.lastUsedId = parent;
   1136                         info.lastUsedTitle = c.getString(0);
   1137                         info.lastUsedAccountName = c.getString(1);
   1138                         info.lastUsedAccountType = c.getString(2);
   1139                     }
   1140                     c.close();
   1141                 }
   1142             } finally {
   1143                 if (c != null) {
   1144                     c.close();
   1145                 }
   1146             }
   1147 
   1148             return info;
   1149         }
   1150 
   1151         @Override
   1152         protected void onStartLoading() {
   1153             forceLoad();
   1154         }
   1155 
   1156     }
   1157 
   1158 }
   1159