diff options
Diffstat (limited to 'src/com/android/swe/browser/AddBookmarkPage.java')
-rw-r--r-- | src/com/android/swe/browser/AddBookmarkPage.java | 1252 |
1 files changed, 1252 insertions, 0 deletions
diff --git a/src/com/android/swe/browser/AddBookmarkPage.java b/src/com/android/swe/browser/AddBookmarkPage.java new file mode 100644 index 00000000..a7ded1ef --- /dev/null +++ b/src/com/android/swe/browser/AddBookmarkPage.java @@ -0,0 +1,1252 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.LoaderManager; +import android.app.LoaderManager.LoaderCallbacks; +import android.content.AsyncTaskLoader; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.CursorLoader; +import android.content.DialogInterface; +import android.content.Loader; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.ParseException; +import android.net.Uri; +import android.net.WebAddress; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.provider.Browser; +import android.provider.BrowserContract; +import android.provider.BrowserContract.Accounts; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.CursorAdapter; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.browser.BrowserUtils; +import com.android.browser.addbookmark.FolderSpinner; +import com.android.browser.addbookmark.FolderSpinnerAdapter; + +import java.net.URI; +import java.net.URISyntaxException; + +public class AddBookmarkPage extends Activity + implements View.OnClickListener, TextView.OnEditorActionListener, + AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor>, + BreadCrumbView.Controller, FolderSpinner.OnSetSelectionListener, + OnItemSelectedListener { + + public static final long DEFAULT_FOLDER_ID = -1; + public static final String TOUCH_ICON_URL = "touch_icon_url"; + // Place on an edited bookmark to remove the saved thumbnail + public static final String REMOVE_THUMBNAIL = "remove_thumbnail"; + public static final String USER_AGENT = "user_agent"; + public static final String CHECK_FOR_DUPE = "check_for_dupe"; + + /* package */ static final String EXTRA_EDIT_BOOKMARK = "bookmark"; + /* package */ static final String EXTRA_IS_FOLDER = "is_folder"; + + private static final int MAX_CRUMBS_SHOWN = 1; + + private final String LOGTAG = "Bookmarks"; + + // IDs for the CursorLoaders that are used. + private final int LOADER_ID_ACCOUNTS = 0; + private final int LOADER_ID_FOLDER_CONTENTS = 1; + private final int LOADER_ID_EDIT_INFO = 2; + + private EditText mTitle; + private EditText mAddress; + private TextView mButton; + private View mCancelButton; + private boolean mEditingExisting; + private boolean mEditingFolder; + private Bundle mMap; + private String mTouchIconUrl; + private String mOriginalUrl; + private FolderSpinner mFolder; + private View mDefaultView; + private View mFolderSelector; + private EditText mFolderNamer; + private View mFolderCancel; + private boolean mIsFolderNamerShowing; + private View mFolderNamerHolder; + private View mAddNewFolder; + private View mAddSeparator; + private long mCurrentFolder; + private FolderAdapter mAdapter; + private BreadCrumbView mCrumbs; + private TextView mFakeTitle; + private View mCrumbHolder; + private CustomListView mListView; + private boolean mSaveToHomeScreen; + private long mRootFolder; + private TextView mTopLevelLabel; + private Drawable mHeaderIcon; + private View mRemoveLink; + private View mFakeTitleHolder; + private FolderSpinnerAdapter mFolderAdapter; + private Spinner mAccountSpinner; + private ArrayAdapter<BookmarkAccount> mAccountAdapter; + // add for carrier which requires same title or address can not exist. + private long mDuplicateId; + private Context mDuplicateContext; + + private static class Folder { + String Name; + long Id; + Folder(String name, long id) { + Name = name; + Id = id; + } + } + + // Message IDs + private static final int SAVE_BOOKMARK = 100; + private static final int TOUCH_ICON_DOWNLOADED = 101; + private static final int BOOKMARK_DELETED = 102; + + private Handler mHandler; + + private InputMethodManager getInputMethodManager() { + return (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + } + + private Uri getUriForFolder(long folder) { + BookmarkAccount account = + (BookmarkAccount) mAccountSpinner.getSelectedItem(); + if (folder == mRootFolder && account != null) { + return BookmarksLoader.addAccount( + BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, + account.accountType, account.accountName); + } + return BrowserContract.Bookmarks.buildFolderUri(folder); + } + + @Override + public void onTop(BreadCrumbView view, int level, Object data) { + if (null == data) return; + Folder folderData = (Folder) data; + long folder = folderData.Id; + LoaderManager manager = getLoaderManager(); + CursorLoader loader = (CursorLoader) ((Loader<?>) manager.getLoader( + LOADER_ID_FOLDER_CONTENTS)); + loader.setUri(getUriForFolder(folder)); + loader.forceLoad(); + if (mIsFolderNamerShowing) { + completeOrCancelFolderNaming(true); + } + setShowBookmarkIcon(level == 1); + } + + /** + * Show or hide the icon for bookmarks next to "Bookmarks" in the crumb view. + * @param show True if the icon should visible, false otherwise. + */ + private void setShowBookmarkIcon(boolean show) { + Drawable drawable = show ? mHeaderIcon: null; + mTopLevelLabel.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null); + } + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (v == mFolderNamer) { + if (v.getText().length() > 0) { + if (actionId == EditorInfo.IME_NULL) { + // Only want to do this once. + if (event.getAction() == KeyEvent.ACTION_UP) { + completeOrCancelFolderNaming(false); + } + } + } + // Steal the key press; otherwise a newline will be added + return true; + } + return false; + } + + private void switchToDefaultView(boolean changedFolder) { + mFolderSelector.setVisibility(View.GONE); + mDefaultView.setVisibility(View.VISIBLE); + mCrumbHolder.setVisibility(View.GONE); + mFakeTitleHolder.setVisibility(View.VISIBLE); + if (changedFolder) { + Object data = mCrumbs.getTopData(); + if (data != null) { + Folder folder = (Folder) data; + mCurrentFolder = folder.Id; + if (mCurrentFolder == mRootFolder) { + // The Spinner changed to show "Other folder ..." Change + // it back to "Bookmarks", which is position 0 if we are + // editing a folder, 1 otherwise. + mFolder.setSelectionIgnoringSelectionChange(mEditingFolder ? 0 : 1); + } else { + mFolderAdapter.setOtherFolderDisplayText(folder.Name); + } + } + } else { + // The user canceled selecting a folder. Revert back to the earlier + // selection. + if (mSaveToHomeScreen) { + mFolder.setSelectionIgnoringSelectionChange(0); + } else { + if (mCurrentFolder == mRootFolder) { + mFolder.setSelectionIgnoringSelectionChange(mEditingFolder ? 0 : 1); + } else { + Object data = mCrumbs.getTopData(); + if (data != null && ((Folder) data).Id == mCurrentFolder) { + // We are showing the correct folder hierarchy. The + // folder selector will say "Other folder..." Change it + // to say the name of the folder once again. + mFolderAdapter.setOtherFolderDisplayText(((Folder) data).Name); + } else { + // We are not showing the correct folder hierarchy. + // Clear the Crumbs and find the proper folder + setupTopCrumb(); + LoaderManager manager = getLoaderManager(); + manager.restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this); + + } + } + } + } + } + + @Override + public void onClick(View v) { + if (v == mButton) { + if (mFolderSelector.getVisibility() == View.VISIBLE) { + // We are showing the folder selector. + if (mIsFolderNamerShowing) { + completeOrCancelFolderNaming(false); + } else { + // User has selected a folder. Go back to the opening page + mSaveToHomeScreen = false; + switchToDefaultView(true); + } + } else { + // add for carrier which requires same title or address can not + // exist. + if (mSaveToHomeScreen) { + if (save()) { + return; + } + } else { + onSaveWithConfirm(); + } + } + } else if (v == mCancelButton) { + if (mIsFolderNamerShowing) { + completeOrCancelFolderNaming(true); + } else if (mFolderSelector.getVisibility() == View.VISIBLE) { + switchToDefaultView(false); + } else { + finish(); + } + } else if (v == mFolderCancel) { + completeOrCancelFolderNaming(true); + } else if (v == mAddNewFolder) { + setShowFolderNamer(true); + mFolderNamer.setText(R.string.new_folder); + mFolderNamer.requestFocus(); + mAddNewFolder.setVisibility(View.GONE); + mAddSeparator.setVisibility(View.GONE); + InputMethodManager imm = getInputMethodManager(); + // Set the InputMethodManager to focus on the ListView so that it + // can transfer the focus to mFolderNamer. + imm.focusIn(mListView); + imm.showSoftInput(mFolderNamer, InputMethodManager.SHOW_IMPLICIT); + } else if (v == mRemoveLink) { + if (!mEditingExisting) { + throw new AssertionError("Remove button should not be shown for" + + " new bookmarks"); + } + long id = mMap.getLong(BrowserContract.Bookmarks._ID); + createHandler(); + Message msg = Message.obtain(mHandler, BOOKMARK_DELETED); + BookmarkUtils.displayRemoveBookmarkDialog(id, + mTitle.getText().toString(), this, msg); + } + } + + // FolderSpinner.OnSetSelectionListener + + @Override + public void onSetSelection(long id) { + int intId = (int) id; + switch (intId) { + case FolderSpinnerAdapter.ROOT_FOLDER: + mCurrentFolder = mRootFolder; + mSaveToHomeScreen = false; + break; + case FolderSpinnerAdapter.HOME_SCREEN: + // Create a short cut to the home screen + mSaveToHomeScreen = true; + break; + case FolderSpinnerAdapter.OTHER_FOLDER: + switchToFolderSelector(); + break; + case FolderSpinnerAdapter.RECENT_FOLDER: + mCurrentFolder = mFolderAdapter.recentFolderId(); + mSaveToHomeScreen = false; + // In case the user decides to select OTHER_FOLDER + // and choose a different one, so that we will start from + // the correct place. + LoaderManager manager = getLoaderManager(); + manager.restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this); + break; + default: + break; + } + } + + /** + * Finish naming a folder, and close the IME + * @param cancel If true, the new folder is not created. If false, the new + * folder is created and the user is taken inside it. + */ + private void completeOrCancelFolderNaming(boolean cancel) { + if (!cancel && !TextUtils.isEmpty(mFolderNamer.getText())) { + String name = mFolderNamer.getText().toString(); + long id = addFolderToCurrent(mFolderNamer.getText().toString()); + descendInto(name, id); + } + setShowFolderNamer(false); + mAddNewFolder.setVisibility(View.VISIBLE); + mAddSeparator.setVisibility(View.VISIBLE); + getInputMethodManager().hideSoftInputFromWindow( + mListView.getWindowToken(), 0); + } + + private long addFolderToCurrent(String name) { + // Add the folder to the database + ContentValues values = new ContentValues(); + values.put(BrowserContract.Bookmarks.TITLE, + name); + values.put(BrowserContract.Bookmarks.IS_FOLDER, 1); + long currentFolder; + Object data = mCrumbs.getTopData(); + if (data != null) { + currentFolder = ((Folder) data).Id; + } else { + currentFolder = mRootFolder; + } + values.put(BrowserContract.Bookmarks.PARENT, currentFolder); + Uri uri = getContentResolver().insert( + BrowserContract.Bookmarks.CONTENT_URI, values); + if (uri != null) { + return ContentUris.parseId(uri); + } else { + return -1; + } + } + + private void switchToFolderSelector() { + // Set the list to the top in case it is scrolled. + mListView.setSelection(0); + mDefaultView.setVisibility(View.GONE); + mFolderSelector.setVisibility(View.VISIBLE); + mCrumbHolder.setVisibility(View.VISIBLE); + mFakeTitleHolder.setVisibility(View.GONE); + mAddNewFolder.setVisibility(View.VISIBLE); + mAddSeparator.setVisibility(View.VISIBLE); + getInputMethodManager().hideSoftInputFromWindow( + mListView.getWindowToken(), 0); + } + + private void descendInto(String foldername, long id) { + if (id != DEFAULT_FOLDER_ID) { + mCrumbs.pushView(foldername, new Folder(foldername, id)); + mCrumbs.notifyController(); + } + } + + private LoaderCallbacks<EditBookmarkInfo> mEditInfoLoaderCallbacks = + new LoaderCallbacks<EditBookmarkInfo>() { + + @Override + public void onLoaderReset(Loader<EditBookmarkInfo> loader) { + // Don't care + } + + @Override + public void onLoadFinished(Loader<EditBookmarkInfo> loader, + EditBookmarkInfo info) { + boolean setAccount = false; + if (info.id != -1) { + mEditingExisting = true; + showRemoveButton(); + mFakeTitle.setText(R.string.edit_bookmark); + mTitle.setText(info.title); + mFolderAdapter.setOtherFolderDisplayText(info.parentTitle); + mMap.putLong(BrowserContract.Bookmarks._ID, info.id); + setAccount = true; + setAccount(info.accountName, info.accountType); + mCurrentFolder = info.parentId; + onCurrentFolderFound(); + } + // TODO: Detect if lastUsedId is a subfolder of info.id in the + // editing folder case. For now, just don't show the last used + // folder at all to prevent any chance of the user adding a parent + // folder to a child folder + if (info.lastUsedId != -1 && info.lastUsedId != info.id + && !mEditingFolder) { + if (setAccount && info.lastUsedId != mRootFolder + && TextUtils.equals(info.lastUsedAccountName, info.accountName) + && TextUtils.equals(info.lastUsedAccountType, info.accountType)) { + mFolderAdapter.addRecentFolder(info.lastUsedId, info.lastUsedTitle); + } else if (!setAccount) { + setAccount = true; + setAccount(info.lastUsedAccountName, info.lastUsedAccountType); + if (info.lastUsedId != mRootFolder) { + mFolderAdapter.addRecentFolder(info.lastUsedId, + info.lastUsedTitle); + } + } + } + if (!setAccount) { + mAccountSpinner.setSelection(0); + } + } + + @Override + public Loader<EditBookmarkInfo> onCreateLoader(int id, Bundle args) { + return new EditBookmarkInfoLoader(AddBookmarkPage.this, mMap); + } + }; + + void setAccount(String accountName, String accountType) { + for (int i = 0; i < mAccountAdapter.getCount(); i++) { + BookmarkAccount account = mAccountAdapter.getItem(i); + if (TextUtils.equals(account.accountName, accountName) + && TextUtils.equals(account.accountType, accountType)) { + mAccountSpinner.setSelection(i); + onRootFolderFound(account.rootFolderId); + return; + } + } + } + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + String[] projection; + switch (id) { + case LOADER_ID_ACCOUNTS: + return new AccountsLoader(this); + case LOADER_ID_FOLDER_CONTENTS: + projection = new String[] { + BrowserContract.Bookmarks._ID, + BrowserContract.Bookmarks.TITLE, + BrowserContract.Bookmarks.IS_FOLDER + }; + String where = BrowserContract.Bookmarks.IS_FOLDER + " != 0"; + String whereArgs[] = null; + if (mEditingFolder) { + where += " AND " + BrowserContract.Bookmarks._ID + " != ?"; + whereArgs = new String[] { Long.toString(mMap.getLong( + BrowserContract.Bookmarks._ID)) }; + } + long currentFolder; + Object data = mCrumbs.getTopData(); + if (data != null) { + currentFolder = ((Folder) data).Id; + } else { + currentFolder = mRootFolder; + } + return new CursorLoader(this, + getUriForFolder(currentFolder), + projection, + where, + whereArgs, + BrowserContract.Bookmarks._ID + " ASC"); + default: + throw new AssertionError("Asking for nonexistant loader!"); + } + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { + switch (loader.getId()) { + case LOADER_ID_ACCOUNTS: + mAccountAdapter.clear(); + while (cursor.moveToNext()) { + mAccountAdapter.add(new BookmarkAccount(this, cursor)); + } + getLoaderManager().destroyLoader(LOADER_ID_ACCOUNTS); + getLoaderManager().restartLoader(LOADER_ID_EDIT_INFO, null, + mEditInfoLoaderCallbacks); + break; + case LOADER_ID_FOLDER_CONTENTS: + mAdapter.changeCursor(cursor); + break; + } + } + + public void onLoaderReset(Loader<Cursor> loader) { + switch (loader.getId()) { + case LOADER_ID_FOLDER_CONTENTS: + mAdapter.changeCursor(null); + break; + } + } + + /** + * Move cursor to the position that has folderToFind as its "_id". + * @param cursor Cursor containing folders in the bookmarks database + * @param folderToFind "_id" of the folder to move to. + * @param idIndex Index in cursor of "_id" + * @throws AssertionError if cursor is empty or there is no row with folderToFind + * as its "_id". + */ + void moveCursorToFolder(Cursor cursor, long folderToFind, int idIndex) + throws AssertionError { + if (!cursor.moveToFirst()) { + throw new AssertionError("No folders in the database!"); + } + long folder; + do { + folder = cursor.getLong(idIndex); + } while (folder != folderToFind && cursor.moveToNext()); + if (cursor.isAfterLast()) { + throw new AssertionError("Folder(id=" + folderToFind + + ") holding this bookmark does not exist!"); + } + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, + long id) { + TextView tv = (TextView) view.findViewById(android.R.id.text1); + // Switch to the folder that was clicked on. + descendInto(tv.getText().toString(), id); + } + + private void setShowFolderNamer(boolean show) { + if (show != mIsFolderNamerShowing) { + mIsFolderNamerShowing = show; + if (show) { + // Set the selection to the folder namer so it will be in + // view. + mListView.addFooterView(mFolderNamerHolder); + } else { + mListView.removeFooterView(mFolderNamerHolder); + } + // Refresh the list. + mListView.setAdapter(mAdapter); + if (show) { + mListView.setSelection(mListView.getCount() - 1); + } + } + } + + /** + * Shows a list of names of folders. + */ + private class FolderAdapter extends CursorAdapter { + public FolderAdapter(Context context) { + super(context, null); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + ((TextView) view.findViewById(android.R.id.text1)).setText( + cursor.getString(cursor.getColumnIndexOrThrow( + BrowserContract.Bookmarks.TITLE))); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + View view = LayoutInflater.from(context).inflate( + R.layout.folder_list_item, null); + view.setBackgroundDrawable(context.getResources(). + getDrawable(android.R.drawable.list_selector_background)); + return view; + } + + @Override + public boolean isEmpty() { + // Do not show the empty view if the user is creating a new folder. + return super.isEmpty() && !mIsFolderNamerShowing; + } + } + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + requestWindowFeature(Window.FEATURE_NO_TITLE); + + mMap = getIntent().getExtras(); + + setContentView(R.layout.browser_add_bookmark); + + Window window = getWindow(); + + String title = null; + String url = null; + + mFakeTitle = (TextView) findViewById(R.id.fake_title); + + if (mMap != null) { + Bundle b = mMap.getBundle(EXTRA_EDIT_BOOKMARK); + if (b != null) { + mEditingFolder = mMap.getBoolean(EXTRA_IS_FOLDER, false); + mMap = b; + mEditingExisting = true; + mFakeTitle.setText(R.string.edit_bookmark); + if (mEditingFolder) { + findViewById(R.id.row_address).setVisibility(View.GONE); + } else { + showRemoveButton(); + } + } else { + int gravity = mMap.getInt("gravity", -1); + if (gravity != -1) { + WindowManager.LayoutParams l = window.getAttributes(); + l.gravity = gravity; + window.setAttributes(l); + } + } + title = mMap.getString(BrowserContract.Bookmarks.TITLE); + url = mOriginalUrl = mMap.getString(BrowserContract.Bookmarks.URL); + mTouchIconUrl = mMap.getString(TOUCH_ICON_URL); + mCurrentFolder = mMap.getLong(BrowserContract.Bookmarks.PARENT, DEFAULT_FOLDER_ID); + } + + mTitle = (EditText) findViewById(R.id.title); + mTitle.setText(title); + BrowserUtils.maxLengthFilter(AddBookmarkPage.this, mTitle, BrowserUtils.FILENAME_MAX_LENGTH); + + mAddress = (EditText) findViewById(R.id.address); + mAddress.setText(url); + BrowserUtils.maxLengthFilter(AddBookmarkPage.this, mAddress, BrowserUtils.ADDRESS_MAX_LENGTH); + + mButton = (TextView) findViewById(R.id.OK); + mButton.setOnClickListener(this); + + mCancelButton = findViewById(R.id.cancel); + mCancelButton.setOnClickListener(this); + + mFolder = (FolderSpinner) findViewById(R.id.folder); + mFolderAdapter = new FolderSpinnerAdapter(this, !mEditingFolder); + mFolder.setAdapter(mFolderAdapter); + mFolder.setOnSetSelectionListener(this); + + mDefaultView = findViewById(R.id.default_view); + mFolderSelector = findViewById(R.id.folder_selector); + + mFolderNamerHolder = getLayoutInflater().inflate(R.layout.new_folder_layout, null); + mFolderNamer = (EditText) mFolderNamerHolder.findViewById(R.id.folder_namer); + mFolderNamer.setOnEditorActionListener(this); + + // add for carrier test about warning limit of edit text + BrowserUtils.maxLengthFilter(AddBookmarkPage.this, mFolderNamer, + BrowserUtils.FILENAME_MAX_LENGTH); + + mFolderCancel = mFolderNamerHolder.findViewById(R.id.close); + mFolderCancel.setOnClickListener(this); + + mAddNewFolder = findViewById(R.id.add_new_folder); + mAddNewFolder.setOnClickListener(this); + mAddSeparator = findViewById(R.id.add_divider); + + mCrumbs = (BreadCrumbView) findViewById(R.id.crumbs); + mCrumbs.setUseBackButton(true); + mCrumbs.setController(this); + mHeaderIcon = getResources().getDrawable(R.drawable.ic_folder_holo_dark); + mCrumbHolder = findViewById(R.id.crumb_holder); + mCrumbs.setMaxVisible(MAX_CRUMBS_SHOWN); + + mAdapter = new FolderAdapter(this); + mListView = (CustomListView) findViewById(R.id.list); + View empty = findViewById(R.id.empty); + mListView.setEmptyView(empty); + mListView.setAdapter(mAdapter); + mListView.setOnItemClickListener(this); + mListView.addEditText(mFolderNamer); + + mAccountAdapter = new ArrayAdapter<BookmarkAccount>(this, + android.R.layout.simple_spinner_item); + mAccountAdapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item); + mAccountSpinner = (Spinner) findViewById(R.id.accounts); + mAccountSpinner.setAdapter(mAccountAdapter); + mAccountSpinner.setOnItemSelectedListener(this); + + + mFakeTitleHolder = findViewById(R.id.title_holder); + + if (!window.getDecorView().isInTouchMode()) { + mButton.requestFocus(); + } + + getLoaderManager().restartLoader(LOADER_ID_ACCOUNTS, null, this); + } + + private void showRemoveButton() { + findViewById(R.id.remove_divider).setVisibility(View.VISIBLE); + mRemoveLink = findViewById(R.id.remove); + mRemoveLink.setVisibility(View.VISIBLE); + mRemoveLink.setOnClickListener(this); + } + + // Called once we have determined which folder is the root folder + private void onRootFolderFound(long root) { + mRootFolder = root; + mCurrentFolder = mRootFolder; + setupTopCrumb(); + onCurrentFolderFound(); + } + + private void setupTopCrumb() { + mCrumbs.clear(); + String name = getString(R.string.bookmarks); + mTopLevelLabel = (TextView) mCrumbs.pushView(name, false, + new Folder(name, mRootFolder)); + // To better match the other folders. + mTopLevelLabel.setCompoundDrawablePadding(6); + } + + private void onCurrentFolderFound() { + LoaderManager manager = getLoaderManager(); + if (mCurrentFolder != mRootFolder) { + // Since we're not in the root folder, change the selection to other + // folder now. The text will get changed once we select the correct + // folder. + mFolder.setSelectionIgnoringSelectionChange(mEditingFolder ? 1 : 2); + } else { + setShowBookmarkIcon(true); + if (!mEditingFolder) { + // Initially the "Bookmarks" folder should be showing, rather than + // the home screen. In the editing folder case, home screen is not + // an option, so "Bookmarks" folder is already at the top. + mFolder.setSelectionIgnoringSelectionChange(FolderSpinnerAdapter.ROOT_FOLDER); + } + } + // Find the contents of the current folder + manager.restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this); + } + + /** + * Runnable to save a bookmark, so it can be performed in its own thread. + */ + private class SaveBookmarkRunnable implements Runnable { + // FIXME: This should be an async task. + private Message mMessage; + private Context mContext; + public SaveBookmarkRunnable(Context ctx, Message msg) { + mContext = ctx.getApplicationContext(); + mMessage = msg; + } + public void run() { + // Unbundle bookmark data. + Bundle bundle = mMessage.getData(); + String title = bundle.getString(BrowserContract.Bookmarks.TITLE); + String url = bundle.getString(BrowserContract.Bookmarks.URL); + boolean invalidateThumbnail = bundle.getBoolean(REMOVE_THUMBNAIL); + Bitmap thumbnail = invalidateThumbnail ? null + : (Bitmap) bundle.getParcelable(BrowserContract.Bookmarks.THUMBNAIL); + String touchIconUrl = bundle.getString(TOUCH_ICON_URL); + + // Save to the bookmarks DB. + try { + final ContentResolver cr = getContentResolver(); + Bookmarks.addBookmark(AddBookmarkPage.this, false, url, + title, thumbnail, mCurrentFolder); + if (touchIconUrl != null) { + new DownloadTouchIcon(mContext, cr, url).execute(mTouchIconUrl); + } + mMessage.arg1 = 1; + } catch (IllegalStateException e) { + mMessage.arg1 = 0; + } + mMessage.sendToTarget(); + } + } + + private static class UpdateBookmarkTask extends AsyncTask<ContentValues, Void, Void> { + Context mContext; + Long mId; + + public UpdateBookmarkTask(Context context, long id) { + mContext = context.getApplicationContext(); + mId = id; + } + + @Override + protected Void doInBackground(ContentValues... params) { + if (params.length != 1) { + throw new IllegalArgumentException("No ContentValues provided!"); + } + Uri uri = ContentUris.withAppendedId(BookmarkUtils.getBookmarksUri(mContext), mId); + mContext.getContentResolver().update( + uri, + params[0], null, null); + return null; + } + } + + private void createHandler() { + if (mHandler == null) { + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case SAVE_BOOKMARK: + if (1 == msg.arg1) { + Toast.makeText(AddBookmarkPage.this, R.string.bookmark_saved, + Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(AddBookmarkPage.this, R.string.bookmark_not_saved, + Toast.LENGTH_LONG).show(); + } + break; + case TOUCH_ICON_DOWNLOADED: + Bundle b = msg.getData(); + sendBroadcast(BookmarkUtils.createAddToHomeIntent( + AddBookmarkPage.this, + b.getString(BrowserContract.Bookmarks.URL), + b.getString(BrowserContract.Bookmarks.TITLE), + (Bitmap) b.getParcelable(BrowserContract.Bookmarks.TOUCH_ICON), + (Bitmap) b.getParcelable(BrowserContract.Bookmarks.FAVICON))); + break; + case BOOKMARK_DELETED: + finish(); + break; + } + } + }; + } + } + + static void deleteDuplicateBookmark(final Context context, final long id) { + Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id); + context.getContentResolver().delete(uri, null, null); + } + + private void onSaveWithConfirm() { + String title = mTitle.getText().toString().trim(); + String unfilteredUrl = UrlUtils.fixUrl(mAddress.getText().toString()); + String url = unfilteredUrl.trim(); + Long id = mMap.getLong(BrowserContract.Bookmarks._ID); + int duplicateCount; + final ContentResolver cr = getContentResolver(); + + Cursor cursor = cr.query(BrowserContract.Bookmarks.CONTENT_URI, + BookmarksLoader.PROJECTION, + "( title = ? OR url = ? ) AND parent = ?", + new String[] { + title, url, Long.toString(mCurrentFolder) + }, + null); + + if (cursor == null) { + save(); + return; + } + + duplicateCount = cursor.getCount(); + if (duplicateCount <= 0) { + cursor.close(); + save(); + return; + } else { + try { + while (cursor.moveToNext()) { + mDuplicateId = cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID); + mDuplicateContext = AddBookmarkPage.this; + } + } catch (IllegalStateException e) { + e.printStackTrace(); + } finally { + if (cursor != null) + cursor.close(); + } + } + + if (mEditingExisting && duplicateCount == 1 && mDuplicateId == id) { + save(); + return; + } + + new AlertDialog.Builder(this) + .setTitle(getString(R.string.save_to_bookmarks_title)) + .setMessage(getString(R.string.overwrite_bookmark_msg)) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (mDuplicateContext == null) { + return; + } + deleteDuplicateBookmark(mDuplicateContext, mDuplicateId); + save(); + } + }) + .show(); + } + + /** + * Parse the data entered in the dialog and post a message to update the bookmarks database. + */ + boolean save() { + createHandler(); + + String title = mTitle.getText().toString().trim(); + String unfilteredUrl = UrlUtils.fixUrl(mAddress.getText().toString()); + + boolean emptyTitle = title.length() == 0; + boolean emptyUrl = unfilteredUrl.trim().length() == 0; + Resources r = getResources(); + if (emptyTitle || (emptyUrl && !mEditingFolder)) { + if (emptyTitle) { + mTitle.setError(r.getText(R.string.bookmark_needs_title)); + } + if (emptyUrl) { + mAddress.setError(r.getText(R.string.bookmark_needs_url)); + } + return false; + } + String url = unfilteredUrl.trim(); + if (!mEditingFolder) { + try { + // We allow bookmarks with a javascript: scheme, but these will in most cases + // fail URI parsing, so don't try it if that's the kind of bookmark we have. + + if (!url.toLowerCase().startsWith("javascript:")) { + URI uriObj = new URI(url); + String scheme = uriObj.getScheme(); + if (!Bookmarks.urlHasAcceptableScheme(url)) { + // If the scheme was non-null, let the user know that we + // can't save their bookmark. If it was null, we'll assume + // they meant http when we parse it in the WebAddress class. + if (scheme != null) { + mAddress.setError(r.getText(R.string.bookmark_cannot_save_url)); + return false; + } + WebAddress address; + try { + address = new WebAddress(unfilteredUrl); + } catch (ParseException e) { + throw new URISyntaxException("", ""); + } + if (address.getHost().length() == 0) { + throw new URISyntaxException("", ""); + } + url = address.toString(); + } + } + } catch (URISyntaxException e) { + mAddress.setError(r.getText(R.string.bookmark_url_not_valid)); + return false; + } + } + + if (mSaveToHomeScreen) { + mEditingExisting = false; + } + + boolean urlUnmodified = url.equals(mOriginalUrl); + + if (mEditingExisting) { + Long id = mMap.getLong(BrowserContract.Bookmarks._ID); + ContentValues values = new ContentValues(); + values.put(BrowserContract.Bookmarks.TITLE, title); + values.put(BrowserContract.Bookmarks.PARENT, mCurrentFolder); + if (!mEditingFolder) { + values.put(BrowserContract.Bookmarks.URL, url); + if (!urlUnmodified) { + values.putNull(BrowserContract.Bookmarks.THUMBNAIL); + } + } + if (values.size() > 0) { + new UpdateBookmarkTask(getApplicationContext(), id).execute(values); + } + setResult(RESULT_OK); + } else { + Bitmap thumbnail; + Bitmap favicon; + if (urlUnmodified) { + thumbnail = (Bitmap) mMap.getParcelable( + BrowserContract.Bookmarks.THUMBNAIL); + favicon = (Bitmap) mMap.getParcelable( + BrowserContract.Bookmarks.FAVICON); + } else { + thumbnail = null; + favicon = null; + } + + Bundle bundle = new Bundle(); + bundle.putString(BrowserContract.Bookmarks.TITLE, title); + bundle.putString(BrowserContract.Bookmarks.URL, url); + bundle.putParcelable(BrowserContract.Bookmarks.FAVICON, favicon); + + if (mSaveToHomeScreen) { + if (mTouchIconUrl != null && urlUnmodified) { + Message msg = Message.obtain(mHandler, + TOUCH_ICON_DOWNLOADED); + msg.setData(bundle); + DownloadTouchIcon icon = new DownloadTouchIcon(this, msg, + mMap.getString(USER_AGENT)); + icon.execute(mTouchIconUrl); + } else { + sendBroadcast(BookmarkUtils.createAddToHomeIntent(this, url, + title, null /*touchIcon*/, favicon)); + } + } else { + bundle.putParcelable(BrowserContract.Bookmarks.THUMBNAIL, thumbnail); + bundle.putBoolean(REMOVE_THUMBNAIL, !urlUnmodified); + bundle.putString(TOUCH_ICON_URL, mTouchIconUrl); + // Post a message to write to the DB. + Message msg = Message.obtain(mHandler, SAVE_BOOKMARK); + msg.setData(bundle); + // Start a new thread so as to not slow down the UI + Thread t = new Thread(new SaveBookmarkRunnable(getApplicationContext(), msg)); + t.start(); + } + setResult(RESULT_OK); + LogTag.logBookmarkAdded(url, "bookmarkview"); + } + finish(); + return true; + } + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, + long id) { + if (mAccountSpinner == parent) { + long root = mAccountAdapter.getItem(position).rootFolderId; + if (root != mRootFolder) { + onRootFolderFound(root); + mFolderAdapter.clearRecentFolder(); + } + } + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + // Don't care + } + + /* + * Class used as a proxy for the InputMethodManager to get to mFolderNamer + */ + public static class CustomListView extends ListView { + private EditText mEditText; + + public void addEditText(EditText editText) { + mEditText = editText; + } + + public CustomListView(Context context) { + super(context); + } + + public CustomListView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CustomListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean checkInputConnectionProxy(View view) { + return view == mEditText; + } + } + + static class AccountsLoader extends CursorLoader { + + static final String[] PROJECTION = new String[] { + Accounts.ACCOUNT_NAME, + Accounts.ACCOUNT_TYPE, + Accounts.ROOT_ID, + }; + + static final int COLUMN_INDEX_ACCOUNT_NAME = 0; + static final int COLUMN_INDEX_ACCOUNT_TYPE = 1; + static final int COLUMN_INDEX_ROOT_ID = 2; + + public AccountsLoader(Context context) { + super(context, Accounts.CONTENT_URI, PROJECTION, null, null, null); + } + + } + + public static class BookmarkAccount { + + private String mLabel; + String accountName, accountType; + public long rootFolderId; + + public BookmarkAccount(Context context, Cursor cursor) { + accountName = cursor.getString( + AccountsLoader.COLUMN_INDEX_ACCOUNT_NAME); + accountType = cursor.getString( + AccountsLoader.COLUMN_INDEX_ACCOUNT_TYPE); + rootFolderId = cursor.getLong( + AccountsLoader.COLUMN_INDEX_ROOT_ID); + mLabel = accountName; + if (TextUtils.isEmpty(mLabel)) { + mLabel = context.getString(R.string.local_bookmarks); + } + } + + @Override + public String toString() { + return mLabel; + } + } + + static class EditBookmarkInfo { + long id = -1; + long parentId = -1; + String parentTitle; + String title; + String accountName; + String accountType; + + long lastUsedId = -1; + String lastUsedTitle; + String lastUsedAccountName; + String lastUsedAccountType; + } + + static class EditBookmarkInfoLoader extends AsyncTaskLoader<EditBookmarkInfo> { + + private Context mContext; + private Bundle mMap; + + public EditBookmarkInfoLoader(Context context, Bundle bundle) { + super(context); + mContext = context.getApplicationContext(); + mMap = bundle; + } + + @Override + public EditBookmarkInfo loadInBackground() { + final ContentResolver cr = mContext.getContentResolver(); + EditBookmarkInfo info = new EditBookmarkInfo(); + Cursor c = null; + + try { + // First, let's lookup the bookmark (check for dupes, get needed info) + String url = mMap.getString(BrowserContract.Bookmarks.URL); + info.id = mMap.getLong(BrowserContract.Bookmarks._ID, -1); + boolean checkForDupe = mMap.getBoolean(CHECK_FOR_DUPE); + if (checkForDupe && info.id == -1 && !TextUtils.isEmpty(url)) { + c = cr.query(BrowserContract.Bookmarks.CONTENT_URI, + new String[] { BrowserContract.Bookmarks._ID}, + BrowserContract.Bookmarks.URL + "=?", + new String[] { url }, null); + if (c.getCount() == 1 && c.moveToFirst()) { + info.id = c.getLong(0); + } + c.close(); + } + if (info.id != -1) { + c = cr.query(ContentUris.withAppendedId( + BrowserContract.Bookmarks.CONTENT_URI, info.id), + new String[] { + BrowserContract.Bookmarks.PARENT, + BrowserContract.Bookmarks.ACCOUNT_NAME, + BrowserContract.Bookmarks.ACCOUNT_TYPE, + BrowserContract.Bookmarks.TITLE}, + null, null, null); + if (c.moveToFirst()) { + info.parentId = c.getLong(0); + info.accountName = c.getString(1); + info.accountType = c.getString(2); + info.title = c.getString(3); + } + c.close(); + c = cr.query(ContentUris.withAppendedId( + BrowserContract.Bookmarks.CONTENT_URI, info.parentId), + new String[] { + BrowserContract.Bookmarks.TITLE,}, + null, null, null); + if (c.moveToFirst()) { + info.parentTitle = c.getString(0); + } + c.close(); + } + + // Figure out the last used folder/account + c = cr.query(BrowserContract.Bookmarks.CONTENT_URI, + new String[] { + BrowserContract.Bookmarks.PARENT, + }, null, null, + BrowserContract.Bookmarks.DATE_MODIFIED + " DESC LIMIT 1"); + if (c.moveToFirst()) { + long parent = c.getLong(0); + c.close(); + c = cr.query(BrowserContract.Bookmarks.CONTENT_URI, + new String[] { + BrowserContract.Bookmarks.TITLE, + BrowserContract.Bookmarks.ACCOUNT_NAME, + BrowserContract.Bookmarks.ACCOUNT_TYPE}, + BrowserContract.Bookmarks._ID + "=?", new String[] { + Long.toString(parent)}, null); + if (c.moveToFirst()) { + info.lastUsedId = parent; + info.lastUsedTitle = c.getString(0); + info.lastUsedAccountName = c.getString(1); + info.lastUsedAccountType = c.getString(2); + } + c.close(); + } + } finally { + if (c != null) { + c.close(); + } + } + + return info; + } + + @Override + protected void onStartLoading() { + forceLoad(); + } + + } + +} |