• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
Folder(String name, 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 
getInputMethodManager()143     private InputMethodManager getInputMethodManager() {
144         return (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
145     }
146 
getUriForFolder(long folder)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
onTop(BreadCrumbView view, int level, Object data)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      */
setShowBookmarkIcon(boolean show)178     private void setShowBookmarkIcon(boolean show) {
179         Drawable drawable = show ? mHeaderIcon: null;
180         mTopLevelLabel.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
181     }
182 
183     @Override
onEditorAction(TextView v, int actionId, KeyEvent event)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 
switchToDefaultView(boolean changedFolder)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
onClick(View v)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
onSetSelection(long id)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      */
completeOrCancelFolderNaming(boolean cancel)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 
addFolderToCurrent(String name)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 
switchToFolderSelector()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 
descendInto(String foldername, long id)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 
setAccount(String accountName, String accountType)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
onCreateLoader(int id, Bundle args)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
onLoadFinished(Loader<Cursor> loader, Cursor cursor)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 
onLoaderReset(Loader<Cursor> loader)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      */
moveCursorToFolder(Cursor cursor, long folderToFind, int idIndex)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
onItemClick(AdapterView<?> parent, View view, int position, long id)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 
setShowFolderNamer(boolean show)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 {
FolderAdapter(Context context)570         public FolderAdapter(Context context) {
571             super(context, null);
572         }
573 
574         @Override
bindView(View view, Context context, Cursor cursor)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
newView(Context context, Cursor cursor, ViewGroup parent)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
isEmpty()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
onCreate(Bundle icicle)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 
showRemoveButton()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
onRootFolderFound(long root)710     private void onRootFolderFound(long root) {
711         mRootFolder = root;
712         mCurrentFolder = mRootFolder;
713         setupTopCrumb();
714         onCurrentFolderFound();
715     }
716 
setupTopCrumb()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 
onCurrentFolderFound()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;
SaveBookmarkRunnable(Context ctx, Message msg)753         public SaveBookmarkRunnable(Context ctx, Message msg) {
754             mContext = ctx.getApplicationContext();
755             mMessage = msg;
756         }
run()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 
UpdateBookmarkTask(Context context, long id)787         public UpdateBookmarkTask(Context context, long id) {
788             mContext = context.getApplicationContext();
789             mId = id;
790         }
791 
792         @Override
doInBackground(ContentValues... params)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 
createHandler()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      */
save()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
onItemSelected(AdapterView<?> parent, View view, int position, long id)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
onNothingSelected(AdapterView<?> parent)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 
addEditText(EditText editText)987         public void addEditText(EditText editText) {
988             mEditText = editText;
989         }
990 
CustomListView(Context context)991         public CustomListView(Context context) {
992             super(context);
993         }
994 
CustomListView(Context context, AttributeSet attrs)995         public CustomListView(Context context, AttributeSet attrs) {
996             super(context, attrs);
997         }
998 
CustomListView(Context context, AttributeSet attrs, int defStyle)999         public CustomListView(Context context, AttributeSet attrs, int defStyle) {
1000             super(context, attrs, defStyle);
1001         }
1002 
1003         @Override
checkInputConnectionProxy(View view)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 
AccountsLoader(Context context)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 
BookmarkAccount(Context context, Cursor cursor)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
toString()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 
EditBookmarkInfoLoader(Context context, Bundle bundle)1071         public EditBookmarkInfoLoader(Context context, Bundle bundle) {
1072             super(context);
1073             mContext = context.getApplicationContext();
1074             mMap = bundle;
1075         }
1076 
1077         @Override
loadInBackground()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
onStartLoading()1159         protected void onStartLoading() {
1160             forceLoad();
1161         }
1162 
1163     }
1164 
1165 }
1166