/** * Copyright (c) 2012 Todoroo Inc * * See the file "LICENSE" for the full license governing this code. */ package com.todoroo.astrid.actfm.sync; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.text.TextUtils; import android.util.Log; import com.timsu.astrid.GCMIntentService; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.billing.BillingConstants; import com.todoroo.astrid.data.RemoteModel; import com.todoroo.astrid.data.TagData; import com.todoroo.astrid.data.User; import com.todoroo.astrid.service.TagDataService; import com.todoroo.astrid.service.abtesting.ABTestEventReportingService; import com.todoroo.astrid.tags.reusable.FeaturedListFilterExposer; /** * Service for synchronizing data on Astrid.com server with local. * * @author Tim Su <tim@todoroo.com> * */ @SuppressWarnings("nls") public final class ActFmSyncService { // --- instance variables @Autowired private TagDataService tagDataService; @Autowired private ActFmPreferenceService actFmPreferenceService; @Autowired private ActFmInvoker actFmInvoker; @Autowired private ABTestEventReportingService abTestEventReportingService; private String token; public ActFmSyncService() { DependencyInjectionService.getInstance().inject(this); } private void addAbTestEventInfo(List<Object> params) { JSONArray abTestInfo = abTestEventReportingService.getTestsWithVariantsArray(); try { for (int i = 0; i < abTestInfo.length(); i++) { params.add("ab_variants[]"); params.add(abTestInfo.getString(i)); } } catch (JSONException e) { Log.e("Error parsing AB test info", abTestInfo.toString(), e); } } // --- data fetch methods public int fetchFeaturedLists(int serverTime) throws JSONException, IOException { if (!checkForToken()) return 0; JSONObject result = actFmInvoker.invoke("featured_lists", "token", token, "modified_after", serverTime); JSONArray featuredLists = result.getJSONArray("list"); if (featuredLists.length() > 0) Preferences.setBoolean(FeaturedListFilterExposer.PREF_SHOULD_SHOW_FEATURED_LISTS, true); for (int i = 0; i < featuredLists.length(); i++) { JSONObject featObject = featuredLists.getJSONObject(i); tagDataService.saveFeaturedList(featObject); } return result.optInt("time", 0); } public void updateUserSubscriptionStatus(Runnable onSuccess, Runnable onRecoverableError, Runnable onInvalidToken) { String purchaseToken = Preferences.getStringValue(BillingConstants.PREF_PURCHASE_TOKEN); String productId = Preferences.getStringValue(BillingConstants.PREF_PRODUCT_ID); try { if (!checkForToken()) throw new ActFmServiceException("Not logged in", null); ArrayList<Object> params = new ArrayList<Object>(); params.add("purchase_token"); params.add(purchaseToken); params.add("product_id"); params.add(productId); addAbTestEventInfo(params); params.add("token"); params.add(token); actFmInvoker.invoke("premium_update_android", params.toArray(new Object[params.size()])); Preferences.setBoolean(BillingConstants.PREF_NEEDS_SERVER_UPDATE, false); if (onSuccess != null) onSuccess.run(); } catch (Exception e) { if (e instanceof ActFmServiceException) { ActFmServiceException ae = (ActFmServiceException)e; if (ae.result != null && ae.result.optString("status").equals("error")) { if (ae.result.optString("code").equals("invalid_purchase_token")) { // Not a valid purchase--expired or duolicate Preferences.setBoolean(ActFmPreferenceService.PREF_LOCAL_PREMIUM, false); Preferences.setBoolean(BillingConstants.PREF_NEEDS_SERVER_UPDATE, false); if (onInvalidToken != null) onInvalidToken.run(); return; } } } Preferences.setBoolean(BillingConstants.PREF_NEEDS_SERVER_UPDATE, true); if (onRecoverableError != null) onRecoverableError.run(); } } public void setGCMRegistration(String regId) { try { String deviceId = GCMIntentService.getDeviceID(); String existingC2DM = Preferences.getStringValue(GCMIntentService.PREF_C2DM_REGISTRATION); ArrayList<Object> params = new ArrayList<Object>(); params.add("gcm"); params.add(regId); if (!TextUtils.isEmpty(deviceId)) { params.add("device_id"); params.add(deviceId); } if (!TextUtils.isEmpty(existingC2DM)) { // Unregisters C2DM with the server for migration purposes params.add("c2dm"); params.add(existingC2DM); } invoke("user_set_gcm", params.toArray(new Object[params.size()])); Preferences.setString(GCMIntentService.PREF_REGISTRATION, regId); Preferences.setString(GCMIntentService.PREF_C2DM_REGISTRATION, null); Preferences.setString(GCMIntentService.PREF_NEEDS_REGISTRATION, null); Preferences.setBoolean(GCMIntentService.PREF_NEEDS_RETRY, false); } catch (IOException e) { Preferences.setString(GCMIntentService.PREF_NEEDS_REGISTRATION, regId); Log.e("gcm", "error-gcm-register", e); } } // --- generic invokation /** invoke authenticated method against the server */ public JSONObject invoke(String method, Object... getParameters) throws IOException, ActFmServiceException { if(!checkForToken()) throw new ActFmServiceException("not logged in", null); Object[] parameters = new Object[getParameters.length + 2]; parameters[0] = "token"; parameters[1] = token; for(int i = 0; i < getParameters.length; i++) parameters[i+2] = getParameters[i]; return actFmInvoker.invoke(method, parameters); } protected void handleException(String message, Exception exception) { Log.w("actfm-sync", message, exception); } private boolean checkForToken() { if(!actFmPreferenceService.isLoggedIn()) return false; token = actFmPreferenceService.getToken(); return true; } // --- json reader helper /** * Read data models from JSON */ public static class JsonHelper { protected static long readDate(JSONObject item, String key) { return item.optLong(key, 0) * 1000L; } public static void jsonFromUser(JSONObject json, User model) throws JSONException { json.put("id", model.getValue(User.UUID)); json.put("name", model.getDisplayName()); json.put("email", model.getValue(User.EMAIL)); json.put("picture", model.getPictureUrl(User.PICTURE, RemoteModel.PICTURE_THUMB)); json.put("first_name", model.getValue(User.FIRST_NAME)); } public static void featuredListFromJson(JSONObject json, TagData model) throws JSONException { parseTagDataFromJson(json, model, true); } private static void parseTagDataFromJson(JSONObject json, TagData model, boolean featuredList) throws JSONException { model.clearValue(TagData.UUID); model.setValue(TagData.UUID, Long.toString(json.getLong("id"))); model.setValue(TagData.NAME, json.getString("name")); if (featuredList) model.setFlag(TagData.FLAGS, TagData.FLAG_FEATURED, true); if(json.has("picture")) model.setValue(TagData.PICTURE, json.optString("picture", "")); if(json.has("thumb")) model.setValue(TagData.THUMB, json.optString("thumb", "")); if(json.has("is_silent")) model.setFlag(TagData.FLAGS, TagData.FLAG_SILENT,json.getBoolean("is_silent")); if(!json.isNull("description")) model.setValue(TagData.TAG_DESCRIPTION, json.getString("description")); if(json.has("members")) { JSONArray members = json.getJSONArray("members"); model.setValue(TagData.MEMBERS, members.toString()); model.setValue(TagData.MEMBER_COUNT, members.length()); } if (json.has("deleted_at")) model.setValue(TagData.DELETION_DATE, readDate(json, "deleted_at")); if(json.has("tasks")) model.setValue(TagData.TASK_COUNT, json.getInt("tasks")); } } }