mobile/android/base/home/HomePanelsManager.java

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko.home;
     8 import static org.mozilla.gecko.home.HomeConfig.createBuiltinPanelConfig;
    10 import java.util.ArrayList;
    11 import java.util.HashSet;
    12 import java.util.List;
    13 import java.util.Queue;
    14 import java.util.Set;
    15 import java.util.concurrent.ConcurrentLinkedQueue;
    17 import org.json.JSONException;
    18 import org.json.JSONObject;
    19 import org.mozilla.gecko.db.HomeProvider;
    20 import org.mozilla.gecko.GeckoAppShell;
    21 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
    22 import org.mozilla.gecko.home.PanelInfoManager.PanelInfo;
    23 import org.mozilla.gecko.home.PanelInfoManager.RequestCallback;
    24 import org.mozilla.gecko.util.GeckoEventListener;
    25 import org.mozilla.gecko.util.ThreadUtils;
    27 import android.content.ContentResolver;
    28 import android.content.Context;
    29 import android.os.Handler;
    30 import android.util.Log;
    32 public class HomePanelsManager implements GeckoEventListener {
    33     public static final String LOGTAG = "HomePanelsManager";
    35     private static final HomePanelsManager sInstance = new HomePanelsManager();
    37     private static final int INVALIDATION_DELAY_MSEC = 500;
    38     private static final int PANEL_INFO_TIMEOUT_MSEC = 1000;
    40     private static final String EVENT_HOMEPANELS_INSTALL = "HomePanels:Install";
    41     private static final String EVENT_HOMEPANELS_UNINSTALL = "HomePanels:Uninstall";
    42     private static final String EVENT_HOMEPANELS_UPDATE = "HomePanels:Update";
    43     private static final String EVENT_HOMEPANELS_REFRESH = "HomePanels:RefreshDataset";
    45     private static final String JSON_KEY_PANEL = "panel";
    46     private static final String JSON_KEY_PANEL_ID = "id";
    48     private enum ChangeType {
    49         UNINSTALL,
    50         INSTALL,
    51         UPDATE,
    52         REFRESH
    53     }
    55     private enum InvalidationMode {
    56         DELAYED,
    57         IMMEDIATE
    58     }
    60     private static class ConfigChange {
    61         private final ChangeType type;
    62         private final Object target;
    64         public ConfigChange(ChangeType type) {
    65             this(type, null);
    66         }
    68         public ConfigChange(ChangeType type, Object target) {
    69             this.type = type;
    70             this.target = target;
    71         }
    72     }
    74     private Context mContext;
    75     private HomeConfig mHomeConfig;
    77     private final Queue<ConfigChange> mPendingChanges = new ConcurrentLinkedQueue<ConfigChange>();
    78     private final Runnable mInvalidationRunnable = new InvalidationRunnable();
    80     public static HomePanelsManager getInstance() {
    81         return sInstance;
    82     }
    84     public void init(Context context) {
    85         mContext = context;
    86         mHomeConfig = HomeConfig.getDefault(context);
    88         GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_INSTALL, this);
    89         GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_UNINSTALL, this);
    90         GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_UPDATE, this);
    91         GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_REFRESH, this);
    92     }
    94     public void onLocaleReady(final String locale) {
    95         ThreadUtils.getBackgroundHandler().post(new Runnable() {
    96             @Override
    97             public void run() {
    98                 final String configLocale = mHomeConfig.getLocale();
    99                 if (configLocale == null || !configLocale.equals(locale)) {
   100                     handleLocaleChange();
   101                 }
   102             }
   103         });
   104     }
   106     @Override
   107     public void handleMessage(String event, JSONObject message) {
   108         try {
   109             if (event.equals(EVENT_HOMEPANELS_INSTALL)) {
   110                 Log.d(LOGTAG, EVENT_HOMEPANELS_INSTALL);
   111                 handlePanelInstall(createPanelConfigFromMessage(message), InvalidationMode.DELAYED);
   112             } else if (event.equals(EVENT_HOMEPANELS_UNINSTALL)) {
   113                 Log.d(LOGTAG, EVENT_HOMEPANELS_UNINSTALL);
   114                 final String panelId = message.getString(JSON_KEY_PANEL_ID);
   115                 handlePanelUninstall(panelId);
   116             } else if (event.equals(EVENT_HOMEPANELS_UPDATE)) {
   117                 Log.d(LOGTAG, EVENT_HOMEPANELS_UPDATE);
   118                 handlePanelUpdate(createPanelConfigFromMessage(message));
   119             } else if (event.equals(EVENT_HOMEPANELS_REFRESH)) {
   120                 Log.d(LOGTAG, EVENT_HOMEPANELS_REFRESH);
   121                 handleDatasetRefresh(message);
   122             }
   123         } catch (Exception e) {
   124             Log.e(LOGTAG, "Failed to handle event " + event, e);
   125         }
   126     }
   128     private PanelConfig createPanelConfigFromMessage(JSONObject message) throws JSONException {
   129         final JSONObject json = message.getJSONObject(JSON_KEY_PANEL);
   130         return new PanelConfig(json);
   131     }
   133      /**
   134      * Adds a new PanelConfig to the HomeConfig.
   135      *
   136      * This posts the invalidation of HomeConfig immediately.
   137      *
   138      * @param panelConfig panel to add
   139      */
   140     public void installPanel(PanelConfig panelConfig) {
   141         Log.d(LOGTAG, "installPanel: " + panelConfig.getTitle());
   142         handlePanelInstall(panelConfig, InvalidationMode.IMMEDIATE);
   143     }
   145     /**
   146      * Runs in the gecko thread.
   147      */
   148     private void handlePanelInstall(PanelConfig panelConfig, InvalidationMode mode) {
   149         mPendingChanges.offer(new ConfigChange(ChangeType.INSTALL, panelConfig));
   150         Log.d(LOGTAG, "handlePanelInstall: " + mPendingChanges.size());
   152         scheduleInvalidation(mode);
   153     }
   155     /**
   156      * Runs in the gecko thread.
   157      */
   158     private void handlePanelUninstall(String panelId) {
   159         mPendingChanges.offer(new ConfigChange(ChangeType.UNINSTALL, panelId));
   160         Log.d(LOGTAG, "handlePanelUninstall: " + mPendingChanges.size());
   162         scheduleInvalidation(InvalidationMode.DELAYED);
   163     }
   165     /**
   166      * Runs in the gecko thread.
   167      */
   168     private void handlePanelUpdate(PanelConfig panelConfig) {
   169         mPendingChanges.offer(new ConfigChange(ChangeType.UPDATE, panelConfig));
   170         Log.d(LOGTAG, "handlePanelUpdate: " + mPendingChanges.size());
   172         scheduleInvalidation(InvalidationMode.DELAYED);
   173     }
   175     /**
   176      * Runs in the background thread.
   177      */
   178     private void handleLocaleChange() {
   179         mPendingChanges.offer(new ConfigChange(ChangeType.REFRESH));
   180         Log.d(LOGTAG, "handleLocaleChange: " + mPendingChanges.size());
   182         scheduleInvalidation(InvalidationMode.IMMEDIATE);
   183     }
   186     /**
   187      * Handles a dataset refresh request from Gecko. This is usually
   188      * triggered by a HomeStorage.save() call in an add-on.
   189      *
   190      * Runs in the gecko thread.
   191      */
   192     private void handleDatasetRefresh(JSONObject message) {
   193         final String datasetId;
   194         try {
   195             datasetId = message.getString("datasetId");
   196         } catch (JSONException e) {
   197             Log.e(LOGTAG, "Failed to handle dataset refresh", e);
   198             return;
   199         }
   201         Log.d(LOGTAG, "Refresh request for dataset: " + datasetId);
   203         final ContentResolver cr = mContext.getContentResolver();
   204         cr.notifyChange(HomeProvider.getDatasetNotificationUri(datasetId), null);
   205     }
   207     /**
   208      * Runs in the gecko or main thread.
   209      */
   210     private void scheduleInvalidation(InvalidationMode mode) {
   211         final Handler handler = ThreadUtils.getBackgroundHandler();
   213         handler.removeCallbacks(mInvalidationRunnable);
   215         if (mode == InvalidationMode.IMMEDIATE) {
   216             handler.post(mInvalidationRunnable);
   217         } else {
   218             handler.postDelayed(mInvalidationRunnable, INVALIDATION_DELAY_MSEC);
   219         }
   221         Log.d(LOGTAG, "scheduleInvalidation: scheduled new invalidation: " + mode);
   222     }
   224     /**
   225      * Runs in the background thread.
   226      */
   227     private void executePendingChanges(HomeConfig.Editor editor) {
   228         boolean shouldRefresh = false;
   230         while (!mPendingChanges.isEmpty()) {
   231             final ConfigChange pendingChange = mPendingChanges.poll();
   233             switch (pendingChange.type) {
   234                 case UNINSTALL: {
   235                     final String panelId = (String) pendingChange.target;
   236                     if (editor.uninstall(panelId)) {
   237                         Log.d(LOGTAG, "executePendingChanges: uninstalled panel " + panelId);
   238                     }
   239                     break;
   240                 }
   242                 case INSTALL: {
   243                     final PanelConfig panelConfig = (PanelConfig) pendingChange.target;
   244                     if (editor.install(panelConfig)) {
   245                         Log.d(LOGTAG, "executePendingChanges: added panel " + panelConfig.getId());
   246                     }
   247                     break;
   248                 }
   250                 case UPDATE: {
   251                     final PanelConfig panelConfig = (PanelConfig) pendingChange.target;
   252                     if (editor.update(panelConfig)) {
   253                         Log.w(LOGTAG, "executePendingChanges: updated panel " + panelConfig.getId());
   254                     }
   255                     break;
   256                 }
   258                 case REFRESH: {
   259                     shouldRefresh = true;
   260                 }
   261             }
   262         }
   264         // The editor still represents the default HomeConfig
   265         // configuration and hasn't been changed by any operation
   266         // above. No need to refresh as the HomeConfig backend will
   267         // take of forcing all existing HomeConfigLoader instances to
   268         // refresh their contents.
   269         if (shouldRefresh && !editor.isDefault()) {
   270             executeRefresh(editor);
   271         }
   272     }
   274     /**
   275      * Runs in the background thread.
   276      */
   277     private void refreshFromPanelInfos(HomeConfig.Editor editor, List<PanelInfo> panelInfos) {
   278         Log.d(LOGTAG, "refreshFromPanelInfos");
   280         for (PanelConfig panelConfig : editor) {
   281             PanelConfig refreshedPanelConfig = null;
   283             if (panelConfig.isDynamic()) {
   284                 for (PanelInfo panelInfo : panelInfos) {
   285                     if (panelInfo.getId().equals(panelConfig.getId())) {
   286                         refreshedPanelConfig = panelInfo.toPanelConfig();
   287                         Log.d(LOGTAG, "refreshFromPanelInfos: refreshing from panel info: " + panelInfo.getId());
   288                         break;
   289                     }
   290                 }
   291             } else {
   292                 refreshedPanelConfig = createBuiltinPanelConfig(mContext, panelConfig.getType());
   293                 Log.d(LOGTAG, "refreshFromPanelInfos: refreshing built-in panel: " + panelConfig.getId());
   294             }
   296             if (refreshedPanelConfig == null) {
   297                 Log.d(LOGTAG, "refreshFromPanelInfos: no refreshed panel, falling back: " + panelConfig.getId());
   298                 continue;
   299             }
   301             Log.d(LOGTAG, "refreshFromPanelInfos: refreshed panel " + refreshedPanelConfig.getId());
   302             editor.update(refreshedPanelConfig);
   303         }
   304     }
   306     /**
   307      * Runs in the background thread.
   308      */
   309     private void executeRefresh(HomeConfig.Editor editor) {
   310         if (editor.isEmpty()) {
   311             return;
   312         }
   314         Log.d(LOGTAG, "executeRefresh");
   316         final Set<String> ids = new HashSet<String>();
   317         for (PanelConfig panelConfig : editor) {
   318             ids.add(panelConfig.getId());
   319         }
   321         final Object panelRequestLock = new Object();
   322         final List<PanelInfo> latestPanelInfos = new ArrayList<PanelInfo>();
   324         final PanelInfoManager pm = new PanelInfoManager();
   325         pm.requestPanelsById(ids, new RequestCallback() {
   326             @Override
   327             public void onComplete(List<PanelInfo> panelInfos) {
   328                 synchronized(panelRequestLock) {
   329                     latestPanelInfos.addAll(panelInfos);
   330                     Log.d(LOGTAG, "executeRefresh: fetched panel infos: " + panelInfos.size());
   332                     panelRequestLock.notifyAll();
   333                 }
   334             }
   335         });
   337         try {
   338             synchronized(panelRequestLock) {
   339                 panelRequestLock.wait(PANEL_INFO_TIMEOUT_MSEC);
   341                 Log.d(LOGTAG, "executeRefresh: done fetching panel infos");
   342                 refreshFromPanelInfos(editor, latestPanelInfos);
   343             }
   344         } catch (InterruptedException e) {
   345             Log.e(LOGTAG, "Failed to fetch panels from gecko", e);
   346         }
   347     }
   349     /**
   350      * Runs in the background thread.
   351      */
   352     private class InvalidationRunnable implements Runnable {
   353         @Override
   354         public void run() {
   355             final HomeConfig.Editor editor = mHomeConfig.load().edit();
   356             executePendingChanges(editor);
   357             editor.commit();
   358         }
   359     };
   360 }

mercurial