Bug 887982: BookmarksListView should use cursor loaders. [r=lucasr]
authorSriram Ramasubramanian <sriram@mozilla.com>
Thu, 27 Jun 2013 14:39:33 -0700
changeset 143371 b3b119e93c0722d32c530071d90074f0853988bf
parent 143370 db454c5472bfea1d93d85e35e782448a78e8a1b4
child 143372 d51cb8a8012064c4cce886f34cf67375882804b5
push id25130
push userlrocha@mozilla.com
push dateWed, 21 Aug 2013 09:41:27 +0000
treeherdermozilla-central@b2486721572e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslucasr
bugs887982
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 887982: BookmarksListView should use cursor loaders. [r=lucasr]
mobile/android/base/home/BookmarksListView.java
mobile/android/base/home/BookmarksPage.java
--- a/mobile/android/base/home/BookmarksListView.java
+++ b/mobile/android/base/home/BookmarksListView.java
@@ -2,25 +2,22 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserDB.URLColumns;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.util.GamepadUtils;
-import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
 import android.database.Cursor;
-import android.os.AsyncTask;
 import android.util.AttributeSet;
 import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
@@ -32,28 +29,37 @@ import java.util.LinkedList;
 /**
  * A ListView of bookmarks.
  */
 public class BookmarksListView extends HomeListView
                                implements AdapterView.OnItemClickListener{
     
     public static final String LOGTAG = "GeckoBookmarksListView";
 
+    // A listener that knows how to refresh the list for a given folder id.
+    // This is usually implemented by the enclosing fragment/activity.
+    public static interface OnRefreshFolderListener {
+
+        // The folder id to refresh the list with.
+        public void onRefreshFolder(int folderId);
+
+    }
+
     // A cursor based adapter.
     private BookmarksListAdapter mCursorAdapter = null;
 
-    // A background task to query the db.
-    private BookmarksQueryTask mQueryTask = null;
-
     // The last motion event that was intercepted.
     private MotionEvent mMotionEvent;
 
     // The default touch slop.
     private int mTouchSlop;
 
+    // Refresh folder listener.
+    private OnRefreshFolderListener mListener;
+
     public BookmarksListView(Context context) {
         this(context, null);
     }
 
     public BookmarksListView(Context context, AttributeSet attrs) {
         this(context, attrs, android.R.attr.listViewStyle);
     }
 
@@ -69,39 +75,23 @@ public class BookmarksListView extends H
         super.onAttachedToWindow();
 
         // Intialize the adapter.
         mCursorAdapter = new BookmarksListAdapter(getContext(), null);
         setAdapter(mCursorAdapter);
 
         setOnItemClickListener(this);
         setOnKeyListener(GamepadUtils.getListItemClickDispatcher());
-
-        mQueryTask = new BookmarksQueryTask();
-        mQueryTask.execute();
     }
 
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-
-        // Can't use getters for adapter. It will create one if null.
-        if (mCursorAdapter != null) {
-            final Cursor cursor = mCursorAdapter.getCursor();
-            mCursorAdapter = null;
-
-            // Gingerbread locks the DB when closing a cursor, so do it in the background.
-            ThreadUtils.postToBackgroundThread(new Runnable() {
-                @Override
-                public void run() {
-                    if (cursor != null && !cursor.isClosed())
-                        cursor.close();
-                }
-            });
-        }
+        mCursorAdapter = null;
+        mListener = null;
     }
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
         switch(event.getAction() & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
                 // Store the event by obtaining a copy.
                 mMotionEvent = MotionEvent.obtain(event);
@@ -171,22 +161,23 @@ public class BookmarksListView extends H
             final String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
             OnUrlOpenListener listener = getOnUrlOpenListener();
             if (listener != null) {
                 listener.onUrlOpen(url);
             }
         }
     }
 
-    private void refreshListWithCursor(Cursor cursor) {
+    public void refreshFromCursor(Cursor cursor) {
         // This will update the cursorAdapter to use the new one if it already exists.
-        mCursorAdapter.changeCursor(cursor);
+        mCursorAdapter.swapCursor(cursor);
+    }
 
-        // Reset the task.
-        mQueryTask = null;
+    public void setOnRefreshFolderListener(OnRefreshFolderListener listener) {
+        mListener = listener;
     }
 
     /**
      * Adapter to back the ListView with a list of bookmarks.
      */
     private class BookmarksListAdapter extends CursorAdapter {
         private static final int VIEW_TYPE_ITEM = 0;
         private static final int VIEW_TYPE_FOLDER = 1;
@@ -205,24 +196,19 @@ public class BookmarksListView extends H
 
             // Add the root folder to the stack
             Pair<Integer, String> rootFolder = new Pair<Integer, String>(Bookmarks.FIXED_ROOT_ID, "");
             mParentStack.addFirst(rootFolder);
         }
 
         // Refresh the current folder by executing a new task.
         private void refreshCurrentFolder() {
-            // Cancel any pre-existing async refresh tasks
-            if (mQueryTask != null) {
-                mQueryTask.cancel(false);
+            if (mListener != null) {
+                mListener.onRefreshFolder(mParentStack.peek().first);
             }
-
-            final Pair<Integer, String> folderPair = mParentStack.getFirst();
-            mQueryTask = new BookmarksQueryTask();
-            mQueryTask.execute(folderPair.first);
         }
 
         /**
          * Moves to parent folder, if one exists.
          */
         public void moveToParentFolder() {
             // If we're already at the root, we can't move to a parent folder
             if (mParentStack.size() != 1) {
@@ -373,30 +359,9 @@ public class BookmarksListView extends H
                 resId = R.layout.home_item_row;
             } else {
                 resId = R.layout.bookmark_folder_row;
             }
 
             return LayoutInflater.from(parent.getContext()).inflate(resId, null);
         }
     }
-
-    /**
-     * AsyncTask to query the DB for bookmarks.
-     */
-    private class BookmarksQueryTask extends AsyncTask<Integer, Void, Cursor> {
-        @Override
-        protected Cursor doInBackground(Integer... folderIds) {
-            int folderId = Bookmarks.FIXED_ROOT_ID;
-
-            if (folderIds.length != 0) {
-                folderId = folderIds[0].intValue();
-            }
-
-            return BrowserDB.getBookmarksInFolder(getContext().getContentResolver(), folderId);
-        }
-
-        @Override
-        protected void onPostExecute(final Cursor cursor) {
-            refreshListWithCursor(cursor);
-        }
-    }
 }
