Bug 994734 - Centralize change notification for dataset cursors. r=margaret a=lsblakk
authorLucas Rocha <lucasr@mozilla.com>
Wed, 23 Apr 2014 15:22:33 +0100
changeset 192133 d5a4d1cb3de43aacdc8c04d768d5180af92880ed
parent 192132 4e17bb626ec1fdf6041bfc035964a30c744ceb07
child 192134 41b3780cc1d36bb31aa163fb8a2deec7965b9beb
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret, lsblakk
bugs994734
milestone30.0a2
Bug 994734 - Centralize change notification for dataset cursors. r=margaret a=lsblakk * * * Bug 994734 - Rename PanelManager to PanelInfoManager (r=margaret) * * * Bug 994734 - Rename HomeConfigInvalidator to HomePanelsManager (r=margaret) * * * Bug 994734 - Pass dataset id as query argument in HomeProvider (r=margaret) * * * Bug 994734 - Set cursor notification URI in HomeProvider (r=margaret) * * * Bug 994734 - Handle dataset refreshes in HomePanelsManager (r=margaret)
mobile/android/base/BrowserApp.java
mobile/android/base/GeckoApplication.java
mobile/android/base/db/BrowserContract.java
mobile/android/base/db/HomeProvider.java
mobile/android/base/home/DynamicPanel.java
mobile/android/base/home/HomeConfigInvalidator.java
mobile/android/base/home/HomeConfigPrefsBackend.java
mobile/android/base/home/HomePanelPicker.java
mobile/android/base/home/HomePanelsManager.java
mobile/android/base/home/PanelInfoManager.java
mobile/android/base/home/PanelManager.java
mobile/android/base/moz.build
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -33,17 +33,17 @@ import org.mozilla.gecko.gfx.GeckoLayerC
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerMarginsAnimator;
 import org.mozilla.gecko.health.BrowserHealthRecorder;
 import org.mozilla.gecko.health.BrowserHealthReporter;
 import org.mozilla.gecko.health.HealthRecorder;
 import org.mozilla.gecko.health.SessionInformation;
 import org.mozilla.gecko.home.BrowserSearch;
 import org.mozilla.gecko.home.HomeBanner;
-import org.mozilla.gecko.home.HomeConfigInvalidator;
+import org.mozilla.gecko.home.HomePanelsManager;
 import org.mozilla.gecko.home.HomePager;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.SearchEngine;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuItem;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.prompts.Prompt;
 import org.mozilla.gecko.prompts.PromptListItem;
