 Issue 29350065:
  Issue 2853 - Settings changes are sometimes not saved if the user quits the app  (Closed)
    
  
    Issue 29350065:
  Issue 2853 - Settings changes are sometimes not saved if the user quits the app  (Closed) 
  | Left: | ||
| Right: | 
| LEFT | RIGHT | 
|---|---|
| 1 /* | 1 /* | 
| 2 * This file is part of Adblock Plus <https://adblockplus.org/>, | 2 * This file is part of Adblock Plus <https://adblockplus.org/>, | 
| 3 * Copyright (C) 2006-2015 Eyeo GmbH | 3 * Copyright (C) 2006-2015 Eyeo GmbH | 
| 4 * | 4 * | 
| 5 * Adblock Plus is free software: you can redistribute it and/or modify | 5 * Adblock Plus is free software: you can redistribute it and/or modify | 
| 6 * it under the terms of the GNU General Public License version 3 as | 6 * it under the terms of the GNU General Public License version 3 as | 
| 7 * published by the Free Software Foundation. | 7 * published by the Free Software Foundation. | 
| 8 * | 8 * | 
| 9 * Adblock Plus is distributed in the hope that it will be useful, | 9 * Adblock Plus is distributed in the hope that it will be useful, | 
| 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| (...skipping 25 matching lines...) Expand all Loading... | |
| 36 import org.mozilla.gecko.GeckoAppShell; | 36 import org.mozilla.gecko.GeckoAppShell; | 
| 37 import org.mozilla.gecko.util.GeckoEventListener; | 37 import org.mozilla.gecko.util.GeckoEventListener; | 
| 38 import org.mozilla.gecko.util.GeckoRequest; | 38 import org.mozilla.gecko.util.GeckoRequest; | 
| 39 import org.mozilla.gecko.util.NativeJSObject; | 39 import org.mozilla.gecko.util.NativeJSObject; | 
| 40 | 40 | 
| 41 @SuppressLint("DefaultLocale") | 41 @SuppressLint("DefaultLocale") | 
| 42 public class AddOnBridge | 42 public class AddOnBridge | 
| 43 { | 43 { | 
| 44 private static final String TAG = "AdblockBrowser.AddOnBridge"; | 44 private static final String TAG = "AdblockBrowser.AddOnBridge"; | 
| 45 private static final String REQUEST_NAME = "AdblockPlus:Api"; | 45 private static final String REQUEST_NAME = "AdblockPlus:Api"; | 
| 46 // Timeout for checking filter loading (in seconds) | |
| 47 private static final int QUERY_GET_FILTERS_LOADED_TIMEOUT = 30; | |
| 48 // How long to wait between retries (in milliseconds) | |
| 49 private static final int QUERY_GET_FILTERS_LOADED_DELAY = 500; | |
| 50 // Handler+HandlerThread for posting delayed re-tries without interfering with | 46 // Handler+HandlerThread for posting delayed re-tries without interfering with | 
| 51 // other threads (e.g. the UI or Gecko thread) | 47 // other threads (e.g. the UI or Gecko thread) | 
| 52 private static final HandlerThread PRIVATE_HANDLER_THREAD; | 48 private static final HandlerThread PRIVATE_HANDLER_THREAD; | 
| 53 private static final Handler PRIVATE_HANDLER; | 49 private static final Handler PRIVATE_HANDLER; | 
| 54 // Global handler, for e.g. UI tasks | 50 // Global handler, for e.g. UI tasks | 
| 55 private static final HandlerThread GLOBAL_HANDLER_THREAD; | 51 private static final HandlerThread GLOBAL_HANDLER_THREAD; | 
| 56 private static final Handler GLOBAL_HANDLER; | 52 private static final Handler GLOBAL_HANDLER; | 
| 57 // Sometimes, the app is killed before the extension is able to save all chang es regarding | 53 // Sometimes, the app is killed before the extension is able to save all chang es regarding | 
| 58 // AddOnBridge requests. Given that, we need to store the uncompleted requests on SharedPrefs, | 54 // AddOnBridge requests. Given that, we need to store the pending requests on SharedPrefs, | 
| 59 // so we can resend them to the extension once the app restarts | 55 // so we can resend them to the extension once the app restarts | 
| 60 // See https://issues.adblockplus.org/ticket/2853 | 56 // See https://issues.adblockplus.org/ticket/2853 | 
| 61 private static final String ON_SAVE_EVENT = "Abb:OnSave"; | 57 private static final AddOnEventListener ADD_ON_EVENT_LISTENER = new AddOnEvent Listener(); | 
| 62 private static final List<ChainedRequest> UNCOMPLETED_REQUESTS = new ArrayList <>(); | 58 private static final String ON_FILTERS_LOAD_EVENT = "Abb:OnFiltersLoad"; | 
| 63 private static final String UNCOMPLETED_REQUESTS_PREFS_KEY = "UNCOMPLETED_REQU ESTS_PREFS_KEY"; | 59 private static final String ON_FILTERS_SAVE_EVENT = "Abb:OnFiltersSave"; | 
| 60 private static final List<AddOnRequest> PENDING_REQUESTS = new ArrayList<>(); | |
| 61 private static final String PENDING_REQUESTS_PREFS_KEY = "PENDING_REQUESTS_PRE FS_KEY"; | |
| 64 | 62 | 
| 65 private static SharedPreferences sharedPrefs; | 63 private static SharedPreferences sharedPrefs; | 
| 64 private static boolean filtersLoaded; | |
| 66 | 65 | 
| 67 static | 66 static | 
| 68 { | 67 { | 
| 69 PRIVATE_HANDLER_THREAD = new HandlerThread("abp-private-handler"); | 68 PRIVATE_HANDLER_THREAD = new HandlerThread("abp-private-handler"); | 
| 70 PRIVATE_HANDLER_THREAD.setDaemon(true); | 69 PRIVATE_HANDLER_THREAD.setDaemon(true); | 
| 71 PRIVATE_HANDLER_THREAD.start(); | 70 PRIVATE_HANDLER_THREAD.start(); | 
| 72 PRIVATE_HANDLER = new Handler(PRIVATE_HANDLER_THREAD.getLooper()); | 71 PRIVATE_HANDLER = new Handler(PRIVATE_HANDLER_THREAD.getLooper()); | 
| 73 | 72 | 
| 74 GLOBAL_HANDLER_THREAD = new HandlerThread("abp-global-handler"); | 73 GLOBAL_HANDLER_THREAD = new HandlerThread("abp-global-handler"); | 
| 75 GLOBAL_HANDLER_THREAD.setDaemon(true); | 74 GLOBAL_HANDLER_THREAD.setDaemon(true); | 
| 76 GLOBAL_HANDLER_THREAD.start(); | 75 GLOBAL_HANDLER_THREAD.start(); | 
| 77 GLOBAL_HANDLER = new Handler(GLOBAL_HANDLER_THREAD.getLooper()); | 76 GLOBAL_HANDLER = new Handler(GLOBAL_HANDLER_THREAD.getLooper()); | 
| 78 } | 77 } | 
| 79 | 78 | 
| 80 public static void init(Context context) | 79 public static void init(Context context) | 
| 81 { | 80 { | 
| 82 sharedPrefs = context.getSharedPreferences(AddOnBridge.class.getName(), Cont ext.MODE_PRIVATE); | 81 sharedPrefs = context.getSharedPreferences(AddOnBridge.class.getName(), Cont ext.MODE_PRIVATE); | 
| 83 EventDispatcher.getInstance().registerGeckoThreadListener(new AddOnEventList ener(), ON_SAVE_EVENT); | 82 EventDispatcher.getInstance().registerGeckoThreadListener(ADD_ON_EVENT_LISTE NER, ON_FILTERS_LOAD_EVENT, ON_FILTERS_SAVE_EVENT); | 
| 84 loadAndResendUncompletedRequests(); | 83 loadPendingRequests(); | 
| 85 } | 84 } | 
| 86 | 85 | 
| 87 public static void postToHandler(Runnable runnable) | 86 public static void postToHandler(Runnable runnable) | 
| 88 { | 87 { | 
| 89 GLOBAL_HANDLER.post(runnable); | 88 GLOBAL_HANDLER.post(runnable); | 
| 90 } | 89 } | 
| 91 | 90 | 
| 92 public static void postToHandlerDelayed(Runnable runnable, long delayMillis) | 91 public static void postToHandlerDelayed(Runnable runnable, long delayMillis) | 
| 93 { | 92 { | 
| 94 GLOBAL_HANDLER.postDelayed(runnable, delayMillis); | 93 GLOBAL_HANDLER.postDelayed(runnable, delayMillis); | 
| 95 } | 94 } | 
| 96 | 95 | 
| 97 private static void loadAndResendUncompletedRequests() | 96 private static void loadPendingRequests() | 
| 98 { | 97 { | 
| 99 postToHandler(new Runnable() | 98 PRIVATE_HANDLER.post(new Runnable() | 
| 100 { | 99 { | 
| 101 @Override | 100 @Override | 
| 102 public void run() | 101 public void run() | 
| 103 { | 102 { | 
| 104 final String jsonString = sharedPrefs.getString(UNCOMPLETED_REQUESTS_PRE FS_KEY, null); | 103 final String jsonString = sharedPrefs.getString(PENDING_REQUESTS_PREFS_K EY, null); | 
| 105 UNCOMPLETED_REQUESTS.clear(); | 104 PENDING_REQUESTS.addAll(0, jsonStringToRequestList(jsonString)); | 
| 106 UNCOMPLETED_REQUESTS.addAll(jsonStringToRequestList(jsonString)); | |
| 107 for (final ChainedRequest request : UNCOMPLETED_REQUESTS) | |
| 108 { | |
| 109 GeckoAppShell.sendRequestToGecko(request); | |
| 110 } | |
| 111 } | 105 } | 
| 112 }); | 106 }); | 
| 113 } | 107 } | 
| 114 | 108 | 
| 115 private static void clearUncompletedRequests() | 109 private static void sendOrEnqueueRequest(final AddOnRequest request) | 
| 116 { | 110 { | 
| 117 postToHandler(new Runnable() | 111 PRIVATE_HANDLER.post(new Runnable() | 
| 118 { | 112 { | 
| 119 @Override | 113 @Override | 
| 120 public void run() | 114 public void run() | 
| 121 { | 115 { | 
| 122 if (!UNCOMPLETED_REQUESTS.isEmpty()) | 116 if (!filtersLoaded) | 
| 123 { | 117 { | 
| 124 UNCOMPLETED_REQUESTS.clear(); | 118 PENDING_REQUESTS.add(request); | 
| 125 storeStringPref(UNCOMPLETED_REQUESTS_PREFS_KEY, requestListToJsonStrin g(UNCOMPLETED_REQUESTS)); | 119 } | 
| 120 else | |
| 121 { | |
| 122 GeckoAppShell.sendRequestToGecko(request); | |
| 126 } | 123 } | 
| 127 } | 124 } | 
| 128 }); | 125 }); | 
| 129 } | 126 } | 
| 130 | 127 | 
| 131 private static void addUncompletedRequest(final ChainedRequest chainedRequest) | 128 private static void sendPendingRequests() | 
| 132 { | 129 { | 
| 133 postToHandler(new Runnable() | 130 PRIVATE_HANDLER.post(new Runnable() | 
| 134 { | 131 { | 
| 135 @Override | 132 @Override | 
| 136 public void run() | 133 public void run() | 
| 137 { | 134 { | 
| 138 UNCOMPLETED_REQUESTS.add(chainedRequest); | 135 for (final AddOnRequest request : PENDING_REQUESTS) | 
| 139 storeStringPref(UNCOMPLETED_REQUESTS_PREFS_KEY, requestListToJsonString( UNCOMPLETED_REQUESTS)); | 136 { | 
| 137 GeckoAppShell.sendRequestToGecko(request); | |
| 138 } | |
| 139 PENDING_REQUESTS.clear(); | |
| 140 } | 140 } | 
| 141 }); | 141 }); | 
| 142 } | 142 } | 
| 143 | 143 | 
| 144 private static String requestListToJsonString(final List<ChainedRequest> reque stList) | 144 private static void clearPendingRequests() | 
| 145 { | 145 { | 
| 146 if (requestList == null) | 146 PRIVATE_HANDLER.post(new Runnable() | 
| 147 { | 147 { | 
| 148 return null; | 148 @Override | 
| 149 } | 149 public void run() | 
| 150 final JSONArray jsonArray = new JSONArray(); | 150 { | 
| 151 for (final ChainedRequest request : requestList) | 151 storeStringPref(PENDING_REQUESTS_PREFS_KEY, null); | 
| 152 { | 152 } | 
| 153 jsonArray.put(request.value); | 153 }); | 
| 154 } | 154 } | 
| 155 return jsonArray.toString(); | 155 | 
| 156 } | 156 private static void storePendingRequest(final AddOnRequest request) | 
| 157 | 157 { | 
| 158 private static List<ChainedRequest> jsonStringToRequestList(final String jsonS tring) | 158 PRIVATE_HANDLER.post(new Runnable() | 
| 159 { | 159 { | 
| 160 final List<ChainedRequest> requestList = new ArrayList<>(); | 160 @Override | 
| 161 public void run() | |
| 162 { | |
| 163 final String jsonString = sharedPrefs.getString(PENDING_REQUESTS_PREFS_K EY, null); | |
| 164 try | |
| 165 { | |
| 166 final JSONArray jsonArray = jsonString != null ? new JSONArray(jsonStr ing) : new JSONArray(); | |
| 167 jsonArray.put(request.value); | |
| 168 storeStringPref(PENDING_REQUESTS_PREFS_KEY, jsonArray.toString()); | |
| 169 } | |
| 170 catch (JSONException e) | |
| 171 { | |
| 172 Log.e(TAG, "Failed to store pending request with error: " + e.getMessa ge(), e); | |
| 173 } | |
| 174 } | |
| 175 }); | |
| 176 } | |
| 177 | |
| 178 private static List<AddOnRequest> jsonStringToRequestList(final String jsonStr ing) | |
| 179 { | |
| 180 final List<AddOnRequest> requestList = new ArrayList<>(); | |
| 161 if (jsonString == null) | 181 if (jsonString == null) | 
| 162 { | 182 { | 
| 163 return requestList; | 183 return requestList; | 
| 164 } | 184 } | 
| 165 try | 185 try | 
| 166 { | 186 { | 
| 167 final JSONArray jsonArray = new JSONArray(jsonString); | 187 final JSONArray jsonArray = new JSONArray(jsonString); | 
| 168 for (int i = 0; i < jsonArray.length(); i++) | 188 for (int i = 0; i < jsonArray.length(); i++) | 
| 169 { | 189 { | 
| 170 final ChainedRequest request = new ChainedRequest(jsonArray.getJSONObjec t(i), null); | 190 final AddOnRequest request = new AddOnRequest(jsonArray.getJSONObject(i) , null); | 
| 171 requestList.add(request); | 191 requestList.add(request); | 
| 172 } | 192 } | 
| 173 } | 193 } | 
| 174 catch (JSONException e) | 194 catch (JSONException e) | 
| 175 { | 195 { | 
| 196 Log.e(TAG, "Failed to parse json to request list with error: " + e.getMess age(), e); | |
| 176 } | 197 } | 
| 177 return requestList; | 198 return requestList; | 
| 178 } | 199 } | 
| 179 | 200 | 
| 180 private static void storeStringPref(String key, String value) | 201 private static void storeStringPref(String key, String value) | 
| 181 { | 202 { | 
| 182 final SharedPreferences.Editor editor = sharedPrefs.edit(); | 203 final SharedPreferences.Editor editor = sharedPrefs.edit(); | 
| 183 editor.putString(key, value); | 204 editor.putString(key, value); | 
| 184 editor.commit(); | 205 editor.commit(); | 
| 185 } | 206 } | 
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 245 if (Character.isUpperCase(str.charAt(0))) | 266 if (Character.isUpperCase(str.charAt(0))) | 
| 246 { | 267 { | 
| 247 return str; | 268 return str; | 
| 248 } | 269 } | 
| 249 return Character.toString(Character.toUpperCase(str.charAt(0))) + str.substr ing(1); | 270 return Character.toString(Character.toUpperCase(str.charAt(0))) + str.substr ing(1); | 
| 250 } | 271 } | 
| 251 | 272 | 
| 252 public static void queryValue(final AdblockPlusApiCallback callback, final Str ing name) | 273 public static void queryValue(final AdblockPlusApiCallback callback, final Str ing name) | 
| 253 { | 274 { | 
| 254 Log.d(TAG, "queryValue for " + name); | 275 Log.d(TAG, "queryValue for " + name); | 
| 255 GeckoAppShell.sendRequestToGecko( | 276 final AddOnRequest request = | 
| 256 new ChainedRequest( | 277 new AddOnRequest(createRequestData("get" + makeFirstCharacterUppercase(n ame)), callback); | 
| 257 createRequestData("get" + makeFirstCharacterUppercase(name)), | 278 sendOrEnqueueRequest(request); | 
| 258 callback)); | |
| 259 } | 279 } | 
| 260 | 280 | 
| 261 public static void setBoolean(final AdblockPlusApiCallback callback, final Str ing name, | 281 public static void setBoolean(final AdblockPlusApiCallback callback, final Str ing name, | 
| 262 final boolean enable) | 282 final boolean enable) | 
| 263 { | 283 { | 
| 264 Log.d(TAG, "setBoolean " + enable + " for " + name); | 284 Log.d(TAG, "setBoolean " + enable + " for " + name); | 
| 265 final ChainedRequest request = | 285 final AddOnRequest request = | 
| 266 new ChainedRequest(createRequestData("set" + makeFirstCharacterUppercase (name), enable), callback); | 286 new AddOnRequest(createRequestData("set" + makeFirstCharacterUppercase(n ame), enable), callback); | 
| 267 GeckoAppShell.sendRequestToGecko(request); | 287 sendOrEnqueueRequest(request); | 
| 268 addUncompletedRequest(request); | 288 storePendingRequest(request); | 
| 269 } | 289 } | 
| 270 | 290 | 
| 271 private static void callFunction(final AdblockPlusApiCallback callback, final String name, | 291 private static void callFunction(final AdblockPlusApiCallback callback, final String name, | 
| 272 final Map<String, Object> parameters) | 292 final Map<String, Object> parameters) | 
| 273 { | 293 { | 
| 274 // By default, requests are not added to the uncompleted request list. This should apply for | 294 // By default, requests are not stored on the pending request prefs. This sh ould apply for | 
| 275 // requests that doesn't result in save operations performed by the extensio n | 295 // requests that doesn't result in save operations performed by the extensio n | 
| 276 callFunction(callback, name, parameters, false); | 296 callFunction(callback, name, parameters, false); | 
| 277 } | 297 } | 
| 278 | 298 | 
| 279 private static void callFunction(final AdblockPlusApiCallback callback, final String name, | 299 private static void callFunction(final AdblockPlusApiCallback callback, final String name, | 
| 280 final Map<String, Object> parameters, boolean shouldAddUncompletedRequest) | 300 final Map<String, Object> parameters, boolean resendIfAborted) | 
| 
Felix Dahlke
2016/12/21 20:23:25
Nit: `shouldAddUncompletedRequest` sounds a bit li
 
diegocarloslima
2016/12/21 20:29:31
Acknowledged.
 | |
| 281 { | 301 { | 
| 282 final JSONObject requestData = createRequestData(name); | 302 final JSONObject requestData = createRequestData(name); | 
| 283 try | 303 try | 
| 284 { | 304 { | 
| 285 for (Map.Entry<String, Object> entry : parameters.entrySet()) | 305 for (Map.Entry<String, Object> entry : parameters.entrySet()) | 
| 306 { | |
| 286 requestData.put(entry.getKey(), entry.getValue()); | 307 requestData.put(entry.getKey(), entry.getValue()); | 
| 308 } | |
| 287 } | 309 } | 
| 288 catch (JSONException e) | 310 catch (JSONException e) | 
| 289 { | 311 { | 
| 290 // we're only adding sane objects | 312 // we're only adding sane objects | 
| 291 Log.e(TAG, "Creating request data failed with: " + e.getMessage(), e); | 313 Log.e(TAG, "Creating request data failed with: " + e.getMessage(), e); | 
| 292 } | 314 } | 
| 293 final ChainedRequest request = new ChainedRequest(requestData, callback); | 315 final AddOnRequest request = new AddOnRequest(requestData, callback); | 
| 294 GeckoAppShell.sendRequestToGecko(request); | 316 sendOrEnqueueRequest(request); | 
| 295 if (shouldAddUncompletedRequest) | 317 if (resendIfAborted) | 
| 296 { | 318 { | 
| 297 addUncompletedRequest(request); | 319 storePendingRequest(request); | 
| 298 } | 320 } | 
| 299 } | 321 } | 
| 300 | 322 | 
| 301 public static void querySubscriptionListStatus(final AdblockPlusApiCallback ca llback, | 323 public static void querySubscriptionListStatus(final AdblockPlusApiCallback ca llback, | 
| 302 final String url) | 324 final String url) | 
| 303 { | 325 { | 
| 304 Log.d(TAG, "querySubscriptionListStatus for " + url); | 326 Log.d(TAG, "querySubscriptionListStatus for " + url); | 
| 305 final Map<String, Object> parameters = new HashMap<String, Object>(); | 327 final Map<String, Object> parameters = new HashMap<String, Object>(); | 
| 306 parameters.put("url", url); | 328 parameters.put("url", url); | 
| 307 callFunction(callback, "isSubscriptionListed", parameters); | 329 callFunction(callback, "isSubscriptionListed", parameters); | 
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 357 public static void whitelistSite(final AdblockPlusApiCallback callback, final String url, | 379 public static void whitelistSite(final AdblockPlusApiCallback callback, final String url, | 
| 358 final boolean whitelisted) | 380 final boolean whitelisted) | 
| 359 { | 381 { | 
| 360 Log.d(TAG, "whitelistSite for " + url); | 382 Log.d(TAG, "whitelistSite for " + url); | 
| 361 final Map<String, Object> parameters = new HashMap<String, Object>(); | 383 final Map<String, Object> parameters = new HashMap<String, Object>(); | 
| 362 parameters.put("url", url); | 384 parameters.put("url", url); | 
| 363 parameters.put("whitelisted", whitelisted); | 385 parameters.put("whitelisted", whitelisted); | 
| 364 callFunction(callback, "whitelistSite", parameters, true); | 386 callFunction(callback, "whitelistSite", parameters, true); | 
| 365 } | 387 } | 
| 366 | 388 | 
| 367 private static class ChainedRequest extends GeckoRequest | 389 private static class AddOnRequest extends GeckoRequest | 
| 368 { | 390 { | 
| 369 private final JSONObject value; | 391 private final JSONObject value; | 
| 370 private final AdblockPlusApiCallback apiCallback; | 392 private final AdblockPlusApiCallback apiCallback; | 
| 371 private final boolean checkForFiltersLoaded; | 393 | 
| 372 private final long creationTime; | 394 AddOnRequest(final JSONObject value, final AdblockPlusApiCallback callback) | 
| 373 | 395 { | 
| 374 public ChainedRequest(final JSONObject value, final AdblockPlusApiCallback c allback, | 396 super(AddOnBridge.REQUEST_NAME, value); | 
| 375 final boolean checkForFiltersLoaded, final long creationTime) | |
| 376 { | |
| 377 super(AddOnBridge.REQUEST_NAME, | |
| 378 checkForFiltersLoaded ? createRequestData("getFiltersLoaded") : value) ; | |
| 379 this.value = value; | 397 this.value = value; | 
| 380 this.apiCallback = callback; | 398 this.apiCallback = callback; | 
| 381 this.checkForFiltersLoaded = checkForFiltersLoaded; | |
| 382 this.creationTime = creationTime; | |
| 383 } | |
| 384 | |
| 385 public ChainedRequest(final JSONObject value, final AdblockPlusApiCallback c allback) | |
| 386 { | |
| 387 this(value, callback, true, System.currentTimeMillis()); | |
| 388 } | |
| 389 | |
| 390 public ChainedRequest cloneForRetry() | |
| 391 { | |
| 392 return new ChainedRequest(this.value, this.apiCallback, true, this.creatio nTime); | |
| 393 } | |
| 394 | |
| 395 public ChainedRequest cloneForRequest() | |
| 396 { | |
| 397 return new ChainedRequest(this.value, this.apiCallback, false, this.creati onTime); | |
| 398 } | 399 } | 
| 399 | 400 | 
| 400 private void invokeSuccessCallback(final NativeJSObject jsObject) | 401 private void invokeSuccessCallback(final NativeJSObject jsObject) | 
| 401 { | 402 { | 
| 402 try | 403 try | 
| 403 { | 404 { | 
| 404 if (this.apiCallback != null) | 405 if (this.apiCallback != null) | 
| 405 { | 406 { | 
| 406 this.apiCallback.onApiRequestSucceeded(jsObject); | 407 this.apiCallback.onApiRequestSucceeded(jsObject); | 
| 407 } | 408 } | 
| 408 } | 409 } | 
| 409 catch (final Exception e) | 410 catch (final Exception e) | 
| 410 { | 411 { | 
| 411 Log.e(TAG, "onApiRequestSucceeded threw exception: " + e.getMessage(), e ); | 412 Log.e(TAG, "onApiRequestSucceeded threw exception: " + e.getMessage(), e ); | 
| 412 } | 413 } | 
| 413 } | 414 } | 
| 414 | 415 | 
| 415 private void invokeFailureCallback(final String msg) | 416 private void invokeFailureCallback(final String msg) | 
| 416 { | 417 { | 
| 417 if (this.apiCallback != null) | 418 if (this.apiCallback != null) | 
| 418 { | 419 { | 
| 419 this.apiCallback.onApiRequestFailed(msg); | 420 this.apiCallback.onApiRequestFailed(msg); | 
| 420 } | 421 } | 
| 421 } | 422 } | 
| 422 | 423 | 
| 423 private void invokeFailureCallback(final NativeJSObject jsObject) | 424 private void invokeFailureCallback(final NativeJSObject jsObject) | 
| 424 { | 425 { | 
| 425 invokeFailureCallback(getStringFromJsObject(jsObject, "error", "unknown er ror")); | 426 invokeFailureCallback(getStringFromJsObject(jsObject, "error", "unknown er ror")); | 
| 426 } | |
| 427 | |
| 428 private void attemptRetry() | |
| 429 { | |
| 430 if (System.currentTimeMillis() - this.creationTime > (QUERY_GET_FILTERS_LO ADED_TIMEOUT * 1000)) | |
| 431 { | |
| 432 this.invokeFailureCallback("getFiltersLoaded timeout"); | |
| 433 } | |
| 434 else | |
| 435 { | |
| 436 Log.d(TAG, "Retrying: " + this.value); | |
| 437 final ChainedRequest next = this.cloneForRetry(); | |
| 438 PRIVATE_HANDLER.postDelayed(new Runnable() | |
| 439 { | |
| 440 @Override | |
| 441 public void run() | |
| 442 { | |
| 443 GeckoAppShell.sendRequestToGecko(next); | |
| 444 } | |
| 445 }, QUERY_GET_FILTERS_LOADED_DELAY); | |
| 446 } | |
| 447 } | 427 } | 
| 448 | 428 | 
| 449 @Override | 429 @Override | 
| 450 public void onError(final NativeJSObject error) | 430 public void onError(final NativeJSObject error) | 
| 451 { | 431 { | 
| 452 if (this.checkForFiltersLoaded) | 432 this.invokeFailureCallback( | 
| 453 { | 433 "GeckoRequest error: " + error.optString("message", "<no message>") + "\n" + | 
| 454 this.attemptRetry(); | 434 error.optString("stack", "<no stack>")); | 
| 455 } | |
| 456 else | |
| 457 { | |
| 458 this.invokeFailureCallback( | |
| 459 "GeckoRequest error: " + error.optString("message", "<no message>") + "\n" + | |
| 460 error.optString("stack", "<no stack>")); | |
| 461 } | |
| 462 } | 435 } | 
| 463 | 436 | 
| 464 @Override | 437 @Override | 
| 465 public void onResponse(final NativeJSObject jsObject) | 438 public void onResponse(final NativeJSObject jsObject) | 
| 466 { | 439 { | 
| 467 if (this.checkForFiltersLoaded) | 440 if (getBooleanFromJsObject(jsObject, "success", false)) | 
| 468 { | 441 { | 
| 469 if (getBooleanFromJsObject(jsObject, "success", false) | 442 this.invokeSuccessCallback(jsObject); | 
| 470 && getBooleanFromJsObject(jsObject, "value", false)) | |
| 471 { | |
| 472 GeckoAppShell.sendRequestToGecko(this.cloneForRequest()); | |
| 473 } | |
| 474 else | |
| 475 { | |
| 476 this.attemptRetry(); | |
| 477 } | |
| 478 } | 443 } | 
| 479 else | 444 else | 
| 480 { | 445 { | 
| 481 if (getBooleanFromJsObject(jsObject, "success", false)) | 446 this.invokeFailureCallback(jsObject); | 
| 482 { | |
| 483 this.invokeSuccessCallback(jsObject); | |
| 484 } | |
| 485 else | |
| 486 { | |
| 487 this.invokeFailureCallback(jsObject); | |
| 488 } | |
| 489 } | 447 } | 
| 490 } | 448 } | 
| 491 } | 449 } | 
| 492 | 450 | 
| 493 private static class AddOnEventListener implements GeckoEventListener | 451 private static class AddOnEventListener implements GeckoEventListener | 
| 494 { | 452 { | 
| 495 @Override | 453 @Override | 
| 496 public void handleMessage(String event, JSONObject message) | 454 public void handleMessage(String event, JSONObject message) | 
| 497 { | 455 { | 
| 498 if (ON_SAVE_EVENT.equals(event)) | 456 if (ON_FILTERS_LOAD_EVENT.equals(event)) | 
| 499 { | 457 { | 
| 500 // This indicates that all changes have been saved by the extension. Tha t way, we can clear | 458 // The filters have been loaded by the extension. Given that, we can sen d all pending requests | 
| 501 // our list of uncompleted requests | 459 filtersLoaded = true; | 
| 460 sendPendingRequests(); | |
| 461 } | |
| 462 else if (ON_FILTERS_SAVE_EVENT.equals(event)) | |
| 463 { | |
| 464 // All changes have been saved by the extension. That way, we can clear our list of | |
| 465 // pending requests | |
| 502 // See https://issues.adblockplus.org/ticket/2853 | 466 // See https://issues.adblockplus.org/ticket/2853 | 
| 503 clearUncompletedRequests(); | 467 clearPendingRequests(); | 
| 504 } | 468 } | 
| 505 } | 469 } | 
| 506 } | 470 } | 
| 507 } | 471 } | 
| LEFT | RIGHT |