--- a/mobile/android/base/home/BookmarksPage.java
+++ b/mobile/android/base/home/BookmarksPage.java
@@ -1,37 +1,53 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract.Bookmarks;
+import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.home.BookmarksListView.OnRefreshFolderListener;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 
 import android.app.Activity;
+import android.content.Context;
 import android.content.res.Configuration;
+import android.database.Cursor;
 import android.os.Bundle;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.Loader;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
 /**
  * A page in about:home that displays a ListView of bookmarks.
  */
 public class BookmarksPage extends HomeFragment {
     public static final String LOGTAG = "GeckoBookmarksPage";
 
+    // Cursor loader ID for list of bookmarks.
+    private static final int BOOKMARKS_LIST_LOADER_ID = 0;
+
+    // Key for bookmarks folder id.
+    private static final String BOOKMARKS_FOLDER_KEY = "folder_id";
+
     // List of bookmarks.
     private BookmarksListView mList;
 
     // Grid of top bookmarks.
     private TopBookmarksView mTopBookmarks;
 
+    // Callback for loaders.
+    private CursorLoaderCallbacks mLoaderCallbacks;
+
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         BookmarksListView list = (BookmarksListView) inflater.inflate(R.layout.home_bookmarks_page, container, false);
 
         mTopBookmarks = new TopBookmarksView(getActivity());
         list.addHeaderView(mTopBookmarks);
 
         return list;
@@ -46,23 +62,43 @@ public class BookmarksPage extends HomeF
             listener = (OnUrlOpenListener) getActivity();
         } catch (ClassCastException e) {
             throw new ClassCastException(getActivity().toString()
                     + " must implement HomePager.OnUrlOpenListener");
         }
 
         mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
         mList.setOnUrlOpenListener(listener);
+        mList.setOnRefreshFolderListener(new OnRefreshFolderListener() {
+            @Override
+            public void onRefreshFolder(int folderId) {
+                // Restart the loader with folder as the argument.
+                Bundle bundle = new Bundle();
+                bundle.putInt(BOOKMARKS_FOLDER_KEY, folderId);
+                getLoaderManager().restartLoader(BOOKMARKS_LIST_LOADER_ID, bundle, mLoaderCallbacks);
+            }
+        });
 
         registerForContextMenu(mList);
 
         mTopBookmarks.setOnUrlOpenListener(listener);
     }
 
     @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Create callbacks before the initial loader is started.
+        mLoaderCallbacks = new CursorLoaderCallbacks();
+
+        // Reconnect to the loader only if present.
+        getLoaderManager().initLoader(BOOKMARKS_LIST_LOADER_ID, null, mLoaderCallbacks);
+    }
+
+    @Override
     public void onDestroyView() {
         mList = null;
         super.onDestroyView();
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -77,9 +113,74 @@ public class BookmarksPage extends HomeF
         // onResume().
         if (isVisible()) {
             getFragmentManager().beginTransaction()
                                 .detach(this)
                                 .attach(this)
                                 .commitAllowingStateLoss();
         }
     }
+
+    /**
+     * Loader for the list for bookmarks.
+     */
+    private static class BookmarksLoader extends SimpleCursorLoader {
+        private final int mFolderId;
+
+        public BookmarksLoader(Context context) {
+            this(context, Bookmarks.FIXED_ROOT_ID);
+        }
+
+        public BookmarksLoader(Context context, int folderId) {
+            super(context);
+            mFolderId = folderId;
+        }
+
+        @Override
+        public Cursor loadCursor() {
+            return BrowserDB.getBookmarksInFolder(getContext().getContentResolver(), mFolderId);
+        }
+    }
+
+    /**
+     * Loader callbacks for the LoaderManager of this fragment.
+     */
+    private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
+        @Override
+        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+            switch(id) {
+                case BOOKMARKS_LIST_LOADER_ID: {
+                    if (args == null) {
+                        return new BookmarksLoader(getActivity());
+                    } else {
+                        return new BookmarksLoader(getActivity(), args.getInt(BOOKMARKS_FOLDER_KEY));
+                    }
+                }
+            }
+
+            return null;
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
+            final int loaderId = loader.getId();
+            switch(loaderId) {
+                case BOOKMARKS_LIST_LOADER_ID: {
+                    mList.refreshFromCursor(c);
+                    break;
+                }
+            }
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Cursor> loader) {
+            final int loaderId = loader.getId();
+            switch(loaderId) {
+                case BOOKMARKS_LIST_LOADER_ID: {
+                    if (mList != null) {
+                        mList.refreshFromCursor(null);
+                    }
+                    break;
+                }
+            }
+        }
+    }
 }