@@ -1689,17 +1689,17 @@ abstract public class BrowserApp extends
             hideHomePager();
         }
     }
 
     @Override
     public void onLocaleReady(final String locale) {
         super.onLocaleReady(locale);
 
-        HomeConfigInvalidator.getInstance().onLocaleReady(locale);
+        HomePanelsManager.getInstance().onLocaleReady(locale);
 
         if (mMenu != null) {
             mMenu.clear();
             onCreateOptionsMenu(mMenu);
         }
     }
 
     private void showHomePager(String panelId) {
--- a/mobile/android/base/GeckoApplication.java
+++ b/mobile/android/base/GeckoApplication.java
@@ -1,17 +1,17 @@
 /* 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;
 
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.home.HomeConfigInvalidator;
+import org.mozilla.gecko.home.HomePanelsManager;
 import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Application;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -93,17 +93,17 @@ public class GeckoApplication extends Ap
     }
 
     @Override
     public void onCreate() {
         HardwareUtils.init(getApplicationContext());
         Clipboard.init(getApplicationContext());
         FilePicker.init(getApplicationContext());
         GeckoLoader.loadMozGlue();
-        HomeConfigInvalidator.getInstance().init(getApplicationContext());
+        HomePanelsManager.getInstance().init(getApplicationContext());
         super.onCreate();
     }
 
     public boolean isApplicationInBackground() {
         return mInBackground;
     }
 
     public LightweightTheme getLightweightTheme() {
--- a/mobile/android/base/db/BrowserContract.java
+++ b/mobile/android/base/db/BrowserContract.java
@@ -34,16 +34,17 @@ public class BrowserContract {
     public static final String PARAM_PROFILE_PATH = "profilePath";
     public static final String PARAM_LIMIT = "limit";
     public static final String PARAM_IS_SYNC = "sync";
     public static final String PARAM_SHOW_DELETED = "show_deleted";
     public static final String PARAM_IS_TEST = "test";
     public static final String PARAM_INSERT_IF_NEEDED = "insert_if_needed";
     public static final String PARAM_INCREMENT_VISITS = "increment_visits";
     public static final String PARAM_EXPIRE_PRIORITY = "priority";
+    public static final String PARAM_DATASET_ID = "dataset_id";
 
     static public enum ExpirePriority {
         NORMAL,
         AGGRESSIVE
     }
 
     static public String getFrecencySortOrder(boolean includesBookmarks, boolean asc) {
         final String age = "(" + Combined.DATE_LAST_VISITED + " - " + System.currentTimeMillis() + ") / 86400000";
--- a/mobile/android/base/db/HomeProvider.java
+++ b/mobile/android/base/db/HomeProvider.java
@@ -7,18 +7,20 @@ package org.mozilla.gecko.db;
 import java.io.IOException;
 import java.io.InputStream;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.db.BrowserContract.HomeItems;
+import org.mozilla.gecko.db.DBUtils;
 import org.mozilla.gecko.sqlite.SQLiteBridge;
 
+import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.UriMatcher;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
 import android.util.Log;
 
 public class HomeProvider extends SQLiteBridgeContentProvider {
@@ -68,18 +70,33 @@ public class HomeProvider extends SQLite
     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
         final int match = URI_MATCHER.match(uri);
 
         // If we're querying the fake items, don't try to get the database.
         if (match == ITEMS_FAKE) {
             return queryFakeItems(uri, projection, selection, selectionArgs, sortOrder);
         }
 
+        final String datasetId = uri.getQueryParameter(BrowserContract.PARAM_DATASET_ID);
+        if (datasetId == null) {
+            throw new IllegalArgumentException("All queries should contain a dataset ID parameter");
+        }
+
+        selection = DBUtils.concatenateWhere(selection, HomeItems.DATASET_ID + " = ?");
+        selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
+                                                    new String[] { datasetId });
+
         // Otherwise, let the SQLiteContentProvider implementation take care of this query for us!
-        return super.query(uri, projection, selection, selectionArgs, sortOrder);
+        final Cursor c = super.query(uri, projection, selection, selectionArgs, sortOrder);
+        if (c != null) {
+            final ContentResolver cr = getContext().getContentResolver();
+            c.setNotificationUri(cr, getDatasetNotificationUri(datasetId));
+        }
+
+        return c;
     }
 
     /**
      * Returns a cursor populated with static fake data.
      */
     private Cursor queryFakeItems(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
         JSONArray items = null;
         try {
@@ -176,10 +193,12 @@ public class HomeProvider extends SQLite
     public void onPreInsert(ContentValues values, Uri uri, SQLiteBridge db) { }
 
     @Override
     public void onPreUpdate(ContentValues values, Uri uri, SQLiteBridge db) { }
 
     @Override
     public void onPostQuery(Cursor cursor, Uri uri, SQLiteBridge db) { }
 
-
+    public static Uri getDatasetNotificationUri(String datasetId) {
+        return Uri.withAppendedPath(HomeItems.CONTENT_URI, datasetId);
+    }
 }
--- a/mobile/android/base/home/DynamicPanel.java
+++ b/mobile/android/base/home/DynamicPanel.java
@@ -4,18 +4,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.HomeItems;
 import org.mozilla.gecko.db.DBUtils;
+import org.mozilla.gecko.db.HomeProvider;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.PanelLayout.DatasetHandler;
 import org.mozilla.gecko.home.PanelLayout.DatasetRequest;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Activity;
@@ -47,18 +49,17 @@ import android.view.ViewGroup;
  * the provided {@code DatasetHandler}. This way it doesn't need to know the
  * details of how datasets are loaded and reset. Each time a dataset is
  * requested, {@code DynamicPanel} restarts a Loader with the respective ID (see
  * {@code PanelDatasetHandler}).
  *
  * See {@code PanelLayout} for more details on how {@code DynamicPanel}
  * receives dataset requests and delivers them back to the {@code PanelLayout}.
  */
-public class DynamicPanel extends HomeFragment
-                          implements GeckoEventListener {
+public class DynamicPanel extends HomeFragment {
     private static final String LOGTAG = "GeckoDynamicPanel";
 
     // Dataset ID to be used by the loader
     private static final String DATASET_REQUEST = "dataset_request";
 
     // The panel layout associated with this panel
     private PanelLayout mLayout;
 
@@ -119,25 +120,22 @@ public class DynamicPanel extends HomeFr
         Log.d(LOGTAG, "Created layout of type: " + mPanelConfig.getLayoutType());
 
         return mLayout;
     }
 
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
-        GeckoAppShell.registerEventListener("HomePanels:RefreshDataset", this);
     }
 
     @Override
     public void onDestroyView() {
         super.onDestroyView();
         mLayout = null;
-
-        GeckoAppShell.unregisterEventListener("HomePanels:RefreshDataset", this);
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
 
         // Detach and reattach the fragment as the layout changes.
         if (isVisible()) {
@@ -158,56 +156,16 @@ public class DynamicPanel extends HomeFr
     }
 
     @Override
     protected void load() {
         Log.d(LOGTAG, "Loading layout");
         mLayout.load();
     }
 
-    @Override
-    public void handleMessage(String event, final JSONObject message) {
-        if (event.equals("HomePanels:RefreshDataset")) {
-            ThreadUtils.postToUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    handleDatasetRefreshRequest(message);
-                }
-            });
-        }
-    }
-
-    /**
-     * Handles a dataset refresh request from Gecko. This is usually
-     * triggered by a HomeStorage.save() call in an add-on.
-     */
-    private void handleDatasetRefreshRequest(JSONObject message) {
-        final String datasetId;
-        try {
-            datasetId = message.getString("datasetId");
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "Failed to handle dataset refresh", e);
-            return;
-        }
-
-        final Activity activity = getActivity();
-        if (activity == null) {
-            return;
-        }
-
-        Log.d(LOGTAG, "Refresh request for dataset: " + datasetId);
-
-        final ContentResolver cr = activity.getContentResolver();
-        cr.notifyChange(getDatasetNotificationUri(datasetId), null);
-    }
-
-    private static Uri getDatasetNotificationUri(String datasetId) {
-        return Uri.withAppendedPath(HomeItems.CONTENT_URI, datasetId);
-    }
-
     /**
      * Used by the PanelLayout to make load and reset requests to
      * the holding fragment.
      */
     private class PanelDatasetHandler implements DatasetHandler {
         @Override
         public void requestDataset(DatasetRequest request) {
             Log.d(LOGTAG, "Requesting request: " + request);
@@ -274,33 +232,31 @@ public class DynamicPanel extends HomeFr
         public Cursor loadCursor() {
             final ContentResolver cr = getContext().getContentResolver();
 
             final String selection;
             final String[] selectionArgs;
 
             // Null represents the root filter
             if (mRequest.getFilter() == null) {
-                selection = DBUtils.concatenateWhere(HomeItems.DATASET_ID + " = ?", HomeItems.FILTER + " IS NULL");
-                selectionArgs = new String[] { mRequest.getDatasetId() };
+                selection = HomeItems.FILTER + " IS NULL";
+                selectionArgs = null;
             } else {
-                selection = DBUtils.concatenateWhere(HomeItems.DATASET_ID + " = ?", HomeItems.FILTER + " = ?");
-                selectionArgs = new String[] { mRequest.getDatasetId(), mRequest.getFilter() };
+                selection = HomeItems.FILTER + " = ?";
+                selectionArgs = new String[] { mRequest.getFilter() };
             }
 
-            // XXX: You can use CONTENT_FAKE_URI for development to pull items from fake_home_items.json.
-            final Cursor c = cr.query(HomeItems.CONTENT_URI, null, selection, selectionArgs, null);
+            final Uri queryUri = HomeItems.CONTENT_URI.buildUpon()
+                                                      .appendQueryParameter(BrowserContract.PARAM_DATASET_ID,
+                                                                            mRequest.getDatasetId())
+                                                      .build();
 
-            // SQLiteBridgeContentProvider may return a null Cursor if the database hasn't been created yet.
-            if (c != null) {
-                final Uri notificationUri = getDatasetNotificationUri(mRequest.getDatasetId());
-                c.setNotificationUri(cr, notificationUri);
-            }
-
-            return c;
+            // XXX: You can use HomeItems.CONTENT_FAKE_URI for development
+            // to pull items from fake_home_items.json.
+            return cr.query(queryUri, null, selection, selectionArgs, null);
         }
     }
 
     /**
      * LoaderCallbacks implementation that interacts with the LoaderManager.
      */
     private class PanelLoaderCallbacks implements LoaderCallbacks<Cursor> {
         @Override
--- a/mobile/android/base/home/HomeConfigPrefsBackend.java
+++ b/mobile/android/base/home/HomeConfigPrefsBackend.java
@@ -150,17 +150,17 @@ class HomeConfigPrefsBackend implements 
 
             final SharedPreferences.Editor editor = prefs.edit();
             editor.putString(PREFS_LOCALE_KEY, currentLocale);
             editor.commit();
 
             // If the user has saved HomeConfig before, return null this
             // one time to trigger a refresh and ensure we use the
             // correct locale for the saved state. For more context,
-            // see HomeConfigInvalidator.onLocaleReady().
+            // see HomePanelsManager.onLocaleReady().
             if (!prefs.contains(PREFS_CONFIG_KEY)) {
                 locale = currentLocale;
             }
         }
 
         return locale;
     }
 
