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

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

mercurial