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 android.app.Activity;
20 import android.app.Fragment;
21 import android.app.LoaderManager;
22 import android.content.ClipData;
23 import android.content.ClipboardManager;
24 import android.content.ContentUris;
25 import android.content.Context;
26 import android.content.CursorLoader;
27 import android.content.Intent;
28 import android.content.Loader;
29 import android.content.SharedPreferences;
30 import android.content.res.Configuration;
31 import android.content.res.Resources;
32 import android.database.Cursor;
33 import android.graphics.Bitmap;
34 import android.graphics.BitmapFactory;
35 import android.graphics.BitmapFactory.Options;
36 import android.net.Uri;
37 import android.os.AsyncTask;
38 import android.os.Bundle;
39 import android.provider.BrowserContract;
40 import android.provider.BrowserContract.Accounts;
41 import android.view.ContextMenu;
42 import android.view.ContextMenu.ContextMenuInfo;
43 import android.view.LayoutInflater;
44 import android.view.MenuInflater;
45 import android.view.MenuItem;
46 import android.view.View;
47 import android.view.ViewGroup;
48 import android.widget.ExpandableListView;
49 import android.widget.ExpandableListView.OnChildClickListener;
50 import android.widget.Toast;
51 
52 import com.android.browser.provider.BrowserProvider2;
53 import com.android.browser.view.BookmarkExpandableView;
54 import com.android.browser.view.BookmarkExpandableView.BookmarkContextMenuInfo;
55 
56 import org.json.JSONException;
57 import org.json.JSONObject;
58 
59 import java.util.HashMap;
60 
61 interface BookmarksPageCallbacks {
62     // Return true if handled
onBookmarkSelected(Cursor c, boolean isFolder)63     boolean onBookmarkSelected(Cursor c, boolean isFolder);
64     // Return true if handled
onOpenInNewWindow(String... urls)65     boolean onOpenInNewWindow(String... urls);
66 }
67 
68 /**
69  *  View showing the user's bookmarks in the browser.
70  */
71 public class BrowserBookmarksPage extends Fragment implements View.OnCreateContextMenuListener,
72         LoaderManager.LoaderCallbacks<Cursor>, BreadCrumbView.Controller,
73         OnChildClickListener {
74 
75     public static class ExtraDragState {
76         public int childPosition;
77         public int groupPosition;
78     }
79 
80     static final String LOGTAG = "browser";
81 
82     static final int LOADER_ACCOUNTS = 1;
83     static final int LOADER_BOOKMARKS = 100;
84 
85     static final String EXTRA_DISABLE_WINDOW = "disable_new_window";
86     static final String PREF_GROUP_STATE = "bbp_group_state";
87 
88     static final String ACCOUNT_TYPE = "account_type";
89     static final String ACCOUNT_NAME = "account_name";
90 
91     BookmarksPageCallbacks mCallbacks;
92     View mRoot;
93     BookmarkExpandableView mGrid;
94     boolean mDisableNewWindow;
95     boolean mEnableContextMenu = true;
96     View mEmptyView;
97     View mHeader;
98     HashMap<Integer, BrowserBookmarksAdapter> mBookmarkAdapters = new HashMap<Integer, BrowserBookmarksAdapter>();
99     JSONObject mState;
100 
101     @Override
onCreateLoader(int id, Bundle args)102     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
103         if (id == LOADER_ACCOUNTS) {
104             return new AccountsLoader(getActivity());
105         } else if (id >= LOADER_BOOKMARKS) {
106             String accountType = args.getString(ACCOUNT_TYPE);
107             String accountName = args.getString(ACCOUNT_NAME);
108             BookmarksLoader bl = new BookmarksLoader(getActivity(),
109                     accountType, accountName);
110             return bl;
111         } else {
112             throw new UnsupportedOperationException("Unknown loader id " + id);
113         }
114     }
115 
116     @Override
onLoadFinished(Loader<Cursor> loader, Cursor cursor)117     public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
118         if (loader.getId() == LOADER_ACCOUNTS) {
119             LoaderManager lm = getLoaderManager();
120             int id = LOADER_BOOKMARKS;
121             while (cursor.moveToNext()) {
122                 String accountName = cursor.getString(0);
123                 String accountType = cursor.getString(1);
124                 Bundle args = new Bundle();
125                 args.putString(ACCOUNT_NAME, accountName);
126                 args.putString(ACCOUNT_TYPE, accountType);
127                 BrowserBookmarksAdapter adapter = new BrowserBookmarksAdapter(
128                         getActivity());
129                 mBookmarkAdapters.put(id, adapter);
130                 boolean expand = true;
131                 try {
132                     expand = mState.getBoolean(accountName != null ? accountName
133                             : BookmarkExpandableView.LOCAL_ACCOUNT_NAME);
134                 } catch (JSONException e) {} // no state for accountName
135                 mGrid.addAccount(accountName, adapter, expand);
136                 lm.restartLoader(id, args, this);
137                 id++;
138             }
139             // TODO: Figure out what a reload of these means
140             // Currently, a reload is triggered whenever bookmarks change
141             // This is less than ideal
142             // It also causes UI flickering as a new adapter is created
143             // instead of re-using an existing one when the account_name is the
144             // same.
145             // For now, this is a one-shot load
146             getLoaderManager().destroyLoader(LOADER_ACCOUNTS);
147         } else if (loader.getId() >= LOADER_BOOKMARKS) {
148             BrowserBookmarksAdapter adapter = mBookmarkAdapters.get(loader.getId());
149             adapter.changeCursor(cursor);
150         }
151     }
152 
153     @Override
onLoaderReset(Loader<Cursor> loader)154     public void onLoaderReset(Loader<Cursor> loader) {
155         if (loader.getId() >= LOADER_BOOKMARKS) {
156             BrowserBookmarksAdapter adapter = mBookmarkAdapters.get(loader.getId());
157             adapter.changeCursor(null);
158         }
159     }
160 
161     @Override
onContextItemSelected(MenuItem item)162     public boolean onContextItemSelected(MenuItem item) {
163         if (!(item.getMenuInfo() instanceof BookmarkContextMenuInfo)) {
164             return false;
165         }
166         BookmarkContextMenuInfo i = (BookmarkContextMenuInfo) item.getMenuInfo();
167         // If we have no menu info, we can't tell which item was selected.
168         if (i == null) {
169             return false;
170         }
171 
172         if (handleContextItem(item.getItemId(), i.groupPosition, i.childPosition)) {
173             return true;
174         }
175         return super.onContextItemSelected(item);
176     }
177 
handleContextItem(int itemId, int groupPosition, int childPosition)178     public boolean handleContextItem(int itemId, int groupPosition,
179             int childPosition) {
180         final Activity activity = getActivity();
181         BrowserBookmarksAdapter adapter = getChildAdapter(groupPosition);
182 
183         switch (itemId) {
184         case R.id.open_context_menu_id:
185             loadUrl(adapter, childPosition);
186             break;
187         case R.id.edit_context_menu_id:
188             editBookmark(adapter, childPosition);
189             break;
190         case R.id.shortcut_context_menu_id:
191             Cursor c = adapter.getItem(childPosition);
192             activity.sendBroadcast(createShortcutIntent(getActivity(), c));
193             break;
194         case R.id.delete_context_menu_id:
195             displayRemoveBookmarkDialog(adapter, childPosition);
196             break;
197         case R.id.new_window_context_menu_id:
198             openInNewWindow(adapter, childPosition);
199             break;
200         case R.id.share_link_context_menu_id: {
201             Cursor cursor = adapter.getItem(childPosition);
202             Controller.sharePage(activity,
203                     cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE),
204                     cursor.getString(BookmarksLoader.COLUMN_INDEX_URL),
205                     getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON),
206                     getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_THUMBNAIL));
207             break;
208         }
209         case R.id.copy_url_context_menu_id:
210             copy(getUrl(adapter, childPosition));
211             break;
212         case R.id.homepage_context_menu_id: {
213             BrowserSettings.getInstance().setHomePage(getUrl(adapter, childPosition));
214             Toast.makeText(activity, R.string.homepage_set, Toast.LENGTH_LONG).show();
215             break;
216         }
217         // Only for the Most visited page
218         case R.id.save_to_bookmarks_menu_id: {
219             Cursor cursor = adapter.getItem(childPosition);
220             String name = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
221             String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
222             // If the site is bookmarked, the item becomes remove from
223             // bookmarks.
224             Bookmarks.removeFromBookmarks(activity, activity.getContentResolver(), url, name);
225             break;
226         }
227         default:
228             return false;
229         }
230         return true;
231     }
232 
getBitmap(Cursor cursor, int columnIndex)233     static Bitmap getBitmap(Cursor cursor, int columnIndex) {
234         return getBitmap(cursor, columnIndex, null);
235     }
236 
237     static ThreadLocal<Options> sOptions = new ThreadLocal<Options>() {
238         @Override
239         protected Options initialValue() {
240             return new Options();
241         };
242     };
getBitmap(Cursor cursor, int columnIndex, Bitmap inBitmap)243     static Bitmap getBitmap(Cursor cursor, int columnIndex, Bitmap inBitmap) {
244         byte[] data = cursor.getBlob(columnIndex);
245         if (data == null) {
246             return null;
247         }
248         Options opts = sOptions.get();
249         opts.inBitmap = inBitmap;
250         opts.inSampleSize = 1;
251         opts.inScaled = false;
252         try {
253             return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
254         } catch (IllegalArgumentException ex) {
255             // Failed to re-use bitmap, create a new one
256             return BitmapFactory.decodeByteArray(data, 0, data.length);
257         }
258     }
259 
260     private MenuItem.OnMenuItemClickListener mContextItemClickListener =
261             new MenuItem.OnMenuItemClickListener() {
262         @Override
263         public boolean onMenuItemClick(MenuItem item) {
264             return onContextItemSelected(item);
265         }
266     };
267 
268     @Override
onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)269     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
270         BookmarkContextMenuInfo info = (BookmarkContextMenuInfo) menuInfo;
271         BrowserBookmarksAdapter adapter = getChildAdapter(info.groupPosition);
272         Cursor cursor = adapter.getItem(info.childPosition);
273         if (!canEdit(cursor)) {
274             return;
275         }
276         boolean isFolder
277                 = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
278 
279         final Activity activity = getActivity();
280         MenuInflater inflater = activity.getMenuInflater();
281         inflater.inflate(R.menu.bookmarkscontext, menu);
282         if (isFolder) {
283             menu.setGroupVisible(R.id.FOLDER_CONTEXT_MENU, true);
284         } else {
285             menu.setGroupVisible(R.id.BOOKMARK_CONTEXT_MENU, true);
286             if (mDisableNewWindow) {
287                 menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
288             }
289         }
290         BookmarkItem header = new BookmarkItem(activity);
291         header.setEnableScrolling(true);
292         populateBookmarkItem(cursor, header, isFolder);
293         menu.setHeaderView(header);
294 
295         int count = menu.size();
296         for (int i = 0; i < count; i++) {
297             menu.getItem(i).setOnMenuItemClickListener(mContextItemClickListener);
298         }
299     }
300 
canEdit(Cursor c)301     boolean canEdit(Cursor c) {
302         int type = c.getInt(BookmarksLoader.COLUMN_INDEX_TYPE);
303         return type == BrowserContract.Bookmarks.BOOKMARK_TYPE_BOOKMARK
304                 || type == BrowserContract.Bookmarks.BOOKMARK_TYPE_FOLDER;
305     }
306 
populateBookmarkItem(Cursor cursor, BookmarkItem item, boolean isFolder)307     private void populateBookmarkItem(Cursor cursor, BookmarkItem item, boolean isFolder) {
308         item.setName(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
309         if (isFolder) {
310             item.setUrl(null);
311             Bitmap bitmap =
312                 BitmapFactory.decodeResource(getResources(), R.drawable.ic_folder_holo_dark);
313             item.setFavicon(bitmap);
314             new LookupBookmarkCount(getActivity(), item)
315                     .execute(cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
316         } else {
317             String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
318             item.setUrl(url);
319             Bitmap bitmap = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
320             item.setFavicon(bitmap);
321         }
322     }
323 
324     /**
325      *  Create a new BrowserBookmarksPage.
326      */
327     @Override
onCreate(Bundle icicle)328     public void onCreate(Bundle icicle) {
329         super.onCreate(icicle);
330         SharedPreferences prefs = BrowserSettings.getInstance().getPreferences();
331         try {
332             mState = new JSONObject(prefs.getString(PREF_GROUP_STATE, "{}"));
333         } catch (JSONException e) {
334             // Parse failed, clear preference and start with empty state
335             prefs.edit().remove(PREF_GROUP_STATE).apply();
336             mState = new JSONObject();
337         }
338         Bundle args = getArguments();
339         mDisableNewWindow = args == null ? false : args.getBoolean(EXTRA_DISABLE_WINDOW, false);
340         setHasOptionsMenu(true);
341         if (mCallbacks == null && getActivity() instanceof CombinedBookmarksCallbacks) {
342             mCallbacks = new CombinedBookmarksCallbackWrapper(
343                     (CombinedBookmarksCallbacks) getActivity());
344         }
345     }
346 
347     @Override
onPause()348     public void onPause() {
349         super.onPause();
350         try {
351             mState = mGrid.saveGroupState();
352             // Save state
353             SharedPreferences prefs = BrowserSettings.getInstance().getPreferences();
354             prefs.edit()
355                     .putString(PREF_GROUP_STATE, mState.toString())
356                     .apply();
357         } catch (JSONException e) {
358             // Not critical, ignore
359         }
360     }
361 
362     private static class CombinedBookmarksCallbackWrapper
363             implements BookmarksPageCallbacks {
364 
365         private CombinedBookmarksCallbacks mCombinedCallback;
366 
CombinedBookmarksCallbackWrapper(CombinedBookmarksCallbacks cb)367         private CombinedBookmarksCallbackWrapper(CombinedBookmarksCallbacks cb) {
368             mCombinedCallback = cb;
369         }
370 
371         @Override
onOpenInNewWindow(String... urls)372         public boolean onOpenInNewWindow(String... urls) {
373             mCombinedCallback.openInNewTab(urls);
374             return true;
375         }
376 
377         @Override
onBookmarkSelected(Cursor c, boolean isFolder)378         public boolean onBookmarkSelected(Cursor c, boolean isFolder) {
379             if (isFolder) {
380                 return false;
381             }
382             mCombinedCallback.openUrl(BrowserBookmarksPage.getUrl(c));
383             return true;
384         }
385     };
386 
387     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)388     public View onCreateView(LayoutInflater inflater, ViewGroup container,
389             Bundle savedInstanceState) {
390         mRoot = inflater.inflate(R.layout.bookmarks, container, false);
391         mEmptyView = mRoot.findViewById(android.R.id.empty);
392 
393         mGrid = (BookmarkExpandableView) mRoot.findViewById(R.id.grid);
394         mGrid.setOnChildClickListener(this);
395         mGrid.setColumnWidthFromLayout(R.layout.bookmark_thumbnail);
396         mGrid.setBreadcrumbController(this);
397         setEnableContextMenu(mEnableContextMenu);
398 
399         // Start the loaders
400         LoaderManager lm = getLoaderManager();
401         lm.restartLoader(LOADER_ACCOUNTS, null, this);
402 
403         return mRoot;
404     }
405 
406     @Override
onDestroyView()407     public void onDestroyView() {
408         super.onDestroyView();
409         mGrid.setBreadcrumbController(null);
410         mGrid.clearAccounts();
411         LoaderManager lm = getLoaderManager();
412         lm.destroyLoader(LOADER_ACCOUNTS);
413         for (int id : mBookmarkAdapters.keySet()) {
414             lm.destroyLoader(id);
415         }
416         mBookmarkAdapters.clear();
417     }
418 
getChildAdapter(int groupPosition)419     private BrowserBookmarksAdapter getChildAdapter(int groupPosition) {
420         return mGrid.getChildAdapter(groupPosition);
421     }
422 
getBreadCrumbs(int groupPosition)423     private BreadCrumbView getBreadCrumbs(int groupPosition) {
424         return mGrid.getBreadCrumbs(groupPosition);
425     }
426 
427     @Override
onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id)428     public boolean onChildClick(ExpandableListView parent, View v,
429             int groupPosition, int childPosition, long id) {
430         BrowserBookmarksAdapter adapter = getChildAdapter(groupPosition);
431         Cursor cursor = adapter.getItem(childPosition);
432         boolean isFolder = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
433         if (mCallbacks != null &&
434                 mCallbacks.onBookmarkSelected(cursor, isFolder)) {
435             return true;
436         }
437 
438         if (isFolder) {
439             String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
440             Uri uri = ContentUris.withAppendedId(
441                     BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, id);
442             BreadCrumbView crumbs = getBreadCrumbs(groupPosition);
443             if (crumbs != null) {
444                 // update crumbs
445                 crumbs.pushView(title, uri);
446                 crumbs.setVisibility(View.VISIBLE);
447             }
448             loadFolder(groupPosition, uri);
449         }
450         return true;
451     }
452 
createShortcutIntent(Context context, Cursor cursor)453     /* package */ static Intent createShortcutIntent(Context context, Cursor cursor) {
454         String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
455         String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
456         Bitmap touchIcon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_TOUCH_ICON);
457         Bitmap favicon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
458         return BookmarkUtils.createAddToHomeIntent(context, url, title, touchIcon, favicon);
459     }
460 
loadUrl(BrowserBookmarksAdapter adapter, int position)461     private void loadUrl(BrowserBookmarksAdapter adapter, int position) {
462         if (mCallbacks != null && adapter != null) {
463             mCallbacks.onBookmarkSelected(adapter.getItem(position), false);
464         }
465     }
466 
openInNewWindow(BrowserBookmarksAdapter adapter, int position)467     private void openInNewWindow(BrowserBookmarksAdapter adapter, int position) {
468         if (mCallbacks != null) {
469             Cursor c = adapter.getItem(position);
470             boolean isFolder = c.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) == 1;
471             if (isFolder) {
472                 long id = c.getLong(BookmarksLoader.COLUMN_INDEX_ID);
473                 new OpenAllInTabsTask(id).execute();
474             } else {
475                 mCallbacks.onOpenInNewWindow(BrowserBookmarksPage.getUrl(c));
476             }
477         }
478     }
479 
480     class OpenAllInTabsTask extends AsyncTask<Void, Void, Cursor> {
481         long mFolderId;
OpenAllInTabsTask(long id)482         public OpenAllInTabsTask(long id) {
483             mFolderId = id;
484         }
485 
486         @Override
doInBackground(Void... params)487         protected Cursor doInBackground(Void... params) {
488             Context c = getActivity();
489             if (c == null) return null;
490             return c.getContentResolver().query(BookmarkUtils.getBookmarksUri(c),
491                     BookmarksLoader.PROJECTION, BrowserContract.Bookmarks.PARENT + "=?",
492                     new String[] { Long.toString(mFolderId) }, null);
493         }
494 
495         @Override
onPostExecute(Cursor result)496         protected void onPostExecute(Cursor result) {
497             if (mCallbacks != null && result.getCount() > 0) {
498                 String[] urls = new String[result.getCount()];
499                 int i = 0;
500                 while (result.moveToNext()) {
501                     urls[i++] = BrowserBookmarksPage.getUrl(result);
502                 }
503                 mCallbacks.onOpenInNewWindow(urls);
504             }
505         }
506 
507     }
508 
editBookmark(BrowserBookmarksAdapter adapter, int position)509     private void editBookmark(BrowserBookmarksAdapter adapter, int position) {
510         Intent intent = new Intent(getActivity(), AddBookmarkPage.class);
511         Cursor cursor = adapter.getItem(position);
512         Bundle item = new Bundle();
513         item.putString(BrowserContract.Bookmarks.TITLE,
514                 cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
515         item.putString(BrowserContract.Bookmarks.URL,
516                 cursor.getString(BookmarksLoader.COLUMN_INDEX_URL));
517         byte[] data = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_FAVICON);
518         if (data != null) {
519             item.putParcelable(BrowserContract.Bookmarks.FAVICON,
520                     BitmapFactory.decodeByteArray(data, 0, data.length));
521         }
522         item.putLong(BrowserContract.Bookmarks._ID,
523                 cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
524         item.putLong(BrowserContract.Bookmarks.PARENT,
525                 cursor.getLong(BookmarksLoader.COLUMN_INDEX_PARENT));
526         intent.putExtra(AddBookmarkPage.EXTRA_EDIT_BOOKMARK, item);
527         intent.putExtra(AddBookmarkPage.EXTRA_IS_FOLDER,
528                 cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) == 1);
529         startActivity(intent);
530     }
531 
displayRemoveBookmarkDialog(BrowserBookmarksAdapter adapter, int position)532     private void displayRemoveBookmarkDialog(BrowserBookmarksAdapter adapter,
533             int position) {
534         // Put up a dialog asking if the user really wants to
535         // delete the bookmark
536         Cursor cursor = adapter.getItem(position);
537         long id = cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID);
538         String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
539         Context context = getActivity();
540         BookmarkUtils.displayRemoveBookmarkDialog(id, title, context, null);
541     }
542 
getUrl(BrowserBookmarksAdapter adapter, int position)543     private String getUrl(BrowserBookmarksAdapter adapter, int position) {
544         return getUrl(adapter.getItem(position));
545     }
546 
getUrl(Cursor c)547     /* package */ static String getUrl(Cursor c) {
548         return c.getString(BookmarksLoader.COLUMN_INDEX_URL);
549     }
550 
copy(CharSequence text)551     private void copy(CharSequence text) {
552         ClipboardManager cm = (ClipboardManager) getActivity().getSystemService(
553                 Context.CLIPBOARD_SERVICE);
554         cm.setPrimaryClip(ClipData.newRawUri(null, Uri.parse(text.toString())));
555     }
556 
557     @Override
onConfigurationChanged(Configuration newConfig)558     public void onConfigurationChanged(Configuration newConfig) {
559         super.onConfigurationChanged(newConfig);
560         Resources res = getActivity().getResources();
561         mGrid.setColumnWidthFromLayout(R.layout.bookmark_thumbnail);
562         int paddingTop = (int) res.getDimension(R.dimen.combo_paddingTop);
563         mRoot.setPadding(0, paddingTop, 0, 0);
564         getActivity().invalidateOptionsMenu();
565     }
566 
567     /**
568      * BreadCrumb controller callback
569      */
570     @Override
onTop(BreadCrumbView view, int level, Object data)571     public void onTop(BreadCrumbView view, int level, Object data) {
572         int groupPosition = (Integer) view.getTag(R.id.group_position);
573         Uri uri = (Uri) data;
574         if (uri == null) {
575             // top level
576             uri = BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER;
577         }
578         loadFolder(groupPosition, uri);
579         if (level <= 1) {
580             view.setVisibility(View.GONE);
581         } else {
582             view.setVisibility(View.VISIBLE);
583         }
584     }
585 
586     /**
587      * @param uri
588      */
loadFolder(int groupPosition, Uri uri)589     private void loadFolder(int groupPosition, Uri uri) {
590         LoaderManager manager = getLoaderManager();
591         // This assumes groups are ordered the same as loaders
592         BookmarksLoader loader = (BookmarksLoader) ((Loader<?>)
593                 manager.getLoader(LOADER_BOOKMARKS + groupPosition));
594         loader.setUri(uri);
595         loader.forceLoad();
596     }
597 
setCallbackListener(BookmarksPageCallbacks callbackListener)598     public void setCallbackListener(BookmarksPageCallbacks callbackListener) {
599         mCallbacks = callbackListener;
600     }
601 
setEnableContextMenu(boolean enable)602     public void setEnableContextMenu(boolean enable) {
603         mEnableContextMenu = enable;
604         if (mGrid != null) {
605             if (mEnableContextMenu) {
606                 registerForContextMenu(mGrid);
607             } else {
608                 unregisterForContextMenu(mGrid);
609                 mGrid.setLongClickable(false);
610             }
611         }
612     }
613 
614     private static class LookupBookmarkCount extends AsyncTask<Long, Void, Integer> {
615         Context mContext;
616         BookmarkItem mHeader;
617 
LookupBookmarkCount(Context context, BookmarkItem header)618         public LookupBookmarkCount(Context context, BookmarkItem header) {
619             mContext = context.getApplicationContext();
620             mHeader = header;
621         }
622 
623         @Override
doInBackground(Long... params)624         protected Integer doInBackground(Long... params) {
625             if (params.length != 1) {
626                 throw new IllegalArgumentException("Missing folder id!");
627             }
628             Uri uri = BookmarkUtils.getBookmarksUri(mContext);
629             Cursor c = null;
630             try {
631                 c = mContext.getContentResolver().query(uri,
632                         null, BrowserContract.Bookmarks.PARENT + "=?",
633                         new String[] {params[0].toString()}, null);
634 
635                 return c.getCount();
636             } finally {
637                 if ( c != null) {
638                     c.close();
639                 }
640             }
641         }
642 
643         @Override
onPostExecute(Integer result)644         protected void onPostExecute(Integer result) {
645             if (result > 0) {
646                 mHeader.setUrl(mContext.getString(R.string.contextheader_folder_bookmarkcount,
647                         result));
648             } else if (result == 0) {
649                 mHeader.setUrl(mContext.getString(R.string.contextheader_folder_empty));
650             }
651         }
652     }
653 
654     static class AccountsLoader extends CursorLoader {
655 
656         static String[] ACCOUNTS_PROJECTION = new String[] {
657             Accounts.ACCOUNT_NAME,
658             Accounts.ACCOUNT_TYPE
659         };
660 
AccountsLoader(Context context)661         public AccountsLoader(Context context) {
662             super(context, Accounts.CONTENT_URI
663                     .buildUpon()
664                     .appendQueryParameter(BrowserProvider2.PARAM_ALLOW_EMPTY_ACCOUNTS, "false")
665                     .build(),
666                     ACCOUNTS_PROJECTION, null, null, null);
667         }
668 
669     }
670 }
671