--- a/mobile/android/base/home/HomePanelPicker.java
+++ b/mobile/android/base/home/HomePanelPicker.java
@@ -23,18 +23,18 @@ import android.widget.Toast;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.home.HomeConfig;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.PanelManager.RequestCallback;
-import org.mozilla.gecko.home.PanelManager.PanelInfo;
+import org.mozilla.gecko.home.PanelInfoManager.RequestCallback;
+import org.mozilla.gecko.home.PanelInfoManager.PanelInfo;
 
 /**
  * Dialog for selecting new home panels to add.
  */
 public class HomePanelPicker extends FragmentActivity {
     private static final String LOGTAG = "HomePanelPicker";
 
     /**
@@ -76,18 +76,18 @@ public class HomePanelPicker extends Fra
         requestAvailablePanels();
     }
 
     /**
      * Get panels and update the adapter to display the ones
      * available for install.
      */
     private void requestAvailablePanels() {
-        final PanelManager panelManager = new PanelManager();
-        panelManager.requestAvailablePanels(new RequestCallback() {
+        final PanelInfoManager pm = new PanelInfoManager();
+        pm.requestAvailablePanels(new RequestCallback() {
             @Override
             public void onComplete(final List<PanelInfo> panelInfos) {
                 mPanelInfos = panelInfos;
 
                 // Fetch current home panels if necessary.
                 if (mCurrentPanelsIds == null) {
                     loadConfig();
                 } else {
@@ -130,17 +130,17 @@ public class HomePanelPicker extends Fra
         }
 
         final PickerAdapter adapter = (PickerAdapter) mListView.getAdapter();
         adapter.updateFromPanelInfos(availablePanels);
     }
 
     private void installNewPanelAndQuit(PanelInfo panelInfo) {
         final PanelConfig newPanelConfig = panelInfo.toPanelConfig();
-        HomeConfigInvalidator.getInstance().installPanel(newPanelConfig);
+        HomePanelsManager.getInstance().installPanel(newPanelConfig);
         showToastForNewPanel(newPanelConfig);
 
         setResult(Activity.RESULT_OK);
         finish();
     }
 
     private void showToastForNewPanel(PanelConfig panelConfig) {
         String panelName = panelConfig.getTitle();
rename from mobile/android/base/home/HomeConfigInvalidator.java
rename to mobile/android/base/home/HomePanelsManager.java
--- a/mobile/android/base/home/HomeConfigInvalidator.java
+++ b/mobile/android/base/home/HomePanelsManager.java
@@ -11,38 +11,41 @@ import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.db.HomeProvider;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.PanelManager.PanelInfo;
-import org.mozilla.gecko.home.PanelManager.RequestCallback;
+import org.mozilla.gecko.home.PanelInfoManager.PanelInfo;
+import org.mozilla.gecko.home.PanelInfoManager.RequestCallback;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.os.Handler;
 import android.util.Log;
 
-public class HomeConfigInvalidator implements GeckoEventListener {
-    public static final String LOGTAG = "HomeConfigInvalidator";
+public class HomePanelsManager implements GeckoEventListener {
+    public static final String LOGTAG = "HomePanelsManager";
 
-    private static final HomeConfigInvalidator sInstance = new HomeConfigInvalidator();
+    private static final HomePanelsManager sInstance = new HomePanelsManager();
 
     private static final int INVALIDATION_DELAY_MSEC = 500;
     private static final int PANEL_INFO_TIMEOUT_MSEC = 1000;
 
     private static final String EVENT_HOMEPANELS_INSTALL = "HomePanels:Install";
     private static final String EVENT_HOMEPANELS_UNINSTALL = "HomePanels:Uninstall";
     private static final String EVENT_HOMEPANELS_UPDATE = "HomePanels:Update";
+    private static final String EVENT_HOMEPANELS_REFRESH = "HomePanels:RefreshDataset";
 
     private static final String JSON_KEY_PANEL = "panel";
     private static final String JSON_KEY_PANEL_ID = "id";
 
     private enum ChangeType {
         UNINSTALL,
         INSTALL,
         UPDATE,
@@ -69,27 +72,28 @@ public class HomeConfigInvalidator imple
     }
 
     private Context mContext;
     private HomeConfig mHomeConfig;
 
     private final Queue<ConfigChange> mPendingChanges = new ConcurrentLinkedQueue<ConfigChange>();
     private final Runnable mInvalidationRunnable = new InvalidationRunnable();
 
-    public static HomeConfigInvalidator getInstance() {
+    public static HomePanelsManager getInstance() {
         return sInstance;
     }
 
     public void init(Context context) {
         mContext = context;
         mHomeConfig = HomeConfig.getDefault(context);
 
         GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_INSTALL, this);
         GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_UNINSTALL, this);
         GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_UPDATE, this);
+        GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_REFRESH, this);
     }
 
     public void onLocaleReady(final String locale) {
         ThreadUtils.getBackgroundHandler().post(new Runnable() {
             @Override
             public void run() {
                 final String configLocale = mHomeConfig.getLocale();
                 if (configLocale == null || !configLocale.equals(locale)) {
@@ -107,16 +111,19 @@ public class HomeConfigInvalidator imple
                 handlePanelInstall(createPanelConfigFromMessage(message), InvalidationMode.DELAYED);
             } else if (event.equals(EVENT_HOMEPANELS_UNINSTALL)) {
                 Log.d(LOGTAG, EVENT_HOMEPANELS_UNINSTALL);
                 final String panelId = message.getString(JSON_KEY_PANEL_ID);
                 handlePanelUninstall(panelId);
             } else if (event.equals(EVENT_HOMEPANELS_UPDATE)) {
                 Log.d(LOGTAG, EVENT_HOMEPANELS_UPDATE);
                 handlePanelUpdate(createPanelConfigFromMessage(message));
+            } else if (event.equals(EVENT_HOMEPANELS_REFRESH)) {
+                Log.d(LOGTAG, EVENT_HOMEPANELS_REFRESH);
+                handleDatasetRefresh(message);
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Failed to handle event " + event, e);
         }
     }
 
     private PanelConfig createPanelConfigFromMessage(JSONObject message) throws JSONException {
         final JSONObject json = message.getJSONObject(JSON_KEY_PANEL);
@@ -170,16 +177,38 @@ public class HomeConfigInvalidator imple
      */
     private void handleLocaleChange() {
         mPendingChanges.offer(new ConfigChange(ChangeType.REFRESH));
         Log.d(LOGTAG, "handleLocaleChange: " + mPendingChanges.size());
 
         scheduleInvalidation(InvalidationMode.IMMEDIATE);
     }
 
+
+    /**
+     * Handles a dataset refresh request from Gecko. This is usually
+     * triggered by a HomeStorage.save() call in an add-on.
+     *
+     * Runs in the gecko thread.
+     */
+    private void handleDatasetRefresh(JSONObject message) {
+        final String datasetId;
+        try {
+            datasetId = message.getString("datasetId");
+        } catch (JSONException e) {
+            Log.e(LOGTAG, "Failed to handle dataset refresh", e);
+            return;
+        }
+
+        Log.d(LOGTAG, "Refresh request for dataset: " + datasetId);
+
+        final ContentResolver cr = mContext.getContentResolver();
+        cr.notifyChange(HomeProvider.getDatasetNotificationUri(datasetId), null);
+    }
+
     /**
      * Runs in the gecko or main thread.
      */
     private void scheduleInvalidation(InvalidationMode mode) {
         final Handler handler = ThreadUtils.getBackgroundHandler();
 
         handler.removeCallbacks(mInvalidationRunnable);
 
@@ -282,17 +311,17 @@ public class HomeConfigInvalidator imple
         final Set<String> ids = new HashSet<String>();
         for (PanelConfig panelConfig : editor) {
             ids.add(panelConfig.getId());
         }
 
         final Object panelRequestLock = new Object();
         final List<PanelInfo> latestPanelInfos = new ArrayList<PanelInfo>();
 
-        final PanelManager pm = new PanelManager();
+        final PanelInfoManager pm = new PanelInfoManager();
         pm.requestPanelsById(ids, new RequestCallback() {
             @Override
             public void onComplete(List<PanelInfo> panelInfos) {
                 synchronized(panelRequestLock) {
                     latestPanelInfos.addAll(panelInfos);
                     Log.d(LOGTAG, "executeRefresh: fetched panel infos: " + panelInfos.size());
 
                     panelRequestLock.notifyAll();
rename from mobile/android/base/home/PanelManager.java
rename to mobile/android/base/home/PanelInfoManager.java
--- a/mobile/android/base/home/PanelManager.java
+++ b/mobile/android/base/home/PanelInfoManager.java
@@ -17,18 +17,18 @@ import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.util.Log;
 import android.util.SparseArray;
 
-public class PanelManager implements GeckoEventListener {
-    private static final String LOGTAG = "GeckoPanelManager";
+public class PanelInfoManager implements GeckoEventListener {
+    private static final String LOGTAG = "GeckoPanelInfoManager";
 
     public class PanelInfo {
         private final String mId;
         private final String mTitle;
         private final JSONObject mJSONData;
 
         public PanelInfo(String id, String title, JSONObject jsonData) {
             mId = id;
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -232,34 +232,34 @@ gbjar.sources += [
     'home/BrowserSearch.java',
     'home/DynamicPanel.java',
     'home/FadedTextView.java',
     'home/FramePanelLayout.java',
     'home/HistoryPanel.java',
     'home/HomeAdapter.java',
     'home/HomeBanner.java',
     'home/HomeConfig.java',
-    'home/HomeConfigInvalidator.java',
     'home/HomeConfigLoader.java',
     'home/HomeConfigPrefsBackend.java',
     'home/HomeContextMenuInfo.java',
     'home/HomeFragment.java',
     'home/HomeListView.java',
     'home/HomePager.java',
     'home/HomePagerTabStrip.java',
     'home/HomePanelPicker.java',
+    'home/HomePanelsManager.java',
     'home/LastTabsPanel.java',
     'home/MostRecentPanel.java',
     'home/MultiTypeCursorAdapter.java',
     'home/PanelBackItemView.java',
     'home/PanelGridView.java',
+    'home/PanelInfoManager.java',
     'home/PanelItemView.java',
     'home/PanelLayout.java',
     'home/PanelListView.java',
-    'home/PanelManager.java',
     'home/PanelViewAdapter.java',
     'home/PanelViewItemHandler.java',
     'home/PinSiteDialog.java',
     'home/ReadingListPanel.java',
     'home/SearchEngine.java',
     'home/SearchEngineRow.java',
     'home/SearchLoader.java',
     'home/SimpleCursorLoader.java',