Bug 949174/964375/952311 - Introduce HomeConfigInvalidator to handle install/invalidation (r=margaret)
authorLucas Rocha <lucasr@mozilla.com>
Wed, 05 Feb 2014 14:14:53 +0000
changeset 178007 9897f4f421768d45578532d059b5f55731bfcdbf
parent 178006 f9977589e824dfa810307385ba85cf5e7f0c4a2a
child 178008 84fef38d71e83aeab4cb0a47484f1f7333c4d4b1
push id5439
push userffxbld
push dateMon, 17 Mar 2014 23:08:15 +0000
treeherdermozilla-aurora@c0befb3c8038 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret
bugs949174, 964375, 952311
milestone30.0a1
Bug 949174/964375/952311 - Introduce HomeConfigInvalidator to handle install/invalidation (r=margaret)
mobile/android/base/GeckoApplication.java
mobile/android/base/home/HomeConfig.java
mobile/android/base/home/HomeConfigInvalidator.java
mobile/android/base/moz.build
--- a/mobile/android/base/GeckoApplication.java
+++ b/mobile/android/base/GeckoApplication.java
@@ -1,16 +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.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.res.Configuration;
 import android.util.Log;
@@ -63,16 +64,17 @@ public class GeckoApplication extends Ap
 
         mLightweightTheme = new LightweightTheme(this);
 
         GeckoConnectivityReceiver.getInstance().init(getApplicationContext());
         GeckoBatteryManager.getInstance().init(getApplicationContext());
         GeckoBatteryManager.getInstance().start();
         GeckoNetworkManager.getInstance().init(getApplicationContext());
         MemoryMonitor.getInstance().init(getApplicationContext());
