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