+        HomeConfigInvalidator.getInstance().init(getApplicationContext());
 
         mInited = true;
     }
 
     public void onActivityPause(GeckoActivityStatus activity) {
         mInBackground = true;
 
         if ((activity.isFinishing() == false) &&
--- a/mobile/android/base/home/HomeConfig.java
+++ b/mobile/android/base/home/HomeConfig.java
@@ -329,16 +329,34 @@ public final class HomeConfig {
             if (mFlags.contains(Flags.DISABLED_PANEL)) {
                 json.put(JSON_KEY_DISABLED, true);
             }
 
             return json;
         }
 
         @Override
+        public boolean equals(Object o) {
+            if (o == null) {
+                return false;
+            }
+
+            if (this == o) {
+                return true;
+            }
+
+            if (!(o instanceof PanelConfig)) {
+                return false;
+            }
+
+            final PanelConfig other = (PanelConfig) o;
+            return mId.equals(other.mId);
+        }
+
+        @Override
         public int describeContents() {
             return 0;
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeParcelable(mType, 0);
             dest.writeString(mTitle);
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/home/HomeConfigInvalidator.java
@@ -0,0 +1,232 @@
+/* -*- 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.GeckoAppShell;
+import org.mozilla.gecko.home.HomeConfig.PanelConfig;
+import org.mozilla.gecko.home.HomeConfig.PanelType;
+import org.mozilla.gecko.home.PanelManager.PanelInfo;
+import org.mozilla.gecko.home.PanelManager.RequestCallback;
+import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import static org.mozilla.gecko.home.HomeConfig.createBuiltinPanelConfig;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class HomeConfigInvalidator implements GeckoEventListener {
+    public static final String LOGTAG = "HomeConfigInvalidator";
+
+    private static final HomeConfigInvalidator sInstance = new HomeConfigInvalidator();
+
+    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_REMOVE = "HomePanels:Remove";
+
+    private static final String JSON_KEY_PANEL = "panel";
+
+    private Context mContext;
+    private HomeConfig mHomeConfig;
+
+    private final ConcurrentLinkedQueue<PanelConfig> mPendingChanges = new ConcurrentLinkedQueue<PanelConfig>();
+    private final Runnable mInvalidationRunnable = new InvalidationRunnable();
+
+    public static HomeConfigInvalidator 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_REMOVE, this);
+    }
+
+    @Override
+    public void handleMessage(String event, JSONObject message) {
+        try {
+            final JSONObject json = message.getJSONObject(JSON_KEY_PANEL);
+            final PanelConfig panelConfig = new PanelConfig(json);
+
+            if (event.equals(EVENT_HOMEPANELS_INSTALL)) {
+                Log.d(LOGTAG, EVENT_HOMEPANELS_INSTALL);
+                handlePanelInstall(panelConfig);
+            } else if (event.equals(EVENT_HOMEPANELS_REMOVE)) {
+                Log.d(LOGTAG, EVENT_HOMEPANELS_REMOVE);
+                handlePanelRemove(panelConfig);
+            }
+        } catch (Exception e) {
+            Log.e(LOGTAG, "Failed to handle event " + event, e);
+        }
+    }
+
+    /**
+     * Runs in the gecko thread.
+     */
+    private void handlePanelInstall(PanelConfig panelConfig) {
+        mPendingChanges.offer(panelConfig);
+        Log.d(LOGTAG, "handlePanelInstall: " + mPendingChanges.size());
+
+        scheduleInvalidation();
+    }
+
+    /**
+     * Runs in the gecko thread.
+     */
+    private void handlePanelRemove(PanelConfig panelConfig) {
+        panelConfig.setIsDeleted(true);
+        mPendingChanges.offer(panelConfig);
+        Log.d(LOGTAG, "handlePanelRemove: " + mPendingChanges.size());
+
+        scheduleInvalidation();
+    }
+
+    /**
+     * Runs in the gecko or main thread.
+     */
+    private void scheduleInvalidation() {
+        final Handler handler = ThreadUtils.getBackgroundHandler();
+
+        handler.removeCallbacks(mInvalidationRunnable);
+        handler.postDelayed(mInvalidationRunnable, INVALIDATION_DELAY_MSEC);
+
+        Log.d(LOGTAG, "scheduleInvalidation: scheduled new invalidation");
+    }
+
+    /**
+     * Runs in the background thread.
+     */
+    private List<PanelConfig> executePendingChanges(List<PanelConfig> panelConfigs) {
+        while (!mPendingChanges.isEmpty()) {
+            final PanelConfig panelConfig = mPendingChanges.poll();
+            final String id = panelConfig.getId();
+
+            if (panelConfig.isDeleted()) {
+                if (panelConfigs.remove(panelConfig)) {
+                    Log.d(LOGTAG, "executePendingChanges: removed panel " + id);
+                }
+            } else {
+                final int index = panelConfigs.indexOf(panelConfig);
+                if (index >= 0) {
+                    panelConfigs.set(index, panelConfig);
+                    Log.d(LOGTAG, "executePendingChanges: replaced position " + index + " with " + id);
+                } else {
+                    panelConfigs.add(panelConfig);
+                    Log.d(LOGTAG, "executePendingChanges: added panel " + id);
+                }
+            }
+        }
+
+        return executeRefresh(panelConfigs);
+    }
+
+    /**
+     * Runs in the background thread.
+     */
+    private List<PanelConfig> refreshFromPanelInfos(List<PanelConfig> panelConfigs, List<PanelInfo> panelInfos) {
+        Log.d(LOGTAG, "refreshFromPanelInfos");
+
+        final int count = panelConfigs.size();
+        for (int i = 0; i < count; i++) {
+            final PanelConfig panelConfig = panelConfigs.get(i);
+
+            PanelConfig refreshedPanelConfig = null;
+            if (panelConfig.isDynamic()) {
+                for (PanelInfo panelInfo : panelInfos) {
+                    if (panelInfo.getId().equals(panelConfig.getId())) {
+                        refreshedPanelConfig = panelInfo.toPanelConfig();
+                        Log.d(LOGTAG, "refreshFromPanelInfos: refreshing from panel info: " + panelInfo.getId());
+                        break;
+                    }
+                }
+            } else {
+                refreshedPanelConfig = createBuiltinPanelConfig(mContext, panelConfig.getType());
+                Log.d(LOGTAG, "refreshFromPanelInfos: refreshing built-in panel: " + panelConfig.getId());
+            }
+
+            if (refreshedPanelConfig == null) {
+                Log.d(LOGTAG, "refreshFromPanelInfos: no refreshed panel, falling back: " + panelConfig.getId());
+                refreshedPanelConfig = panelConfig;
+            }
+
+            refreshedPanelConfig.setIsDefault(panelConfig.isDefault());
+            refreshedPanelConfig.setIsDisabled(panelConfig.isDisabled());
+
+            Log.d(LOGTAG, "refreshFromPanelInfos: set " + i + " with " + refreshedPanelConfig.getId());
+            panelConfigs.set(i, refreshedPanelConfig);
+        }
+
+        return panelConfigs;
+    }
+
+    /**
+     * Runs in the background thread.
+     */
+    private List<PanelConfig> executeRefresh(List<PanelConfig> panelConfigs) {
+        if (panelConfigs.isEmpty()) {
+            return panelConfigs;
+        }
+
+        Log.d(LOGTAG, "executeRefresh");
+
+        final Set<String> ids = new HashSet<String>();
+        for (PanelConfig panelConfig : panelConfigs) {
+            ids.add(panelConfig.getId());
+        }
+
+        final Object panelRequestLock = new Object();
+        final List<PanelInfo> latestPanelInfos = new ArrayList<PanelInfo>();
+
+        final PanelManager pm = new PanelManager();
+        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();
+                }
+            }
+        });
+
+        try {
+            synchronized(panelRequestLock) {
+                panelRequestLock.wait(PANEL_INFO_TIMEOUT_MSEC);
+
+                Log.d(LOGTAG, "executeRefresh: done fetching panel infos");
+                return refreshFromPanelInfos(panelConfigs, latestPanelInfos);
+            }
+        } catch (InterruptedException e) {
+            Log.e(LOGTAG, "Failed to fetch panels from gecko", e);
+            return panelConfigs;
+        }
+    }
+
+    /**
+     * Runs in the background thread.
+     */
+    private class InvalidationRunnable implements Runnable {
+        @Override
+        public void run() {
+            mHomeConfig.save(executePendingChanges(mHomeConfig.load()));
+        }
+    };
+}
\ No newline at end of file
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -217,16 +217,17 @@ 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/LastTabsPanel.java',