From 6726fc9cc26eec8b108df1905b633ef330ea1125 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 17 Jan 2017 14:33:45 -0800 Subject: [PATCH 001/465] Added basic code for representing an SDK component --- .../sdk/ApptentiveComponentActivity.java | 55 ++++++++++++ .../android/sdk/ApptentiveInternal.java | 7 ++ .../apptentive/android/sdk/ApptentiveLog.java | 5 +- .../android/sdk/ApptentiveViewActivity.java | 3 +- .../apptentive/android/sdk/debug/Assert.java | 86 ++++++++++++++++++ .../android/sdk/util/ObjectUtils.java | 19 ++++ .../android/sdk/util/StringUtils.java | 36 ++++++++ .../util/registry/ApptentiveComponent.java | 8 ++ .../ApptentiveComponentReference.java | 17 ++++ .../registry/ApptentiveComponentRegistry.java | 87 +++++++++++++++++++ 10 files changed, 320 insertions(+), 3 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponent.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentReference.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java new file mode 100644 index 000000000..5e30cbabc --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java @@ -0,0 +1,55 @@ +package com.apptentive.android.sdk; + +import android.support.v7.app.AppCompatActivity; + +import com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry; +import com.apptentive.android.sdk.util.registry.ApptentiveComponent; + +import static com.apptentive.android.sdk.util.ObjectUtils.*; + +/** A base class for any SDK activity */ +public class ApptentiveComponentActivity extends AppCompatActivity implements ApptentiveComponent { + + //region Life cycle + + @Override + protected void onStart() { + super.onStart(); + registerComponent(); + } + + @Override + protected void onStop() { + super.onStop(); + unregisterComponent(); + } + + //endregion + + //region Object registry + + private void registerComponent() { + ApptentiveComponentRegistry componentRegistry = getComponentRegistry(); + if (componentRegistry != null) { + componentRegistry.register(this); + } + } + + private void unregisterComponent() { + ApptentiveComponentRegistry componentRegistry = getComponentRegistry(); + if (componentRegistry != null) { + componentRegistry.unregister(this); + } + } + + private ApptentiveComponentRegistry getComponentRegistry() { + ApptentiveInternal instance = notNull(ApptentiveInternal.getInstance(), "ApptentiveInternal is not initialized"); + if (instance == null) { + return null; + } + + return notNull(instance.getComponentRegistry(), "ApptentiveComponentRegistry is not initialized"); + } + + //endregion +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 422739037..249a3322d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -56,6 +56,7 @@ import com.apptentive.android.sdk.storage.VersionHistoryStore; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; +import com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry; import org.json.JSONException; import org.json.JSONObject; @@ -82,6 +83,7 @@ public class ApptentiveInternal { ApptentiveTaskManager taskManager; CodePointStore codePointStore; ApptentiveActivityLifecycleCallbacks lifecycleCallbacks; + ApptentiveComponentRegistry componentRegistry; // These variables are initialized in Apptentive.register(), and so they are freely thereafter. If they are unexpectedly null, then if means the host app did not register Apptentive. Context appContext; @@ -174,6 +176,7 @@ public static ApptentiveInternal createInstance(Context context, final String ap PayloadSendWorker payloadWorker = new PayloadSendWorker(); InteractionManager interactionMgr = new InteractionManager(); ApptentiveTaskManager worker = new ApptentiveTaskManager(sApptentiveInternal.appContext); + ApptentiveComponentRegistry componentRegistry = new ApptentiveComponentRegistry(); sApptentiveInternal.messageManager = msgManager; sApptentiveInternal.payloadWorker = payloadWorker; @@ -181,6 +184,7 @@ public static ApptentiveInternal createInstance(Context context, final String ap sApptentiveInternal.taskManager = worker; sApptentiveInternal.codePointStore = new CodePointStore(); sApptentiveInternal.cachedExecutor = Executors.newCachedThreadPool(); + sApptentiveInternal.componentRegistry = componentRegistry; sApptentiveInternal.apiKey = Util.trim(apptentiveApiKey); } } @@ -320,6 +324,9 @@ public ApptentiveActivityLifecycleCallbacks getRegisteredLifecycleCallbacks() { return lifecycleCallbacks; } + public ApptentiveComponentRegistry getComponentRegistry() { + return componentRegistry; + } /* Get the foreground activity from the current application, i.e. at the top of the task * It is tracked through {@link #onActivityStarted(Activity)} and {@link #onActivityStopped(Activity)} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java index 74c70917d..e7f521ee6 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java @@ -80,6 +80,9 @@ public static void e(String message, Object... args){ public static void e(String message, Throwable throwable, Object... args){ doLog(Level.ERROR, throwable, message, args); } + public static void e(Throwable throwable, String message, Object... args){ + doLog(Level.ERROR, throwable, message, args); + } public static void a(String message, Object... args){ doLog(Level.ASSERT, null, message, args); @@ -88,7 +91,7 @@ public static void a(String message, Throwable throwable, Object... args){ doLog(Level.ASSERT, throwable, message, args); } - public static enum Level { + public enum Level { VERBOSE(Log.VERBOSE), DEBUG(Log.DEBUG), INFO(Log.INFO), diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index 478d3295e..0d8a9e84a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -24,7 +24,6 @@ import android.support.v4.content.res.ResourcesCompat; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.View; @@ -40,7 +39,7 @@ import com.apptentive.android.sdk.util.Util; -public class ApptentiveViewActivity extends AppCompatActivity implements ApptentiveBaseFragment.OnFragmentTransitionListener { +public class ApptentiveViewActivity extends ApptentiveComponentActivity implements ApptentiveBaseFragment.OnFragmentTransitionListener { private static final String FRAGMENT_TAG = "fragmentTag"; private int fragmentType; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java new file mode 100644 index 000000000..956da5fba --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -0,0 +1,86 @@ +package com.apptentive.android.sdk.debug; + +import java.util.Collection; + +/** + * A set of assertion methods useful for writing 'runtime' tests. These methods can be used directly: + * Assert.assertEquals(...), however, they read better if they are referenced through static import: + * + * import static org.junit.Assert.*; + * ... + * assertEquals(...); + * + */ +public class Assert { + + /** + * Asserts that an object isn't null + */ + public static void assertNotNull(Object object) { + // FIXME: implement me + } + + /** + * Asserts that an object isn't null + */ + public static void assertNotNull(Object object, String message) { + // FIXME: implement me + } + + /** + * Asserts that an object isn't null + */ + public static void assertNotNull(Object object, String format, Object... args) { + // FIXME: implement me + } + + /** + * Asserts that collection contains an object + */ + public static void assertContains(Collection collection, Object object) { + // FIXME: implement me + } + + /** + * Asserts that collection contains an object + */ + public static void assertContains(Collection collection, Object object, String message) { + // FIXME: implement me + } + + /** + * Asserts that collection contains an object + */ + public static void assertContains(Collection collection, Object object, String format, Object... args) { + // FIXME: implement me + } + + /** + * Asserts that collection doesn't contain an object + */ + public static void assertNotContains(Collection collection, Object object) { + // FIXME: implement me + } + + /** + * Asserts that collection doesn't contain an object + */ + public static void assertNotContains(Collection collection, Object object, String message) { + // FIXME: implement me + } + + /** + * Asserts that collection doesn't contain an object + */ + public static void assertNotContains(Collection collection, Object object, String format, Object... args) { + // FIXME: implement me + } + + /** Asserts that code is executed on the main thread */ + public static void assertMainThread() { + } + + /** Asserts that code is not executed on the main thread */ + public static void assertNotMainThread() { + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java new file mode 100644 index 000000000..e86229872 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java @@ -0,0 +1,19 @@ +package com.apptentive.android.sdk.util; + +import static com.apptentive.android.sdk.debug.Assert.*; + +/** + * A collection of useful object-related functions + */ +public final class ObjectUtils { + + @SuppressWarnings("unchecked") + public static T as(Object object, Class cls) { + return cls.isInstance(object) ? (T) object : null; + } + + public static T notNull(T object, String message) { + assertNotNull(object, message); + return object; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java new file mode 100644 index 000000000..fbf149057 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java @@ -0,0 +1,36 @@ +package com.apptentive.android.sdk.util; + +/** + * A collection of useful string-related functions + */ +public final class StringUtils { + + /** + * Safe String.format + */ + public static String format(String format, Object... args) { + if (format != null && args != null && args.length > 0) { + try { + return String.format(format, args); + } catch (Exception e) { + android.util.Log.e("Lunar", "Error while formatting String: " + e.getMessage()); // FIXME: better system loggingb + } + } + + return format; + } + + /** + * Safe Object.toString() + */ + public static String toString(Object value) { + return value != null ? value.toString() : "null"; + } + + /** + * Returns true is string is null or empty + */ + public static boolean isNullOrEmpty(String str) { + return str == null || str.length() == 0; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponent.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponent.java new file mode 100644 index 000000000..11875ee84 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponent.java @@ -0,0 +1,8 @@ +package com.apptentive.android.sdk.util.registry; + +/** + * Interface marker for any class which represents an SDK component + * in {@link ApptentiveComponentRegistry} + */ +public interface ApptentiveComponent { +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentReference.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentReference.java new file mode 100644 index 000000000..63f6d0dbc --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentReference.java @@ -0,0 +1,17 @@ +package com.apptentive.android.sdk.util.registry; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +/** + * A simple subclass of WeakReference to handle {@link ApptentiveComponent} objects + */ +class ApptentiveComponentReference extends WeakReference { + public ApptentiveComponentReference(ApptentiveComponent referent) { + super(referent); + } + + public ApptentiveComponentReference(ApptentiveComponent referent, ReferenceQueue q) { + super(referent, q); + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java new file mode 100644 index 000000000..3903927c9 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java @@ -0,0 +1,87 @@ +package com.apptentive.android.sdk.util.registry; + +import com.apptentive.android.sdk.ApptentiveLog; + +import java.util.ArrayList; +import java.util.List; + +import static com.apptentive.android.sdk.debug.Assert.*; +import static com.apptentive.android.sdk.util.ObjectUtils.*; + +public class ApptentiveComponentRegistry { + /** + * List of currently registered components + */ + private final List components; // TODO: weak references? + + public ApptentiveComponentRegistry() { + components = new ArrayList<>(); + } + + //region Object registration + + public void register(ApptentiveComponent object) { + assertNotNull(object, "Attempted to register a null object"); + if (object != null) { + assertNotContains(components, object, "Object already registered: %s", object); + if (!components.contains(object)) { + components.add(object); + } + } + } + + public void unregister(ApptentiveComponent object) { + assertNotNull(object, "Attempted to unregister a null object"); + if (object != null) { + assertContains(components, object, "Object is not registered: %s", object); + components.remove(object); + } + } + + //endregion + + //region Notifications + + public void notify(Notifier notifier) { + + List qualifiedObjects = new ArrayList<>(components.size()); + + // we put all the qualified components into a separate list to avoid ConcurrentModificationException + Class type = notifier.getType(); + for (ApptentiveComponent object : components) { + T instance = as(object, type); + if (instance != null) { + qualifiedObjects.add(instance); + } + } + + // notify each object safely + for (T object : qualifiedObjects) { + try { + notifier.notify(object); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while notifying object: %s", object); + } + } + } + + //endregion + + //region Notifier helper class + + public static abstract class Notifier { + private final Class type; + + public Notifier(Class type) { + this.type = type; + } + + public abstract void notify(T object); + + public Class getType() { + return type; + } + } + + //endregion +} \ No newline at end of file From 152f929da8480c346900b067792efb50f0b0db73 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 17 Jan 2017 15:17:05 -0800 Subject: [PATCH 002/465] ApptentiveComponent progress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Used weak references instead of components directly • Switched to onCreate/onDestroy callbacks • Cosmetic refactoring --- .../sdk/ApptentiveComponentActivity.java | 12 ++- .../apptentive/android/sdk/debug/Assert.java | 63 ++++++++++++- .../ApptentiveComponentReference.java | 4 + .../registry/ApptentiveComponentRegistry.java | 93 +++++++++++++------ 4 files changed, 137 insertions(+), 35 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java index 5e30cbabc..112abeeac 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java @@ -1,5 +1,7 @@ package com.apptentive.android.sdk; +import android.os.Bundle; +import android.os.PersistableBundle; import android.support.v7.app.AppCompatActivity; import com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry; @@ -10,17 +12,17 @@ /** A base class for any SDK activity */ public class ApptentiveComponentActivity extends AppCompatActivity implements ApptentiveComponent { - //region Life cycle + //region Activity life cycle @Override - protected void onStart() { - super.onStart(); + public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) { + super.onCreate(savedInstanceState, persistentState); registerComponent(); } @Override - protected void onStop() { - super.onStop(); + protected void onDestroy() { + super.onDestroy(); unregisterComponent(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java index 956da5fba..92810f51c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -13,6 +13,48 @@ */ public class Assert { + /** + * Asserts that condition is true + */ + public static void assertTrue(boolean condition) { + // FIXME: implement me + } + + /** + * Asserts that condition is true + */ + public static void assertTrue(boolean condition, String message) { + // FIXME: implement me + } + + /** + * Asserts that condition is true + */ + public static void assertTrue(boolean condition, String format, Object... args) { + // FIXME: implement me + } + + /** + * Asserts that condition is false + */ + public static void assertFalse(boolean condition) { + // FIXME: implement me + } + + /** + * Asserts that condition is false + */ + public static void assertFalse(boolean condition, String message) { + // FIXME: implement me + } + + /** + * Asserts that condition is false + */ + public static void assertFalse(boolean condition, String format, Object... args) { + // FIXME: implement me + } + /** * Asserts that an object isn't null */ @@ -34,22 +76,37 @@ public static void assertNotNull(Object object, String format, Object... args) { // FIXME: implement me } + /** Asserts that executed is not equal to actual */ + public static void assertNotEquals(int expected, int actual) { + // FIXME: implement me + } + + /** Asserts that executed is not equal to actual */ + public static void assertNotEquals(int expected, int actual, String message) { + // FIXME: implement me + } + + /** Asserts that executed is not equal to actual */ + public static void assertNotEquals(int expected, int actual, String format, Object... args) { + // FIXME: implement me + } + /** - * Asserts that collection contains an object + * Asserts that collection isRegistered an object */ public static void assertContains(Collection collection, Object object) { // FIXME: implement me } /** - * Asserts that collection contains an object + * Asserts that collection isRegistered an object */ public static void assertContains(Collection collection, Object object, String message) { // FIXME: implement me } /** - * Asserts that collection contains an object + * Asserts that collection isRegistered an object */ public static void assertContains(Collection collection, Object object, String format, Object... args) { // FIXME: implement me diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentReference.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentReference.java index 63f6d0dbc..af25a997b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentReference.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentReference.java @@ -14,4 +14,8 @@ public ApptentiveComponentReference(ApptentiveComponent referent) { public ApptentiveComponentReference(ApptentiveComponent referent, ReferenceQueue q) { super(referent, q); } + + public boolean isReferenceLost() { + return get() == null; + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java index 3903927c9..1f526c66a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java @@ -10,73 +10,112 @@ public class ApptentiveComponentRegistry { /** - * List of currently registered components + * List of references for currently registered components */ - private final List components; // TODO: weak references? + private final List componentReferences; public ApptentiveComponentRegistry() { - components = new ArrayList<>(); + componentReferences = new ArrayList<>(); } //region Object registration - public void register(ApptentiveComponent object) { - assertNotNull(object, "Attempted to register a null object"); - if (object != null) { - assertNotContains(components, object, "Object already registered: %s", object); - if (!components.contains(object)) { - components.add(object); + /** Register < */ + public void register(ApptentiveComponent component) { + assertNotNull(component, "Attempted to register a null component"); + if (component != null) { + boolean alreadyRegistered = isRegistered(component); + assertFalse(alreadyRegistered, "Attempted to register component twice: %s", component); + if (!alreadyRegistered) { + componentReferences.add(new ApptentiveComponentReference(component)); } } } - public void unregister(ApptentiveComponent object) { - assertNotNull(object, "Attempted to unregister a null object"); - if (object != null) { - assertContains(components, object, "Object is not registered: %s", object); - components.remove(object); + public void unregister(ApptentiveComponent component) { + assertNotNull(component, "Attempted to unregister a null component"); + if (component != null) { + int index = indexOf(component); + assertNotEquals(index, -1, "Attempted to unregister component twice: %s", component); + if (index != -1) { + componentReferences.remove(index); + } } } + /** Returns true if component is already registered */ + public boolean isRegistered(ApptentiveComponent component) { + assertNotNull(component); + return component != null && indexOf(component) != -1; + } + + private int indexOf(ApptentiveComponent component) { + int index = 0; + for (ApptentiveComponentReference componentReference : componentReferences) { + if (componentReference.get() == component) { + return index; + } + ++index; + } + + return -1; + } + //endregion //region Notifications - public void notify(Notifier notifier) { + public void notifyComponents(ComponentNotifierOperation operation) { - List qualifiedObjects = new ArrayList<>(components.size()); + List notifyees = new ArrayList<>(componentReferences.size()); + boolean hasLostReferences = false; // we put all the qualified components into a separate list to avoid ConcurrentModificationException - Class type = notifier.getType(); - for (ApptentiveComponent object : components) { - T instance = as(object, type); - if (instance != null) { - qualifiedObjects.add(instance); + Class notifyeeType = operation.getType(); + for (ApptentiveComponentReference reference : componentReferences) { + ApptentiveComponent component = reference.get(); + if (component == null) { + hasLostReferences = true; + continue; + } + + T notifyee = as(component, notifyeeType); + if (notifyee != null) { + notifyees.add(notifyee); } } // notify each object safely - for (T object : qualifiedObjects) { + for (T object : notifyees) { try { - notifier.notify(object); + operation.onComponentNotify(object); } catch (Exception e) { ApptentiveLog.e(e, "Exception while notifying object: %s", object); } } + + // cleanup up lost references + if (hasLostReferences) { + for (int i = componentReferences.size() - 1; i >= 0; --i) { + if (componentReferences.get(i).isReferenceLost()) { + componentReferences.remove(i); + } + } + } } //endregion - //region Notifier helper class + //region ComponentNotifierOperation helper class - public static abstract class Notifier { + public static abstract class ComponentNotifierOperation { private final Class type; - public Notifier(Class type) { + public ComponentNotifierOperation(Class type) { this.type = type; } - public abstract void notify(T object); + public abstract void onComponentNotify(T object); public Class getType() { return type; From 490880b7772ea999f76b45c33e1d888afedccfc3 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 17 Jan 2017 15:42:37 -0800 Subject: [PATCH 003/465] Added user logout + some refactoring --- .../android/sdk/ApptentiveInternal.java | 14 +++++++++++ .../android/sdk/ApptentiveViewActivity.java | 11 ++++++++- .../sdk/listeners/OnUserLogOutListener.java | 8 +++++++ .../registry/ApptentiveComponentRegistry.java | 24 +++++++++---------- 4 files changed, 44 insertions(+), 13 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/listeners/OnUserLogOutListener.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 249a3322d..c2f76349d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -29,6 +29,7 @@ import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.lifecycle.ApptentiveActivityLifecycleCallbacks; +import com.apptentive.android.sdk.listeners.OnUserLogOutListener; import com.apptentive.android.sdk.model.AppRelease; import com.apptentive.android.sdk.model.CodePointStore; import com.apptentive.android.sdk.model.Configuration; @@ -71,6 +72,8 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; +import static com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry.ComponentNotifier; + /** * This class contains only internal methods. These methods should not be access directly by the host app. */ @@ -1184,4 +1187,15 @@ public static boolean checkRegistered() { } return true; } + + /** Ends current user session */ + public static void logout() { + getInstance().getComponentRegistry() + .notifyComponents(new ComponentNotifier(OnUserLogOutListener.class) { + @Override + public void onComponentNotify(OnUserLogOutListener component) { + component.onUserLogOut(); + } + }); + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index 0d8a9e84a..790b91fd2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -31,6 +31,7 @@ import android.view.WindowManager; import com.apptentive.android.sdk.adapter.ApptentiveViewPagerAdapter; +import com.apptentive.android.sdk.listeners.OnUserLogOutListener; import com.apptentive.android.sdk.model.FragmentFactory; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.fragment.ApptentiveBaseFragment; @@ -39,7 +40,8 @@ import com.apptentive.android.sdk.util.Util; -public class ApptentiveViewActivity extends ApptentiveComponentActivity implements ApptentiveBaseFragment.OnFragmentTransitionListener { +public class ApptentiveViewActivity extends ApptentiveComponentActivity + implements ApptentiveBaseFragment.OnFragmentTransitionListener, OnUserLogOutListener { private static final String FRAGMENT_TAG = "fragmentTag"; private int fragmentType; @@ -401,4 +403,11 @@ private void setStatusBarColor() { getWindow().setStatusBarColor(Util.alphaMixColors(statusBarDefaultColor, overlayColor)); } } + + //region User log out listener + @Override + public void onUserLogOut() { + finish(); + } + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/listeners/OnUserLogOutListener.java b/apptentive/src/main/java/com/apptentive/android/sdk/listeners/OnUserLogOutListener.java new file mode 100644 index 000000000..997c871c0 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/listeners/OnUserLogOutListener.java @@ -0,0 +1,8 @@ +package com.apptentive.android.sdk.listeners; + +/** + * Interface definition for a callback to be invoked when user is logged out. + */ +public interface OnUserLogOutListener { + void onUserLogOut(); +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java index 1f526c66a..bba5f9cea 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java @@ -65,13 +65,13 @@ private int indexOf(ApptentiveComponent component) { //region Notifications - public void notifyComponents(ComponentNotifierOperation operation) { + public void notifyComponents(ComponentNotifier notifier) { - List notifyees = new ArrayList<>(componentReferences.size()); + List components = new ArrayList<>(componentReferences.size()); boolean hasLostReferences = false; // we put all the qualified components into a separate list to avoid ConcurrentModificationException - Class notifyeeType = operation.getType(); + Class targetType = notifier.getType(); for (ApptentiveComponentReference reference : componentReferences) { ApptentiveComponent component = reference.get(); if (component == null) { @@ -79,18 +79,18 @@ public void notifyComponents(ComponentNotifierOp continue; } - T notifyee = as(component, notifyeeType); - if (notifyee != null) { - notifyees.add(notifyee); + T target = as(component, targetType); + if (target != null) { + components.add(target); } } // notify each object safely - for (T object : notifyees) { + for (T component : components) { try { - operation.onComponentNotify(object); + notifier.onComponentNotify(component); } catch (Exception e) { - ApptentiveLog.e(e, "Exception while notifying object: %s", object); + ApptentiveLog.e(e, "Exception while notifying object: %s", component); } } @@ -106,12 +106,12 @@ public void notifyComponents(ComponentNotifierOp //endregion - //region ComponentNotifierOperation helper class + //region Component notifier helper class - public static abstract class ComponentNotifierOperation { + public static abstract class ComponentNotifier { private final Class type; - public ComponentNotifierOperation(Class type) { + public ComponentNotifier(Class type) { this.type = type; } From 02060dc05f6c5a0138c700ebf53879b41a2c5d74 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 17 Jan 2017 16:35:46 -0800 Subject: [PATCH 004/465] Fixed registering app components ApptentiveComponentActivity used the wrong callback --- .../apptentive/android/sdk/ApptentiveComponentActivity.java | 6 ++++-- .../com/apptentive/android/sdk/ApptentiveViewActivity.java | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java index 112abeeac..70c416b31 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java @@ -2,6 +2,7 @@ import android.os.Bundle; import android.os.PersistableBundle; +import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry; @@ -14,9 +15,10 @@ public class ApptentiveComponentActivity extends AppCompatActivity implements Ap //region Activity life cycle + @Override - public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) { - super.onCreate(savedInstanceState, persistentState); + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); registerComponent(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index 790b91fd2..47e1092eb 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -56,6 +56,7 @@ public class ApptentiveViewActivity extends ApptentiveComponentActivity private View decorView; private View contentView; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); From e1a8f3005d833270ed5c693938471dc60fb4830e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 17 Jan 2017 16:55:46 -0800 Subject: [PATCH 005/465] Make public methods of ApptentiveComponentRegistry synchronized --- .../registry/ApptentiveComponentRegistry.java | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java index bba5f9cea..a847c9dd3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java @@ -8,6 +8,35 @@ import static com.apptentive.android.sdk.debug.Assert.*; import static com.apptentive.android.sdk.util.ObjectUtils.*; +/** + * A registry that holds a list of weak references to {@link ApptentiveComponent} and + * provides an easy mechanism for broadcasting information within a program. + * Each registered component would receive notifications based on types of the interfaces it + * implements. + *

+ * Component example: + * + * public class MyActivity extends Activity implements ApptentiveComponent, OnUserLogOutListener { + * ... + * public void onUserLogOut() { + * finish(); + * } + * ... + * } + * + *

+ * Notification example: + * + * public void logout() { + * getComponentRegistry() + * .notifyComponents(new ComponentNotifier(OnUserLogOutListener.class) { + * public void onComponentNotify(OnUserLogOutListener component) { + * component.onUserLogOut(); + * } + * }); + * } + * + */ public class ApptentiveComponentRegistry { /** * List of references for currently registered components @@ -20,8 +49,10 @@ public ApptentiveComponentRegistry() { //region Object registration - /** Register < */ - public void register(ApptentiveComponent component) { + /** + * Registers component + */ + public synchronized void register(ApptentiveComponent component) { assertNotNull(component, "Attempted to register a null component"); if (component != null) { boolean alreadyRegistered = isRegistered(component); @@ -32,7 +63,10 @@ public void register(ApptentiveComponent component) { } } - public void unregister(ApptentiveComponent component) { + /** + * Unregisters component + */ + public synchronized void unregister(ApptentiveComponent component) { assertNotNull(component, "Attempted to unregister a null component"); if (component != null) { int index = indexOf(component); @@ -43,13 +77,18 @@ public void unregister(ApptentiveComponent component) { } } - /** Returns true if component is already registered */ - public boolean isRegistered(ApptentiveComponent component) { + /** + * Returns true if component is already registered + */ + public synchronized boolean isRegistered(ApptentiveComponent component) { assertNotNull(component); return component != null && indexOf(component) != -1; } - private int indexOf(ApptentiveComponent component) { + /** + * Return the index of component or -1 if component is not registered + */ + private synchronized int indexOf(ApptentiveComponent component) { int index = 0; for (ApptentiveComponentReference componentReference : componentReferences) { if (componentReference.get() == component) { @@ -65,7 +104,7 @@ private int indexOf(ApptentiveComponent component) { //region Notifications - public void notifyComponents(ComponentNotifier notifier) { + public synchronized void notifyComponents(ComponentNotifier notifier) { List components = new ArrayList<>(componentReferences.size()); boolean hasLostReferences = false; From 2e57e9657c9fcf6fd800617b1cca6f46e696bf09 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 17 Jan 2017 17:25:39 -0800 Subject: [PATCH 006/465] Check if activity is already finishing before finishing it --- .../com/apptentive/android/sdk/ApptentiveViewActivity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index 47e1092eb..c43d03290 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -408,7 +408,9 @@ private void setStatusBarColor() { //region User log out listener @Override public void onUserLogOut() { - finish(); + if (isFinishing()) { + finish(); + } } //endregion } From 4eb1ae3c6526a8fa50da64b4676c651b608bcacf Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 17 Jan 2017 17:30:40 -0800 Subject: [PATCH 007/465] Dispatch queue progress --- .../sdk/util/threading/DispatchQueue.java | 31 +++++++++++++++++++ .../util/threading/HandlerDispatchQueue.java | 26 ++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java new file mode 100644 index 000000000..50117bc97 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util.threading; + +import android.os.Looper; + +/** + * A class representing dispatch queue where {@link Runnable} tasks can be executed serially + */ +public abstract class DispatchQueue { + /** + * Add {@link Runnable} task to the queue + */ + public abstract void dispatchAsync(Runnable runnable); + + /** + * A global dispatch queue associated with main thread + */ + public static DispatchQueue mainQueue() { + return Holder.INSTANCE; + } + + /** Thread safe singleton trick */ + private static class Holder { + private static final DispatchQueue INSTANCE = new HandlerDispatchQueue(Looper.getMainLooper()); + } +} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java new file mode 100644 index 000000000..38f6d60ff --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util.threading; + +import android.os.Handler; +import android.os.Looper; + +public class HandlerDispatchQueue extends DispatchQueue { + private final Handler handler; + + public HandlerDispatchQueue(Looper looper) { + if (looper == null) { + throw new NullPointerException("Looper is null"); + } + handler = new Handler(looper); + } + + @Override + public void dispatchAsync(Runnable runnable) { + handler.post(runnable); + } +} \ No newline at end of file From 6ec2215c246a4e4384285f49ca913002dd11f6bb Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 18 Jan 2017 01:29:38 -0800 Subject: [PATCH 008/465] Made ApptentiveComponentRegistry thread safe + added a few unit tests --- apptentive/build.gradle | 1 + .../android/sdk/util/StringUtils.java | 41 ++++ .../registry/ApptentiveComponentRegistry.java | 15 +- .../sdk/util/threading/DispatchQueue.java | 16 +- .../apptentive/android/sdk/TestCaseBase.java | 41 ++++ .../ApptentiveComponentRegistryTest.java | 176 ++++++++++++++++++ .../sdk/util/threading/TestDispatchQueue.java | 37 ++++ 7 files changed, 324 insertions(+), 3 deletions(-) create mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java create mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java create mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java diff --git a/apptentive/build.gradle b/apptentive/build.gradle index 8eb7d47b4..5c03638d2 100644 --- a/apptentive/build.gradle +++ b/apptentive/build.gradle @@ -11,6 +11,7 @@ dependencies { compile 'com.android.support:appcompat-v7:24.2.1' compile 'com.android.support:cardview-v7:24.2.1' compile 'com.android.support:design:24.2.1' + testCompile 'junit:junit:4.12' } android { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java index fbf149057..8a97a0fd8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java @@ -1,5 +1,7 @@ package com.apptentive.android.sdk.util; +import java.util.List; + /** * A collection of useful string-related functions */ @@ -27,6 +29,45 @@ public static String toString(Object value) { return value != null ? value.toString() : "null"; } + /** + * Constructs and returns a string object that is the result of interposing a separator between the elements of the array + */ + public static String join(T[] array) { + return join(array, ","); + } + + /** + * Constructs and returns a string object that is the result of interposing a separator between the elements of the array + */ + public static String join(T[] array, String separator) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < array.length; ++i) { + builder.append(array[i]); + if (i < array.length - 1) builder.append(separator); + } + return builder.toString(); + } + + /** + * Constructs and returns a string object that is the result of interposing a separator between the elements of the list + */ + public static String join(List list) { + return join(list, ","); + } + + /** + * Constructs and returns a string object that is the result of interposing a separator between the elements of the list + */ + public static String join(List list, String separator) { + StringBuilder builder = new StringBuilder(); + int i = 0; + for (T t : list) { + builder.append(t); + if (++i < list.size()) builder.append(separator); + } + return builder.toString(); + } + /** * Returns true is string is null or empty */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java index a847c9dd3..caaab9074 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java @@ -1,6 +1,7 @@ package com.apptentive.android.sdk.util.registry; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.util.threading.DispatchQueue; import java.util.ArrayList; import java.util.List; @@ -104,8 +105,20 @@ private synchronized int indexOf(ApptentiveComponent component) { //region Notifications - public synchronized void notifyComponents(ComponentNotifier notifier) { + /** + * Notify all the listeners of type T about event + */ + public synchronized void notifyComponents(final ComponentNotifier notifier) { + // in order to avoid UI-related issues - we dispatch all the notification on main thread + DispatchQueue.mainQueue().dispatchAsync(new Runnable() { + @Override + public void run() { + notifyComponentsSafe(notifier); + } + }); + } + private synchronized void notifyComponentsSafe(final ComponentNotifier notifier) { List components = new ArrayList<>(componentReferences.size()); boolean hasLostReferences = false; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java index 50117bc97..cd05bf745 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java @@ -24,8 +24,20 @@ public static DispatchQueue mainQueue() { return Holder.INSTANCE; } - /** Thread safe singleton trick */ + /** + * Thread safe singleton trick + */ private static class Holder { - private static final DispatchQueue INSTANCE = new HandlerDispatchQueue(Looper.getMainLooper()); + private static final DispatchQueue INSTANCE = createMainQueue(); + + private static DispatchQueue createMainQueue() { + try { + // this call will fail when running a unit test + // we would allow that and make test responsible for setting the implementation + return new HandlerDispatchQueue(Looper.getMainLooper()); + } catch (Exception e) { + return null; + } + } } } \ No newline at end of file diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java b/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java new file mode 100644 index 000000000..74603c4ae --- /dev/null +++ b/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk; + +import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.util.threading.TestDispatchQueue; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; + +public class TestCaseBase { + + private List result = new ArrayList<>(); + + static { + TestDispatchQueue.overrideMainQueue(); + } + + protected void addResult(String str) { + result.add(str); + } + + protected void assertResult(String... expected) { + assertEquals("\nExpected: " + StringUtils.join(expected) + + "\nActual: " + StringUtils.join(result), expected.length, result.size()); + + for (int i = 0; i < expected.length; ++i) { + assertEquals("\nExpected: " + StringUtils.join(expected) + + "\nActual: " + StringUtils.join(result), + expected[i], result.get(i)); + } + + result.clear(); + } +} diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java new file mode 100644 index 000000000..5f5ae9daa --- /dev/null +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util.registry; + +import com.apptentive.android.sdk.TestCaseBase; +import org.junit.Test; + +import static com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry.ComponentNotifier; + +public class ApptentiveComponentRegistryTest extends TestCaseBase { + + //region Testing + + @Test + public void testComponentRegistration() { + + ApptentiveComponentRegistry registry = new ApptentiveComponentRegistry(); + registry.notifyComponents(new ComponentNotifier(ListenerA.class) { + @Override + public void onComponentNotify(ListenerA listener) { + listener.OnTestEventA(); + } + }); + + final CompA c1 = new CompA("A-1"); + final CompA c2 = new CompA("A-2"); + final CompA c3 = new CompA("A-3"); + + registry.register(c1); + registry.register(c2); + registry.register(c3); + + registry.notifyComponents(new ComponentNotifier(ListenerA.class) { + @Override + public void onComponentNotify(ListenerA listener) { + listener.OnTestEventA(); + } + }); + assertResult("A-A-1", "A-A-2", "A-A-3"); + + registry.unregister(c2); + registry.notifyComponents(new ComponentNotifier(ListenerA.class) { + @Override + public void onComponentNotify(ListenerA listener) { + listener.OnTestEventA(); + } + }); + assertResult("A-A-1", "A-A-3"); + + registry.unregister(c1); + registry.notifyComponents(new ComponentNotifier(ListenerA.class) { + @Override + public void onComponentNotify(ListenerA listener) { + listener.OnTestEventA(); + } + }); + assertResult("A-A-3"); + + registry.unregister(c3); + registry.notifyComponents(new ComponentNotifier(ListenerA.class) { + @Override + public void onComponentNotify(ListenerA listener) { + listener.OnTestEventA(); + } + }); + assertResult(); + } + + @Test + public void testComponentNotification() { + + ApptentiveComponentRegistry registry = new ApptentiveComponentRegistry(); + registry.register(new CompA("A-1")); + registry.register(new CompA("A-2")); + registry.register(new CompAB("AB-1")); + registry.register(new CompAB("AB-2")); + registry.register(new CompC("C-1")); + registry.register(new CompC("C-2")); + + registry.notifyComponents(new ComponentNotifier(ListenerA.class) { + @Override + public void onComponentNotify(ListenerA listener) { + listener.OnTestEventA(); + } + }); + assertResult("A-A-1", "A-A-2", "A-AB-1", "A-AB-2"); + + registry.notifyComponents(new ComponentNotifier(ListenerB.class) { + @Override + public void onComponentNotify(ListenerB listener) { + listener.OnTestEventB(); + } + }); + assertResult("B-AB-1", "B-AB-2"); + + registry.notifyComponents(new ComponentNotifier(ListenerC.class) { + @Override + public void onComponentNotify(ListenerC listener) { + listener.OnTestEventC(); + } + }); + assertResult("C-C-1", "C-C-2"); + } + + //endregion + + //region Helpers + + interface ListenerA { + void OnTestEventA(); + } + + interface ListenerB { + void OnTestEventB(); + } + + interface ListenerC { + void OnTestEventC(); + } + + class BaseComponent implements ApptentiveComponent{ + + protected final String name; + + BaseComponent(String name) { + this.name = name; + } + } + + class CompA extends BaseComponent implements ListenerA { + + CompA(String name) { + super(name); + } + + @Override + public void OnTestEventA() { + addResult("A-" + name); + } + } + + class CompAB extends BaseComponent implements ListenerA, ListenerB { + + CompAB(String name) { + super(name); + } + + @Override + public void OnTestEventA() { + addResult("A-" + name); + } + + @Override + public void OnTestEventB() { + addResult("B-" + name); + } + } + + class CompC extends BaseComponent implements ListenerC { + + CompC(String name) { + super(name); + } + + @Override + public void OnTestEventC() { + addResult("C-" + name); + } + } + + //endregion +} \ No newline at end of file diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java new file mode 100644 index 000000000..3b9126c3d --- /dev/null +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util.threading; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +public class TestDispatchQueue extends DispatchQueue { + @Override + public void dispatchAsync(Runnable runnable) { + runnable.run(); + } + + public static void overrideMainQueue() { + overrideMainQueue(new TestDispatchQueue()); + } + + private static void overrideMainQueue(DispatchQueue queue) { + try { + Class holderClass = DispatchQueue.class.getDeclaredClasses()[0]; + Field instanceField = holderClass.getDeclaredField("INSTANCE"); + instanceField.setAccessible(true); + + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(instanceField, instanceField.getModifiers() & ~Modifier.FINAL); + + instanceField.set(null, queue); + } catch (Exception e) { + throw new AssertionError(e); + } + } +} From 5848a5d467bb982308c8ae1956c38960e6997ff8 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 18 Jan 2017 01:38:55 -0800 Subject: [PATCH 009/465] A few cosmetic fixes --- .../com/apptentive/android/sdk/ApptentiveComponentActivity.java | 1 - .../main/java/com/apptentive/android/sdk/util/StringUtils.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java index 70c416b31..4767ab1bf 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java @@ -15,7 +15,6 @@ public class ApptentiveComponentActivity extends AppCompatActivity implements Ap //region Activity life cycle - @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java index 8a97a0fd8..c7b8ded46 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java @@ -15,7 +15,7 @@ public static String format(String format, Object... args) { try { return String.format(format, args); } catch (Exception e) { - android.util.Log.e("Lunar", "Error while formatting String: " + e.getMessage()); // FIXME: better system loggingb + android.util.Log.e("Apptentive", "Error while formatting String: " + e.getMessage()); // FIXME: better system logging } } From becd6d24b0d122d84ead56293778af0f3f9ec066 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 18 Jan 2017 01:50:03 -0800 Subject: [PATCH 010/465] Fixed dismissing ApptentiveViewActivity on user log out --- .../java/com/apptentive/android/sdk/ApptentiveViewActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index c43d03290..cb6fa34a4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -408,7 +408,7 @@ private void setStatusBarColor() { //region User log out listener @Override public void onUserLogOut() { - if (isFinishing()) { + if (!isFinishing()) { finish(); } } From 039e8005c96a5d489f6bb728a58210a189862038 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 18 Jan 2017 20:45:14 -0800 Subject: [PATCH 011/465] Add `SessionData` class, test. --- apptentive/build.gradle | 1 + .../android/sdk/storage/SessionData.java | 39 ++++++++++++ .../android/sdk/storage/SessionDataTest.java | 60 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java create mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java diff --git a/apptentive/build.gradle b/apptentive/build.gradle index 8eb7d47b4..5c03638d2 100644 --- a/apptentive/build.gradle +++ b/apptentive/build.gradle @@ -11,6 +11,7 @@ dependencies { compile 'com.android.support:appcompat-v7:24.2.1' compile 'com.android.support:cardview-v7:24.2.1' compile 'com.android.support:design:24.2.1' + testCompile 'junit:junit:4.12' } android { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java new file mode 100644 index 000000000..697c7a46e --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import java.io.Serializable; + +public class SessionData implements Serializable { + + private static final long serialVersionUID = 1L; + + private String conversationToken; + + private String conversationId; + + public String getConversationToken() { + return conversationToken; + } + + public void setConversationToken(String conversationToken) { + this.conversationToken = conversationToken; + } + + public String getConversationId() { + return conversationId; + } + + public void setConversationId(String conversationId) { + this.conversationId = conversationId; + } +// version history + // code point store + // Various prefs + // Messages + // Interactions +} diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java new file mode 100644 index 000000000..e7bb43f22 --- /dev/null +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import com.apptentive.android.sdk.util.Util; + +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class SessionDataTest { + + @Test + public void testSerialization() { + String conversationId = "jvnuveanesndndnadldbj"; + String conversationToken = "watgsiovncsagjmcneiusdolnfcs"; + + SessionData expected = new SessionData(); + expected.setConversationId(conversationId); + expected.setConversationToken(conversationToken); + + ByteArrayOutputStream baos = null; + ObjectOutputStream oos = null; + + ByteArrayInputStream bais = null; + ObjectInputStream ois = null; + + try { + baos = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(baos); + oos.writeObject(expected); + + bais = new ByteArrayInputStream(baos.toByteArray()); + ois = new ObjectInputStream(bais); + + SessionData result = (SessionData) ois.readObject(); + assertEquals(expected.getConversationId(), result.getConversationId()); + assertEquals(expected.getConversationToken(), result.getConversationToken()); + } catch (Exception e) { + fail(e.getMessage()); + } finally { + Util.ensureClosed(baos); + Util.ensureClosed(oos); + Util.ensureClosed(bais); + Util.ensureClosed(ois); + } + + + } +} \ No newline at end of file From 0772fad68b6fac61cc4a2cfc054b569765560e75 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 19 Jan 2017 11:30:49 -0800 Subject: [PATCH 012/465] Move conversation token and id into SessionData. Store SessionData serialized on disk. Use a handler for creating conversation. --- .../android/sdk/ApptentiveInternal.java | 84 ++++++++++++++----- .../android/sdk/comm/ApptentiveClient.java | 20 ++--- .../module/messagecenter/MessageManager.java | 2 +- .../android/sdk/storage/FileSerializer.java | 64 ++++++++++++++ .../sdk/storage/PayloadSendWorker.java | 11 ++- .../android/sdk/storage/Serializer.java | 14 ++++ .../image/ApptentiveAttachmentLoader.java | 2 +- 7 files changed, 164 insertions(+), 33 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/Serializer.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 422739037..19d2290fd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -22,6 +22,9 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; import android.provider.Settings; import android.support.v4.content.ContextCompat; import android.text.TextUtils; @@ -49,9 +52,11 @@ import com.apptentive.android.sdk.storage.AppReleaseManager; import com.apptentive.android.sdk.storage.ApptentiveTaskManager; import com.apptentive.android.sdk.storage.DeviceManager; +import com.apptentive.android.sdk.storage.FileSerializer; import com.apptentive.android.sdk.storage.PayloadSendWorker; import com.apptentive.android.sdk.storage.PersonManager; import com.apptentive.android.sdk.storage.SdkManager; +import com.apptentive.android.sdk.storage.SessionData; import com.apptentive.android.sdk.storage.VersionHistoryEntry; import com.apptentive.android.sdk.storage.VersionHistoryStore; import com.apptentive.android.sdk.util.Constants; @@ -60,6 +65,7 @@ import org.json.JSONException; import org.json.JSONObject; +import java.io.File; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Iterator; @@ -73,7 +79,7 @@ /** * This class contains only internal methods. These methods should not be access directly by the host app. */ -public class ApptentiveInternal { +public class ApptentiveInternal implements Handler.Callback { static AtomicBoolean isApptentiveInitialized = new AtomicBoolean(false); InteractionManager interactionManager; @@ -92,11 +98,12 @@ public class ApptentiveInternal { boolean isAppDebuggable; SharedPreferences prefs; String apiKey; - String conversationToken; - String conversationId; String personId; String androidId; String appPackageName; + SessionData sessionData; + + Handler backgroundHandler; // toolbar theme specified in R.attr.apptentiveToolbarTheme Resources.Theme apptentiveToolbarTheme; @@ -182,6 +189,12 @@ public static ApptentiveInternal createInstance(Context context, final String ap sApptentiveInternal.codePointStore = new CodePointStore(); sApptentiveInternal.cachedExecutor = Executors.newCachedThreadPool(); sApptentiveInternal.apiKey = Util.trim(apptentiveApiKey); + + + HandlerThread handlerThread = new HandlerThread("ApptentiveInternalHandlerThread"); + handlerThread.start(); + sApptentiveInternal.backgroundHandler = new Handler(handlerThread.getLooper(), sApptentiveInternal); + } } } @@ -362,8 +375,8 @@ public int getDefaultStatusBarColor() { return statusBarColorDefault; } - public String getApptentiveConversationToken() { - return conversationToken; + public SessionData getSessionData() { + return sessionData; } public String getApptentiveApiKey() { @@ -431,7 +444,7 @@ public void scheduleOnWorkerThread(Runnable r) { public void checkAndUpdateApptentiveConfigurations() { // Initialize the Conversation Token, or fetch if needed. Fetch config it the token is available. - if (conversationToken == null || personId == null) { + if (sessionData == null) { asyncFetchConversationToken(); } else { asyncFetchAppConfigurationAndInteractions(); @@ -453,6 +466,7 @@ public void onActivityStarted(Activity activity) { messageManager.setCurrentForegroundActivity(activity); } + //FIXME: Kick this off on Application initialization instead. checkAndUpdateApptentiveConfigurations(); syncDevice(); @@ -552,8 +566,11 @@ public boolean init() { if (featureEverUsed) { messageManager.init(); } - conversationToken = prefs.getString(Constants.PREF_KEY_CONVERSATION_TOKEN, null); - conversationId = prefs.getString(Constants.PREF_KEY_CONVERSATION_ID, null); + FileSerializer fileSerializer = new FileSerializer(new File(appContext.getFilesDir(), "apptentive/SessionData.ser")); + sessionData = (SessionData) fileSerializer.deserialize(); + if (sessionData != null) { + ApptentiveLog.d("Restored existing SessionData"); + } personId = prefs.getString(Constants.PREF_KEY_PERSON_ID, null); apptentiveToolbarTheme = appContext.getResources().newTheme(); @@ -757,9 +774,11 @@ private boolean fetchConversationToken() { String conversationId = root.getString("id"); ApptentiveLog.d("New Conversation id: %s", conversationId); + sessionData = new SessionData(); if (conversationToken != null && !conversationToken.equals("")) { - setConversationToken(conversationToken); - setConversationId(conversationId); + sessionData.setConversationToken(conversationToken); + sessionData.setConversationId(conversationId); + saveSessionData(); } String personId = root.getString("person_id"); ApptentiveLog.d("PersonId: " + personId); @@ -1119,16 +1138,6 @@ public Map getAndClearCustomData() { return customData; } - private void setConversationToken(String newConversationToken) { - conversationToken = newConversationToken; - prefs.edit().putString(Constants.PREF_KEY_CONVERSATION_TOKEN, conversationToken).apply(); - } - - private void setConversationId(String newConversationId) { - conversationId = newConversationId; - prefs.edit().putString(Constants.PREF_KEY_CONVERSATION_ID, conversationId).apply(); - } - private void setPersonId(String newPersonId) { personId = newPersonId; prefs.edit().putString(Constants.PREF_KEY_PERSON_ID, personId).apply(); @@ -1168,6 +1177,7 @@ public static PendingIntent prepareMessageCenterPendingIntent(Context context) { /** * Checks to see if Apptentive was properly registered, and logs a message if not. + * * @return true if properly registered, else false. */ public static boolean checkRegistered() { @@ -1177,4 +1187,38 @@ public static boolean checkRegistered() { } return true; } + + + // Multi-tenancy work + + private void saveSessionData() { + if (!backgroundHandler.hasMessages(MESSAGE_SAVE_SESSION_DATA)) { + backgroundHandler.sendEmptyMessageDelayed(MESSAGE_SAVE_SESSION_DATA, 100); + } + } + + private static final int MESSAGE_SAVE_SESSION_DATA = 0; + private static final int MESSAGE_CREATE_CONVERSATION = 1; + + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_SAVE_SESSION_DATA: + ApptentiveLog.e("Saing SessionData"); + File internalStorage = appContext.getFilesDir(); + File sessionDataFile = new File(internalStorage, "apptentive/SessionData.ser"); + try { + sessionDataFile.getParentFile().mkdirs(); + } catch (SecurityException e) { + ApptentiveLog.e("Security Error creating DataSession file.", e); + } + FileSerializer fileSerializer = new FileSerializer(sessionDataFile); + fileSerializer.serialize(sApptentiveInternal.sessionData); + break; + case MESSAGE_CREATE_CONVERSATION: + // TODO: This. + break; + } + return false; + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index b07adab94..88b1bf77d 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -56,7 +56,7 @@ public static ApptentiveHttpResponse getConversationToken(ConversationTokenReque } public static ApptentiveHttpResponse getAppConfiguration() { - return performHttpRequest(ApptentiveInternal.getInstance().getApptentiveConversationToken(), ENDPOINT_CONFIGURATION, Method.GET, null); + return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_CONFIGURATION, Method.GET, null); } /** @@ -66,7 +66,7 @@ public static ApptentiveHttpResponse getAppConfiguration() { */ public static ApptentiveHttpResponse getMessages(Integer count, String afterId, String beforeId) { String uri = String.format(ENDPOINT_CONVERSATION_FETCH, count == null ? "" : count.toString(), afterId == null ? "" : afterId, beforeId == null ? "" : beforeId); - return performHttpRequest(ApptentiveInternal.getInstance().getApptentiveConversationToken(), uri, Method.GET, null); + return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), uri, Method.GET, null); } public static ApptentiveHttpResponse postMessage(ApptentiveMessage apptentiveMessage) { @@ -74,7 +74,7 @@ public static ApptentiveHttpResponse postMessage(ApptentiveMessage apptentiveMes case CompoundMessage: { CompoundMessage compoundMessage = (CompoundMessage) apptentiveMessage; List associatedFiles = compoundMessage.getAssociatedFiles(); - return performMultipartFilePost(ApptentiveInternal.getInstance().getApptentiveConversationToken(), ENDPOINT_MESSAGES, apptentiveMessage.marshallForSending(), associatedFiles); + return performMultipartFilePost(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_MESSAGES, apptentiveMessage.marshallForSending(), associatedFiles); } case unknown: break; @@ -83,32 +83,32 @@ public static ApptentiveHttpResponse postMessage(ApptentiveMessage apptentiveMes } public static ApptentiveHttpResponse postEvent(Event event) { - return performHttpRequest(ApptentiveInternal.getInstance().getApptentiveConversationToken(), ENDPOINT_EVENTS, Method.POST, event.marshallForSending()); + return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_EVENTS, Method.POST, event.marshallForSending()); } public static ApptentiveHttpResponse putDevice(Device device) { - return performHttpRequest(ApptentiveInternal.getInstance().getApptentiveConversationToken(), ENDPOINT_DEVICES, Method.PUT, device.marshallForSending()); + return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_DEVICES, Method.PUT, device.marshallForSending()); } public static ApptentiveHttpResponse putSdk(Sdk sdk) { - return performHttpRequest(ApptentiveInternal.getInstance().getApptentiveConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, sdk.marshallForSending()); + return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, sdk.marshallForSending()); } public static ApptentiveHttpResponse putAppRelease(AppRelease appRelease) { - return performHttpRequest(ApptentiveInternal.getInstance().getApptentiveConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, appRelease.marshallForSending()); + return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, appRelease.marshallForSending()); } public static ApptentiveHttpResponse putPerson(Person person) { - return performHttpRequest(ApptentiveInternal.getInstance().getApptentiveConversationToken(), ENDPOINT_PEOPLE, Method.PUT, person.marshallForSending()); + return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_PEOPLE, Method.PUT, person.marshallForSending()); } public static ApptentiveHttpResponse postSurvey(SurveyResponse survey) { String endpoint = String.format(ENDPOINT_SURVEYS_POST, survey.getId()); - return performHttpRequest(ApptentiveInternal.getInstance().getApptentiveConversationToken(), endpoint, Method.POST, survey.marshallForSending()); + return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), endpoint, Method.POST, survey.marshallForSending()); } public static ApptentiveHttpResponse getInteractions() { - return performHttpRequest(ApptentiveInternal.getInstance().getApptentiveConversationToken(), ENDPOINT_INTERACTIONS, Method.GET, null); + return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_INTERACTIONS, Method.GET, null); } /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index bb8390de8..c1967a1ab 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -161,7 +161,7 @@ protected void onPostExecute(Void v) { * @return true if messages were returned, else false. */ public synchronized boolean fetchAndStoreMessages(boolean isMessageCenterForeground, boolean showToast) { - if (ApptentiveInternal.getInstance().getApptentiveConversationToken() == null) { + if (ApptentiveInternal.getInstance().getSessionData().getConversationToken() == null) { ApptentiveLog.d("Can't fetch messages because the conversation has not yet been initialized."); return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java new file mode 100644 index 000000000..7f8764159 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.util.Util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +public class FileSerializer implements Serializer { + + private File file; + + public FileSerializer(File file) { + this.file = file; + } + + @Override + public void serialize(Object object) { + FileOutputStream fos = null; + ObjectOutputStream oos = null; + try { + fos = new FileOutputStream(file); + oos = new ObjectOutputStream(fos); + oos.writeObject(object); + } catch (IOException e) { + ApptentiveLog.e("Error", e); + } finally { + Util.ensureClosed(fos); + Util.ensureClosed(oos); + } + } + + @Override + public Object deserialize() { + FileInputStream fis = null; + ObjectInputStream ois = null; + try { + fis = new FileInputStream(file); + ois = new ObjectInputStream(fis); + return ois.readObject(); + } catch (ClassNotFoundException e) { + ApptentiveLog.e("Error", e); + } catch (FileNotFoundException e) { + ApptentiveLog.e("DataSession file does not yet exist."); + } catch (IOException e) { + ApptentiveLog.e("Error", e); + } finally { + Util.ensureClosed(fis); + Util.ensureClosed(ois); + } + return null; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java index f997d4c4e..0c71fb17b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java @@ -102,7 +102,16 @@ public void run() { while (appInForeground.get()) { MessageManager mgr = ApptentiveInternal.getInstance().getMessageManager(); - if (TextUtils.isEmpty(ApptentiveInternal.getInstance().getApptentiveConversationToken())){ + if (ApptentiveInternal.getInstance().getSessionData() == null){ + ApptentiveLog.i("SessionData is null."); + if (mgr != null) { + mgr.pauseSending(MessageManager.SEND_PAUSE_REASON_SERVER); + } + retryLater(NO_TOKEN_SLEEP); + break; + } + + if (TextUtils.isEmpty(ApptentiveInternal.getInstance().getSessionData().getConversationToken())){ ApptentiveLog.i("No conversation token yet."); if (mgr != null) { mgr.pauseSending(MessageManager.SEND_PAUSE_REASON_SERVER); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Serializer.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Serializer.java new file mode 100644 index 000000000..c9636cdbf --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Serializer.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +public interface Serializer { + + void serialize(Object object); + + Object deserialize(); +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java index 67fb5d9be..137c22531 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java @@ -217,7 +217,7 @@ public void doDownload() { try { ApptentiveLog.v("ApptentiveAttachmentLoader doDownload: " + uri); // Conversation token is needed if the download url is a redirect link from an Apptentive endpoint - String conversationToken = ApptentiveInternal.getInstance().getApptentiveConversationToken(); + String conversationToken = ApptentiveInternal.getInstance().getSessionData().getConversationToken(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { mDrawableDownloaderTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uri, diskCacheFilePath, conversationToken); } else { From d0925f5a16c844ebcd8677435dcc8247ae06b729 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 19 Jan 2017 11:39:51 -0800 Subject: [PATCH 013/465] Start to move data from `SharedPreferences` to `SessionData`. --- .../android/sdk/ApptentiveInternal.java | 11 +--------- .../android/sdk/storage/SessionData.java | 13 ++++++++++-- .../android/sdk/util/Constants.java | 21 ++++++++----------- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 19d2290fd..398716a4f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -571,7 +571,6 @@ public boolean init() { if (sessionData != null) { ApptentiveLog.d("Restored existing SessionData"); } - personId = prefs.getString(Constants.PREF_KEY_PERSON_ID, null); apptentiveToolbarTheme = appContext.getResources().newTheme(); boolean apptentiveDebug = false; @@ -671,7 +670,6 @@ public boolean init() { androidId = Settings.Secure.getString(appContext.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID); ApptentiveLog.d("Android ID: ", androidId); ApptentiveLog.d("Default Locale: %s", Locale.getDefault().toString()); - ApptentiveLog.d("Conversation id: %s", prefs.getString(Constants.PREF_KEY_CONVERSATION_ID, "null")); return bRet; } @@ -782,9 +780,7 @@ private boolean fetchConversationToken() { } String personId = root.getString("person_id"); ApptentiveLog.d("PersonId: " + personId); - if (personId != null && !personId.equals("")) { - setPersonId(personId); - } + sessionData.setPersonId(personId); return true; } catch (JSONException e) { ApptentiveLog.e("Error parsing ConversationToken response json.", e); @@ -1138,11 +1134,6 @@ public Map getAndClearCustomData() { return customData; } - private void setPersonId(String newPersonId) { - personId = newPersonId; - prefs.edit().putString(Constants.PREF_KEY_PERSON_ID, personId).apply(); - } - public void resetSdkState() { prefs.edit().clear().apply(); taskManager.reset(appContext); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index 697c7a46e..b425aca04 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -13,8 +13,8 @@ public class SessionData implements Serializable { private static final long serialVersionUID = 1L; private String conversationToken; - private String conversationId; + private String personId; public String getConversationToken() { return conversationToken; @@ -31,7 +31,16 @@ public String getConversationId() { public void setConversationId(String conversationId) { this.conversationId = conversationId; } -// version history + + public String getPersonId() { + return personId; + } + + public void setPersonId(String personId) { + this.personId = personId; + } + + // version history // code point store // Various prefs // Messages diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 8a7ea6ce8..0d8448660 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -18,39 +18,29 @@ public class Constants { public static final String PREF_NAME = "APPTENTIVE"; + // Globals public static final String PREF_KEY_SERVER_URL = "serverUrl"; // Just in case a customer copies the example text verbatim. public static final String EXAMPLE_API_KEY_VALUE = "YOUR_APPTENTIVE_API_KEY"; - public static final String PREF_KEY_CONVERSATION_TOKEN = "conversationToken"; - public static final String PREF_KEY_CONVERSATION_ID = "conversationId"; - public static final String PREF_KEY_PERSON_ID = "personId"; + // Session Data public static final String PREF_KEY_DEVICE = "device"; public static final String PREF_KEY_DEVICE_DATA = "deviceData"; - public static final String PREF_KEY_DEVICE_INTEGRATION_CONFIG = "integrationConfig"; - public static final String PREF_KEY_SDK = "sdk"; public static final String PREF_KEY_APP_RELEASE = "app_release"; public static final String PREF_KEY_PERSON = "person"; public static final String PREF_KEY_PERSON_DATA = "personData"; public static final String PREF_KEY_PERSON_EMAIL = "personEmail"; public static final String PREF_KEY_PERSON_NAME = "personName"; - public static final String PREF_KEY_LAST_SEEN_SDK_VERSION = "lastSeenSdkVersion"; - - public static final String PREF_KEY_APP_ACTIVITY_STATE_QUEUE = "appActivityStateQueue"; - public static final String PREF_KEY_MESSAGE_CENTER_FEATURE_USED = "messageCenterFeatureUsed"; public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE = "messageCenterPendingComposingMessage"; public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS = "messageCenterPendingComposingAttachments"; public static final String PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT = "messageCenterServerErrorLastAttempt"; - public static final String PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE = "messageCenterWhoCardSet"; - public static final String PREF_KEY_APP_CONFIG_PREFIX = "appConfiguration."; public static final String PREF_KEY_APP_CONFIG_JSON = PREF_KEY_APP_CONFIG_PREFIX+"json"; - public static final String PREF_KEY_VERSION_HISTORY = "versionHistory"; public static final String PREF_KEY_VERSION_HISTORY_V2 = "versionHistoryV2"; // Boolean true if migration from v1 to V2 has occurred. @@ -111,6 +101,13 @@ public class Constants { public static final String PREF_KEY_MESSAGE_CENTER_SHOULD_SHOW_INTRO_DIALOG = "messageCenterShouldShowIntroDialog"; public static final String MANIFEST_KEY_USE_STAGING_SERVER = "apptentive_use_staging_server"; + // FIXME: We will need to migrate this data. + public static final String PREF_KEY_APP_ACTIVITY_STATE_QUEUE = "appActivityStateQueue"; + public static final String PREF_KEY_CONVERSATION_TOKEN = "conversationToken"; + public static final String PREF_KEY_CONVERSATION_ID = "conversationId"; + public static final String PREF_KEY_PERSON_ID = "personId"; + + public interface FragmentConfigKeys { From fd813d197f0bde2da92b1dc2348253ffe7fefb67 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 19 Jan 2017 11:45:30 -0800 Subject: [PATCH 014/465] Remove old deprecated push handling code. --- .../apptentive/android/sdk/Apptentive.java | 92 ------------------- .../android/sdk/ApptentiveInternal.java | 16 ---- .../fragment/MessageCenterFragment.java | 26 ------ .../android/sdk/util/Constants.java | 2 +- 4 files changed, 1 insertion(+), 135 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 98e065672..b64a6bf6c 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -730,98 +730,6 @@ public static String getBodyFromApptentivePush(Map data) { return data.get(ApptentiveInternal.BODY_DEFAULT); } - /** - *

Saves Apptentive specific data from a push notification Intent. In your BroadcastReceiver, if the push notification - * came from Apptentive, it will have data that needs to be saved before you launch your Activity. You must call this - * method every time you get a push opened Intent, and before you launch your Activity. If the push - * notification did not come from Apptentive, this method has no effect.

- *

Use this method when using Parse and Amazon SNS as push providers.

- * - * @param intent The Intent that you received when the user opened a push notification. - * @return true if the push data came from Apptentive. - * @deprecated - */ - @Deprecated - public static boolean setPendingPushNotification(Intent intent) { - if (!ApptentiveInternal.checkRegistered()) { - return false; - } - String apptentive = ApptentiveInternal.getApptentivePushNotificationData(intent); - if (apptentive != null) { - return ApptentiveInternal.getInstance().setPendingPushNotification(apptentive); - } - return false; - } - - /** - * Saves off the data contained in a push notification sent to this device from Apptentive. Use - * this method when a push notification is opened, and you only have access to a push data - * Bundle containing an "apptentive" key. This will generally be used with direct Apptentive Push - * notifications, or when using Urban Airship as a push provider. Calling this method for a push - * that did not come from Apptentive has no effect. - * - * @param data A Bundle containing the GCM data object from the push notification. - * @return true if the push data came from Apptentive. - * @deprecated - */ - @Deprecated - public static boolean setPendingPushNotification(Bundle data) { - if (!ApptentiveInternal.checkRegistered()) { - return false; - } - String apptentive = ApptentiveInternal.getApptentivePushNotificationData(data); - if (apptentive != null) { - return ApptentiveInternal.getInstance().setPendingPushNotification(apptentive); - } - return false; - } - - /** - * Launches Apptentive features based on a push notification Intent. Before you call this, you - * must call {@link #setPendingPushNotification(Intent)} or - * {@link #setPendingPushNotification(Bundle)} in your Broadcast receiver when - * a push notification is opened by the user. This method must be called from the Activity that - * you launched from the BroadcastReceiver. This method will only handle Apptentive originated - * push notifications, so you can and should call it any time your push notification launches an - * Activity. - * - * @param context The context from which this method is called. - * @return True if a call to this method resulted in Apptentive displaying a View. - * @deprecated - */ - @Deprecated - public static boolean handleOpenedPushNotification(Context context) { - if (!ApptentiveInternal.checkRegistered()) { - return false; - } - - SharedPreferences prefs = context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); - String pushData = prefs.getString(Constants.PREF_KEY_PENDING_PUSH_NOTIFICATION, null); - prefs.edit().remove(Constants.PREF_KEY_PENDING_PUSH_NOTIFICATION).apply(); // Remove our data so this won't run twice. - if (pushData != null) { - ApptentiveLog.i("Handling opened Apptentive push notification."); - try { - JSONObject pushJson = new JSONObject(pushData); - ApptentiveInternal.PushAction action = ApptentiveInternal.PushAction.unknown; - if (pushJson.has(ApptentiveInternal.PUSH_ACTION)) { - action = ApptentiveInternal.PushAction.parse(pushJson.getString(ApptentiveInternal.PUSH_ACTION)); - } - switch (action) { - case pmc: - Apptentive.showMessageCenter(context); - return true; - default: - ApptentiveLog.v("Unknown Apptentive push notification action: \"%s\"", action.name()); - } - } catch (JSONException e) { - ApptentiveLog.w("Error parsing JSON from push notification.", e); - MetricModule.sendError(e, "Parsing Push notification", pushData); - } - } - return false; - } - - // **************************************************************************************** // RATINGS // **************************************************************************************** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 398716a4f..ffa5bb7b4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -1030,22 +1030,6 @@ static String getApptentivePushNotificationData(Map pushData) { return null; } - boolean setPendingPushNotification(String apptentivePushData) { - if (apptentivePushData != null) { - ApptentiveLog.d("Saving Apptentive push notification data."); - prefs.edit().putString(Constants.PREF_KEY_PENDING_PUSH_NOTIFICATION, apptentivePushData).apply(); - messageManager.startMessagePreFetchTask(); - return true; - } - return false; - } - - boolean clearPendingPushNotification() { - ApptentiveLog.d("Clearing Apptentive push notification data."); - prefs.edit().remove(Constants.PREF_KEY_PENDING_PUSH_NOTIFICATION).apply(); - return true; - } - public void showAboutInternal(Context context, boolean showBrandingBand) { Intent intent = new Intent(); intent.setClass(context, ApptentiveViewActivity.class); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index f50710ef9..371eac067 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -253,7 +253,6 @@ public void onStart() { public void onStop() { super.onStop(); - clearPendingMessageCenterPushNotification(); ApptentiveInternal.getInstance().getMessageManager().setMessageCenterInForeground(false); } @@ -524,7 +523,6 @@ public boolean onBackPressed(boolean hardwareButton) { } public boolean cleanup() { - clearPendingMessageCenterPushNotification(); // Set to null, otherwise they will hold reference to the activity context MessageManager mgr = ApptentiveInternal.getInstance().getMessageManager(); @@ -536,30 +534,6 @@ public boolean cleanup() { return true; } - - private void clearPendingMessageCenterPushNotification() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - String pushData = prefs.getString(Constants.PREF_KEY_PENDING_PUSH_NOTIFICATION, null); - if (pushData != null) { - try { - JSONObject pushJson = new JSONObject(pushData); - ApptentiveInternal.PushAction action = ApptentiveInternal.PushAction.unknown; - if (pushJson.has(ApptentiveInternal.PUSH_ACTION)) { - action = ApptentiveInternal.PushAction.parse(pushJson.getString(ApptentiveInternal.PUSH_ACTION)); - } - switch (action) { - case pmc: - ApptentiveLog.i("Clearing pending Message Center push notification."); - prefs.edit().remove(Constants.PREF_KEY_PENDING_PUSH_NOTIFICATION).apply(); - break; - } - } catch (JSONException e) { - ApptentiveLog.w("Error parsing JSON from push notification.", e); - MetricModule.sendError(e, "Parsing Push notification", pushData); - } - } - } - public void addComposingCard() { hideFab(); hideProfileButton(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 0d8448660..56af85031 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -46,7 +46,6 @@ public class Constants { // Boolean true if migration from v1 to V2 has occurred. public static final String PREF_KEY_VERSION_HISTORY_V2_MIGRATED = "versionHistoryV2Migrated"; - public static final String PREF_KEY_PENDING_PUSH_NOTIFICATION = "pendingPushNotification"; // Engagement public static final String PREF_KEY_INTERACTIONS = "interactions"; @@ -100,6 +99,7 @@ public class Constants { public static final String PREF_KEY_AUTO_MESSAGE_SHOWN_MANUAL = "autoMessageShownManual"; public static final String PREF_KEY_MESSAGE_CENTER_SHOULD_SHOW_INTRO_DIALOG = "messageCenterShouldShowIntroDialog"; public static final String MANIFEST_KEY_USE_STAGING_SERVER = "apptentive_use_staging_server"; + public static final String PREF_KEY_PENDING_PUSH_NOTIFICATION = "pendingPushNotification"; // FIXME: We will need to migrate this data. public static final String PREF_KEY_APP_ACTIVITY_STATE_QUEUE = "appActivityStateQueue"; From 42ff1e30391ba5473566e1379e039f620b9c8266 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 19 Jan 2017 12:04:20 -0800 Subject: [PATCH 015/465] Move Person email and name into Session Data. Hook up SessionData saving inside setters for now. --- .../apptentive/android/sdk/Apptentive.java | 24 +++++++--- .../android/sdk/ApptentiveInternal.java | 2 +- .../android/sdk/storage/PersonManager.java | 44 +++++-------------- .../android/sdk/storage/SessionData.java | 34 ++++++++++++++ .../android/sdk/util/Constants.java | 4 +- 5 files changed, 65 insertions(+), 43 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index b64a6bf6c..3feb00251 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -10,7 +10,6 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.support.annotation.NonNull; @@ -32,7 +31,8 @@ import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; import com.apptentive.android.sdk.storage.DeviceManager; import com.apptentive.android.sdk.storage.PersonManager; -import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.storage.SessionData; + import com.apptentive.android.sdk.util.Util; import org.json.JSONException; @@ -79,7 +79,10 @@ public static void register(Application application, String apptentiveApiKey) { */ public static void setPersonEmail(String email) { if (ApptentiveInternal.isApptentiveRegistered()) { - PersonManager.storePersonEmail(email); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.setPersonEmail(email); + } } } @@ -91,7 +94,10 @@ public static void setPersonEmail(String email) { */ public static String getPersonEmail() { if (ApptentiveInternal.isApptentiveRegistered()) { - return PersonManager.loadPersonEmail(); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + return sessionData.getPersonEmail(); + } } return null; } @@ -107,7 +113,10 @@ public static String getPersonEmail() { */ public static void setPersonName(String name) { if (ApptentiveInternal.isApptentiveRegistered()) { - PersonManager.storePersonName(name); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.setPersonName(name); + } } } @@ -119,7 +128,10 @@ public static void setPersonName(String name) { */ public static String getPersonName() { if (ApptentiveInternal.isApptentiveRegistered()) { - return PersonManager.loadPersonName(); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + return sessionData.getPersonName(); + } } return null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index ffa5bb7b4..5a732fe68 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -1166,7 +1166,7 @@ public static boolean checkRegistered() { // Multi-tenancy work - private void saveSessionData() { + public void saveSessionData() { if (!backgroundHandler.hasMessages(MESSAGE_SAVE_SESSION_DATA)) { backgroundHandler.sendEmptyMessageDelayed(MESSAGE_SAVE_SESSION_DATA, 100); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java index d6d926c87..b32cd792f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java @@ -17,9 +17,6 @@ import org.json.JSONException; -/** - * @author Sky Kelsey - */ public class PersonManager { public static Person storePersonAndReturnDiff() { @@ -29,11 +26,11 @@ public static Person storePersonAndReturnDiff() { CustomData customData = loadCustomPersonData(); current.setCustomData(customData); - String email = loadPersonEmail(); - current.setEmail(email); - - String name = loadPersonName(); - current.setName(name); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + current.setEmail(sessionData.getPersonEmail()); + current.setName(sessionData.getPersonName()); + } Object diff = JsonDiffer.getDiff(stored, current); if (diff != null) { @@ -58,12 +55,11 @@ public static Person storePersonAndReturnIt() { CustomData customData = loadCustomPersonData(); current.setCustomData(customData); - String email = loadPersonEmail(); - current.setEmail(email); - - String name = loadPersonName(); - current.setName(name); - + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + current.setEmail(sessionData.getPersonEmail()); + current.setName(sessionData.getPersonName()); + } storePerson(current); return current; } @@ -94,26 +90,6 @@ private static Person generateCurrentPerson() { return new Person(); } - public static String loadPersonEmail() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - return prefs.getString(Constants.PREF_KEY_PERSON_EMAIL, null); - } - - public static void storePersonEmail(String email) { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - prefs.edit().putString(Constants.PREF_KEY_PERSON_EMAIL, email).apply(); - } - - public static String loadPersonName() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - return prefs.getString(Constants.PREF_KEY_PERSON_NAME, null); - } - - public static void storePersonName(String name) { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - prefs.edit().putString(Constants.PREF_KEY_PERSON_NAME, name).apply(); - } - public static Person getStoredPerson() { SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); String PersonString = prefs.getString(Constants.PREF_KEY_PERSON, null); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index b425aca04..185302f79 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -6,6 +6,8 @@ package com.apptentive.android.sdk.storage; +import com.apptentive.android.sdk.ApptentiveInternal; + import java.io.Serializable; public class SessionData implements Serializable { @@ -15,6 +17,10 @@ public class SessionData implements Serializable { private String conversationToken; private String conversationId; private String personId; + private String personEmail; + private String personName; + + //region Getters & Setters public String getConversationToken() { return conversationToken; @@ -22,6 +28,7 @@ public String getConversationToken() { public void setConversationToken(String conversationToken) { this.conversationToken = conversationToken; + save(); } public String getConversationId() { @@ -30,6 +37,7 @@ public String getConversationId() { public void setConversationId(String conversationId) { this.conversationId = conversationId; + save(); } public String getPersonId() { @@ -38,8 +46,34 @@ public String getPersonId() { public void setPersonId(String personId) { this.personId = personId; + save(); + } + + public String getPersonEmail() { + return personEmail; + } + + public void setPersonEmail(String personEmail) { + this.personEmail = personEmail; + save(); + } + + public String getPersonName() { + return personName; + } + + public void setPersonName(String personName) { + this.personName = personName; + save(); } + //endregion + + private void save() { + ApptentiveInternal.getInstance().saveSessionData(); + } + + // version history // code point store // Various prefs diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 56af85031..c0c86dff7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -31,8 +31,6 @@ public class Constants { public static final String PREF_KEY_APP_RELEASE = "app_release"; public static final String PREF_KEY_PERSON = "person"; public static final String PREF_KEY_PERSON_DATA = "personData"; - public static final String PREF_KEY_PERSON_EMAIL = "personEmail"; - public static final String PREF_KEY_PERSON_NAME = "personName"; public static final String PREF_KEY_LAST_SEEN_SDK_VERSION = "lastSeenSdkVersion"; public static final String PREF_KEY_MESSAGE_CENTER_FEATURE_USED = "messageCenterFeatureUsed"; public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE = "messageCenterPendingComposingMessage"; @@ -106,6 +104,8 @@ public class Constants { public static final String PREF_KEY_CONVERSATION_TOKEN = "conversationToken"; public static final String PREF_KEY_CONVERSATION_ID = "conversationId"; public static final String PREF_KEY_PERSON_ID = "personId"; + public static final String PREF_KEY_PERSON_EMAIL = "personEmail"; + public static final String PREF_KEY_PERSON_NAME = "personName"; From e6ed44544a8fdacc6b5eed111c2ad5a10c22667c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 20 Jan 2017 10:30:52 -0800 Subject: [PATCH 016/465] Refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced ‘indexOf(component) != -1’ with ‘contains(component)’ --- .../sdk/util/registry/ApptentiveComponentRegistry.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java index caaab9074..5503520b2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java @@ -83,7 +83,14 @@ public synchronized void unregister(ApptentiveComponent component) { */ public synchronized boolean isRegistered(ApptentiveComponent component) { assertNotNull(component); - return component != null && indexOf(component) != -1; + return component != null && contains(component); + } + + /** + * Return true if component is registered + */ + private synchronized boolean contains(ApptentiveComponent component) { + return indexOf(component) != -1; } /** From 977adbe1c4544f67c5a6938bf986161cf9a3db65 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 20 Jan 2017 10:34:57 -0800 Subject: [PATCH 017/465] Refactoring Removed unused StringUtils.isNullOrEmpty method --- .../java/com/apptentive/android/sdk/util/StringUtils.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java index c7b8ded46..e001af06e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java @@ -67,11 +67,4 @@ public static String join(List list, String separator) { } return builder.toString(); } - - /** - * Returns true is string is null or empty - */ - public static boolean isNullOrEmpty(String str) { - return str == null || str.length() == 0; - } } From ad40d1c47b69a97d5c26ee86c02a52b88d3dd6d1 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 20 Jan 2017 10:38:50 -0800 Subject: [PATCH 018/465] Refactoring Removed unused import in ApptentiveComponentActivity.java --- .../com/apptentive/android/sdk/ApptentiveComponentActivity.java | 1 - 1 file changed, 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java index 4767ab1bf..bb340bac1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java @@ -1,7 +1,6 @@ package com.apptentive.android.sdk; import android.os.Bundle; -import android.os.PersistableBundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; From 9aff77607a3b31840bf378208b21e1e118ecf4f5 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 20 Jan 2017 10:46:31 -0800 Subject: [PATCH 019/465] Unit-test refactoring Added more descriptive names and output in ApptentiveComponentRegistryTest.java --- .../ApptentiveComponentRegistryTest.java | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java index 5f5ae9daa..0096a78f1 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java @@ -26,9 +26,9 @@ public void onComponentNotify(ListenerA listener) { } }); - final CompA c1 = new CompA("A-1"); - final CompA c2 = new CompA("A-2"); - final CompA c3 = new CompA("A-3"); + final ComponentA c1 = new ComponentA("ComponentA-1"); + final ComponentA c2 = new ComponentA("ComponentA-2"); + final ComponentA c3 = new ComponentA("ComponentA-3"); registry.register(c1); registry.register(c2); @@ -40,7 +40,7 @@ public void onComponentNotify(ListenerA listener) { listener.OnTestEventA(); } }); - assertResult("A-A-1", "A-A-2", "A-A-3"); + assertResult("ListenerA-ComponentA-1", "ListenerA-ComponentA-2", "ListenerA-ComponentA-3"); registry.unregister(c2); registry.notifyComponents(new ComponentNotifier(ListenerA.class) { @@ -49,7 +49,7 @@ public void onComponentNotify(ListenerA listener) { listener.OnTestEventA(); } }); - assertResult("A-A-1", "A-A-3"); + assertResult("ListenerA-ComponentA-1", "ListenerA-ComponentA-3"); registry.unregister(c1); registry.notifyComponents(new ComponentNotifier(ListenerA.class) { @@ -58,7 +58,7 @@ public void onComponentNotify(ListenerA listener) { listener.OnTestEventA(); } }); - assertResult("A-A-3"); + assertResult("ListenerA-ComponentA-3"); registry.unregister(c3); registry.notifyComponents(new ComponentNotifier(ListenerA.class) { @@ -74,12 +74,12 @@ public void onComponentNotify(ListenerA listener) { public void testComponentNotification() { ApptentiveComponentRegistry registry = new ApptentiveComponentRegistry(); - registry.register(new CompA("A-1")); - registry.register(new CompA("A-2")); - registry.register(new CompAB("AB-1")); - registry.register(new CompAB("AB-2")); - registry.register(new CompC("C-1")); - registry.register(new CompC("C-2")); + registry.register(new ComponentA("ComponentA-1")); + registry.register(new ComponentA("ComponentA-2")); + registry.register(new ComponentAB("ComponentAB-1")); + registry.register(new ComponentAB("ComponentAB-2")); + registry.register(new ComponentC("ComponentC-1")); + registry.register(new ComponentC("ComponentC-2")); registry.notifyComponents(new ComponentNotifier(ListenerA.class) { @Override @@ -87,7 +87,7 @@ public void onComponentNotify(ListenerA listener) { listener.OnTestEventA(); } }); - assertResult("A-A-1", "A-A-2", "A-AB-1", "A-AB-2"); + assertResult("ListenerA-ComponentA-1", "ListenerA-ComponentA-2", "ListenerA-ComponentAB-1", "ListenerA-ComponentAB-2"); registry.notifyComponents(new ComponentNotifier(ListenerB.class) { @Override @@ -95,7 +95,7 @@ public void onComponentNotify(ListenerB listener) { listener.OnTestEventB(); } }); - assertResult("B-AB-1", "B-AB-2"); + assertResult("ListenerB-ComponentAB-1", "ListenerB-ComponentAB-2"); registry.notifyComponents(new ComponentNotifier(ListenerC.class) { @Override @@ -103,7 +103,7 @@ public void onComponentNotify(ListenerC listener) { listener.OnTestEventC(); } }); - assertResult("C-C-1", "C-C-2"); + assertResult("ListenerC-ComponentC-1", "ListenerC-ComponentC-2"); } //endregion @@ -131,44 +131,44 @@ class BaseComponent implements ApptentiveComponent{ } } - class CompA extends BaseComponent implements ListenerA { + class ComponentA extends BaseComponent implements ListenerA { - CompA(String name) { + ComponentA(String name) { super(name); } @Override public void OnTestEventA() { - addResult("A-" + name); + addResult("ListenerA-" + name); } } - class CompAB extends BaseComponent implements ListenerA, ListenerB { + class ComponentAB extends BaseComponent implements ListenerA, ListenerB { - CompAB(String name) { + ComponentAB(String name) { super(name); } @Override public void OnTestEventA() { - addResult("A-" + name); + addResult("ListenerA-" + name); } @Override public void OnTestEventB() { - addResult("B-" + name); + addResult("ListenerB-" + name); } } - class CompC extends BaseComponent implements ListenerC { + class ComponentC extends BaseComponent implements ListenerC { - CompC(String name) { + ComponentC(String name) { super(name); } @Override public void OnTestEventC() { - addResult("C-" + name); + addResult("ListenerC-" + name); } } From 39cbbf3108dddbed54a4b5c0b1cff2a19996c448 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 20 Jan 2017 10:47:37 -0800 Subject: [PATCH 020/465] Fixed unit-test Added another conditional check --- .../sdk/util/registry/ApptentiveComponentRegistryTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java index 0096a78f1..5785b9e97 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java @@ -25,6 +25,7 @@ public void onComponentNotify(ListenerA listener) { listener.OnTestEventA(); } }); + assertResult(); final ComponentA c1 = new ComponentA("ComponentA-1"); final ComponentA c2 = new ComponentA("ComponentA-2"); From d008924a22d28b47e045314acc152fabc444421e Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 20 Jan 2017 13:32:21 -0800 Subject: [PATCH 021/465] Get Device information sending again. 1. Create Device storage objects, and mechanism for turning it into a Payload for sending. 2. Hook up Device creation at Conversation creation. 3. Hook up Device saving and restoring. 4. Hook up integration config and custom data. --- .../apptentive/android/sdk/Apptentive.java | 212 ++++++--------- .../android/sdk/ApptentiveInternal.java | 90 +------ .../module/engagement/logic/FieldManager.java | 32 ++- .../android/sdk/storage/CustomData.java | 28 ++ .../android/sdk/storage/Device.java | 248 ++++++++++++++++++ .../android/sdk/storage/DeviceManager.java | 221 ++++++++-------- .../sdk/storage/IntegrationConfig.java | 79 ++++++ .../sdk/storage/IntegrationConfigItem.java | 28 ++ .../sdk/storage/PayloadSendWorker.java | 4 +- .../android/sdk/storage/Person.java | 122 +++++++++ .../android/sdk/storage/PersonManager.java | 112 +++----- .../android/sdk/storage/SessionData.java | 51 +++- .../com/apptentive/android/sdk/util/Util.java | 8 + 13 files changed, 812 insertions(+), 423 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 3feb00251..d3dccc7ac 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -17,7 +17,6 @@ import android.webkit.MimeTypeMap; import com.apptentive.android.sdk.model.CommerceExtendedData; -import com.apptentive.android.sdk.model.CustomData; import com.apptentive.android.sdk.model.ExtendedData; import com.apptentive.android.sdk.model.LocationExtendedData; import com.apptentive.android.sdk.model.StoredFile; @@ -29,10 +28,9 @@ import com.apptentive.android.sdk.module.metric.MetricModule; import com.apptentive.android.sdk.module.rating.IRatingProvider; import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; -import com.apptentive.android.sdk.storage.DeviceManager; -import com.apptentive.android.sdk.storage.PersonManager; +import com.apptentive.android.sdk.storage.IntegrationConfig; +import com.apptentive.android.sdk.storage.IntegrationConfigItem; import com.apptentive.android.sdk.storage.SessionData; - import com.apptentive.android.sdk.util.Util; import org.json.JSONException; @@ -136,30 +134,6 @@ public static String getPersonName() { return null; } - - /** - *

Allows you to pass arbitrary string data to the server along with this device's info. This method will replace all - * custom device data that you have set for this app. Calls to this method are idempotent.

- *

To add a single piece of custom device data, use {@link #addCustomDeviceData}

- *

To remove a single piece of custom device data, use {@link #removeCustomDeviceData}

- * - * @param customDeviceData A Map of key/value pairs to send to the server. - * @deprecated - */ - public static void setCustomDeviceData(Map customDeviceData) { - if (ApptentiveInternal.isApptentiveRegistered()) { - try { - CustomData customData = new CustomData(); - for (String key : customDeviceData.keySet()) { - customData.put(key, customDeviceData.get(key)); - } - DeviceManager.storeCustomDeviceData(customData); - } catch (JSONException e) { - ApptentiveLog.w("Unable to set custom device data.", e); - } - } - } - /** * Add a custom data String to the Device. Custom data will be sent to the server, is displayed * in the Conversation view, and can be used in Interaction targeting. Calls to this method are @@ -173,7 +147,11 @@ public static void addCustomDeviceData(String key, String value) { if (value != null) { value = value.trim(); } - ApptentiveInternal.getInstance().addCustomDeviceData(key, value); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getDevice().getCustomData().put(key, value); + sessionData.save(); + } } } @@ -187,7 +165,11 @@ public static void addCustomDeviceData(String key, String value) { */ public static void addCustomDeviceData(String key, Number value) { if (ApptentiveInternal.isApptentiveRegistered()) { - ApptentiveInternal.getInstance().addCustomDeviceData(key, value); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getDevice().getCustomData().put(key, value); + sessionData.save(); + } } } @@ -201,19 +183,31 @@ public static void addCustomDeviceData(String key, Number value) { */ public static void addCustomDeviceData(String key, Boolean value) { if (ApptentiveInternal.isApptentiveRegistered()) { - ApptentiveInternal.getInstance().addCustomDeviceData(key, value); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getDevice().getCustomData().put(key, value); + sessionData.save(); + } } } private static void addCustomDeviceData(String key, Version version) { if (ApptentiveInternal.isApptentiveRegistered()) { - ApptentiveInternal.getInstance().addCustomDeviceData(key, version); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getDevice().getCustomData().put(key, version); + sessionData.save(); + } } } private static void addCustomDeviceData(String key, DateTime dateTime) { if (ApptentiveInternal.isApptentiveRegistered()) { - ApptentiveInternal.getInstance().addCustomDeviceData(key, dateTime); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getDevice().getCustomData().put(key, dateTime); + sessionData.save(); + } } } @@ -224,34 +218,10 @@ private static void addCustomDeviceData(String key, DateTime dateTime) { */ public static void removeCustomDeviceData(String key) { if (ApptentiveInternal.isApptentiveRegistered()) { - CustomData customData = DeviceManager.loadCustomDeviceData(); - if (customData != null) { - customData.remove(key); - DeviceManager.storeCustomDeviceData(customData); - } - } - } - - /** - *

Allows you to pass arbitrary string data to the server along with this person's info. This method will replace all - * custom person data that you have set for this app. Calls to this method are idempotent.

- *

To add a single piece of custom person data, use {@link #addCustomPersonData}

- *

To remove a single piece of custom person data, use {@link #removeCustomPersonData}

- * - * @param customPersonData A Map of key/value pairs to send to the server. - * @deprecated - */ - public static void setCustomPersonData(Map customPersonData) { - ApptentiveLog.w("Setting custom person data: %s", customPersonData.toString()); - if (ApptentiveInternal.isApptentiveRegistered()) { - try { - CustomData customData = new CustomData(); - for (String key : customPersonData.keySet()) { - customData.put(key, customPersonData.get(key)); - } - PersonManager.storeCustomPersonData(customData); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set custom person data.", e); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getDevice().getCustomData().remove(key); + sessionData.save(); } } } @@ -269,7 +239,11 @@ public static void addCustomPersonData(String key, String value) { if (value != null) { value = value.trim(); } - ApptentiveInternal.getInstance().addCustomPersonData(key, value); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getPerson().getCustomData().put(key, value); + sessionData.save(); + } } } @@ -283,7 +257,11 @@ public static void addCustomPersonData(String key, String value) { */ public static void addCustomPersonData(String key, Number value) { if (ApptentiveInternal.isApptentiveRegistered()) { - ApptentiveInternal.getInstance().addCustomPersonData(key, value); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getPerson().getCustomData().put(key, value); + sessionData.save(); + } } } @@ -297,19 +275,31 @@ public static void addCustomPersonData(String key, Number value) { */ public static void addCustomPersonData(String key, Boolean value) { if (ApptentiveInternal.isApptentiveRegistered()) { - ApptentiveInternal.getInstance().addCustomPersonData(key, value); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getPerson().getCustomData().put(key, value); + sessionData.save(); + } } } private static void addCustomPersonData(String key, Version version) { if (ApptentiveInternal.isApptentiveRegistered()) { - ApptentiveInternal.getInstance().addCustomPersonData(key, version); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getPerson().getCustomData().put(key, version); + sessionData.save(); + } } } private static void addCustomPersonData(String key, DateTime dateTime) { if (ApptentiveInternal.isApptentiveRegistered()) { - ApptentiveInternal.getInstance().addCustomPersonData(key, dateTime); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getPerson().getCustomData().remove(key); + sessionData.save(); + } } } @@ -320,10 +310,10 @@ private static void addCustomPersonData(String key, DateTime dateTime) { */ public static void removeCustomPersonData(String key) { if (ApptentiveInternal.isApptentiveRegistered()) { - CustomData customData = PersonManager.loadCustomPersonData(); - if (customData != null) { - customData.remove(key); - PersonManager.storeCustomPersonData(customData); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getPerson().getCustomData().remove(key); + sessionData.save(); } } } @@ -333,41 +323,8 @@ public static void removeCustomPersonData(String key) { // THIRD PARTY INTEGRATIONS // **************************************************************************************** - private static final String INTEGRATION_APPTENTIVE_PUSH = "apptentive_push"; - private static final String INTEGRATION_PARSE = "parse"; - private static final String INTEGRATION_URBAN_AIRSHIP = "urban_airship"; - private static final String INTEGRATION_AWS_SNS = "aws_sns"; - private static final String INTEGRATION_PUSH_TOKEN = "token"; - private static void addIntegration(String integration, Map config) { - if (integration == null || config == null) { - return; - } - if (!ApptentiveInternal.isApptentiveRegistered()) { - return; - } - - CustomData integrationConfig = DeviceManager.loadIntegrationConfig(); - try { - JSONObject configJson = null; - if (!integrationConfig.isNull(integration)) { - configJson = integrationConfig.getJSONObject(integration); - } else { - configJson = new JSONObject(); - integrationConfig.put(integration, configJson); - } - for (String key : config.keySet()) { - configJson.put(key, config.get(key)); - } - ApptentiveLog.d("Adding integration config: %s", config.toString()); - DeviceManager.storeIntegrationConfig(integrationConfig); - ApptentiveInternal.getInstance().syncDevice(); - } catch (JSONException e) { - ApptentiveLog.e("Error adding integration: %s, %s", e, integration, config.toString()); - } - } - /** * Call {@link #setPushNotificationIntegration(int, String)} with this value to allow Apptentive to send pushes * to this device without a third party push provider. Requires a valid GCM configuration. @@ -425,47 +382,33 @@ private static void addIntegration(String integration, Map confi * */ public static void setPushNotificationIntegration(int pushProvider, String token) { - try { - if (!ApptentiveInternal.isApptentiveRegistered()) { - return; - } - CustomData integrationConfig = getIntegrationConfigurationWithoutPushProviders(); - JSONObject pushObject = new JSONObject(); - pushObject.put(INTEGRATION_PUSH_TOKEN, token); + if (!ApptentiveInternal.isApptentiveRegistered()) { + return; + } + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + IntegrationConfig integrationConfig = ApptentiveInternal.getInstance().getSessionData().getDevice().getIntegrationConfig(); + IntegrationConfigItem item = new IntegrationConfigItem(); + item.put(INTEGRATION_PUSH_TOKEN, token); switch (pushProvider) { case PUSH_PROVIDER_APPTENTIVE: - integrationConfig.put(INTEGRATION_APPTENTIVE_PUSH, pushObject); + integrationConfig.setApptentive(item); break; case PUSH_PROVIDER_PARSE: - integrationConfig.put(INTEGRATION_PARSE, pushObject); + integrationConfig.setParse(item); break; case PUSH_PROVIDER_URBAN_AIRSHIP: - integrationConfig.put(INTEGRATION_URBAN_AIRSHIP, pushObject); + integrationConfig.setUrbanAirship(item); break; case PUSH_PROVIDER_AMAZON_AWS_SNS: - integrationConfig.put(INTEGRATION_AWS_SNS, pushObject); + integrationConfig.setAmazonAwsSns(item); break; default: ApptentiveLog.e("Invalid pushProvider: %d", pushProvider); return; } - DeviceManager.storeIntegrationConfig(integrationConfig); - ApptentiveInternal.getInstance().syncDevice(); - } catch (JSONException e) { - ApptentiveLog.e("Error setting push integration.", e); - return; - } - } - - private static CustomData getIntegrationConfigurationWithoutPushProviders() { - CustomData integrationConfig = DeviceManager.loadIntegrationConfig(); - if (integrationConfig != null) { - integrationConfig.remove(INTEGRATION_APPTENTIVE_PUSH); - integrationConfig.remove(INTEGRATION_PARSE); - integrationConfig.remove(INTEGRATION_URBAN_AIRSHIP); - integrationConfig.remove(INTEGRATION_AWS_SNS); + sessionData.save(); } - return integrationConfig; } // **************************************************************************************** @@ -516,8 +459,7 @@ public static boolean isApptentivePushNotification(Map data) { *

Use this method in your push receiver to build a pending Intent when an Apptentive push * notification is received. Pass the generated PendingIntent to * {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent} to allow Apptentive - * to display Interactions such as Message Center. This method replaces the deprecated - * {@link #setPendingPushNotification(Intent)}. Calling this method for a push {@link Intent} that did + * to display Interactions such as Message Center. Calling this method for a push {@link Intent} that did * not come from Apptentive will return a null object. If you receive a null object, your app will * need to handle this notification itself.

*

This is the method you will likely need if you integrated using:

@@ -543,8 +485,7 @@ public static PendingIntent buildPendingIntentFromPushNotification(@NonNull Inte *

Use this method in your push receiver to build a pending Intent when an Apptentive push * notification is received. Pass the generated PendingIntent to * {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent} to allow Apptentive - * to display Interactions such as Message Center. This method replaces the deprecated - * {@link #setPendingPushNotification(Bundle)}. Calling this method for a push {@link Bundle} that + * to display Interactions such as Message Center. Calling this method for a push {@link Bundle} that * did not come from Apptentive will return a null object. If you receive a null object, your app * will need to handle this notification itself.

*

This is the method you will likely need if you integrated using:

@@ -568,8 +509,7 @@ public static PendingIntent buildPendingIntentFromPushNotification(@NonNull Bund *

Use this method in your push receiver to build a pending Intent when an Apptentive push * notification is received. Pass the generated PendingIntent to * {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent} to allow Apptentive - * to display Interactions such as Message Center. This method replaces the deprecated - * {@link #setPendingPushNotification(Bundle)}. Calling this method for a push {@link Bundle} that + * to display Interactions such as Message Center. Calling this method for a push {@link Bundle} that * did not come from Apptentive will return a null object. If you receive a null object, your app * will need to handle this notification itself.

*

This is the method you will likely need if you integrated using:

diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 5a732fe68..cfcab8497 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -36,11 +36,7 @@ import com.apptentive.android.sdk.model.CodePointStore; import com.apptentive.android.sdk.model.Configuration; import com.apptentive.android.sdk.model.ConversationTokenRequest; -import com.apptentive.android.sdk.model.CustomData; -import com.apptentive.android.sdk.model.Device; import com.apptentive.android.sdk.model.Event; -import com.apptentive.android.sdk.model.Person; -import com.apptentive.android.sdk.model.Sdk; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; @@ -51,10 +47,10 @@ import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; import com.apptentive.android.sdk.storage.AppReleaseManager; import com.apptentive.android.sdk.storage.ApptentiveTaskManager; +import com.apptentive.android.sdk.storage.Device; import com.apptentive.android.sdk.storage.DeviceManager; import com.apptentive.android.sdk.storage.FileSerializer; import com.apptentive.android.sdk.storage.PayloadSendWorker; -import com.apptentive.android.sdk.storage.PersonManager; import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.storage.SessionData; import com.apptentive.android.sdk.storage.VersionHistoryEntry; @@ -403,37 +399,6 @@ public SharedPreferences getSharedPrefs() { return prefs; } - public void addCustomDeviceData(String key, Object value) { - if (key == null || key.trim().length() == 0) { - return; - } - key = key.trim(); - CustomData customData = DeviceManager.loadCustomDeviceData(); - if (customData != null) { - try { - customData.put(key, value); - DeviceManager.storeCustomDeviceData(customData); - } catch (JSONException e) { - ApptentiveLog.w("Unable to add custom device data.", e); - } - } - } - - public void addCustomPersonData(String key, Object value) { - if (key == null || key.trim().length() == 0) { - return; - } - CustomData customData = PersonManager.loadCustomPersonData(); - if (customData != null) { - try { - customData.put(key, value); - PersonManager.storeCustomPersonData(customData); - } catch (JSONException e) { - ApptentiveLog.w("Unable to add custom person data.", e); - } - } - } - public void runOnWorkerThread(Runnable r) { cachedExecutor.execute(r); } @@ -468,9 +433,6 @@ public void onActivityStarted(Activity activity) { //FIXME: Kick this off on Application initialization instead. checkAndUpdateApptentiveConfigurations(); - - syncDevice(); - syncPerson(); } public void onActivityResumed(Activity activity) { @@ -686,7 +648,7 @@ private void onVersionChanged(Integer previousVersionCode, Integer currentVersio private void onSdkVersionChanged(Context context, String previousSdkVersion, String currentSdkVersion) { ApptentiveLog.i("SDK version changed: %s => %s", previousSdkVersion, currentSdkVersion); context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE).edit().putString(Constants.PREF_KEY_LAST_SEEN_SDK_VERSION, currentSdkVersion).apply(); - syncSdk(); + // FIXME: Save to SessionData instead; invalidateCaches(); } @@ -752,9 +714,11 @@ private boolean fetchConversationToken() { ConversationTokenRequest request = new ConversationTokenRequest(); // Send the Device and Sdk now, so they are available on the server from the start. - request.setDevice(DeviceManager.storeDeviceAndReturnIt()); + Device device = DeviceManager.generateNewDevice(); + request.setDevice(DeviceManager.getDiffPayload(null, device)); request.setSdk(SdkManager.storeSdkAndReturnIt()); - request.setPerson(PersonManager.storePersonAndReturnIt()); + // FIXME: Set up initial person sending here. + //request.setPerson(PersonManager.storePersonAndReturnIt()); AppRelease currentAppRelease = AppRelease.generateCurrentAppRelease(appContext); AppReleaseManager.storeAppRelease(currentAppRelease); request.setAppRelease(currentAppRelease); @@ -776,6 +740,7 @@ private boolean fetchConversationToken() { if (conversationToken != null && !conversationToken.equals("")) { sessionData.setConversationToken(conversationToken); sessionData.setConversationId(conversationId); + sessionData.setDevice(device); saveSessionData(); } String personId = root.getString("person_id"); @@ -860,44 +825,6 @@ protected void onPostExecute(Void v) { } } - /** - * Sends current Device to the server if it differs from the last time it was sent. - */ - void syncDevice() { - Device deviceInfo = DeviceManager.storeDeviceAndReturnDiff(); - if (deviceInfo != null) { - ApptentiveLog.d("Device info was updated."); - ApptentiveLog.v(deviceInfo.toString()); - taskManager.addPayload(deviceInfo); - } else { - ApptentiveLog.d("Device info was not updated."); - } - } - - /** - * Sends current SDK to the server. - */ - private void syncSdk() { - Sdk sdk = SdkManager.generateCurrentSdk(); - SdkManager.storeSdk(sdk); - ApptentiveLog.v(sdk.toString()); - taskManager.addPayload(sdk); - } - - /** - * Sends current Person to the server if it differs from the last time it was sent. - */ - private void syncPerson() { - Person person = PersonManager.storePersonAndReturnDiff(); - if (person != null) { - ApptentiveLog.d("Person was updated."); - ApptentiveLog.v(person.toString()); - taskManager.addPayload(person); - } else { - ApptentiveLog.d("Person was not updated."); - } - } - public IRatingProvider getRatingProvider() { if (ratingProvider == null) { ratingProvider = new GooglePlayRatingProvider(); @@ -1179,7 +1106,7 @@ public void saveSessionData() { public boolean handleMessage(Message msg) { switch (msg.what) { case MESSAGE_SAVE_SESSION_DATA: - ApptentiveLog.e("Saing SessionData"); + ApptentiveLog.d("Saving SessionData"); File internalStorage = appContext.getFilesDir(); File sessionDataFile = new File(internalStorage, "apptentive/SessionData.ser"); try { @@ -1189,6 +1116,7 @@ public boolean handleMessage(Message msg) { } FileSerializer fileSerializer = new FileSerializer(sessionDataFile); fileSerializer.serialize(sApptentiveInternal.sessionData); + ApptentiveLog.d("Session data written to file of length: %s", Util.humanReadableByteCount(sessionDataFile.length(), false)); break; case MESSAGE_CREATE_CONVERSATION: // TODO: This. diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java index aa54f1dd1..d36e624ea 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java @@ -10,11 +10,9 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.BuildConfig; -import com.apptentive.android.sdk.model.CustomData; -import com.apptentive.android.sdk.model.Device; -import com.apptentive.android.sdk.model.Person; -import com.apptentive.android.sdk.storage.DeviceManager; -import com.apptentive.android.sdk.storage.PersonManager; +import com.apptentive.android.sdk.storage.CustomData; +import com.apptentive.android.sdk.storage.Device; +import com.apptentive.android.sdk.storage.Person; import com.apptentive.android.sdk.storage.VersionHistoryStore; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -137,7 +135,7 @@ public static Object doGetValue(String query) { } case person: { QueryPart subQuery = QueryPart.parse(tokens[1]); - Person person = PersonManager.getStoredPerson(); + Person person = ApptentiveInternal.getInstance().getSessionData().getPerson(); if (person == null) { return null; } @@ -146,7 +144,7 @@ public static Object doGetValue(String query) { String customDataKey = tokens[2].trim(); CustomData customData = person.getCustomData(); if (customData != null) { - return customData.opt(customDataKey); + return customData.get(customDataKey); } break; case name: @@ -154,13 +152,16 @@ public static Object doGetValue(String query) { case email: return person.getEmail(); case other: +/* +FIXME: Choose the correct Getter for each query String key = tokens[1]; - return person.opt(key); + return person.get(key); +*/ } } case device: { QueryPart subQuery = QueryPart.parse(tokens[1]); - Device device = DeviceManager.getStoredDevice(); + Device device = ApptentiveInternal.getInstance().getSessionData().getDevice(); if (device == null) { return null; } @@ -169,16 +170,19 @@ public static Object doGetValue(String query) { String customDataKey = tokens[2].trim(); CustomData customData = device.getCustomData(); if (customData != null) { - return customData.opt(customDataKey); + return customData.get(customDataKey); } break; case os_version: - String osVersion = device.optString(subQuery.name(), "0"); + String osVersion = device.getOsVersion(); + if (osVersion == null) { + osVersion = "0"; + } Apptentive.Version ret = new Apptentive.Version(); ret.setVersion(osVersion); return ret; case os_api_level: - return device.optInt(subQuery.name(), 0); + return device.getOsApiLevel(); case board: case bootloader_version: case brand: @@ -201,7 +205,9 @@ public static Object doGetValue(String query) { case radio_version: case uuid: case other: - return device.opt(subQuery.name()); + // FIXME: Choose the correct Getter for each query + //return device.opt(subQuery.name()); + return null; } } default: diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java new file mode 100644 index 000000000..f7d566254 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import org.json.JSONException; + +import java.util.HashMap; +import java.util.Set; + +public class CustomData extends HashMap { + + public com.apptentive.android.sdk.model.CustomData toJson() { + try { + com.apptentive.android.sdk.model.CustomData ret = new com.apptentive.android.sdk.model.CustomData(); + Set keys = keySet(); + for (String key : keys) { + ret.put(key, get(key)); + } + } catch (JSONException e) { + // This can't happen. + } + return null; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java new file mode 100644 index 000000000..184015622 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import java.io.Serializable; + +public class Device implements Serializable { + + private String uuid; + private String osName; + private String osVersion; + private String osBuild; + private int osApiLevel; + private String manufacturer; + private String model; + private String board; + private String product; + private String brand; + private String cpu; + private String device; + private String carrier; + private String currentCarrier; + private String networkType; + private String buildType; + private String buildId; + private String bootloaderVersion; + private String radioVersion; + private CustomData customData; + private String localeCountryCode; + private String localeLanguageCode; + private String localeRaw; + private String utcOffset; + private IntegrationConfig integrationConfig; + + public Device() { + this.customData = new CustomData(); + this.integrationConfig = new IntegrationConfig(); + } + +//region Getters & Setters + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getOsName() { + return osName; + } + + public void setOsName(String osName) { + this.osName = osName; + } + + public String getOsVersion() { + return osVersion; + } + + public void setOsVersion(String osVersion) { + this.osVersion = osVersion; + } + + public String getOsBuild() { + return osBuild; + } + + public void setOsBuild(String osBuild) { + this.osBuild = osBuild; + } + + public int getOsApiLevel() { + return osApiLevel; + } + + public void setOsApiLevel(int osApiLevel) { + this.osApiLevel = osApiLevel; + } + + public String getManufacturer() { + return manufacturer; + } + + public void setManufacturer(String manufacturer) { + this.manufacturer = manufacturer; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public String getBoard() { + return board; + } + + public void setBoard(String board) { + this.board = board; + } + + public String getProduct() { + return product; + } + + public void setProduct(String product) { + this.product = product; + } + + public String getBrand() { + return brand; + } + + public void setBrand(String brand) { + this.brand = brand; + } + + public String getCpu() { + return cpu; + } + + public void setCpu(String cpu) { + this.cpu = cpu; + } + + public String getDevice() { + return device; + } + + public void setDevice(String device) { + this.device = device; + } + + public String getCarrier() { + return carrier; + } + + public void setCarrier(String carrier) { + this.carrier = carrier; + } + + public String getCurrentCarrier() { + return currentCarrier; + } + + public void setCurrentCarrier(String currentCarrier) { + this.currentCarrier = currentCarrier; + } + + public String getNetworkType() { + return networkType; + } + + public void setNetworkType(String networkType) { + this.networkType = networkType; + } + + public String getBuildType() { + return buildType; + } + + public void setBuildType(String buildType) { + this.buildType = buildType; + } + + public String getBuildId() { + return buildId; + } + + public void setBuildId(String buildId) { + this.buildId = buildId; + } + + public String getBootloaderVersion() { + return bootloaderVersion; + } + + public void setBootloaderVersion(String bootloaderVersion) { + this.bootloaderVersion = bootloaderVersion; + } + + public String getRadioVersion() { + return radioVersion; + } + + public void setRadioVersion(String radioVersion) { + this.radioVersion = radioVersion; + } + + public CustomData getCustomData() { + return customData; + } + + public void setCustomData(CustomData customData) { + this.customData = customData; + } + + public String getLocaleCountryCode() { + return localeCountryCode; + } + + public void setLocaleCountryCode(String localeCountryCode) { + this.localeCountryCode = localeCountryCode; + } + + public String getLocaleLanguageCode() { + return localeLanguageCode; + } + + public void setLocaleLanguageCode(String localeLanguageCode) { + this.localeLanguageCode = localeLanguageCode; + } + + public String getLocaleRaw() { + return localeRaw; + } + + public void setLocaleRaw(String localeRaw) { + this.localeRaw = localeRaw; + } + + public String getUtcOffset() { + return utcOffset; + } + + public void setUtcOffset(String utcOffset) { + this.utcOffset = utcOffset; + } + + public IntegrationConfig getIntegrationConfig() { + return integrationConfig; + } + + public void setIntegrationConfig(IntegrationConfig integrationConfig) { + this.integrationConfig = integrationConfig; + } + +//endregion + +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java index 70bdf4197..d34302f8d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java @@ -7,122 +7,28 @@ package com.apptentive.android.sdk.storage; import android.content.Context; -import android.content.SharedPreferences; import android.os.Build; import android.telephony.TelephonyManager; import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.model.CustomData; -import com.apptentive.android.sdk.model.Device; import com.apptentive.android.sdk.util.Constants; -import com.apptentive.android.sdk.util.JsonDiffer; -import org.json.JSONException; import java.util.Locale; import java.util.TimeZone; /** - * A helper class with static methods for getting, storing, retrieving, and diffing information about the current device. - * - * @author Sky Kelsey + * A helper class with static methods for and diffing information about the current device. */ public class DeviceManager { - /** - * If any device setting has changed, return only the changed fields in a new Device object. If a field's value was - * cleared, set that value to null in the Device. The first time this is called, all Device will be returned. - * - * @return A Device containing diff data which, when added to the last sent Device, yields the new Device. - */ - public static Device storeDeviceAndReturnDiff() { - - Device stored = getStoredDevice(); - - Device current = generateNewDevice(); - CustomData customData = loadCustomDeviceData(); - current.setCustomData(customData); - CustomData integrationConfig = loadIntegrationConfig(); - current.setIntegrationConfig(integrationConfig); - - Object diff = JsonDiffer.getDiff(stored, current); - if (diff != null) { - try { - storeDevice(current); - return new Device(diff.toString()); - } catch (JSONException e) { - ApptentiveLog.e("Error casting to Device.", e); - } - } - return null; - } - - /** - * Provided so we can be sure that the device we send during conversation creation is 100% accurate. Since we do not - * queue this device up in the payload queue, it could otherwise be lost. - */ - public static Device storeDeviceAndReturnIt() { - Device current = generateNewDevice(); - CustomData customData = loadCustomDeviceData(); - current.setCustomData(customData); - CustomData integrationConfig = loadIntegrationConfig(); - current.setIntegrationConfig(integrationConfig); - storeDevice(current); - return current; - } - - public static CustomData loadCustomDeviceData() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - String deviceDataString = prefs.getString(Constants.PREF_KEY_DEVICE_DATA, null); - try { - return new CustomData(deviceDataString); - } catch (Exception e) { - // Ignore - } - try { - return new CustomData(); - } catch (JSONException e) { - // Ignore - } - return null; // This should never happen. - } - - public static void storeCustomDeviceData(CustomData deviceData) { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - String deviceDataString = deviceData.toString(); - prefs.edit().putString(Constants.PREF_KEY_DEVICE_DATA, deviceDataString).apply(); - } - - public static CustomData loadIntegrationConfig() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - String integrationConfigString = prefs.getString(Constants.PREF_KEY_DEVICE_INTEGRATION_CONFIG, null); - try { - return new CustomData(integrationConfigString); - } catch (Exception e) { - // Ignore - } - try { - return new CustomData(); - } catch (JSONException e) { - // Ignore - } - return null; // This should never happen. - } - - public static void storeIntegrationConfig(CustomData integrationConfig) { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - String integrationConfigString = integrationConfig.toString(); - prefs.edit().putString(Constants.PREF_KEY_DEVICE_INTEGRATION_CONFIG, integrationConfigString).apply(); - } - - private static Device generateNewDevice() { + public static Device generateNewDevice() { Device device = new Device(); // First, get all the information we can load from static resources. device.setOsName("Android"); device.setOsVersion(Build.VERSION.RELEASE); device.setOsBuild(Build.VERSION.INCREMENTAL); - device.setOsApiLevel(String.valueOf(Build.VERSION.SDK_INT)); + device.setOsApiLevel(Build.VERSION.SDK_INT); device.setManufacturer(Build.MANUFACTURER); device.setModel(Build.MODEL); device.setBoard(Build.BOARD); @@ -156,22 +62,115 @@ private static Device generateNewDevice() { return device; } - public static Device getStoredDevice() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - String deviceString = prefs.getString(Constants.PREF_KEY_DEVICE, null); - try { - return new Device(deviceString); - } catch (Exception e) { - // Ignore - } - return null; + public static void onSentDeviceInfo() { } - private static void storeDevice(Device device) { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - prefs.edit().putString(Constants.PREF_KEY_DEVICE, device.toString()).apply(); - } + public static com.apptentive.android.sdk.model.Device getDiffPayload(com.apptentive.android.sdk.storage.Device oldDevice, com.apptentive.android.sdk.storage.Device newDevice) { + if (newDevice == null) { + return null; + } - public static void onSentDeviceInfo() { + com.apptentive.android.sdk.model.Device ret = new com.apptentive.android.sdk.model.Device(); + + if (oldDevice == null || !oldDevice.getUuid().equals(newDevice.getUuid())) { + ret.setUuid(newDevice.getUuid()); + } + + if (oldDevice == null || !oldDevice.getOsName().equals(newDevice.getOsName())) { + ret.setOsName(newDevice.getOsName()); + } + + if (oldDevice == null || !oldDevice.getOsVersion().equals(newDevice.getOsVersion())) { + ret.setOsVersion(newDevice.getOsVersion()); + } + + if (oldDevice == null || !oldDevice.getOsBuild().equals(newDevice.getOsBuild())) { + ret.setOsBuild(newDevice.getOsBuild()); + } + + if (oldDevice == null || oldDevice.getOsApiLevel() != newDevice.getOsApiLevel()) { + ret.setOsApiLevel(String.valueOf(newDevice.getOsApiLevel())); + } + + if (oldDevice == null || !oldDevice.getManufacturer().equals(newDevice.getManufacturer())) { + ret.setManufacturer(newDevice.getManufacturer()); + } + + if (oldDevice == null || !oldDevice.getModel().equals(newDevice.getModel())) { + ret.setModel(newDevice.getModel()); + } + + if (oldDevice == null || !oldDevice.getBoard().equals(newDevice.getBoard())) { + ret.setBoard(newDevice.getBoard()); + } + + if (oldDevice == null || !oldDevice.getProduct().equals(newDevice.getProduct())) { + ret.setProduct(newDevice.getProduct()); + } + + if (oldDevice == null || !oldDevice.getBrand().equals(newDevice.getBrand())) { + ret.setBrand(newDevice.getBrand()); + } + + if (oldDevice == null || !oldDevice.getCpu().equals(newDevice.getCpu())) { + ret.setCpu(newDevice.getCpu()); + } + + if (oldDevice == null || !oldDevice.getDevice().equals(newDevice.getDevice())) { + ret.setDevice(newDevice.getDevice()); + } + + if (oldDevice == null || !oldDevice.getCarrier().equals(newDevice.getCarrier())) { + ret.setCarrier(newDevice.getCarrier()); + } + + if (oldDevice == null || !oldDevice.getCurrentCarrier().equals(newDevice.getCurrentCarrier())) { + ret.setCurrentCarrier(newDevice.getCurrentCarrier()); + } + + if (oldDevice == null || !oldDevice.getNetworkType().equals(newDevice.getNetworkType())) { + ret.setNetworkType(newDevice.getNetworkType()); + } + + if (oldDevice == null || !oldDevice.getBuildType().equals(newDevice.getBuildType())) { + ret.setBuildType(newDevice.getBuildType()); + } + + if (oldDevice == null || !oldDevice.getBuildId().equals(newDevice.getBuildId())) { + ret.setBuildId(newDevice.getBuildId()); + } + + if (oldDevice == null || !oldDevice.getBootloaderVersion().equals(newDevice.getBootloaderVersion())) { + ret.setBootloaderVersion(newDevice.getBootloaderVersion()); + } + + if (oldDevice == null || !oldDevice.getRadioVersion().equals(newDevice.getRadioVersion())) { + ret.setRadioVersion(newDevice.getRadioVersion()); + } + + if (oldDevice == null || !oldDevice.getCustomData().equals(newDevice.getCustomData())) { + ret.setCustomData(newDevice.getCustomData().toJson()); + } + + if (oldDevice == null || !oldDevice.getLocaleCountryCode().equals(newDevice.getLocaleCountryCode())) { + ret.setLocaleCountryCode(newDevice.getLocaleCountryCode()); + } + + if (oldDevice == null || !oldDevice.getLocaleLanguageCode().equals(newDevice.getLocaleLanguageCode())) { + ret.setLocaleLanguageCode(newDevice.getLocaleLanguageCode()); + } + + if (oldDevice == null || !oldDevice.getLocaleRaw().equals(newDevice.getLocaleRaw())) { + ret.setLocaleRaw(newDevice.getLocaleRaw()); + } + + if (oldDevice == null || !oldDevice.getUtcOffset().equals(newDevice.getUtcOffset())) { + ret.setUtcOffset(newDevice.getUtcOffset()); + } + + if (oldDevice == null || !oldDevice.getIntegrationConfig().equals(newDevice.getIntegrationConfig())) { + ret.setIntegrationConfig(newDevice.getIntegrationConfig().toJson()); + } + return ret; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java new file mode 100644 index 000000000..f0389ac97 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import org.json.JSONException; + +import java.io.Serializable; + + +public class IntegrationConfig implements Serializable { + + private static final String INTEGRATION_APPTENTIVE_PUSH = "apptentive_push"; + private static final String INTEGRATION_AWS_SNS = "aws_sns"; + private static final String INTEGRATION_URBAN_AIRSHIP = "urban_airship"; + private static final String INTEGRATION_PARSE = "parse"; + + private IntegrationConfigItem apptentive; + private IntegrationConfigItem amazonAwsSns; + private IntegrationConfigItem urbanAirship; + private IntegrationConfigItem parse; + + public IntegrationConfigItem getApptentive() { + return apptentive; + } + + public void setApptentive(IntegrationConfigItem apptentive) { + this.apptentive = apptentive; + } + + public IntegrationConfigItem getAmazonAwsSns() { + return amazonAwsSns; + } + + public void setAmazonAwsSns(IntegrationConfigItem amazonAwsSns) { + this.amazonAwsSns = amazonAwsSns; + } + + public IntegrationConfigItem getUrbanAirship() { + return urbanAirship; + } + + public void setUrbanAirship(IntegrationConfigItem urbanAirship) { + this.urbanAirship = urbanAirship; + } + + public IntegrationConfigItem getParse() { + return parse; + } + + public void setParse(IntegrationConfigItem parse) { + this.parse = parse; + } + + public com.apptentive.android.sdk.model.CustomData toJson() { + try { + com.apptentive.android.sdk.model.CustomData ret = new com.apptentive.android.sdk.model.CustomData(); + if (apptentive != null) { + ret.put(INTEGRATION_APPTENTIVE_PUSH, apptentive.toJson()); + } + if (amazonAwsSns != null) { + ret.put(INTEGRATION_AWS_SNS, amazonAwsSns.toJson()); + } + if (urbanAirship != null) { + ret.put(INTEGRATION_URBAN_AIRSHIP, urbanAirship.toJson()); + } + if (parse != null) { + ret.put(INTEGRATION_PARSE, parse.toJson()); + } + } catch (JSONException e) { + // This can't happen. + } + return null; + } + +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java new file mode 100644 index 000000000..15627510b --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import org.json.JSONException; + +import java.util.HashMap; +import java.util.Set; + +public class IntegrationConfigItem extends HashMap { + + public com.apptentive.android.sdk.model.CustomData toJson() { + try { + com.apptentive.android.sdk.model.CustomData ret = new com.apptentive.android.sdk.model.CustomData(); + Set keys = keySet(); + for (String key : keys) { + ret.put(key, get(key)); + } + } catch (JSONException e) { + // This can't happen. + } + return null; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java index 0c71fb17b..ea5cb4bef 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java @@ -161,7 +161,7 @@ public void run() { response = ApptentiveClient.postEvent((Event) payload); break; case device: - response = ApptentiveClient.putDevice((Device) payload); + response = ApptentiveClient.putDevice((com.apptentive.android.sdk.model.Device) payload); DeviceManager.onSentDeviceInfo(); break; case sdk: @@ -171,7 +171,7 @@ public void run() { response = ApptentiveClient.putAppRelease((AppRelease) payload); break; case person: - response = ApptentiveClient.putPerson((Person) payload); + response = ApptentiveClient.putPerson((com.apptentive.android.sdk.model.Person) payload); break; case survey: response = ApptentiveClient.postSurvey((SurveyResponse) payload); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java new file mode 100644 index 000000000..7849f572e --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import java.io.Serializable; + +public class Person implements Serializable { + + private String id; + private String email; + private String name; + private String facebookId; + private String phoneNumber; + private String street; + private String city; + private String zip; + private String country; + private String birthday; + private CustomData customData; + + //region Getters & Setters + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFacebookId() { + return facebookId; + } + + public void setFacebookId(String facebookId) { + this.facebookId = facebookId; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getZip() { + return zip; + } + + public void setZip(String zip) { + this.zip = zip; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getBirthday() { + return birthday; + } + + public void setBirthday(String birthday) { + this.birthday = birthday; + } + + public CustomData getCustomData() { + return customData; + } + + public void setCustomData(CustomData customData) { + this.customData = customData; + } + + //endregion + + public void addCustomData(String key, Object value) { + customData.put(key, value); + } + + +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java index b32cd792f..0d67a6228 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java @@ -6,103 +6,59 @@ package com.apptentive.android.sdk.storage; -import android.content.SharedPreferences; - -import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.model.CustomData; -import com.apptentive.android.sdk.model.Person; -import com.apptentive.android.sdk.util.Constants; -import com.apptentive.android.sdk.util.JsonDiffer; - -import org.json.JSONException; - public class PersonManager { - public static Person storePersonAndReturnDiff() { - Person stored = getStoredPerson(); + public static com.apptentive.android.sdk.model.Person getDiffPayload(com.apptentive.android.sdk.storage.Person oldPerson, com.apptentive.android.sdk.storage.Person newPerson) { + if (newPerson == null) { + return null; + } - Person current = generateCurrentPerson(); - CustomData customData = loadCustomPersonData(); - current.setCustomData(customData); + com.apptentive.android.sdk.model.Person ret = new com.apptentive.android.sdk.model.Person(); - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - current.setEmail(sessionData.getPersonEmail()); - current.setName(sessionData.getPersonName()); + if (oldPerson == null || !oldPerson.getId().equals(newPerson.getId())) { + ret.setId(newPerson.getId()); } - Object diff = JsonDiffer.getDiff(stored, current); - if (diff != null) { - try { - storePerson(current); - return new Person(diff.toString()); - } catch (JSONException e) { - ApptentiveLog.e("Error casting to Person.", e); - } + if (oldPerson == null || !oldPerson.getEmail().equals(newPerson.getEmail())) { + ret.setEmail(newPerson.getEmail()); } - return null; - } + if (oldPerson == null || !oldPerson.getName().equals(newPerson.getName())) { + ret.setName(newPerson.getName()); + } - /** - * Provided so we can be sure that the person we send during conversation creation is 100% accurate. Since we do not - * queue this person up in the payload queue, it could otherwise be lost. - */ - public static Person storePersonAndReturnIt() { - Person current = generateCurrentPerson(); + if (oldPerson == null || !oldPerson.getFacebookId().equals(newPerson.getFacebookId())) { + ret.setFacebookId(newPerson.getFacebookId()); + } - CustomData customData = loadCustomPersonData(); - current.setCustomData(customData); + if (oldPerson == null || !oldPerson.getPhoneNumber().equals(newPerson.getPhoneNumber())) { + ret.setPhoneNumber(newPerson.getPhoneNumber()); + } - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - current.setEmail(sessionData.getPersonEmail()); - current.setName(sessionData.getPersonName()); + if (oldPerson == null || !oldPerson.getStreet().equals(newPerson.getStreet())) { + ret.setStreet(newPerson.getStreet()); } - storePerson(current); - return current; - } - public static CustomData loadCustomPersonData() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - String personDataString = prefs.getString(Constants.PREF_KEY_PERSON_DATA, null); - try { - return new CustomData(personDataString); - } catch (Exception e) { - // Ignore + if (oldPerson == null || !oldPerson.getCity().equals(newPerson.getCity())) { + ret.setCity(newPerson.getCity()); } - try { - return new CustomData(); - } catch (JSONException e) { - // Ignore + + if (oldPerson == null || !oldPerson.getZip().equals(newPerson.getZip())) { + ret.setZip(newPerson.getZip()); } - return null; - } - public static void storeCustomPersonData(CustomData deviceData) { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - String personDataString = deviceData.toString(); - prefs.edit().putString(Constants.PREF_KEY_PERSON_DATA, personDataString).apply(); - } + if (oldPerson == null || !oldPerson.getCountry().equals(newPerson.getCountry())) { + ret.setCountry(newPerson.getCountry()); + } - private static Person generateCurrentPerson() { - return new Person(); - } + if (oldPerson == null || !oldPerson.getBirthday().equals(newPerson.getBirthday())) { + ret.setBirthday(newPerson.getBirthday()); + } - public static Person getStoredPerson() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - String PersonString = prefs.getString(Constants.PREF_KEY_PERSON, null); - try { - return new Person(PersonString); - } catch (Exception e) { - // Ignore + if (oldPerson == null || !oldPerson.getCustomData().equals(newPerson.getCustomData())) { + ret.setCustomData(newPerson.getCustomData().toJson()); } - return null; - } - private static void storePerson(Person Person) { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - prefs.edit().putString(Constants.PREF_KEY_PERSON, Person.toString()).apply(); + return ret; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index 185302f79..f2068ca50 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -19,6 +19,15 @@ public class SessionData implements Serializable { private String personId; private String personEmail; private String personName; + private Device device; + private Device lastSentDevice; + private Person person; + private Person lastSentPerson; + + public SessionData() { + this.device = new Device(); + this.person = new Person(); + } //region Getters & Setters @@ -67,9 +76,47 @@ public void setPersonName(String personName) { save(); } - //endregion + public Device getDevice() { + return device; + } + + public void setDevice(Device device) { + this.device = device; + } + + public Device getLastSentDevice() { + return lastSentDevice; + } + + public void setLastSentDevice(Device lastSentDevice) { + this.lastSentDevice = lastSentDevice; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + + public Person getLastSentPerson() { + return lastSentPerson; + } + + public void setLastSentPerson(Person lastSentPerson) { + this.lastSentPerson = lastSentPerson; + } + +//endregion + + public com.apptentive.android.sdk.model.Device getDeviceDiffPayload() { + com.apptentive.android.sdk.model.Device ret = new com.apptentive.android.sdk.model.Device(); + + return ret; + } - private void save() { + public void save() { ApptentiveInternal.getInstance().saveSessionData(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java index 188cb3349..6d9975d1b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java @@ -832,4 +832,12 @@ public static void replaceDefaultFont(Context context, String fontFilePath) { } } } + + public static String humanReadableByteCount(long bytes, boolean si) { + int unit = si ? 1000 : 1024; + if (bytes < unit) return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); + return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); + } } From bfc90b7df89408bb1c3a88afd09459084438aba1 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 20 Jan 2017 15:31:48 -0800 Subject: [PATCH 022/465] Get AppRelease and Sdk sending again. Create storage objects, creation, and saving/sending functionality. --- .../android/sdk/ApptentiveInternal.java | 36 ++++--- .../android/sdk/storage/AppRelease.java | 98 +++++++++++++++++++ .../sdk/storage/AppReleaseManager.java | 69 +++++++++++-- .../sdk/storage/PayloadSendWorker.java | 4 +- .../apptentive/android/sdk/storage/Sdk.java | 79 +++++++++++++++ .../android/sdk/storage/SdkManager.java | 26 ++--- .../android/sdk/storage/SessionData.java | 27 ++++- 7 files changed, 302 insertions(+), 37 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/AppRelease.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/Sdk.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index cfcab8497..865b8ee52 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -32,7 +32,6 @@ import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.lifecycle.ApptentiveActivityLifecycleCallbacks; -import com.apptentive.android.sdk.model.AppRelease; import com.apptentive.android.sdk.model.CodePointStore; import com.apptentive.android.sdk.model.Configuration; import com.apptentive.android.sdk.model.ConversationTokenRequest; @@ -45,12 +44,14 @@ import com.apptentive.android.sdk.module.rating.IRatingProvider; import com.apptentive.android.sdk.module.rating.impl.GooglePlayRatingProvider; import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; +import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.AppReleaseManager; import com.apptentive.android.sdk.storage.ApptentiveTaskManager; import com.apptentive.android.sdk.storage.Device; import com.apptentive.android.sdk.storage.DeviceManager; import com.apptentive.android.sdk.storage.FileSerializer; import com.apptentive.android.sdk.storage.PayloadSendWorker; +import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.storage.SessionData; import com.apptentive.android.sdk.storage.VersionHistoryEntry; @@ -554,9 +555,9 @@ public boolean init() { // Used for application theme inheritance if the theme is an AppCompat theme. setApplicationDefaultTheme(ai.theme); - AppRelease appRelease = AppRelease.generateCurrentAppRelease(appContext); + AppRelease appRelease = AppReleaseManager.generateCurrentAppRelease(appContext); - isAppDebuggable = appRelease.getDebug(); + isAppDebuggable = appRelease.isDebug(); currentVersionCode = appRelease.getVersionCode(); currentVersionName = appRelease.getVersionName(); @@ -635,20 +636,30 @@ public boolean init() { return bRet; } + // FIXME: Do this kind of thing when a session becomes active instead of at app initialization? Otherwise there is no active session to send the information to. private void onVersionChanged(Integer previousVersionCode, Integer currentVersionCode, String previousVersionName, String currentVersionName, AppRelease currentAppRelease) { ApptentiveLog.i("Version changed: Name: %s => %s, Code: %d => %d", previousVersionName, currentVersionName, previousVersionCode, currentVersionCode); VersionHistoryStore.updateVersionHistory(currentVersionCode, currentVersionName); if (previousVersionCode != null) { - AppReleaseManager.storeAppRelease(currentAppRelease); - taskManager.addPayload(currentAppRelease); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + taskManager.addPayload(AppReleaseManager.getPayload(currentAppRelease)); + if (sessionData != null) { + sessionData.setAppRelease(currentAppRelease); + } } invalidateCaches(); } + // FIXME: Do this kind of thing when a session becomes active instead of at app initialization? Otherwise there is no active session to send the information to. private void onSdkVersionChanged(Context context, String previousSdkVersion, String currentSdkVersion) { ApptentiveLog.i("SDK version changed: %s => %s", previousSdkVersion, currentSdkVersion); context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE).edit().putString(Constants.PREF_KEY_LAST_SEEN_SDK_VERSION, currentSdkVersion).apply(); - // FIXME: Save to SessionData instead; + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + Sdk sdk = SdkManager.generateCurrentSdk(); + taskManager.addPayload(SdkManager.getPayload(sdk)); + if (sessionData != null) { + sessionData.setSdk(sdk); + } invalidateCaches(); } @@ -715,13 +726,12 @@ private boolean fetchConversationToken() { // Send the Device and Sdk now, so they are available on the server from the start. Device device = DeviceManager.generateNewDevice(); + Sdk sdk = SdkManager.generateCurrentSdk(); + AppRelease appRelease = AppReleaseManager.generateCurrentAppRelease(appContext); + request.setDevice(DeviceManager.getDiffPayload(null, device)); - request.setSdk(SdkManager.storeSdkAndReturnIt()); - // FIXME: Set up initial person sending here. - //request.setPerson(PersonManager.storePersonAndReturnIt()); - AppRelease currentAppRelease = AppRelease.generateCurrentAppRelease(appContext); - AppReleaseManager.storeAppRelease(currentAppRelease); - request.setAppRelease(currentAppRelease); + request.setSdk(SdkManager.getPayload(sdk)); + request.setAppRelease(AppReleaseManager.getPayload(appRelease)); ApptentiveHttpResponse response = ApptentiveClient.getConversationToken(request); if (response == null) { @@ -741,6 +751,8 @@ private boolean fetchConversationToken() { sessionData.setConversationToken(conversationToken); sessionData.setConversationId(conversationId); sessionData.setDevice(device); + sessionData.setSdk(sdk); + sessionData.setAppRelease(appRelease); saveSessionData(); } String personId = root.getString("person_id"); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppRelease.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppRelease.java new file mode 100644 index 000000000..1f0ccbee5 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppRelease.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import java.io.Serializable; + +public class AppRelease implements Serializable { + + private String appStore; + private boolean debug; + private String identifier; + private boolean inheritStyle; + private boolean overrideStyle; + private String targetSdkVersion; + private String type; + private int versionCode; + private String versionName; + + //region Getters & Setters + + public String getAppStore() { + return appStore; + } + + public void setAppStore(String appStore) { + this.appStore = appStore; + } + + public boolean isDebug() { + return debug; + } + + public void setDebug(boolean debug) { + this.debug = debug; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public boolean isInheritStyle() { + return inheritStyle; + } + + public void setInheritStyle(boolean inheritStyle) { + this.inheritStyle = inheritStyle; + } + + public boolean isOverrideStyle() { + return overrideStyle; + } + + public void setOverrideStyle(boolean overrideStyle) { + this.overrideStyle = overrideStyle; + } + + public String getTargetSdkVersion() { + return targetSdkVersion; + } + + public void setTargetSdkVersion(String targetSdkVersion) { + this.targetSdkVersion = targetSdkVersion; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public int getVersionCode() { + return versionCode; + } + + public void setVersionCode(int versionCode) { + this.versionCode = versionCode; + } + + public String getVersionName() { + return versionName; + } + + public void setVersionName(String versionName) { + this.versionName = versionName; + } + + //endregion +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java index 272678752..88566051c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java @@ -6,16 +6,73 @@ package com.apptentive.android.sdk.storage; -import android.content.SharedPreferences; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.model.AppRelease; -import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.util.Util; public class AppReleaseManager { - public static void storeAppRelease(AppRelease appRelease) { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - prefs.edit().putString(Constants.PREF_KEY_APP_RELEASE, appRelease.toString()).apply(); + public static AppRelease generateCurrentAppRelease(Context context) { + + AppRelease appRelease = new AppRelease(); + + String appPackageName = context.getPackageName(); + PackageManager packageManager = context.getPackageManager(); + + int currentVersionCode = 0; + String currentVersionName = "0"; + int targetSdkVersion = 0; + boolean isAppDebuggable = false; + try { + PackageInfo packageInfo = packageManager.getPackageInfo(appPackageName, PackageManager.GET_META_DATA | PackageManager.GET_RECEIVERS); + ApplicationInfo ai = packageInfo.applicationInfo; + currentVersionCode = packageInfo.versionCode; + currentVersionName = packageInfo.versionName; + targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion; + Bundle metaData = ai.metaData; + if (metaData != null) { + isAppDebuggable = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } + } catch (PackageManager.NameNotFoundException e) { + ApptentiveLog.e("Failed to read app's PackageInfo."); + } + + int themeOverrideResId = context.getResources().getIdentifier("ApptentiveThemeOverride", "style", appPackageName); + + appRelease.setAppStore(Util.getInstallerPackageName(context)); + appRelease.setDebug(isAppDebuggable); + appRelease.setIdentifier(appPackageName); + appRelease.setInheritStyle(ApptentiveInternal.getInstance().isAppUsingAppCompatTheme()); + appRelease.setOverrideStyle(themeOverrideResId != 0); + appRelease.setTargetSdkVersion(String.valueOf(targetSdkVersion)); + appRelease.setType("android"); + appRelease.setVersionCode(currentVersionCode); + appRelease.setVersionName(currentVersionName); + + return appRelease; + } + + public static com.apptentive.android.sdk.model.AppRelease getPayload(AppRelease appRelease) { + com.apptentive.android.sdk.model.AppRelease ret = new com.apptentive.android.sdk.model.AppRelease(); + if (appRelease == null) { + return ret; + } + + ret.setAppStore(appRelease.getAppStore()); + ret.setDebug(appRelease.isDebug()); + ret.setIdentifier(appRelease.getIdentifier()); + ret.setInheritStyle(appRelease.isInheritStyle()); + ret.setOverrideStyle(appRelease.isOverrideStyle()); + ret.setTargetSdkVersion(appRelease.getTargetSdkVersion()); + ret.setType(appRelease.getType()); + ret.setVersionCode(appRelease.getVersionCode()); + ret.setVersionName(appRelease.getVersionName()); + return ret; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java index ea5cb4bef..44a719d23 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java @@ -165,10 +165,10 @@ public void run() { DeviceManager.onSentDeviceInfo(); break; case sdk: - response = ApptentiveClient.putSdk((Sdk) payload); + response = ApptentiveClient.putSdk((com.apptentive.android.sdk.model.Sdk) payload); break; case app_release: - response = ApptentiveClient.putAppRelease((AppRelease) payload); + response = ApptentiveClient.putAppRelease((com.apptentive.android.sdk.model.AppRelease) payload); break; case person: response = ApptentiveClient.putPerson((com.apptentive.android.sdk.model.Person) payload); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Sdk.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Sdk.java new file mode 100644 index 000000000..d9ae5a27f --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Sdk.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import java.io.Serializable; + +public class Sdk implements Serializable { + private String version; + private String programmingLanguage; + private String authorName; + private String authorEmail; + private String platform; + private String distribution; + private String distributionVersion; + + //region Getters & Setters + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getProgrammingLanguage() { + return programmingLanguage; + } + + public void setProgrammingLanguage(String programmingLanguage) { + this.programmingLanguage = programmingLanguage; + } + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public String getAuthorEmail() { + return authorEmail; + } + + public void setAuthorEmail(String authorEmail) { + this.authorEmail = authorEmail; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public String getDistribution() { + return distribution; + } + + public void setDistribution(String distribution) { + this.distribution = distribution; + } + + public String getDistributionVersion() { + return distributionVersion; + } + + public void setDistributionVersion(String distributionVersion) { + this.distributionVersion = distributionVersion; + } + + //endregion +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SdkManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SdkManager.java index b8e7e5ee5..3f135ccf4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SdkManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SdkManager.java @@ -6,21 +6,12 @@ package com.apptentive.android.sdk.storage; -import android.content.SharedPreferences; - import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.model.Sdk; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; public class SdkManager { - public static Sdk storeSdkAndReturnIt() { - Sdk current = generateCurrentSdk(); - storeSdk(current); - return current; - } - public static Sdk generateCurrentSdk() { Sdk sdk = new Sdk(); @@ -40,8 +31,19 @@ public static Sdk generateCurrentSdk() { return sdk; } - public static void storeSdk(Sdk sdk) { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - prefs.edit().putString(Constants.PREF_KEY_SDK, sdk.toString()).apply(); + public static com.apptentive.android.sdk.model.Sdk getPayload(Sdk sdk) { + com.apptentive.android.sdk.model.Sdk ret = new com.apptentive.android.sdk.model.Sdk(); + if (sdk == null) { + return ret; + } + + ret.setAuthorEmail(sdk.getAuthorEmail()); + ret.setAuthorName(sdk.getAuthorName()); + ret.setDistribution(sdk.getDistribution()); + ret.setDistributionVersion(sdk.getDistributionVersion()); + ret.setPlatform(sdk.getPlatform()); + ret.setProgrammingLanguage(sdk.getProgrammingLanguage()); + ret.setVersion(sdk.getVersion()); + return ret; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index f2068ca50..ce4776e7a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -23,6 +23,8 @@ public class SessionData implements Serializable { private Device lastSentDevice; private Person person; private Person lastSentPerson; + private Sdk sdk; + private AppRelease appRelease; public SessionData() { this.device = new Device(); @@ -82,6 +84,7 @@ public Device getDevice() { public void setDevice(Device device) { this.device = device; + save(); } public Device getLastSentDevice() { @@ -90,6 +93,7 @@ public Device getLastSentDevice() { public void setLastSentDevice(Device lastSentDevice) { this.lastSentDevice = lastSentDevice; + save(); } public Person getPerson() { @@ -98,6 +102,7 @@ public Person getPerson() { public void setPerson(Person person) { this.person = person; + save(); } public Person getLastSentPerson() { @@ -106,21 +111,33 @@ public Person getLastSentPerson() { public void setLastSentPerson(Person lastSentPerson) { this.lastSentPerson = lastSentPerson; + save(); + } + + public Sdk getSdk() { + return sdk; } -//endregion + public void setSdk(Sdk sdk) { + this.sdk = sdk; + save(); + } - public com.apptentive.android.sdk.model.Device getDeviceDiffPayload() { - com.apptentive.android.sdk.model.Device ret = new com.apptentive.android.sdk.model.Device(); + public AppRelease getAppRelease() { + return appRelease; + } - return ret; + public void setAppRelease(AppRelease appRelease) { + this.appRelease = appRelease; + save(); } + //endregion + public void save() { ApptentiveInternal.getInstance().saveSessionData(); } - // version history // code point store // Various prefs From 7c200e10f55651b628393e8117ef9ea31348e910 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Sat, 21 Jan 2017 12:20:52 -0800 Subject: [PATCH 023/465] Hook `FieldManager` queries against `Person` and `Device` up so they function properly now that `Person` and `Device` are not hashes. --- .../module/engagement/logic/FieldManager.java | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java index d36e624ea..790cbf21e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java @@ -148,15 +148,11 @@ public static Object doGetValue(String query) { } break; case name: - return person.getEmail(); + return person.getName(); case email: return person.getEmail(); case other: -/* -FIXME: Choose the correct Getter for each query - String key = tokens[1]; - return person.get(key); -*/ + return null; } } case device: { @@ -184,29 +180,48 @@ public static Object doGetValue(String query) { case os_api_level: return device.getOsApiLevel(); case board: + return device.getBoard(); case bootloader_version: + return device.getBootloaderVersion(); case brand: + return device.getBrand(); case build_id: + return device.getBuildId(); case build_type: + return device.getBuildType(); case carrier: + return device.getCarrier(); case cpu: + return device.getCpu(); case current_carrier: + return device.getCurrentCarrier(); case device: + return device.getDevice(); case hardware: + return null; // What is this key? case locale_country_code: + return device.getLocaleCountryCode(); case locale_language_code: + return device.getLocaleLanguageCode(); case locale_raw: + return device.getLocaleRaw(); case manufacturer: + return device.getManufacturer(); case model: + return device.getModel(); case network_type: + return device.getNetworkType(); case os_name: + return device.getOsName(); case os_build: + return device.getOsBuild(); case product: + return device.getProduct(); case radio_version: + return device.getRadioVersion(); case uuid: + return device.getUuid(); case other: - // FIXME: Choose the correct Getter for each query - //return device.opt(subQuery.name()); return null; } } From 5a74bd0e2f3f0b3c0895c3f1d2e92b4abe094ad8 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Sat, 21 Jan 2017 14:34:23 -0800 Subject: [PATCH 024/465] Refactor `CodePointStore` into `EventData` class. Make the class a member of `SessionData`. --- .../android/sdk/ApptentiveInternal.java | 12 +- .../module/engagement/EngagementModule.java | 18 ++- .../module/engagement/logic/FieldManager.java | 104 +++++++++----- .../android/sdk/storage/EventData.java | 129 ++++++++++++++++++ .../android/sdk/storage/EventRecord.java | 87 ++++++++++++ .../android/sdk/storage/SessionData.java | 10 ++ 6 files changed, 316 insertions(+), 44 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/EventRecord.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 865b8ee52..3de645599 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -32,7 +32,6 @@ import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.lifecycle.ApptentiveActivityLifecycleCallbacks; -import com.apptentive.android.sdk.model.CodePointStore; import com.apptentive.android.sdk.model.Configuration; import com.apptentive.android.sdk.model.ConversationTokenRequest; import com.apptentive.android.sdk.model.Event; @@ -83,7 +82,7 @@ public class ApptentiveInternal implements Handler.Callback { MessageManager messageManager; PayloadSendWorker payloadWorker; ApptentiveTaskManager taskManager; - CodePointStore codePointStore; + ApptentiveActivityLifecycleCallbacks lifecycleCallbacks; // These variables are initialized in Apptentive.register(), and so they are freely thereafter. If they are unexpectedly null, then if means the host app did not register Apptentive. @@ -183,7 +182,6 @@ public static ApptentiveInternal createInstance(Context context, final String ap sApptentiveInternal.payloadWorker = payloadWorker; sApptentiveInternal.interactionManager = interactionMgr; sApptentiveInternal.taskManager = worker; - sApptentiveInternal.codePointStore = new CodePointStore(); sApptentiveInternal.cachedExecutor = Executors.newCachedThreadPool(); sApptentiveInternal.apiKey = Util.trim(apptentiveApiKey); @@ -360,10 +358,6 @@ public ApptentiveTaskManager getApptentiveTaskManager() { return taskManager; } - public CodePointStore getCodePointStore() { - return codePointStore; - } - public Resources.Theme getApptentiveToolbarTheme() { return apptentiveToolbarTheme; } @@ -518,7 +512,6 @@ public void updateApptentiveInteractionTheme(Resources.Theme interactionTheme, C public boolean init() { boolean bRet = true; - codePointStore.init(); /* If Message Center feature has never been used before, don't initialize message polling thread. * Message Center feature will be seen as used, if one of the following conditions has been met: * 1. Message Center has been opened for the first time @@ -533,6 +526,7 @@ public boolean init() { sessionData = (SessionData) fileSerializer.deserialize(); if (sessionData != null) { ApptentiveLog.d("Restored existing SessionData"); + ApptentiveLog.v("Restored EventData: %s", sessionData.getEventData()); } apptentiveToolbarTheme = appContext.getResources().newTheme(); @@ -1119,6 +1113,8 @@ public boolean handleMessage(Message msg) { switch (msg.what) { case MESSAGE_SAVE_SESSION_DATA: ApptentiveLog.d("Saving SessionData"); + ApptentiveLog.v("EventData: %s", sessionData.getEventData().toString()); + File internalStorage = appContext.getFilesDir(); File sessionDataFile = new File(internalStorage, "apptentive/SessionData.ser"); try { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java index 2d518cf05..86e8d35b8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java @@ -19,7 +19,9 @@ import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; import com.apptentive.android.sdk.module.metric.MetricModule; +import com.apptentive.android.sdk.storage.SessionData; import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.util.Util; import java.util.Map; @@ -52,9 +54,13 @@ public static synchronized boolean engage(Context context, String vendor, String String eventLabel = generateEventLabel(vendor, interaction, eventName); ApptentiveLog.d("engage(%s)", eventLabel); - ApptentiveInternal.getInstance().getCodePointStore().storeCodePointForCurrentAppVersion(eventLabel); - EventManager.sendEvent(new Event(eventLabel, interactionId, data, customData, extendedData)); - return doEngage(context, eventLabel); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getEventData().storeEventForCurrentAppVersion(Util.currentTimeSeconds(), eventLabel); + sessionData.save(); + EventManager.sendEvent(new Event(eventLabel, interactionId, data, customData, extendedData)); + return doEngage(context, eventLabel); + } } catch (Exception e) { MetricModule.sendError(e, null, null); } @@ -64,7 +70,11 @@ public static synchronized boolean engage(Context context, String vendor, String public static boolean doEngage(Context context, String eventLabel) { Interaction interaction = ApptentiveInternal.getInstance().getInteractionManager().getApplicableInteraction(eventLabel); if (interaction != null) { - ApptentiveInternal.getInstance().getCodePointStore().storeInteractionForCurrentAppVersion(interaction.getId()); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getEventData().storeInteractionForCurrentAppVersion(Util.currentTimeSeconds(), interaction.getId()); + sessionData.save(); + } launchInteraction(context, interaction); return true; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java index 790cbf21e..baafd211a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java @@ -13,6 +13,7 @@ import com.apptentive.android.sdk.storage.CustomData; import com.apptentive.android.sdk.storage.Device; import com.apptentive.android.sdk.storage.Person; +import com.apptentive.android.sdk.storage.SessionData; import com.apptentive.android.sdk.storage.VersionHistoryStore; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -96,40 +97,79 @@ public static Object doGetValue(String query) { } return new Apptentive.DateTime(Util.currentTimeSeconds()); } - case interactions: + case interactions: { + String interactionId = tokens[1]; + QueryPart queryPart1 = QueryPart.parse(tokens[2]); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + switch (queryPart1) { + case invokes: + QueryPart queryPart2 = QueryPart.parse(tokens[3]); + switch (queryPart2) { + case total: // Get total for all versions of the app. + return new BigDecimal(sessionData.getEventData().getInteractionCountTotal(interactionId)); + case version_code: + Integer appVersionCode = Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext()); + return new BigDecimal(sessionData.getEventData().getInteractionCountForVersionCode(interactionId, appVersionCode)); + case version_name: + String appVersionName = Util.getAppVersionName(ApptentiveInternal.getInstance().getApplicationContext()); + return new BigDecimal(sessionData.getEventData().getInteractionCountForVersionName(interactionId, appVersionName)); + default: + break; + } + break; + case last_invoked_at: + QueryPart queryPart3 = QueryPart.parse(tokens[3]); + switch (queryPart3) { + case total: + Double lastInvoke = sessionData.getEventData().getTimeOfLastInteractionInvocation(interactionId); + if (lastInvoke != null) { + return new Apptentive.DateTime(lastInvoke); + } + default: + break; + } + default: + break; + } + } + break; + } case code_point: { - boolean isInteraction = topLevelQuery.equals(QueryPart.interactions); - String name = tokens[1]; + String eventLabel = tokens[1]; QueryPart queryPart1 = QueryPart.parse(tokens[2]); - - switch (queryPart1) { - case invokes: - QueryPart queryPart2 = QueryPart.parse(tokens[3]); - switch (queryPart2) { - case total: // Get total for all versions of the app. - return new BigDecimal(ApptentiveInternal.getInstance().getCodePointStore().getTotalInvokes(isInteraction, name)); - case version_code: - String appVersionCode = String.valueOf(Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext())); - return new BigDecimal(ApptentiveInternal.getInstance().getCodePointStore().getVersionCodeInvokes(isInteraction, name, appVersionCode)); - case version_name: - String appVersionName = Util.getAppVersionName(ApptentiveInternal.getInstance().getApplicationContext()); - return new BigDecimal(ApptentiveInternal.getInstance().getCodePointStore().getVersionNameInvokes(isInteraction, name, appVersionName)); - default: - break; - } - case last_invoked_at: - QueryPart queryPart3 = QueryPart.parse(tokens[3]); - switch (queryPart3) { - case total: - Double lastInvoke = ApptentiveInternal.getInstance().getCodePointStore().getLastInvoke(isInteraction, name); - if (lastInvoke != null) { - return new Apptentive.DateTime(lastInvoke); - } - default: - break; - } - default: - break; + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + switch (queryPart1) { + case invokes: + QueryPart queryPart2 = QueryPart.parse(tokens[3]); + switch (queryPart2) { + case total: // Get total for all versions of the app. + return new BigDecimal(sessionData.getEventData().getEventCountTotal(eventLabel)); + case version_code: + Integer appVersionCode = Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext()); + return new BigDecimal(sessionData.getEventData().getEventCountForVersionCode(eventLabel, appVersionCode)); + case version_name: + String appVersionName = Util.getAppVersionName(ApptentiveInternal.getInstance().getApplicationContext()); + return new BigDecimal(sessionData.getEventData().getEventCountForVersionName(eventLabel, appVersionName)); + default: + break; + } + break; + case last_invoked_at: + QueryPart queryPart3 = QueryPart.parse(tokens[3]); + switch (queryPart3) { + case total: + Double lastInvoke = sessionData.getEventData().getTimeOfLastEventInvocation(eventLabel); + if (lastInvoke != null) { + return new Apptentive.DateTime(lastInvoke); + } + default: + break; + } + default: + break; + } } return null; // Default Value } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java new file mode 100644 index 000000000..9c0d0e8cf --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import com.apptentive.android.sdk.ApptentiveInternal; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * Stores a record of when events and interactions were triggered, as well as the number of times per versionName or versionCode. + */ +public class EventData implements Serializable { + + private Map events; + private Map interactions; + + public EventData() { + events = new HashMap(); + interactions = new HashMap(); + } + + // FIXME: Find all usage of this and ensure they use the same timestamp for saving events and runnign interaction queries. + public synchronized void storeEventForCurrentAppVersion(double timestamp, String eventLabel) { + EventRecord eventRecord = events.get(eventLabel); + if (eventRecord == null) { + eventRecord = new EventRecord(); + events.put(eventLabel, eventRecord); + } + String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); + int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); + eventRecord.update(timestamp, versionName, versionCode); + } + + // FIXME: Find all usage of this and ensure they use the same timestamp for saving events and runnign interaction queries. + public synchronized void storeInteractionForCurrentAppVersion(double timestamp, String interactionId) { + EventRecord eventRecord = interactions.get(interactionId); + if (eventRecord == null) { + eventRecord = new EventRecord(); + interactions.put(interactionId, eventRecord); + } + String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); + int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); + eventRecord.update(timestamp, versionName, versionCode); + } + + public Long getEventCountTotal(String eventLabel) { + EventRecord eventRecord = events.get(eventLabel); + if (eventRecord == null) { + return 0L; + } + return eventRecord.getTotal(); + } + + public Long getInteractionCountTotal(String interactionId) { + EventRecord eventRecord = interactions.get(interactionId); + if (eventRecord != null) { + return eventRecord.getTotal(); + } + return 0L; + } + + public Double getTimeOfLastEventInvocation(String eventLabel) { + EventRecord eventRecord = events.get(eventLabel); + if (eventRecord != null) { + return eventRecord.getLast(); + } + return null; + } + + public Double getTimeOfLastInteractionInvocation(String interactionId) { + EventRecord eventRecord = interactions.get(interactionId); + if (eventRecord != null) { + return eventRecord.getLast(); + } + return null; + } + + public Long getEventCountForVersionCode(String eventLabel, Integer versionCode) { + EventRecord eventRecord = events.get(eventLabel); + if (eventRecord != null) { + return eventRecord.getCountForVersionCode(versionCode); + } + return 0L; + } + + public Long getInteractionCountForVersionCode(String interactionId, Integer versionCode) { + EventRecord eventRecord = interactions.get(interactionId); + if (eventRecord != null) { + return eventRecord.getCountForVersionCode(versionCode); + } + return 0L; + } + + public Long getEventCountForVersionName(String eventLabel, String versionName) { + EventRecord eventRecord = events.get(eventLabel); + if (eventRecord != null) { + return eventRecord.getCountForVersionName(versionName); + } + return 0L; + } + + public Long getInteractionCountForVersionName(String interactionId, String versionName) { + EventRecord eventRecord = interactions.get(interactionId); + if (eventRecord != null) { + return eventRecord.getCountForVersionName(versionName); + } + return 0L; + } + + + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Events: "); + for (String key : events.keySet()) { + builder.append("\n\t").append(key).append(": ").append(events.get(key).toString()); + } + builder.append("\nInteractions: "); + for (String key : interactions.keySet()) { + builder.append("\n\t").append(key).append(": ").append(interactions.get(key).toString()); + } + return builder.toString(); + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventRecord.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventRecord.java new file mode 100644 index 000000000..627943929 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventRecord.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * Stores a record of an event occurring. + */ +public class EventRecord implements Serializable { + private double last; + private long total; + private Map versionCodes; + private Map versionNames; + + public EventRecord() { + last = 0D; + total = 0L; + versionCodes = new HashMap(); + versionNames = new HashMap(); + } + + //region Getters & Setters + + public double getLast() { + return last; + } + + public long getTotal() { + return total; + } + + //endregion + + /** + * Initializes an event record or updates it with a subsequent event. + * @param timestamp The timestamp in seconds at which and Event occurred. + * @param versionName The Android versionName of the app when the event occurred. + * @param versionCode The Android versionCode of the app when the event occurred. + */ + public void update(double timestamp, String versionName, Integer versionCode) { + last = timestamp; + total++; + Long countForVersionName = versionNames.get(versionName); + if (countForVersionName == null) { + countForVersionName = 0L; + } + Long countForVersionCode = versionCodes.get(versionCode); + if (countForVersionCode == null) { + countForVersionCode = 0L; + } + versionNames.put(versionName, countForVersionName + 1); + versionCodes.put(versionCode, countForVersionCode + 1); + } + + public Long getCountForVersionName(String versionName) { + Long count = versionNames.get(versionName); + if (count != null) { + return count; + } + return 0L; + } + + public Long getCountForVersionCode(Integer versionCode) { + Long count = versionCodes.get(versionCode); + if (count != null) { + return count; + } + return 0L; + } + + @Override + public String toString() { + return "EventRecord{" + + "last=" + last + + ", total=" + total + + ", versionNames=" + versionNames + + ", versionCodes=" + versionCodes + + '}'; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index ce4776e7a..4f64f1262 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -25,10 +25,12 @@ public class SessionData implements Serializable { private Person lastSentPerson; private Sdk sdk; private AppRelease appRelease; + private EventData eventData; public SessionData() { this.device = new Device(); this.person = new Person(); + this.eventData = new EventData(); } //region Getters & Setters @@ -132,6 +134,14 @@ public void setAppRelease(AppRelease appRelease) { save(); } + public EventData getEventData() { + return eventData; + } + + public void setEventData(EventData eventData) { + this.eventData = eventData; + } + //endregion public void save() { From e412b4edbed4636fcfc0d9a2e6fb141c1a1714f6 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Sun, 22 Jan 2017 17:31:22 -0800 Subject: [PATCH 025/465] Move `lastSeenSdkVersion` from `SharedPreferences` to `SessionData` --- .../android/sdk/ApptentiveInternal.java | 7 +++---- .../android/sdk/storage/SessionData.java | 9 +++++++++ .../apptentive/android/sdk/util/Constants.java | 16 ++++++++-------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 3de645599..a05559640 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -600,8 +600,8 @@ public boolean init() { } ApptentiveLog.i("Debug mode enabled? %b", isAppDebuggable); - String lastSeenSdkVersion = prefs.getString(Constants.PREF_KEY_LAST_SEEN_SDK_VERSION, ""); - if (!lastSeenSdkVersion.equals(Constants.APPTENTIVE_SDK_VERSION)) { + String lastSeenSdkVersion = sessionData.getLastSeenSdkVersion(); + if (!TextUtils.equals(lastSeenSdkVersion, Constants.APPTENTIVE_SDK_VERSION)) { onSdkVersionChanged(appContext, lastSeenSdkVersion, Constants.APPTENTIVE_SDK_VERSION); } @@ -647,11 +647,10 @@ private void onVersionChanged(Integer previousVersionCode, Integer currentVersio // FIXME: Do this kind of thing when a session becomes active instead of at app initialization? Otherwise there is no active session to send the information to. private void onSdkVersionChanged(Context context, String previousSdkVersion, String currentSdkVersion) { ApptentiveLog.i("SDK version changed: %s => %s", previousSdkVersion, currentSdkVersion); - context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE).edit().putString(Constants.PREF_KEY_LAST_SEEN_SDK_VERSION, currentSdkVersion).apply(); - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); Sdk sdk = SdkManager.generateCurrentSdk(); taskManager.addPayload(SdkManager.getPayload(sdk)); if (sessionData != null) { + sessionData.setLastSeenSdkVersion(currentSdkVersion); sessionData.setSdk(sdk); } invalidateCaches(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index 4f64f1262..f1f425c74 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -26,6 +26,7 @@ public class SessionData implements Serializable { private Sdk sdk; private AppRelease appRelease; private EventData eventData; + private String lastSeenSdkVersion; public SessionData() { this.device = new Device(); @@ -142,6 +143,14 @@ public void setEventData(EventData eventData) { this.eventData = eventData; } + public String getLastSeenSdkVersion() { + return lastSeenSdkVersion; + } + + public void setLastSeenSdkVersion(String lastSeenSdkVersion) { + this.lastSeenSdkVersion = lastSeenSdkVersion; + } + //endregion public void save() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index c0c86dff7..350fae37e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -24,14 +24,6 @@ public class Constants { public static final String EXAMPLE_API_KEY_VALUE = "YOUR_APPTENTIVE_API_KEY"; // Session Data - public static final String PREF_KEY_DEVICE = "device"; - public static final String PREF_KEY_DEVICE_DATA = "deviceData"; - public static final String PREF_KEY_DEVICE_INTEGRATION_CONFIG = "integrationConfig"; - public static final String PREF_KEY_SDK = "sdk"; - public static final String PREF_KEY_APP_RELEASE = "app_release"; - public static final String PREF_KEY_PERSON = "person"; - public static final String PREF_KEY_PERSON_DATA = "personData"; - public static final String PREF_KEY_LAST_SEEN_SDK_VERSION = "lastSeenSdkVersion"; public static final String PREF_KEY_MESSAGE_CENTER_FEATURE_USED = "messageCenterFeatureUsed"; public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE = "messageCenterPendingComposingMessage"; public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS = "messageCenterPendingComposingAttachments"; @@ -106,6 +98,14 @@ public class Constants { public static final String PREF_KEY_PERSON_ID = "personId"; public static final String PREF_KEY_PERSON_EMAIL = "personEmail"; public static final String PREF_KEY_PERSON_NAME = "personName"; + public static final String PREF_KEY_DEVICE = "device"; + public static final String PREF_KEY_DEVICE_DATA = "deviceData"; + public static final String PREF_KEY_DEVICE_INTEGRATION_CONFIG = "integrationConfig"; + public static final String PREF_KEY_SDK = "sdk"; + public static final String PREF_KEY_APP_RELEASE = "app_release"; + public static final String PREF_KEY_PERSON = "person"; + public static final String PREF_KEY_PERSON_DATA = "personData"; + public static final String PREF_KEY_LAST_SEEN_SDK_VERSION = "lastSeenSdkVersion"; From 03c80810e93c357a1b824cabe48898e0c5c032b0 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Sun, 22 Jan 2017 17:46:15 -0800 Subject: [PATCH 026/465] Move flag for whether Message Center has been used before into `SessionData`. --- .../apptentive/android/sdk/ApptentiveInternal.java | 11 +++++++---- .../sdk/module/messagecenter/MessageManager.java | 9 ++++----- .../apptentive/android/sdk/storage/SessionData.java | 11 +++++++++++ .../com/apptentive/android/sdk/util/Constants.java | 2 +- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index a05559640..ee6c00bd0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -518,16 +518,19 @@ public boolean init() { * 2. The first Push is received which would open Message Center * 3. An unreadMessageCountListener() is set up */ - boolean featureEverUsed = prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_FEATURE_USED, false); - if (featureEverUsed) { - messageManager.init(); - } + FileSerializer fileSerializer = new FileSerializer(new File(appContext.getFilesDir(), "apptentive/SessionData.ser")); sessionData = (SessionData) fileSerializer.deserialize(); if (sessionData != null) { ApptentiveLog.d("Restored existing SessionData"); ApptentiveLog.v("Restored EventData: %s", sessionData.getEventData()); + // FIXME: Move this to whereever the sessions first comes online? + boolean featureEverUsed = sessionData != null && sessionData.isMessageCenterFeatureUsed(); + if (featureEverUsed) { + messageManager.init(); + } } + apptentiveToolbarTheme = appContext.getResources().newTheme(); boolean apptentiveDebug = false; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index c1967a1ab..31dfbc843 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -29,7 +29,7 @@ import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; import com.apptentive.android.sdk.module.metric.MetricModule; import com.apptentive.android.sdk.storage.MessageStore; -import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.storage.SessionData; import com.apptentive.android.sdk.util.Util; import org.json.JSONArray; @@ -109,10 +109,9 @@ public void handleMessage(android.os.Message msg) { /* Set SharePreference to indicate Message Center feature is desired. It will always be checked * during Apptentive initialization. */ - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - boolean featureEverUsed = prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_FEATURE_USED, false); - if (!featureEverUsed) { - prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_FEATURE_USED, true).apply(); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.setMessageCenterFeatureUsed(true); } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index f1f425c74..e53530bbf 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -27,6 +27,7 @@ public class SessionData implements Serializable { private AppRelease appRelease; private EventData eventData; private String lastSeenSdkVersion; + private boolean messageCenterFeatureUsed; public SessionData() { this.device = new Device(); @@ -151,8 +152,18 @@ public void setLastSeenSdkVersion(String lastSeenSdkVersion) { this.lastSeenSdkVersion = lastSeenSdkVersion; } + public boolean isMessageCenterFeatureUsed() { + return messageCenterFeatureUsed; + } + + public void setMessageCenterFeatureUsed(boolean messageCenterFeatureUsed) { + this.messageCenterFeatureUsed = messageCenterFeatureUsed; + save(); + } + //endregion + // TODO: Only save when a value has changed. public void save() { ApptentiveInternal.getInstance().saveSessionData(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 350fae37e..6cc7b875d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -24,7 +24,6 @@ public class Constants { public static final String EXAMPLE_API_KEY_VALUE = "YOUR_APPTENTIVE_API_KEY"; // Session Data - public static final String PREF_KEY_MESSAGE_CENTER_FEATURE_USED = "messageCenterFeatureUsed"; public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE = "messageCenterPendingComposingMessage"; public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS = "messageCenterPendingComposingAttachments"; public static final String PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT = "messageCenterServerErrorLastAttempt"; @@ -106,6 +105,7 @@ public class Constants { public static final String PREF_KEY_PERSON = "person"; public static final String PREF_KEY_PERSON_DATA = "personData"; public static final String PREF_KEY_LAST_SEEN_SDK_VERSION = "lastSeenSdkVersion"; + public static final String PREF_KEY_MESSAGE_CENTER_FEATURE_USED = "messageCenterFeatureUsed"; From e43654e1b56261a5cfef6aeca186650d8bd13f46 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Sun, 22 Jan 2017 17:48:53 -0800 Subject: [PATCH 027/465] Move existing `VersionHistoryStore` into legacy package. --- .../com/apptentive/android/sdk/ApptentiveInternal.java | 4 ++-- .../android/sdk/module/engagement/logic/FieldManager.java | 2 +- .../sdk/storage/{ => legacy}/VersionHistoryEntry.java | 2 +- .../sdk/storage/{ => legacy}/VersionHistoryStore.java | 2 +- .../storage/{ => legacy}/VersionHistoryStoreMigrator.java | 2 +- .../java/com/apptentive/android/sdk/util/Constants.java | 8 ++++---- 6 files changed, 10 insertions(+), 10 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/storage/{ => legacy}/VersionHistoryEntry.java (96%) rename apptentive/src/main/java/com/apptentive/android/sdk/storage/{ => legacy}/VersionHistoryStore.java (99%) rename apptentive/src/main/java/com/apptentive/android/sdk/storage/{ => legacy}/VersionHistoryStoreMigrator.java (97%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index ee6c00bd0..7b82d1600 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -53,8 +53,8 @@ import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.storage.SessionData; -import com.apptentive.android.sdk.storage.VersionHistoryEntry; -import com.apptentive.android.sdk.storage.VersionHistoryStore; +import com.apptentive.android.sdk.storage.legacy.VersionHistoryEntry; +import com.apptentive.android.sdk.storage.legacy.VersionHistoryStore; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java index baafd211a..28a63db98 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java @@ -14,7 +14,7 @@ import com.apptentive.android.sdk.storage.Device; import com.apptentive.android.sdk.storage.Person; import com.apptentive.android.sdk.storage.SessionData; -import com.apptentive.android.sdk.storage.VersionHistoryStore; +import com.apptentive.android.sdk.storage.legacy.VersionHistoryStore; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistoryEntry.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryEntry.java similarity index 96% rename from apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistoryEntry.java rename to apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryEntry.java index bb63cd3ac..45f9c9a56 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistoryEntry.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryEntry.java @@ -4,7 +4,7 @@ * under which redistribution and use of this file is permitted. */ -package com.apptentive.android.sdk.storage; +package com.apptentive.android.sdk.storage.legacy; import org.json.JSONException; import org.json.JSONObject; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistoryStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStore.java similarity index 99% rename from apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistoryStore.java rename to apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStore.java index f74811c0e..3a92f32f8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistoryStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStore.java @@ -4,7 +4,7 @@ * under which redistribution and use of this file is permitted. */ -package com.apptentive.android.sdk.storage; +package com.apptentive.android.sdk.storage.legacy; import android.content.SharedPreferences; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistoryStoreMigrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStoreMigrator.java similarity index 97% rename from apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistoryStoreMigrator.java rename to apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStoreMigrator.java index fdc8c70a0..d8ae0b4cf 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistoryStoreMigrator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStoreMigrator.java @@ -4,7 +4,7 @@ * under which redistribution and use of this file is permitted. */ -package com.apptentive.android.sdk.storage; +package com.apptentive.android.sdk.storage.legacy; import android.content.SharedPreferences; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 6cc7b875d..2ee2f3958 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -30,10 +30,6 @@ public class Constants { public static final String PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE = "messageCenterWhoCardSet"; public static final String PREF_KEY_APP_CONFIG_PREFIX = "appConfiguration."; public static final String PREF_KEY_APP_CONFIG_JSON = PREF_KEY_APP_CONFIG_PREFIX+"json"; - public static final String PREF_KEY_VERSION_HISTORY = "versionHistory"; - public static final String PREF_KEY_VERSION_HISTORY_V2 = "versionHistoryV2"; - // Boolean true if migration from v1 to V2 has occurred. - public static final String PREF_KEY_VERSION_HISTORY_V2_MIGRATED = "versionHistoryV2Migrated"; // Engagement @@ -106,6 +102,10 @@ public class Constants { public static final String PREF_KEY_PERSON_DATA = "personData"; public static final String PREF_KEY_LAST_SEEN_SDK_VERSION = "lastSeenSdkVersion"; public static final String PREF_KEY_MESSAGE_CENTER_FEATURE_USED = "messageCenterFeatureUsed"; + public static final String PREF_KEY_VERSION_HISTORY = "versionHistory"; + public static final String PREF_KEY_VERSION_HISTORY_V2 = "versionHistoryV2"; + // Boolean true if migration from v1 to V2 has occurred. + public static final String PREF_KEY_VERSION_HISTORY_V2_MIGRATED = "versionHistoryV2Migrated"; From d7bd71a063ac51e987a30a672defbf0e1a49849e Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 23 Jan 2017 09:04:54 -0800 Subject: [PATCH 028/465] Create new `Serializable` `VersionHistory` to replace JSON backed `VersionHistoryStore`. Migrate FieldManager instances to use this new version. Leave the old one around in a legacy package for now so we can use it for migration. --- .../android/sdk/ApptentiveInternal.java | 27 ++-- .../module/engagement/logic/FieldManager.java | 140 +++++++++--------- .../android/sdk/storage/SessionData.java | 9 ++ .../android/sdk/storage/VersionHistory.java | 117 +++++++++++++++ .../sdk/storage/VersionHistoryItem.java | 50 +++++++ 5 files changed, 261 insertions(+), 82 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistoryItem.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 7b82d1600..1239eaed4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -53,8 +53,7 @@ import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.storage.SessionData; -import com.apptentive.android.sdk.storage.legacy.VersionHistoryEntry; -import com.apptentive.android.sdk.storage.legacy.VersionHistoryStore; +import com.apptentive.android.sdk.storage.VersionHistoryItem; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -519,6 +518,9 @@ public boolean init() { * 3. An unreadMessageCountListener() is set up */ + VersionHistoryItem lastVersionItemSeen = null; + String lastSeenSdkVersion = null; + FileSerializer fileSerializer = new FileSerializer(new File(appContext.getFilesDir(), "apptentive/SessionData.ser")); sessionData = (SessionData) fileSerializer.deserialize(); if (sessionData != null) { @@ -529,6 +531,8 @@ public boolean init() { if (featureEverUsed) { messageManager.init(); } + lastVersionItemSeen = sessionData.getVersionHistory().getLastVersionSeen(); + lastSeenSdkVersion = sessionData.getLastSeenSdkVersion(); } apptentiveToolbarTheme = appContext.getResources().newTheme(); @@ -558,16 +562,16 @@ public boolean init() { currentVersionCode = appRelease.getVersionCode(); currentVersionName = appRelease.getVersionName(); - VersionHistoryEntry lastVersionEntrySeen = VersionHistoryStore.getLastVersionSeen(); - if (lastVersionEntrySeen == null) { + + if (lastVersionItemSeen == null) { onVersionChanged(null, currentVersionCode, null, currentVersionName, appRelease); } else { - int lastSeenVersionCode = lastVersionEntrySeen.getVersionCode(); + int lastSeenVersionCode = lastVersionItemSeen.getVersionCode(); Apptentive.Version lastSeenVersionNameVersion = new Apptentive.Version(); - lastSeenVersionNameVersion.setVersion(lastVersionEntrySeen.getVersionName()); + lastSeenVersionNameVersion.setVersion(lastVersionItemSeen.getVersionName()); if (!(currentVersionCode == lastSeenVersionCode) || !currentVersionName.equals(lastSeenVersionNameVersion.getVersion())) { - onVersionChanged(lastVersionEntrySeen.getVersionCode(), currentVersionCode, lastVersionEntrySeen.getVersionName(), currentVersionName, appRelease); + onVersionChanged(lastVersionItemSeen.getVersionCode(), currentVersionCode, lastVersionItemSeen.getVersionName(), currentVersionName, appRelease); } } defaultAppDisplayName = packageManager.getApplicationLabel(packageManager.getApplicationInfo(packageInfo.packageName, 0)).toString(); @@ -603,7 +607,6 @@ public boolean init() { } ApptentiveLog.i("Debug mode enabled? %b", isAppDebuggable); - String lastSeenSdkVersion = sessionData.getLastSeenSdkVersion(); if (!TextUtils.equals(lastSeenSdkVersion, Constants.APPTENTIVE_SDK_VERSION)) { onSdkVersionChanged(appContext, lastSeenSdkVersion, Constants.APPTENTIVE_SDK_VERSION); } @@ -636,9 +639,12 @@ public boolean init() { // FIXME: Do this kind of thing when a session becomes active instead of at app initialization? Otherwise there is no active session to send the information to. private void onVersionChanged(Integer previousVersionCode, Integer currentVersionCode, String previousVersionName, String currentVersionName, AppRelease currentAppRelease) { ApptentiveLog.i("Version changed: Name: %s => %s, Code: %d => %d", previousVersionName, currentVersionName, previousVersionCode, currentVersionCode); - VersionHistoryStore.updateVersionHistory(currentVersionCode, currentVersionName); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.getVersionHistory().updateVersionHistory(Util.currentTimeSeconds(), currentVersionCode, currentVersionName); + sessionData.save(); + } if (previousVersionCode != null) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); taskManager.addPayload(AppReleaseManager.getPayload(currentAppRelease)); if (sessionData != null) { sessionData.setAppRelease(currentAppRelease); @@ -1056,7 +1062,6 @@ public Map getAndClearCustomData() { public void resetSdkState() { prefs.edit().clear().apply(); taskManager.reset(appContext); - VersionHistoryStore.clear(); } public void notifyInteractionUpdated(boolean successful) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java index 28a63db98..a5bf7ccf7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java @@ -32,6 +32,10 @@ public static Comparable getValue(String query) { } public static Object doGetValue(String query) { + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData == null) { + return null; + } query = query.trim(); String[] tokens = query.split("/"); QueryPart topLevelQuery = QueryPart.parse(tokens[0]); @@ -77,9 +81,9 @@ public static Object doGetValue(String query) { QueryPart subQuery = QueryPart.parse(tokens[1]); switch (subQuery) { case version_code: - return VersionHistoryStore.isUpdate(VersionHistoryStore.Selector.version_code); + return sessionData.getVersionHistory().isUpdateForVersionCode(); case version_name: - return VersionHistoryStore.isUpdate(VersionHistoryStore.Selector.version_name); + return sessionData.getVersionHistory().isUpdateForVersionName(); default: break; } @@ -89,93 +93,87 @@ public static Object doGetValue(String query) { QueryPart subQuery = QueryPart.parse(tokens[1]); switch (subQuery) { case total: - return VersionHistoryStore.getTimeAtInstall(VersionHistoryStore.Selector.total); + return sessionData.getVersionHistory().getTimeAtInstallTotal(); case version_code: - return VersionHistoryStore.getTimeAtInstall(VersionHistoryStore.Selector.version_code); + return sessionData.getVersionHistory().getTimeAtInstallForCurrentVersionCode(); case version_name: - return VersionHistoryStore.getTimeAtInstall(VersionHistoryStore.Selector.version_name); + return sessionData.getVersionHistory().getTimeAtInstallForCurrentVersionName(); } return new Apptentive.DateTime(Util.currentTimeSeconds()); } case interactions: { String interactionId = tokens[1]; QueryPart queryPart1 = QueryPart.parse(tokens[2]); - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - switch (queryPart1) { - case invokes: - QueryPart queryPart2 = QueryPart.parse(tokens[3]); - switch (queryPart2) { - case total: // Get total for all versions of the app. - return new BigDecimal(sessionData.getEventData().getInteractionCountTotal(interactionId)); - case version_code: - Integer appVersionCode = Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext()); - return new BigDecimal(sessionData.getEventData().getInteractionCountForVersionCode(interactionId, appVersionCode)); - case version_name: - String appVersionName = Util.getAppVersionName(ApptentiveInternal.getInstance().getApplicationContext()); - return new BigDecimal(sessionData.getEventData().getInteractionCountForVersionName(interactionId, appVersionName)); - default: - break; - } - break; - case last_invoked_at: - QueryPart queryPart3 = QueryPart.parse(tokens[3]); - switch (queryPart3) { - case total: - Double lastInvoke = sessionData.getEventData().getTimeOfLastInteractionInvocation(interactionId); - if (lastInvoke != null) { - return new Apptentive.DateTime(lastInvoke); - } - default: - break; - } - default: - break; - } + switch (queryPart1) { + case invokes: + QueryPart queryPart2 = QueryPart.parse(tokens[3]); + switch (queryPart2) { + case total: // Get total for all versions of the app. + return new BigDecimal(sessionData.getEventData().getInteractionCountTotal(interactionId)); + case version_code: + Integer appVersionCode = Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext()); + return new BigDecimal(sessionData.getEventData().getInteractionCountForVersionCode(interactionId, appVersionCode)); + case version_name: + String appVersionName = Util.getAppVersionName(ApptentiveInternal.getInstance().getApplicationContext()); + return new BigDecimal(sessionData.getEventData().getInteractionCountForVersionName(interactionId, appVersionName)); + default: + break; + } + break; + case last_invoked_at: + QueryPart queryPart3 = QueryPart.parse(tokens[3]); + switch (queryPart3) { + case total: + Double lastInvoke = sessionData.getEventData().getTimeOfLastInteractionInvocation(interactionId); + if (lastInvoke != null) { + return new Apptentive.DateTime(lastInvoke); + } + default: + break; + } + default: + break; } break; } case code_point: { String eventLabel = tokens[1]; QueryPart queryPart1 = QueryPart.parse(tokens[2]); - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - switch (queryPart1) { - case invokes: - QueryPart queryPart2 = QueryPart.parse(tokens[3]); - switch (queryPart2) { - case total: // Get total for all versions of the app. - return new BigDecimal(sessionData.getEventData().getEventCountTotal(eventLabel)); - case version_code: - Integer appVersionCode = Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext()); - return new BigDecimal(sessionData.getEventData().getEventCountForVersionCode(eventLabel, appVersionCode)); - case version_name: - String appVersionName = Util.getAppVersionName(ApptentiveInternal.getInstance().getApplicationContext()); - return new BigDecimal(sessionData.getEventData().getEventCountForVersionName(eventLabel, appVersionName)); - default: - break; - } - break; - case last_invoked_at: - QueryPart queryPart3 = QueryPart.parse(tokens[3]); - switch (queryPart3) { - case total: - Double lastInvoke = sessionData.getEventData().getTimeOfLastEventInvocation(eventLabel); - if (lastInvoke != null) { - return new Apptentive.DateTime(lastInvoke); - } - default: - break; - } - default: - break; - } + switch (queryPart1) { + case invokes: + QueryPart queryPart2 = QueryPart.parse(tokens[3]); + switch (queryPart2) { + case total: // Get total for all versions of the app. + return new BigDecimal(sessionData.getEventData().getEventCountTotal(eventLabel)); + case version_code: + Integer appVersionCode = Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext()); + return new BigDecimal(sessionData.getEventData().getEventCountForVersionCode(eventLabel, appVersionCode)); + case version_name: + String appVersionName = Util.getAppVersionName(ApptentiveInternal.getInstance().getApplicationContext()); + return new BigDecimal(sessionData.getEventData().getEventCountForVersionName(eventLabel, appVersionName)); + default: + break; + } + break; + case last_invoked_at: + QueryPart queryPart3 = QueryPart.parse(tokens[3]); + switch (queryPart3) { + case total: + Double lastInvoke = sessionData.getEventData().getTimeOfLastEventInvocation(eventLabel); + if (lastInvoke != null) { + return new Apptentive.DateTime(lastInvoke); + } + default: + break; + } + default: + break; } return null; // Default Value } case person: { QueryPart subQuery = QueryPart.parse(tokens[1]); - Person person = ApptentiveInternal.getInstance().getSessionData().getPerson(); + Person person = sessionData.getPerson(); if (person == null) { return null; } @@ -197,7 +195,7 @@ public static Object doGetValue(String query) { } case device: { QueryPart subQuery = QueryPart.parse(tokens[1]); - Device device = ApptentiveInternal.getInstance().getSessionData().getDevice(); + Device device = sessionData.getDevice(); if (device == null) { return null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index e53530bbf..a7bcfde1a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -28,6 +28,7 @@ public class SessionData implements Serializable { private EventData eventData; private String lastSeenSdkVersion; private boolean messageCenterFeatureUsed; + private VersionHistory versionHistory; public SessionData() { this.device = new Device(); @@ -161,6 +162,14 @@ public void setMessageCenterFeatureUsed(boolean messageCenterFeatureUsed) { save(); } + public VersionHistory getVersionHistory() { + return versionHistory; + } + + public void setVersionHistory(VersionHistory versionHistory) { + this.versionHistory = versionHistory; + } + //endregion // TODO: Only save when a value has changed. diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java new file mode 100644 index 000000000..ba19abc47 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import com.apptentive.android.sdk.Apptentive; +import com.apptentive.android.sdk.ApptentiveInternal; +import com.apptentive.android.sdk.util.Util; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class VersionHistory implements Serializable { + + /** + * An ordered list of version history. Older versions are first, new versions are added to the end. + */ + private List versionHistoryItems; + + public VersionHistory() { + this.versionHistoryItems = new ArrayList<>(); + } + + public void updateVersionHistory(double timestamp, Integer newVersionCode, String newVersionName) { + boolean exists = false; + for (VersionHistoryItem item : versionHistoryItems) { + if (item.getVersionCode() == newVersionCode && item.getVersionName().equals(newVersionName)) { + exists = true; + break; + } + } + if (!exists) { + VersionHistoryItem newVersionHistoryItem = new VersionHistoryItem(timestamp, newVersionCode, newVersionName); + versionHistoryItems.add(newVersionHistoryItem); + } + } + + /** + * Returns the timestamp at the first install of this app that Apptentive was aware of. + */ + public Apptentive.DateTime getTimeAtInstallTotal() { + // Simply return the first item's timestamp, if there is one. + if (versionHistoryItems.size() > 0) { + return new Apptentive.DateTime(versionHistoryItems.get(0).getTimestamp()); + } + return new Apptentive.DateTime(Util.currentTimeSeconds()); + } + + /** + * Returns the timestamp at the first install of the current versionCode of this app that Apptentive was aware of. + */ + public Apptentive.DateTime getTimeAtInstallForCurrentVersionCode() { + for (VersionHistoryItem item : versionHistoryItems) { + if (item.getVersionCode() == Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext())) { + return new Apptentive.DateTime(item.getTimestamp()); + } + } + return new Apptentive.DateTime(Util.currentTimeSeconds()); + } + + /** + * Returns the timestamp at the first install of the current versionName of this app that Apptentive was aware of. + */ + public Apptentive.DateTime getTimeAtInstallForCurrentVersionName() { + for (VersionHistoryItem item : versionHistoryItems) { + Apptentive.Version entryVersionName = new Apptentive.Version(); + Apptentive.Version currentVersionName = new Apptentive.Version(); + entryVersionName.setVersion(item.getVersionName()); + currentVersionName.setVersion(Util.getAppVersionName(ApptentiveInternal.getInstance().getApplicationContext())); + if (entryVersionName.equals(currentVersionName)) { + return new Apptentive.DateTime(item.getTimestamp()); + } + } + return new Apptentive.DateTime(Util.currentTimeSeconds()); + } + + /** + * Returns true if the current versionCode is not the first version or build that we have seen. Basically, it just + * looks for two or more versionCodes. + * + * @return True if this is not the first versionCode of the app we've seen. + */ + public boolean isUpdateForVersionCode() { + Set uniques = new HashSet(); + for (VersionHistoryItem item : versionHistoryItems) { + uniques.add(item.getVersionCode()); + } + return uniques.size() > 1; + } + + /** + * Returns true if the current versionName is not the first version or build that we have seen. Basically, it just + * looks for two or more versionNames. + * + * @return True if this is not the first versionName of the app we've seen. + */ + public boolean isUpdateForVersionName() { + Set uniques = new HashSet(); + for (VersionHistoryItem item : versionHistoryItems) { + uniques.add(item.getVersionName()); + } + return uniques.size() > 1; + } + + public VersionHistoryItem getLastVersionSeen() { + if (!versionHistoryItems.isEmpty()) { + return versionHistoryItems.get(versionHistoryItems.size() - 1); + } + return null; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistoryItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistoryItem.java new file mode 100644 index 000000000..79f733197 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistoryItem.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import java.io.Serializable; + +public class VersionHistoryItem implements Serializable { + + private double timestamp; + private int versionCode; + private String versionName; + + public VersionHistoryItem(double timestamp, int versionCode, String versionName) { + this.timestamp = timestamp; + this.versionCode = versionCode; + this.versionName = versionName; + } + + //region Getters & Setters + + public int getVersionCode() { + return versionCode; + } + + public void setVersionCode(int versionCode) { + this.versionCode = versionCode; + } + + public String getVersionName() { + return versionName; + } + + public void setVersionName(String versionName) { + this.versionName = versionName; + } + + public double getTimestamp() { + return timestamp; + } + + public void setTimestamp(double timestamp) { + this.timestamp = timestamp; + } + + //endregion +} From 2430b9566460d5f77b0ca6a25fbf19c48b5529f5 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 23 Jan 2017 15:22:17 -0800 Subject: [PATCH 029/465] Move `CodePointStore` to legacy package It might be useful to keep it around for migration. --- .../android/sdk/{model => storage/legacy}/CodePointStore.java | 2 +- .../main/java/com/apptentive/android/sdk/util/Constants.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/{model => storage/legacy}/CodePointStore.java (99%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CodePointStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/CodePointStore.java similarity index 99% rename from apptentive/src/main/java/com/apptentive/android/sdk/model/CodePointStore.java rename to apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/CodePointStore.java index acf4ffb11..01e7b7ac4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CodePointStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/CodePointStore.java @@ -4,7 +4,7 @@ * under which redistribution and use of this file is permitted. */ -package com.apptentive.android.sdk.model; +package com.apptentive.android.sdk.storage.legacy; import android.content.SharedPreferences; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 2ee2f3958..ced99b531 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -36,7 +36,6 @@ public class Constants { public static final String PREF_KEY_INTERACTIONS = "interactions"; public static final String PREF_KEY_TARGETS = "targets"; public static final String PREF_KEY_INTERACTIONS_PAYLOAD_CACHE_EXPIRATION = "interactionsCacheExpiration"; - public static final String PREF_KEY_CODE_POINT_STORE = "codePointStore"; // Used to turn off Interaction polling so that contrived payloads can be manually tested. public static final String PREF_KEY_POLL_FOR_INTERACTIONS = "pollForInteractions"; @@ -102,6 +101,7 @@ public class Constants { public static final String PREF_KEY_PERSON_DATA = "personData"; public static final String PREF_KEY_LAST_SEEN_SDK_VERSION = "lastSeenSdkVersion"; public static final String PREF_KEY_MESSAGE_CENTER_FEATURE_USED = "messageCenterFeatureUsed"; + public static final String PREF_KEY_CODE_POINT_STORE = "codePointStore"; public static final String PREF_KEY_VERSION_HISTORY = "versionHistory"; public static final String PREF_KEY_VERSION_HISTORY_V2 = "versionHistoryV2"; // Boolean true if migration from v1 to V2 has occurred. From e2bab4a0b0c63f3fd64445350c02baf0db37569f Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 23 Jan 2017 16:29:36 -0800 Subject: [PATCH 030/465] Move pending MC message into `SessionData` --- .../fragment/MessageCenterFragment.java | 50 ++++++++++--------- .../android/sdk/storage/SessionData.java | 22 +++++++- .../android/sdk/util/Constants.java | 4 +- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index 371eac067..982bcacb5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -57,7 +57,7 @@ import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerView; import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerViewAdapter; import com.apptentive.android.sdk.module.messagecenter.view.holder.MessageComposerHolder; -import com.apptentive.android.sdk.module.metric.MetricModule; +import com.apptentive.android.sdk.storage.SessionData; import com.apptentive.android.sdk.util.AnimationUtil; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -434,10 +434,12 @@ else if (pendingWhoCardName != null || pendingWhoCardEmail != null || pendingWho // Retrieve any saved attachments final SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - if (prefs.contains(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS)) { + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null && sessionData.getMessageCenterPendingAttachments() != null) { + String pendingAttachmentsString = sessionData.getMessageCenterPendingAttachments(); JSONArray savedAttachmentsJsonArray = null; try { - savedAttachmentsJsonArray = new JSONArray(prefs.getString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS, "")); + savedAttachmentsJsonArray = new JSONArray(pendingAttachmentsString); } catch (JSONException e) { e.printStackTrace(); } @@ -459,8 +461,7 @@ else if (pendingWhoCardName != null || pendingWhoCardEmail != null || pendingWho } } // Stored pending attachments have been restored, remove it from the persistent storage - SharedPreferences.Editor editor = prefs.edit(); - editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS).apply(); + sessionData.setMessageCenterPendingAttachments(null); } updateMessageSentStates(); } @@ -742,6 +743,7 @@ public void onComposingViewCreated(MessageComposerHolder composer, final EditTex this.composer = composer; this.composerEditText = composerEditText; + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); // Restore composing text editing state, such as cursor position, after rotation if (composingViewSavedState != null) { @@ -749,18 +751,19 @@ public void onComposingViewCreated(MessageComposerHolder composer, final EditTex this.composerEditText.onRestoreInstanceState(composingViewSavedState); } composingViewSavedState = null; - SharedPreferences.Editor editor = prefs.edit(); - editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE).apply(); + // Stored pending composing text has been restored from the saved state, so it's not needed here anymore + if (sessionData != null) { + sessionData.setMessageCenterPendingMessage(null); + } } // Restore composing text - if (prefs.contains(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE)) { - String messageText = prefs.getString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE, null); + else if (sessionData != null && !TextUtils.isEmpty(sessionData.getMessageCenterPendingMessage())) { + String messageText = sessionData.getMessageCenterPendingMessage(); if (messageText != null && this.composerEditText != null) { this.composerEditText.setText(messageText); } // Stored pending composing text has been restored, remove it from the persistent storage - SharedPreferences.Editor editor = prefs.edit(); - editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE).apply(); + sessionData.setMessageCenterPendingMessage(null); } setAttachmentsInComposer(pendingAttachments); @@ -1010,12 +1013,15 @@ public void savePendingComposingMessage() { Editable content = getPendingComposingContent(); SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); SharedPreferences.Editor editor = prefs.edit(); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData == null) { + return; + } if (content != null) { - editor.putString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE, content.toString().trim()); + sessionData.setMessageCenterPendingMessage(content.toString().trim()); } else { - editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE); + sessionData.setMessageCenterPendingMessage(null); } - JSONArray pendingAttachmentsJsonArray = new JSONArray(); // Save pending attachment for (ImageItem pendingAttachment : pendingAttachments) { @@ -1023,10 +1029,9 @@ public void savePendingComposingMessage() { } if (pendingAttachmentsJsonArray.length() > 0) { - editor.putString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS, pendingAttachmentsJsonArray.toString()); + sessionData.setMessageCenterPendingAttachments(pendingAttachmentsJsonArray.toString()); } else { - editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS); - editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS); + sessionData.setMessageCenterPendingAttachments(null); } editor.apply(); } @@ -1035,12 +1040,11 @@ public void savePendingComposingMessage() { * will clear the pending composing message previously saved in shared preference */ public void clearPendingComposingMessage() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - prefs.edit() - .remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE) - .remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS) - .remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS) - .apply(); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + sessionData.setMessageCenterPendingMessage(null); + sessionData.setMessageCenterPendingAttachments(null); + } } private Parcelable saveEditTextInstanceState() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index a7bcfde1a..aad8c803b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -29,6 +29,8 @@ public class SessionData implements Serializable { private String lastSeenSdkVersion; private boolean messageCenterFeatureUsed; private VersionHistory versionHistory; + private String messageCenterPendingMessage; + private String messageCenterPendingAttachments; public SessionData() { this.device = new Device(); @@ -170,7 +172,25 @@ public void setVersionHistory(VersionHistory versionHistory) { this.versionHistory = versionHistory; } - //endregion + public String getMessageCenterPendingMessage() { + return messageCenterPendingMessage; + } + + public void setMessageCenterPendingMessage(String messageCenterPendingMessage) { + this.messageCenterPendingMessage = messageCenterPendingMessage; + save(); + } + + public String getMessageCenterPendingAttachments() { + return messageCenterPendingAttachments; + } + + public void setMessageCenterPendingAttachments(String messageCenterPendingAttachments) { + this.messageCenterPendingAttachments = messageCenterPendingAttachments; + save(); + } + +//endregion // TODO: Only save when a value has changed. public void save() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index ced99b531..bf1b8ce39 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -24,8 +24,6 @@ public class Constants { public static final String EXAMPLE_API_KEY_VALUE = "YOUR_APPTENTIVE_API_KEY"; // Session Data - public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE = "messageCenterPendingComposingMessage"; - public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS = "messageCenterPendingComposingAttachments"; public static final String PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT = "messageCenterServerErrorLastAttempt"; public static final String PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE = "messageCenterWhoCardSet"; public static final String PREF_KEY_APP_CONFIG_PREFIX = "appConfiguration."; @@ -106,6 +104,8 @@ public class Constants { public static final String PREF_KEY_VERSION_HISTORY_V2 = "versionHistoryV2"; // Boolean true if migration from v1 to V2 has occurred. public static final String PREF_KEY_VERSION_HISTORY_V2_MIGRATED = "versionHistoryV2Migrated"; + public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE = "messageCenterPendingComposingMessage"; + public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS = "messageCenterPendingComposingAttachments"; From 191449539825c902f43884ec67134703bfba9915 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 23 Jan 2017 16:30:04 -0800 Subject: [PATCH 031/465] Initialize `VersionHistory` in `SessionData` constructor. --- .../java/com/apptentive/android/sdk/storage/SessionData.java | 1 + 1 file changed, 1 insertion(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index aad8c803b..db57e5cd7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -36,6 +36,7 @@ public SessionData() { this.device = new Device(); this.person = new Person(); this.eventData = new EventData(); + this.versionHistory = new VersionHistory(); } //region Getters & Setters From d92e92cc99e4f1c781c1713bc68dd978bc5171c0 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 23 Jan 2017 16:50:09 -0800 Subject: [PATCH 032/465] Rename `prefs` to `globalSharedPrefs` --- .../android/sdk/ApptentiveInternal.java | 10 +++++----- .../android/sdk/comm/ApptentiveClient.java | 2 +- .../android/sdk/model/Configuration.java | 4 ++-- .../interaction/InteractionManager.java | 20 +++++++++---------- .../fragment/MessageCenterFragment.java | 9 ++++----- .../sdk/storage/legacy/CodePointStore.java | 6 +++--- .../storage/legacy/VersionHistoryStore.java | 6 +++--- .../legacy/VersionHistoryStoreMigrator.java | 2 +- .../android/sdk/util/Constants.java | 5 +++-- 9 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 1239eaed4..4ddcba1c9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -91,7 +91,7 @@ public class ApptentiveInternal implements Handler.Callback { boolean appIsInForeground; boolean isAppDebuggable; - SharedPreferences prefs; + SharedPreferences globalSharedPrefs; String apiKey; String personId; String androidId; @@ -170,7 +170,7 @@ public static ApptentiveInternal createInstance(Context context, final String ap sApptentiveInternal = new ApptentiveInternal(); isApptentiveInitialized.set(false); sApptentiveInternal.appContext = context.getApplicationContext(); - sApptentiveInternal.prefs = sApptentiveInternal.appContext.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); + sApptentiveInternal.globalSharedPrefs = sApptentiveInternal.appContext.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); MessageManager msgManager = new MessageManager(); PayloadSendWorker payloadWorker = new PayloadSendWorker(); @@ -389,8 +389,8 @@ public String getAndroidId() { return androidId; } - public SharedPreferences getSharedPrefs() { - return prefs; + public SharedPreferences getGlobalSharedPrefs() { + return globalSharedPrefs; } public void runOnWorkerThread(Runnable r) { @@ -1060,7 +1060,7 @@ public Map getAndClearCustomData() { } public void resetSdkState() { - prefs.edit().clear().apply(); + globalSharedPrefs.edit().clear().apply(); taskManager.reset(appContext); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 88b1bf77d..ca52ad894 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -395,7 +395,7 @@ public static String getUserAgentString() { } private static String getEndpointBase() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); String url = prefs.getString(Constants.PREF_KEY_SERVER_URL, null); if (url == null) { url = Constants.CONFIG_DEFAULT_SERVER_URL; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Configuration.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Configuration.java index 1de0fe189..77dc26f4e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Configuration.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Configuration.java @@ -46,12 +46,12 @@ public Configuration(String json) throws JSONException { } public void save() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); prefs.edit().putString(Constants.PREF_KEY_APP_CONFIG_JSON, toString()).apply(); } public static Configuration load() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); return Configuration.load(prefs); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java index 3791189d3..eeeb23e13 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java @@ -121,7 +121,7 @@ private boolean fetchAndStoreInteractions() { ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); // We weren't able to connect to the internet. - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); boolean updateSuccessful = true; if (response.isException()) { prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, false).apply(); @@ -171,7 +171,7 @@ public void storeInteractionsPayloadString(String interactionsPayloadString) { } public void clear() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); prefs.edit().remove(Constants.PREF_KEY_INTERACTIONS).apply(); prefs.edit().remove(Constants.PREF_KEY_TARGETS).apply(); interactions = null; @@ -179,12 +179,12 @@ public void clear() { } private void saveInteractions() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); prefs.edit().putString(Constants.PREF_KEY_INTERACTIONS, interactions.toString()).apply(); } private Interactions loadInteractions() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); String interactionsString = prefs.getString(Constants.PREF_KEY_INTERACTIONS, null); if (interactionsString != null) { try { @@ -197,12 +197,12 @@ private Interactions loadInteractions() { } private void saveTargets() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); prefs.edit().putString(Constants.PREF_KEY_TARGETS, targets.toString()).apply(); } private Targets loadTargets() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); String targetsString = prefs.getString(Constants.PREF_KEY_TARGETS, null); if (targetsString != null) { try { @@ -215,20 +215,20 @@ private Targets loadTargets() { } private boolean hasCacheExpired() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); long expiration = prefs.getLong(Constants.PREF_KEY_INTERACTIONS_PAYLOAD_CACHE_EXPIRATION, 0); return expiration < System.currentTimeMillis(); } public void updateCacheExpiration(long duration) { long expiration = System.currentTimeMillis() + (duration * 1000); - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); prefs.edit().putLong(Constants.PREF_KEY_INTERACTIONS_PAYLOAD_CACHE_EXPIRATION, expiration).apply(); } public boolean isPollForInteractions() { if (pollForInteractions == null) { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); pollForInteractions = prefs.getBoolean(Constants.PREF_KEY_POLL_FOR_INTERACTIONS, true); } return pollForInteractions; @@ -236,7 +236,7 @@ public boolean isPollForInteractions() { public void setPollForInteractions(boolean pollForInteractions) { this.pollForInteractions = pollForInteractions; - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); prefs.edit().putBoolean(Constants.PREF_KEY_POLL_FOR_INTERACTIONS, pollForInteractions).apply(); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index 982bcacb5..dc7b4a480 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -433,7 +433,7 @@ else if (pendingWhoCardName != null || pendingWhoCardEmail != null || pendingWho } // Retrieve any saved attachments - final SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + final SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null && sessionData.getMessageCenterPendingAttachments() != null) { String pendingAttachmentsString = sessionData.getMessageCenterPendingAttachments(); @@ -744,7 +744,6 @@ public void onComposingViewCreated(MessageComposerHolder composer, final EditTex this.composerEditText = composerEditText; SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); // Restore composing text editing state, such as cursor position, after rotation if (composingViewSavedState != null) { if (this.composerEditText != null) { @@ -992,14 +991,14 @@ public void onAttachImage() { } private void setWhoCardAsPreviouslyDisplayed() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE, true); editor.apply(); } private boolean wasWhoCardAsPreviouslyDisplayed() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); return prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE, false); } @@ -1011,7 +1010,7 @@ public Editable getPendingComposingContent() { public void savePendingComposingMessage() { Editable content = getPendingComposingContent(); - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); SharedPreferences.Editor editor = prefs.edit(); SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData == null) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/CodePointStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/CodePointStore.java index 01e7b7ac4..a11319ade 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/CodePointStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/CodePointStore.java @@ -72,12 +72,12 @@ public void init() { } private void saveToPreference() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); prefs.edit().putString(Constants.PREF_KEY_CODE_POINT_STORE, store.toString()).apply(); } private JSONObject loadFromPreference() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); String json = prefs.getString(Constants.PREF_KEY_CODE_POINT_STORE, null); try { if (json != null) { @@ -255,7 +255,7 @@ public String toString() { } public void clear() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); prefs.edit().remove(Constants.PREF_KEY_CODE_POINT_STORE).apply(); store = new JSONObject(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStore.java index 3a92f32f8..fb256f505 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStore.java @@ -36,7 +36,7 @@ private VersionHistoryStore() { } private static void save() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); JSONArray baseArray = getBaseArray(); if (baseArray != null) { prefs.edit().putString(Constants.PREF_KEY_VERSION_HISTORY_V2, baseArray.toString()).apply(); @@ -46,7 +46,7 @@ private static void save() { private static void ensureLoaded() { if (versionHistoryEntries == null) { versionHistoryEntries = new ArrayList(); - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); try { String json = prefs.getString(Constants.PREF_KEY_VERSION_HISTORY_V2, "[]"); JSONArray baseArray = new JSONArray(json); @@ -61,7 +61,7 @@ private static void ensureLoaded() { } public static synchronized void clear() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); prefs.edit().remove(Constants.PREF_KEY_VERSION_HISTORY_V2).apply(); versionHistoryEntries.clear(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStoreMigrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStoreMigrator.java index d8ae0b4cf..9a1b033c6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStoreMigrator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStoreMigrator.java @@ -53,7 +53,7 @@ private static void performMigrationIfNeededV1ToV2() { if (migrated_to_v2) { return; } - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); if (prefs != null) { migrated_to_v2 = prefs.getBoolean(Constants.PREF_KEY_VERSION_HISTORY_V2_MIGRATED, false); if (migrated_to_v2) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index bf1b8ce39..49245f845 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -24,7 +24,6 @@ public class Constants { public static final String EXAMPLE_API_KEY_VALUE = "YOUR_APPTENTIVE_API_KEY"; // Session Data - public static final String PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT = "messageCenterServerErrorLastAttempt"; public static final String PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE = "messageCenterWhoCardSet"; public static final String PREF_KEY_APP_CONFIG_PREFIX = "appConfiguration."; public static final String PREF_KEY_APP_CONFIG_JSON = PREF_KEY_APP_CONFIG_PREFIX+"json"; @@ -83,7 +82,7 @@ public class Constants { public static final String MANIFEST_KEY_USE_STAGING_SERVER = "apptentive_use_staging_server"; public static final String PREF_KEY_PENDING_PUSH_NOTIFICATION = "pendingPushNotification"; - // FIXME: We will need to migrate this data. + // FIXME: Migrate into session data public static final String PREF_KEY_APP_ACTIVITY_STATE_QUEUE = "appActivityStateQueue"; public static final String PREF_KEY_CONVERSATION_TOKEN = "conversationToken"; public static final String PREF_KEY_CONVERSATION_ID = "conversationId"; @@ -107,6 +106,8 @@ public class Constants { public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE = "messageCenterPendingComposingMessage"; public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS = "messageCenterPendingComposingAttachments"; + // FIXME: Migrate into global data. + public static final String PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT = "messageCenterServerErrorLastAttempt"; public interface FragmentConfigKeys { From 48b8da7853363c18e0b69ab27365db43b2c10444 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 24 Jan 2017 09:43:35 -0800 Subject: [PATCH 033/465] Move interactions manifest into `SessionData` Also move related fields around cache expiration. --- .../android/sdk/ApptentiveInternal.java | 5 +- .../interaction/InteractionManager.java | 133 ++++++------------ .../interaction/fragment/NoteFragment.java | 15 +- ...sPayload.java => InteractionManifest.java} | 11 +- .../android/sdk/storage/SessionData.java | 29 +++- .../android/sdk/util/Constants.java | 8 +- 6 files changed, 94 insertions(+), 107 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/{InteractionsPayload.java => InteractionManifest.java} (87%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 4ddcba1c9..1ca765460 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -669,7 +669,10 @@ private void onSdkVersionChanged(Context context, String previousSdkVersion, Str * We want to make sure the app is using the latest configuration from the server if the app or sdk version changes. */ private void invalidateCaches() { - interactionManager.updateCacheExpiration(0); + SessionData sessionData = getSessionData(); + if (sessionData != null) { + sessionData.setInteractionExpiration(0L); + } Configuration config = Configuration.load(); config.setConfigurationCacheExpirationMillis(System.currentTimeMillis()); config.save(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java index eeeb23e13..761621792 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java @@ -14,11 +14,12 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; +import com.apptentive.android.sdk.module.engagement.interaction.model.InteractionManifest; import com.apptentive.android.sdk.module.engagement.interaction.model.Interactions; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; -import com.apptentive.android.sdk.module.engagement.interaction.model.InteractionsPayload; import com.apptentive.android.sdk.module.engagement.interaction.model.Targets; import com.apptentive.android.sdk.module.metric.MetricModule; +import com.apptentive.android.sdk.storage.SessionData; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -28,8 +29,6 @@ public class InteractionManager { - private Interactions interactions; - private Targets targets; private Boolean pollForInteractions; // boolean to prevent multiple fetching threads private AtomicBoolean isFetchPending = new AtomicBoolean(false); @@ -38,44 +37,45 @@ public interface InteractionUpdateListener { void onInteractionUpdated(boolean successful); } - public Interactions getInteractions() { - if (interactions == null) { - interactions = loadInteractions(); - } - return interactions; - } - - public Targets getTargets() { - if (targets == null) { - targets = loadTargets(); - } - return targets; - } - public Interaction getApplicableInteraction(String eventLabel) { - Targets targets = getTargets(); - - if (targets != null) { - String interactionId = targets.getApplicableInteraction(eventLabel); - if (interactionId != null) { - Interactions interactions = getInteractions(); - return interactions.getInteraction(interactionId); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData == null) { + return null; + } + String targetsString = sessionData.getTargets(); + if (targetsString != null) { + try { + Targets targets = new Targets(sessionData.getTargets()); + String interactionId = targets.getApplicableInteraction(eventLabel); + if (interactionId != null) { + String interactionsString = sessionData.getInteractions(); + if (interactionsString != null) { + Interactions interactions = new Interactions(interactionsString); + return interactions.getInteraction(interactionId); + } + } + } catch (JSONException e) { + ApptentiveLog.e(""); } } return null; } public void asyncFetchAndStoreInteractions() { - + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData == null) { + return; + } if (!isPollForInteractions()) { ApptentiveLog.v("Interaction polling is disabled."); return; } + boolean cacheExpired = sessionData.isMessageCenterFeatureUsed(); boolean force = ApptentiveInternal.getInstance().isApptentiveDebuggable(); // Check isFetchPending to only allow one asyncTask at a time when fetching interaction - if (isFetchPending.compareAndSet(false, true) && (force || hasCacheExpired())) { + if (isFetchPending.compareAndSet(false, true) && (force || cacheExpired)) { AsyncTask task = new AsyncTask() { // Hold onto the exception from the AsyncTask instance for later handling in UI thread private Exception e = null; @@ -117,6 +117,10 @@ protected void onPostExecute(Boolean successful) { // This method will be run from a worker thread created by asyncTask private boolean fetchAndStoreInteractions() { + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData == null) { + return false; + } ApptentiveLog.i("Fetching new Interactions asyncTask started"); ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); @@ -142,8 +146,8 @@ else if (!response.isSuccessful()) { if (cacheSeconds == null) { cacheSeconds = Constants.CONFIG_DEFAULT_INTERACTION_CACHE_EXPIRATION_DURATION_SECONDS; } - updateCacheExpiration(cacheSeconds); - storeInteractionsPayloadString(interactionsPayloadString); + sessionData.setInteractionExpiration(System.currentTimeMillis() + (cacheSeconds * 1000)); + storeInteractionManifest(interactionsPayloadString); } return updateSuccessful; @@ -152,78 +156,25 @@ else if (!response.isSuccessful()) { /** * Made public for testing. There is no other reason to use this method directly. */ - public void storeInteractionsPayloadString(String interactionsPayloadString) { + public void storeInteractionManifest(String interactionManifest) { + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData == null) { + return; + } try { - InteractionsPayload payload = new InteractionsPayload(interactionsPayloadString); + InteractionManifest payload = new InteractionManifest(interactionManifest); Interactions interactions = payload.getInteractions(); Targets targets = payload.getTargets(); if (interactions != null && targets != null) { - this.interactions = interactions; - this.targets = targets; - saveInteractions(); - saveTargets(); + sessionData.setTargets(targets.toString()); + sessionData.setInteractions(interactions.toString()); } else { - ApptentiveLog.e("Unable to save payloads."); + ApptentiveLog.e("Unable to save interactionManifest."); } } catch (JSONException e) { - ApptentiveLog.w("Invalid InteractionsPayload received."); - } - } - - public void clear() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - prefs.edit().remove(Constants.PREF_KEY_INTERACTIONS).apply(); - prefs.edit().remove(Constants.PREF_KEY_TARGETS).apply(); - interactions = null; - targets = null; - } - - private void saveInteractions() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - prefs.edit().putString(Constants.PREF_KEY_INTERACTIONS, interactions.toString()).apply(); - } - - private Interactions loadInteractions() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - String interactionsString = prefs.getString(Constants.PREF_KEY_INTERACTIONS, null); - if (interactionsString != null) { - try { - return new Interactions(interactionsString); - } catch (JSONException e) { - ApptentiveLog.w("Exception creating Interactions object.", e); - } + ApptentiveLog.w("Invalid InteractionManifest received."); } - return null; - } - - private void saveTargets() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - prefs.edit().putString(Constants.PREF_KEY_TARGETS, targets.toString()).apply(); - } - private Targets loadTargets() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - String targetsString = prefs.getString(Constants.PREF_KEY_TARGETS, null); - if (targetsString != null) { - try { - return new Targets(targetsString); - } catch (JSONException e) { - ApptentiveLog.w("Exception creating Targets object.", e); - } - } - return null; - } - - private boolean hasCacheExpired() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - long expiration = prefs.getLong(Constants.PREF_KEY_INTERACTIONS_PAYLOAD_CACHE_EXPIRATION, 0); - return expiration < System.currentTimeMillis(); - } - - public void updateCacheExpiration(long duration) { - long expiration = System.currentTimeMillis() + (duration * 1000); - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - prefs.edit().putLong(Constants.PREF_KEY_INTERACTIONS_PAYLOAD_CACHE_EXPIRATION, expiration).apply(); } public boolean isPollForInteractions() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java index 37b3d6efb..7a07366ee 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java @@ -26,6 +26,7 @@ import com.apptentive.android.sdk.module.engagement.interaction.model.common.Action; import com.apptentive.android.sdk.module.engagement.interaction.model.common.Actions; import com.apptentive.android.sdk.module.engagement.interaction.model.common.LaunchInteractionAction; +import com.apptentive.android.sdk.storage.SessionData; import org.json.JSONException; import org.json.JSONObject; @@ -131,9 +132,17 @@ public void onClick(View view) { Interaction invokedInteraction = null; if (interactionIdToLaunch != null) { - Interactions interactions = ApptentiveInternal.getInstance().getInteractionManager().getInteractions(); - if (interactions != null) { - invokedInteraction = interactions.getInteraction(interactionIdToLaunch); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData != null) { + String interactionsString = sessionData.getInteractions(); + if (interactionsString != null) { + try { + Interactions interactions = new Interactions(interactionsString); + invokedInteraction = interactions.getInteraction(interactionIdToLaunch); + }catch (JSONException e) { + // Should never happen. + } + } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionsPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionManifest.java similarity index 87% rename from apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionsPayload.java rename to apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionManifest.java index 6ce2d0b2a..50f733a3a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionsPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionManifest.java @@ -12,12 +12,9 @@ import org.json.JSONException; import org.json.JSONObject; -/** - * @author Sky Kelsey - */ -public class InteractionsPayload extends JSONObject { +public class InteractionManifest extends JSONObject { - public InteractionsPayload(String json) throws JSONException { + public InteractionManifest(String json) throws JSONException { super(json); } @@ -45,7 +42,7 @@ public Interactions getInteractions() { } } } catch (JSONException e) { - ApptentiveLog.w("Unable to load Interactions from InteractionsPayload.", e); + ApptentiveLog.w("Unable to load Interactions from InteractionManifest.", e); } return null; } @@ -57,7 +54,7 @@ public Targets getTargets() { return new Targets(targets.toString()); } } catch (JSONException e) { - ApptentiveLog.w("Unable to load Targets from InteractionsPayload.", e); + ApptentiveLog.w("Unable to load Targets from InteractionManifest.", e); } return null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index db57e5cd7..c6254a018 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -31,6 +31,9 @@ public class SessionData implements Serializable { private VersionHistory versionHistory; private String messageCenterPendingMessage; private String messageCenterPendingAttachments; + private String targets; + private String interactions; + private long interactionExpiration; public SessionData() { this.device = new Device(); @@ -191,7 +194,31 @@ public void setMessageCenterPendingAttachments(String messageCenterPendingAttach save(); } -//endregion + public String getTargets() { + return targets; + } + + public void setTargets(String targets) { + this.targets = targets; + } + + public String getInteractions() { + return interactions; + } + + public void setInteractions(String interactions) { + this.interactions = interactions; + } + + public long getInteractionExpiration() { + return interactionExpiration; + } + + public void setInteractionExpiration(long interactionExpiration) { + this.interactionExpiration = interactionExpiration; + } + + //endregion // TODO: Only save when a value has changed. public void save() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 49245f845..50ad7e675 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -30,11 +30,7 @@ public class Constants { // Engagement - public static final String PREF_KEY_INTERACTIONS = "interactions"; - public static final String PREF_KEY_TARGETS = "targets"; - public static final String PREF_KEY_INTERACTIONS_PAYLOAD_CACHE_EXPIRATION = "interactionsCacheExpiration"; // Used to turn off Interaction polling so that contrived payloads can be manually tested. - public static final String PREF_KEY_POLL_FOR_INTERACTIONS = "pollForInteractions"; // Config Defaults public static final String CONFIG_DEFAULT_SERVER_URL = "https://api.apptentive.com"; @@ -105,9 +101,13 @@ public class Constants { public static final String PREF_KEY_VERSION_HISTORY_V2_MIGRATED = "versionHistoryV2Migrated"; public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE = "messageCenterPendingComposingMessage"; public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS = "messageCenterPendingComposingAttachments"; + public static final String PREF_KEY_INTERACTIONS = "interactions"; + public static final String PREF_KEY_TARGETS = "targets"; + public static final String PREF_KEY_INTERACTIONS_PAYLOAD_CACHE_EXPIRATION = "interactionsCacheExpiration"; // FIXME: Migrate into global data. public static final String PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT = "messageCenterServerErrorLastAttempt"; + public static final String PREF_KEY_POLL_FOR_INTERACTIONS = "pollForInteractions"; public interface FragmentConfigKeys { From fe531d5a6f6eb74d73361357b7d329b99a3140bc Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 24 Jan 2017 10:01:03 -0800 Subject: [PATCH 034/465] Move Who Card display status into `SessionData` --- .../fragment/MessageCenterFragment.java | 16 +++++++++------ .../android/sdk/storage/SessionData.java | 20 ++++++++++++++----- .../android/sdk/util/Constants.java | 2 +- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index dc7b4a480..51080998e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -991,15 +991,19 @@ public void onAttachImage() { } private void setWhoCardAsPreviouslyDisplayed() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE, true); - editor.apply(); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData == null) { + return; + } + sessionData.setMessageCenterWhoCardPreviouslyDisplayed(true); } private boolean wasWhoCardAsPreviouslyDisplayed() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - return prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE, false); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + if (sessionData == null) { + return false; + } + return sessionData.isMessageCenterWhoCardPreviouslyDisplayed(); } // Retrieve the content from the composing area diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index c6254a018..0cf354ab4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -27,8 +27,9 @@ public class SessionData implements Serializable { private AppRelease appRelease; private EventData eventData; private String lastSeenSdkVersion; - private boolean messageCenterFeatureUsed; private VersionHistory versionHistory; + private boolean messageCenterFeatureUsed; + private boolean messageCenterWhoCardPreviouslyDisplayed; private String messageCenterPendingMessage; private String messageCenterPendingAttachments; private String targets; @@ -159,6 +160,14 @@ public void setLastSeenSdkVersion(String lastSeenSdkVersion) { this.lastSeenSdkVersion = lastSeenSdkVersion; } + public VersionHistory getVersionHistory() { + return versionHistory; + } + + public void setVersionHistory(VersionHistory versionHistory) { + this.versionHistory = versionHistory; + } + public boolean isMessageCenterFeatureUsed() { return messageCenterFeatureUsed; } @@ -168,12 +177,13 @@ public void setMessageCenterFeatureUsed(boolean messageCenterFeatureUsed) { save(); } - public VersionHistory getVersionHistory() { - return versionHistory; + public boolean isMessageCenterWhoCardPreviouslyDisplayed() { + return messageCenterWhoCardPreviouslyDisplayed; } - public void setVersionHistory(VersionHistory versionHistory) { - this.versionHistory = versionHistory; + public void setMessageCenterWhoCardPreviouslyDisplayed(boolean messageCenterWhoCardPreviouslyDisplayed) { + this.messageCenterWhoCardPreviouslyDisplayed = messageCenterWhoCardPreviouslyDisplayed; + save(); } public String getMessageCenterPendingMessage() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 50ad7e675..c884ccac2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -24,7 +24,6 @@ public class Constants { public static final String EXAMPLE_API_KEY_VALUE = "YOUR_APPTENTIVE_API_KEY"; // Session Data - public static final String PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE = "messageCenterWhoCardSet"; public static final String PREF_KEY_APP_CONFIG_PREFIX = "appConfiguration."; public static final String PREF_KEY_APP_CONFIG_JSON = PREF_KEY_APP_CONFIG_PREFIX+"json"; @@ -104,6 +103,7 @@ public class Constants { public static final String PREF_KEY_INTERACTIONS = "interactions"; public static final String PREF_KEY_TARGETS = "targets"; public static final String PREF_KEY_INTERACTIONS_PAYLOAD_CACHE_EXPIRATION = "interactionsCacheExpiration"; + public static final String PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE = "messageCenterWhoCardSet"; // FIXME: Migrate into global data. public static final String PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT = "messageCenterServerErrorLastAttempt"; From 51567254b68c080c479d34725f2c814f320d9de2 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 24 Jan 2017 10:05:59 -0800 Subject: [PATCH 035/465] Add newest network types to lookup table. --- .../main/java/com/apptentive/android/sdk/util/Constants.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index c884ccac2..5a5b6ca4b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -148,7 +148,10 @@ public interface FragmentTypes { "EVDO_B", // 12 "LTE", // 13 "EHRPD", // 14 - "HSPAP" // 15 + "HSPAP", // 15 + "GSM", // 16 + "TD_SCDMA",// 17 + "IWLAN" // 18 }; public static String networkTypeAsString(int networkTypeAsInt) { From d2e821b2fea751439d118e342023d1c4fae4de02 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 25 Jan 2017 21:53:18 -0800 Subject: [PATCH 036/465] Hook up `SessionData` listeners Set up listeners withing the `SessionData` object graph so that changing any value it contains results in the entire object being saved. Properly reconnect listeners when `SessionData` is restored. --- .../apptentive/android/sdk/Apptentive.java | 15 +- .../android/sdk/ApptentiveInternal.java | 44 +++-- .../module/engagement/EngagementModule.java | 2 - .../android/sdk/storage/CustomData.java | 48 +++++- .../sdk/storage/DataChangedListener.java | 11 ++ .../android/sdk/storage/Device.java | 150 ++++++++++++++---- .../android/sdk/storage/DeviceManager.java | 6 +- .../android/sdk/storage/EventData.java | 23 ++- .../android/sdk/storage/FileSerializer.java | 2 + .../sdk/storage/IntegrationConfig.java | 27 +++- .../android/sdk/storage/Person.java | 87 +++++++--- .../android/sdk/storage/PersonManager.java | 3 +- .../android/sdk/storage/Saveable.java | 15 ++ .../android/sdk/storage/SessionData.java | 130 ++++++++++----- .../android/sdk/storage/VersionHistory.java | 22 ++- 15 files changed, 453 insertions(+), 132 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/DataChangedListener.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/Saveable.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index d3dccc7ac..a24038135 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -79,6 +79,7 @@ public static void setPersonEmail(String email) { if (ApptentiveInternal.isApptentiveRegistered()) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { + // FIXME: Make sure Person object diff is sent. sessionData.setPersonEmail(email); } } @@ -113,6 +114,7 @@ public static void setPersonName(String name) { if (ApptentiveInternal.isApptentiveRegistered()) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { + // FIXME: Make sure Person object diff is sent. sessionData.setPersonName(name); } } @@ -150,7 +152,6 @@ public static void addCustomDeviceData(String key, String value) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getDevice().getCustomData().put(key, value); - sessionData.save(); } } } @@ -168,7 +169,6 @@ public static void addCustomDeviceData(String key, Number value) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getDevice().getCustomData().put(key, value); - sessionData.save(); } } } @@ -186,7 +186,6 @@ public static void addCustomDeviceData(String key, Boolean value) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getDevice().getCustomData().put(key, value); - sessionData.save(); } } } @@ -196,7 +195,6 @@ private static void addCustomDeviceData(String key, Version version) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getDevice().getCustomData().put(key, version); - sessionData.save(); } } } @@ -206,7 +204,6 @@ private static void addCustomDeviceData(String key, DateTime dateTime) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getDevice().getCustomData().put(key, dateTime); - sessionData.save(); } } } @@ -221,7 +218,6 @@ public static void removeCustomDeviceData(String key) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getDevice().getCustomData().remove(key); - sessionData.save(); } } } @@ -242,7 +238,6 @@ public static void addCustomPersonData(String key, String value) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getPerson().getCustomData().put(key, value); - sessionData.save(); } } } @@ -260,7 +255,6 @@ public static void addCustomPersonData(String key, Number value) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getPerson().getCustomData().put(key, value); - sessionData.save(); } } } @@ -278,7 +272,6 @@ public static void addCustomPersonData(String key, Boolean value) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getPerson().getCustomData().put(key, value); - sessionData.save(); } } } @@ -288,7 +281,6 @@ private static void addCustomPersonData(String key, Version version) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getPerson().getCustomData().put(key, version); - sessionData.save(); } } } @@ -298,7 +290,6 @@ private static void addCustomPersonData(String key, DateTime dateTime) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getPerson().getCustomData().remove(key); - sessionData.save(); } } } @@ -313,7 +304,6 @@ public static void removeCustomPersonData(String key) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getPerson().getCustomData().remove(key); - sessionData.save(); } } } @@ -407,7 +397,6 @@ public static void setPushNotificationIntegration(int pushProvider, String token ApptentiveLog.e("Invalid pushProvider: %d", pushProvider); return; } - sessionData.save(); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 1ca765460..837caa2db 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -53,6 +53,7 @@ import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.storage.SessionData; +import com.apptentive.android.sdk.storage.DataChangedListener; import com.apptentive.android.sdk.storage.VersionHistoryItem; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -74,7 +75,7 @@ /** * This class contains only internal methods. These methods should not be access directly by the host app. */ -public class ApptentiveInternal implements Handler.Callback { +public class ApptentiveInternal implements Handler.Callback, DataChangedListener { static AtomicBoolean isApptentiveInitialized = new AtomicBoolean(false); InteractionManager interactionManager; @@ -97,6 +98,8 @@ public class ApptentiveInternal implements Handler.Callback { String androidId; String appPackageName; SessionData sessionData; + private FileSerializer fileSerializer; + Handler backgroundHandler; @@ -303,6 +306,7 @@ public boolean setApplicationDefaultTheme(int themeResId) { /** * Must be called after {@link ApptentiveInternal#setApplicationDefaultTheme(int)} + * * @return true it the app is using an AppCompat theme */ public boolean isAppUsingAppCompatTheme() { @@ -521,9 +525,13 @@ public boolean init() { VersionHistoryItem lastVersionItemSeen = null; String lastSeenSdkVersion = null; - FileSerializer fileSerializer = new FileSerializer(new File(appContext.getFilesDir(), "apptentive/SessionData.ser")); + File internalStorage = appContext.getFilesDir(); + File sessionDataFile = new File(internalStorage, "apptentive/SessionData.ser"); + sessionDataFile.getParentFile().mkdirs(); + fileSerializer = new FileSerializer(sessionDataFile); sessionData = (SessionData) fileSerializer.deserialize(); if (sessionData != null) { + sessionData.setDataChangedListener(this); ApptentiveLog.d("Restored existing SessionData"); ApptentiveLog.v("Restored EventData: %s", sessionData.getEventData()); // FIXME: Move this to whereever the sessions first comes online? @@ -563,7 +571,6 @@ public boolean init() { currentVersionName = appRelease.getVersionName(); - if (lastVersionItemSeen == null) { onVersionChanged(null, currentVersionCode, null, currentVersionName, appRelease); } else { @@ -642,7 +649,6 @@ private void onVersionChanged(Integer previousVersionCode, Integer currentVersio SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getVersionHistory().updateVersionHistory(Util.currentTimeSeconds(), currentVersionCode, currentVersionName); - sessionData.save(); } if (previousVersionCode != null) { taskManager.addPayload(AppReleaseManager.getPayload(currentAppRelease)); @@ -752,13 +758,13 @@ private boolean fetchConversationToken() { ApptentiveLog.d("New Conversation id: %s", conversationId); sessionData = new SessionData(); + sessionData.setDataChangedListener(this); if (conversationToken != null && !conversationToken.equals("")) { sessionData.setConversationToken(conversationToken); sessionData.setConversationId(conversationId); sessionData.setDevice(device); sessionData.setSdk(sdk); sessionData.setAppRelease(appRelease); - saveSessionData(); } String personId = root.getString("person_id"); ApptentiveLog.d("PersonId: " + personId); @@ -1109,9 +1115,13 @@ public static boolean checkRegistered() { // Multi-tenancy work - public void saveSessionData() { + public void scheduleSessionDataSave() { + ApptentiveLog.e("Schedule SessionData save?"); if (!backgroundHandler.hasMessages(MESSAGE_SAVE_SESSION_DATA)) { + ApptentiveLog.e("Scheduling SessionData save."); backgroundHandler.sendEmptyMessageDelayed(MESSAGE_SAVE_SESSION_DATA, 100); + } else { + ApptentiveLog.e("SessionData save already scheduled."); } } @@ -1122,19 +1132,11 @@ public void saveSessionData() { public boolean handleMessage(Message msg) { switch (msg.what) { case MESSAGE_SAVE_SESSION_DATA: - ApptentiveLog.d("Saving SessionData"); + ApptentiveLog.e("Saving SessionData"); ApptentiveLog.v("EventData: %s", sessionData.getEventData().toString()); - - File internalStorage = appContext.getFilesDir(); - File sessionDataFile = new File(internalStorage, "apptentive/SessionData.ser"); - try { - sessionDataFile.getParentFile().mkdirs(); - } catch (SecurityException e) { - ApptentiveLog.e("Security Error creating DataSession file.", e); + if (fileSerializer != null) { + fileSerializer.serialize(sessionData); } - FileSerializer fileSerializer = new FileSerializer(sessionDataFile); - fileSerializer.serialize(sApptentiveInternal.sessionData); - ApptentiveLog.d("Session data written to file of length: %s", Util.humanReadableByteCount(sessionDataFile.length(), false)); break; case MESSAGE_CREATE_CONVERSATION: // TODO: This. @@ -1142,4 +1144,12 @@ public boolean handleMessage(Message msg) { } return false; } + + //region Listeners + + @Override + public void onDataChanged() { + scheduleSessionDataSave(); + } + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java index 86e8d35b8..e341182e0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java @@ -57,7 +57,6 @@ public static synchronized boolean engage(Context context, String vendor, String SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getEventData().storeEventForCurrentAppVersion(Util.currentTimeSeconds(), eventLabel); - sessionData.save(); EventManager.sendEvent(new Event(eventLabel, interactionId, data, customData, extendedData)); return doEngage(context, eventLabel); } @@ -73,7 +72,6 @@ public static boolean doEngage(Context context, String eventLabel) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { sessionData.getEventData().storeInteractionForCurrentAppVersion(Util.currentTimeSeconds(), interaction.getId()); - sessionData.save(); } launchInteraction(context, interaction); return true; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java index f7d566254..168db52a2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java @@ -9,9 +9,55 @@ import org.json.JSONException; import java.util.HashMap; +import java.util.Map; import java.util.Set; -public class CustomData extends HashMap { +public class CustomData extends HashMap implements Saveable { + + //region Listeners + private transient DataChangedListener listener; + + @Override + public void setDataChangedListener(DataChangedListener listener) { + this.listener = listener; + } + + @Override + public void notifyDataChanged() { + if (listener != null) { + listener.onDataChanged(); + } + } + //endregion + + + //region Saving when modified + @Override + public Object put(String key, Object value) { + Object ret = super.put(key, value); + notifyDataChanged(); + return ret; + } + + @Override + public void putAll(Map m) { + super.putAll(m); + notifyDataChanged(); + } + + @Override + public Object remove(Object key) { + Object ret = super.remove(key); + notifyDataChanged(); + return ret; + } + + @Override + public void clear() { + super.clear(); + notifyDataChanged(); + } + //endregion public com.apptentive.android.sdk.model.CustomData toJson() { try { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/DataChangedListener.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/DataChangedListener.java new file mode 100644 index 000000000..f7ecb24a1 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/DataChangedListener.java @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +public interface DataChangedListener { + void onDataChanged(); +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java index 184015622..0923b0e04 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java @@ -6,9 +6,9 @@ package com.apptentive.android.sdk.storage; -import java.io.Serializable; +import android.text.TextUtils; -public class Device implements Serializable { +public class Device implements Saveable, DataChangedListener { private String uuid; private String osName; @@ -36,19 +36,43 @@ public class Device implements Serializable { private String utcOffset; private IntegrationConfig integrationConfig; + private transient DataChangedListener listener; + public Device() { - this.customData = new CustomData(); - this.integrationConfig = new IntegrationConfig(); + customData = new CustomData(); + integrationConfig = new IntegrationConfig(); + } + + @Override + public void setDataChangedListener(DataChangedListener listener) { + this.listener = listener; + customData.setDataChangedListener(this); + integrationConfig.setDataChangedListener(this); + } + + @Override + public void notifyDataChanged() { + if (listener != null) { + listener.onDataChanged(); + } + } + + @Override + public void onDataChanged() { + notifyDataChanged(); } -//region Getters & Setters + //region Getters & Setters public String getUuid() { return uuid; } public void setUuid(String uuid) { - this.uuid = uuid; + if (!TextUtils.equals(this.uuid, uuid)) { + this.uuid = uuid; + notifyDataChanged(); + } } public String getOsName() { @@ -56,7 +80,10 @@ public String getOsName() { } public void setOsName(String osName) { - this.osName = osName; + if (!TextUtils.equals(this.osName, osName)) { + this.osName = osName; + notifyDataChanged(); + } } public String getOsVersion() { @@ -64,7 +91,10 @@ public String getOsVersion() { } public void setOsVersion(String osVersion) { - this.osVersion = osVersion; + if (!TextUtils.equals(this.osVersion, osVersion)) { + this.osVersion = osVersion; + notifyDataChanged(); + } } public String getOsBuild() { @@ -72,7 +102,10 @@ public String getOsBuild() { } public void setOsBuild(String osBuild) { - this.osBuild = osBuild; + if (!TextUtils.equals(this.osBuild, osBuild)) { + this.osBuild = osBuild; + notifyDataChanged(); + } } public int getOsApiLevel() { @@ -80,7 +113,10 @@ public int getOsApiLevel() { } public void setOsApiLevel(int osApiLevel) { - this.osApiLevel = osApiLevel; + if (this.osApiLevel != osApiLevel) { + this.osApiLevel = osApiLevel; + notifyDataChanged(); + } } public String getManufacturer() { @@ -88,7 +124,10 @@ public String getManufacturer() { } public void setManufacturer(String manufacturer) { - this.manufacturer = manufacturer; + if (!TextUtils.equals(this.manufacturer, manufacturer)) { + this.manufacturer = manufacturer; + notifyDataChanged(); + } } public String getModel() { @@ -96,7 +135,10 @@ public String getModel() { } public void setModel(String model) { - this.model = model; + if (!TextUtils.equals(this.model, model)) { + this.model = model; + notifyDataChanged(); + } } public String getBoard() { @@ -104,7 +146,10 @@ public String getBoard() { } public void setBoard(String board) { - this.board = board; + if (!TextUtils.equals(this.board, board)) { + this.board = board; + notifyDataChanged(); + } } public String getProduct() { @@ -112,7 +157,10 @@ public String getProduct() { } public void setProduct(String product) { - this.product = product; + if (!TextUtils.equals(this.product, product)) { + this.product = product; + notifyDataChanged(); + } } public String getBrand() { @@ -120,7 +168,10 @@ public String getBrand() { } public void setBrand(String brand) { - this.brand = brand; + if (!TextUtils.equals(this.brand, brand)) { + this.brand = brand; + notifyDataChanged(); + } } public String getCpu() { @@ -128,7 +179,10 @@ public String getCpu() { } public void setCpu(String cpu) { - this.cpu = cpu; + if (!TextUtils.equals(this.cpu, cpu)) { + this.cpu = cpu; + notifyDataChanged(); + } } public String getDevice() { @@ -136,7 +190,10 @@ public String getDevice() { } public void setDevice(String device) { - this.device = device; + if (!TextUtils.equals(this.device, device)) { + this.device = device; + notifyDataChanged(); + } } public String getCarrier() { @@ -144,7 +201,10 @@ public String getCarrier() { } public void setCarrier(String carrier) { - this.carrier = carrier; + if (!TextUtils.equals(this.carrier, carrier)) { + this.carrier = carrier; + notifyDataChanged(); + } } public String getCurrentCarrier() { @@ -152,7 +212,10 @@ public String getCurrentCarrier() { } public void setCurrentCarrier(String currentCarrier) { - this.currentCarrier = currentCarrier; + if (!TextUtils.equals(this.currentCarrier, currentCarrier)) { + this.currentCarrier = currentCarrier; + notifyDataChanged(); + } } public String getNetworkType() { @@ -160,7 +223,10 @@ public String getNetworkType() { } public void setNetworkType(String networkType) { - this.networkType = networkType; + if (!TextUtils.equals(this.networkType, networkType)) { + this.networkType = networkType; + notifyDataChanged(); + } } public String getBuildType() { @@ -168,7 +234,10 @@ public String getBuildType() { } public void setBuildType(String buildType) { - this.buildType = buildType; + if (!TextUtils.equals(this.buildType, buildType)) { + this.buildType = buildType; + notifyDataChanged(); + } } public String getBuildId() { @@ -176,7 +245,10 @@ public String getBuildId() { } public void setBuildId(String buildId) { - this.buildId = buildId; + if (!TextUtils.equals(this.buildId, buildId)) { + this.buildId = buildId; + notifyDataChanged(); + } } public String getBootloaderVersion() { @@ -184,7 +256,10 @@ public String getBootloaderVersion() { } public void setBootloaderVersion(String bootloaderVersion) { - this.bootloaderVersion = bootloaderVersion; + if (!TextUtils.equals(this.bootloaderVersion, bootloaderVersion)) { + this.bootloaderVersion = bootloaderVersion; + notifyDataChanged(); + } } public String getRadioVersion() { @@ -192,7 +267,10 @@ public String getRadioVersion() { } public void setRadioVersion(String radioVersion) { - this.radioVersion = radioVersion; + if (!TextUtils.equals(this.radioVersion, radioVersion)) { + this.radioVersion = radioVersion; + notifyDataChanged(); + } } public CustomData getCustomData() { @@ -201,6 +279,8 @@ public CustomData getCustomData() { public void setCustomData(CustomData customData) { this.customData = customData; + this.customData.setDataChangedListener(this); + notifyDataChanged(); } public String getLocaleCountryCode() { @@ -208,7 +288,10 @@ public String getLocaleCountryCode() { } public void setLocaleCountryCode(String localeCountryCode) { - this.localeCountryCode = localeCountryCode; + if (!TextUtils.equals(this.localeCountryCode, localeCountryCode)) { + this.localeCountryCode = localeCountryCode; + notifyDataChanged(); + } } public String getLocaleLanguageCode() { @@ -216,7 +299,10 @@ public String getLocaleLanguageCode() { } public void setLocaleLanguageCode(String localeLanguageCode) { - this.localeLanguageCode = localeLanguageCode; + if (!TextUtils.equals(this.localeLanguageCode, localeLanguageCode)) { + this.localeLanguageCode = localeLanguageCode; + notifyDataChanged(); + } } public String getLocaleRaw() { @@ -224,7 +310,10 @@ public String getLocaleRaw() { } public void setLocaleRaw(String localeRaw) { - this.localeRaw = localeRaw; + if (!TextUtils.equals(this.localeRaw, localeRaw)) { + this.localeRaw = localeRaw; + notifyDataChanged(); + } } public String getUtcOffset() { @@ -232,7 +321,10 @@ public String getUtcOffset() { } public void setUtcOffset(String utcOffset) { - this.utcOffset = utcOffset; + if (!TextUtils.equals(this.utcOffset, utcOffset)) { + this.utcOffset = utcOffset; + notifyDataChanged(); + } } public IntegrationConfig getIntegrationConfig() { @@ -241,6 +333,8 @@ public IntegrationConfig getIntegrationConfig() { public void setIntegrationConfig(IntegrationConfig integrationConfig) { this.integrationConfig = integrationConfig; + this.integrationConfig.setDataChangedListener(this); + notifyDataChanged(); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java index d34302f8d..de36062d8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java @@ -149,7 +149,8 @@ public static com.apptentive.android.sdk.model.Device getDiffPayload(com.apptent } if (oldDevice == null || !oldDevice.getCustomData().equals(newDevice.getCustomData())) { - ret.setCustomData(newDevice.getCustomData().toJson()); + CustomData customData = newDevice.getCustomData(); + ret.setCustomData(customData != null ? customData.toJson() : null); } if (oldDevice == null || !oldDevice.getLocaleCountryCode().equals(newDevice.getLocaleCountryCode())) { @@ -169,7 +170,8 @@ public static com.apptentive.android.sdk.model.Device getDiffPayload(com.apptent } if (oldDevice == null || !oldDevice.getIntegrationConfig().equals(newDevice.getIntegrationConfig())) { - ret.setIntegrationConfig(newDevice.getIntegrationConfig().toJson()); + IntegrationConfig integrationConfig = newDevice.getIntegrationConfig(); + ret.setIntegrationConfig(integrationConfig != null ? integrationConfig.toJson() : null); } return ret; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java index 9c0d0e8cf..3b21ca467 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java @@ -8,14 +8,13 @@ import com.apptentive.android.sdk.ApptentiveInternal; -import java.io.Serializable; import java.util.HashMap; import java.util.Map; /** * Stores a record of when events and interactions were triggered, as well as the number of times per versionName or versionCode. */ -public class EventData implements Serializable { +public class EventData implements Saveable { private Map events; private Map interactions; @@ -25,6 +24,24 @@ public EventData() { interactions = new HashMap(); } + //region Listeners + private transient DataChangedListener listener; + + @Override + public void setDataChangedListener(DataChangedListener listener) { + this.listener = listener; + } + + @Override + public void notifyDataChanged() { + if (listener != null) { + listener.onDataChanged(); + } + } + + //endregion + + // FIXME: Find all usage of this and ensure they use the same timestamp for saving events and runnign interaction queries. public synchronized void storeEventForCurrentAppVersion(double timestamp, String eventLabel) { EventRecord eventRecord = events.get(eventLabel); @@ -35,6 +52,7 @@ public synchronized void storeEventForCurrentAppVersion(double timestamp, String String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); eventRecord.update(timestamp, versionName, versionCode); + notifyDataChanged(); } // FIXME: Find all usage of this and ensure they use the same timestamp for saving events and runnign interaction queries. @@ -47,6 +65,7 @@ public synchronized void storeInteractionForCurrentAppVersion(double timestamp, String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); eventRecord.update(timestamp, versionName, versionCode); + notifyDataChanged(); } public Long getEventCountTotal(String eventLabel) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java index 7f8764159..ae35469ba 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java @@ -30,9 +30,11 @@ public void serialize(Object object) { FileOutputStream fos = null; ObjectOutputStream oos = null; try { + file.getParentFile().mkdirs(); fos = new FileOutputStream(file); oos = new ObjectOutputStream(fos); oos.writeObject(object); + ApptentiveLog.v("Session data written to file of length: %s",Util.humanReadableByteCount(file.length(),false)); } catch (IOException e) { ApptentiveLog.e("Error", e); } finally { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java index f0389ac97..ab046eb80 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java @@ -8,10 +8,8 @@ import org.json.JSONException; -import java.io.Serializable; - -public class IntegrationConfig implements Serializable { +public class IntegrationConfig implements Saveable { private static final String INTEGRATION_APPTENTIVE_PUSH = "apptentive_push"; private static final String INTEGRATION_AWS_SNS = "aws_sns"; @@ -23,12 +21,31 @@ public class IntegrationConfig implements Serializable { private IntegrationConfigItem urbanAirship; private IntegrationConfigItem parse; + private DataChangedListener listener; + + + //region Listeners + @Override + public void setDataChangedListener(DataChangedListener listener) { + this.listener = listener; + } + + @Override + public void notifyDataChanged() { + if (listener != null) { + listener.onDataChanged(); + } + } + //endregion + + //region Getters & Setters public IntegrationConfigItem getApptentive() { return apptentive; } public void setApptentive(IntegrationConfigItem apptentive) { this.apptentive = apptentive; + notifyDataChanged(); } public IntegrationConfigItem getAmazonAwsSns() { @@ -37,6 +54,7 @@ public IntegrationConfigItem getAmazonAwsSns() { public void setAmazonAwsSns(IntegrationConfigItem amazonAwsSns) { this.amazonAwsSns = amazonAwsSns; + notifyDataChanged(); } public IntegrationConfigItem getUrbanAirship() { @@ -45,6 +63,7 @@ public IntegrationConfigItem getUrbanAirship() { public void setUrbanAirship(IntegrationConfigItem urbanAirship) { this.urbanAirship = urbanAirship; + notifyDataChanged(); } public IntegrationConfigItem getParse() { @@ -53,7 +72,9 @@ public IntegrationConfigItem getParse() { public void setParse(IntegrationConfigItem parse) { this.parse = parse; + notifyDataChanged(); } + //endregion public com.apptentive.android.sdk.model.CustomData toJson() { try { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java index 7849f572e..e2275310a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java @@ -6,9 +6,9 @@ package com.apptentive.android.sdk.storage; -import java.io.Serializable; +import android.text.TextUtils; -public class Person implements Serializable { +public class Person implements Saveable, DataChangedListener { private String id; private String email; @@ -22,6 +22,32 @@ public class Person implements Serializable { private String birthday; private CustomData customData; + public Person() { + customData = new CustomData(); + } + + //region Listeners + private transient DataChangedListener listener; + + @Override + public void setDataChangedListener(DataChangedListener listener) { + this.listener = listener; + customData.setDataChangedListener(this); + } + + @Override + public void notifyDataChanged() { + if (listener != null) { + listener.onDataChanged(); + } + } + + @Override + public void onDataChanged() { + notifyDataChanged(); + } + //endregion + //region Getters & Setters public String getId() { @@ -29,7 +55,10 @@ public String getId() { } public void setId(String id) { - this.id = id; + if (!TextUtils.equals(this.id, id)) { + this.id = id; + notifyDataChanged(); + } } public String getEmail() { @@ -37,7 +66,10 @@ public String getEmail() { } public void setEmail(String email) { - this.email = email; + if (!TextUtils.equals(this.email, email)) { + this.email = email; + notifyDataChanged(); + } } public String getName() { @@ -45,7 +77,10 @@ public String getName() { } public void setName(String name) { - this.name = name; + if (!TextUtils.equals(this.name, name)) { + this.name = name; + notifyDataChanged(); + } } public String getFacebookId() { @@ -53,7 +88,10 @@ public String getFacebookId() { } public void setFacebookId(String facebookId) { - this.facebookId = facebookId; + if (!TextUtils.equals(this.facebookId, facebookId)) { + this.facebookId = facebookId; + notifyDataChanged(); + } } public String getPhoneNumber() { @@ -61,7 +99,10 @@ public String getPhoneNumber() { } public void setPhoneNumber(String phoneNumber) { - this.phoneNumber = phoneNumber; + if (!TextUtils.equals(this.phoneNumber, phoneNumber)) { + this.phoneNumber = phoneNumber; + notifyDataChanged(); + } } public String getStreet() { @@ -69,7 +110,10 @@ public String getStreet() { } public void setStreet(String street) { - this.street = street; + if (!TextUtils.equals(this.street, street)) { + this.street = street; + notifyDataChanged(); + } } public String getCity() { @@ -77,7 +121,10 @@ public String getCity() { } public void setCity(String city) { - this.city = city; + if (!TextUtils.equals(this.city, city)) { + this.city = city; + notifyDataChanged(); + } } public String getZip() { @@ -85,7 +132,10 @@ public String getZip() { } public void setZip(String zip) { - this.zip = zip; + if (!TextUtils.equals(this.zip, zip)) { + this.zip = zip; + notifyDataChanged(); + } } public String getCountry() { @@ -93,7 +143,10 @@ public String getCountry() { } public void setCountry(String country) { - this.country = country; + if (!TextUtils.equals(this.country, country)) { + this.country = country; + notifyDataChanged(); + } } public String getBirthday() { @@ -101,7 +154,10 @@ public String getBirthday() { } public void setBirthday(String birthday) { - this.birthday = birthday; + if (!TextUtils.equals(this.birthday, birthday)) { + this.birthday = birthday; + notifyDataChanged(); + } } public CustomData getCustomData() { @@ -110,13 +166,8 @@ public CustomData getCustomData() { public void setCustomData(CustomData customData) { this.customData = customData; + this.customData.setDataChangedListener(this); } //endregion - - public void addCustomData(String key, Object value) { - customData.put(key, value); - } - - } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java index 0d67a6228..82ed5f801 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java @@ -56,7 +56,8 @@ public static com.apptentive.android.sdk.model.Person getDiffPayload(com.apptent } if (oldPerson == null || !oldPerson.getCustomData().equals(newPerson.getCustomData())) { - ret.setCustomData(newPerson.getCustomData().toJson()); + CustomData customData = newPerson.getCustomData(); + ret.setCustomData(customData != null ? customData.toJson() : null); } return ret; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Saveable.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Saveable.java new file mode 100644 index 000000000..07b076e43 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Saveable.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import java.io.Serializable; + + +public interface Saveable extends Serializable { + void setDataChangedListener(DataChangedListener listener); + void notifyDataChanged(); +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index 0cf354ab4..16863656f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -6,11 +6,9 @@ package com.apptentive.android.sdk.storage; -import com.apptentive.android.sdk.ApptentiveInternal; +import android.text.TextUtils; -import java.io.Serializable; - -public class SessionData implements Serializable { +public class SessionData implements Saveable, DataChangedListener { private static final long serialVersionUID = 1L; @@ -43,6 +41,31 @@ public SessionData() { this.versionHistory = new VersionHistory(); } + //region Listeners + private transient DataChangedListener listener; + + @Override + public void setDataChangedListener(DataChangedListener listener) { + this.listener = listener; + device.setDataChangedListener(this); + person.setDataChangedListener(this); + eventData.setDataChangedListener(this); + versionHistory.setDataChangedListener(this); + } + + @Override + public void notifyDataChanged() { + if (listener != null) { + listener.onDataChanged(); + } + } + + @Override + public void onDataChanged() { + notifyDataChanged(); + } + //endregion + //region Getters & Setters public String getConversationToken() { @@ -50,8 +73,10 @@ public String getConversationToken() { } public void setConversationToken(String conversationToken) { - this.conversationToken = conversationToken; - save(); + if (!TextUtils.equals(this.conversationToken, conversationToken)) { + this.conversationToken = conversationToken; + notifyDataChanged(); + } } public String getConversationId() { @@ -59,8 +84,10 @@ public String getConversationId() { } public void setConversationId(String conversationId) { - this.conversationId = conversationId; - save(); + if (!TextUtils.equals(this.conversationId, conversationId)) { + this.conversationId = conversationId; + notifyDataChanged(); + } } public String getPersonId() { @@ -68,8 +95,10 @@ public String getPersonId() { } public void setPersonId(String personId) { - this.personId = personId; - save(); + if (!TextUtils.equals(this.personId, personId)) { + this.personId = personId; + notifyDataChanged(); + } } public String getPersonEmail() { @@ -77,8 +106,10 @@ public String getPersonEmail() { } public void setPersonEmail(String personEmail) { - this.personEmail = personEmail; - save(); + if (!TextUtils.equals(this.personEmail, personEmail)) { + this.personEmail = personEmail; + notifyDataChanged(); + } } public String getPersonName() { @@ -86,8 +117,10 @@ public String getPersonName() { } public void setPersonName(String personName) { - this.personName = personName; - save(); + if (!TextUtils.equals(this.personName, personName)) { + this.personName = personName; + notifyDataChanged(); + } } public Device getDevice() { @@ -96,7 +129,8 @@ public Device getDevice() { public void setDevice(Device device) { this.device = device; - save(); + device.setDataChangedListener(this); + notifyDataChanged(); } public Device getLastSentDevice() { @@ -105,7 +139,7 @@ public Device getLastSentDevice() { public void setLastSentDevice(Device lastSentDevice) { this.lastSentDevice = lastSentDevice; - save(); + notifyDataChanged(); } public Person getPerson() { @@ -114,7 +148,8 @@ public Person getPerson() { public void setPerson(Person person) { this.person = person; - save(); + this.person.setDataChangedListener(this); + notifyDataChanged(); } public Person getLastSentPerson() { @@ -123,7 +158,7 @@ public Person getLastSentPerson() { public void setLastSentPerson(Person lastSentPerson) { this.lastSentPerson = lastSentPerson; - save(); + notifyDataChanged(); } public Sdk getSdk() { @@ -132,7 +167,7 @@ public Sdk getSdk() { public void setSdk(Sdk sdk) { this.sdk = sdk; - save(); + notifyDataChanged(); } public AppRelease getAppRelease() { @@ -141,7 +176,7 @@ public AppRelease getAppRelease() { public void setAppRelease(AppRelease appRelease) { this.appRelease = appRelease; - save(); + notifyDataChanged(); } public EventData getEventData() { @@ -150,6 +185,8 @@ public EventData getEventData() { public void setEventData(EventData eventData) { this.eventData = eventData; + this.eventData.setDataChangedListener(this); + notifyDataChanged(); } public String getLastSeenSdkVersion() { @@ -158,6 +195,7 @@ public String getLastSeenSdkVersion() { public void setLastSeenSdkVersion(String lastSeenSdkVersion) { this.lastSeenSdkVersion = lastSeenSdkVersion; + notifyDataChanged(); } public VersionHistory getVersionHistory() { @@ -166,6 +204,8 @@ public VersionHistory getVersionHistory() { public void setVersionHistory(VersionHistory versionHistory) { this.versionHistory = versionHistory; + this.versionHistory.setDataChangedListener(this); + notifyDataChanged(); } public boolean isMessageCenterFeatureUsed() { @@ -173,8 +213,10 @@ public boolean isMessageCenterFeatureUsed() { } public void setMessageCenterFeatureUsed(boolean messageCenterFeatureUsed) { - this.messageCenterFeatureUsed = messageCenterFeatureUsed; - save(); + if (this.messageCenterFeatureUsed != messageCenterFeatureUsed) { + this.messageCenterFeatureUsed = messageCenterFeatureUsed; + notifyDataChanged(); + } } public boolean isMessageCenterWhoCardPreviouslyDisplayed() { @@ -182,8 +224,10 @@ public boolean isMessageCenterWhoCardPreviouslyDisplayed() { } public void setMessageCenterWhoCardPreviouslyDisplayed(boolean messageCenterWhoCardPreviouslyDisplayed) { - this.messageCenterWhoCardPreviouslyDisplayed = messageCenterWhoCardPreviouslyDisplayed; - save(); + if (this.messageCenterWhoCardPreviouslyDisplayed != messageCenterWhoCardPreviouslyDisplayed) { + this.messageCenterWhoCardPreviouslyDisplayed = messageCenterWhoCardPreviouslyDisplayed; + notifyDataChanged(); + } } public String getMessageCenterPendingMessage() { @@ -191,8 +235,10 @@ public String getMessageCenterPendingMessage() { } public void setMessageCenterPendingMessage(String messageCenterPendingMessage) { - this.messageCenterPendingMessage = messageCenterPendingMessage; - save(); + if (!TextUtils.equals(this.messageCenterPendingMessage, messageCenterPendingMessage)) { + this.messageCenterPendingMessage = messageCenterPendingMessage; + notifyDataChanged(); + } } public String getMessageCenterPendingAttachments() { @@ -200,8 +246,10 @@ public String getMessageCenterPendingAttachments() { } public void setMessageCenterPendingAttachments(String messageCenterPendingAttachments) { - this.messageCenterPendingAttachments = messageCenterPendingAttachments; - save(); + if (!TextUtils.equals(this.messageCenterPendingAttachments, messageCenterPendingAttachments)) { + this.messageCenterPendingAttachments = messageCenterPendingAttachments; + notifyDataChanged(); + } } public String getTargets() { @@ -209,7 +257,10 @@ public String getTargets() { } public void setTargets(String targets) { - this.targets = targets; + if (!TextUtils.equals(this.targets, targets)) { + this.targets = targets; + notifyDataChanged(); + } } public String getInteractions() { @@ -217,7 +268,10 @@ public String getInteractions() { } public void setInteractions(String interactions) { - this.interactions = interactions; + if (!TextUtils.equals(this.interactions, interactions)) { + this.interactions = interactions; + notifyDataChanged(); + } } public long getInteractionExpiration() { @@ -225,19 +279,11 @@ public long getInteractionExpiration() { } public void setInteractionExpiration(long interactionExpiration) { - this.interactionExpiration = interactionExpiration; + if (this.interactionExpiration != interactionExpiration) { + this.interactionExpiration = interactionExpiration; + notifyDataChanged(); + } } //endregion - - // TODO: Only save when a value has changed. - public void save() { - ApptentiveInternal.getInstance().saveSessionData(); - } - - // version history - // code point store - // Various prefs - // Messages - // Interactions } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java index ba19abc47..bd30983cd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java @@ -10,13 +10,12 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.util.Util; -import java.io.Serializable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -public class VersionHistory implements Serializable { +public class VersionHistory implements Saveable { /** * An ordered list of version history. Older versions are first, new versions are added to the end. @@ -24,9 +23,25 @@ public class VersionHistory implements Serializable { private List versionHistoryItems; public VersionHistory() { - this.versionHistoryItems = new ArrayList<>(); + versionHistoryItems = new ArrayList<>(); } + //region Listeners + private transient DataChangedListener listener; + + @Override + public void setDataChangedListener(DataChangedListener listener) { + this.listener = listener; + } + + @Override + public void notifyDataChanged() { + if (listener != null) { + listener.onDataChanged(); + } + } + //endregion + public void updateVersionHistory(double timestamp, Integer newVersionCode, String newVersionName) { boolean exists = false; for (VersionHistoryItem item : versionHistoryItems) { @@ -38,6 +53,7 @@ public void updateVersionHistory(double timestamp, Integer newVersionCode, Strin if (!exists) { VersionHistoryItem newVersionHistoryItem = new VersionHistoryItem(timestamp, newVersionCode, newVersionName); versionHistoryItems.add(newVersionHistoryItem); + notifyDataChanged(); } } From 8621eec94a7a972aad41f084319707d4a575d09f Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 26 Jan 2017 13:52:10 -0800 Subject: [PATCH 037/465] Added missing copyrights Lunar Unity Mobile Console https://github.com/SpaceMadness/lunar-unity-console --- .../apptentive/android/sdk/debug/Assert.java | 21 ++++++++++++++++ .../android/sdk/util/ObjectUtils.java | 21 ++++++++++++++++ .../android/sdk/util/StringUtils.java | 21 ++++++++++++++++ .../sdk/util/threading/DispatchQueue.java | 25 +++++++++++++++---- .../util/threading/HandlerDispatchQueue.java | 25 +++++++++++++++---- 5 files changed, 103 insertions(+), 10 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java index 92810f51c..593d08835 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -1,3 +1,24 @@ +// +// Assert.java +// +// Lunar Unity Mobile Console +// https://github.com/SpaceMadness/lunar-unity-console +// +// Copyright 2017 Alex Lementuev, SpaceMadness. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + package com.apptentive.android.sdk.debug; import java.util.Collection; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java index e86229872..21f21d4da 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java @@ -1,3 +1,24 @@ +// +// ObjectUtils.java +// +// Lunar Unity Mobile Console +// https://github.com/SpaceMadness/lunar-unity-console +// +// Copyright 2017 Alex Lementuev, SpaceMadness. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + package com.apptentive.android.sdk.util; import static com.apptentive.android.sdk.debug.Assert.*; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java index e001af06e..d89e40423 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java @@ -1,3 +1,24 @@ +// +// StringUtils.java +// +// Lunar Unity Mobile Console +// https://github.com/SpaceMadness/lunar-unity-console +// +// Copyright 2017 Alex Lementuev, SpaceMadness. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + package com.apptentive.android.sdk.util; import java.util.List; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java index cd05bf745..c7cfb455a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java @@ -1,8 +1,23 @@ -/* - * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ +// +// DispatchQueue.java +// +// Lunar Unity Mobile Console +// https://github.com/SpaceMadness/lunar-unity-console +// +// Copyright 2017 Alex Lementuev, SpaceMadness. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package com.apptentive.android.sdk.util.threading; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java index 38f6d60ff..afdfd3a2a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java @@ -1,8 +1,23 @@ -/* - * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ +// +// HandlerDispatchQueue.java +// +// Lunar Unity Mobile Console +// https://github.com/SpaceMadness/lunar-unity-console +// +// Copyright 2017 Alex Lementuev, SpaceMadness. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package com.apptentive.android.sdk.util.threading; From 07eeccd8f2191928bd23ba9daf51cc77f9a69ca5 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 26 Jan 2017 16:25:50 -0800 Subject: [PATCH 038/465] Use Handler for Conversation creation. Also fix UUID setting. --- .../android/sdk/ApptentiveInternal.java | 169 +++++++----------- .../android/sdk/storage/DeviceManager.java | 5 +- .../com/apptentive/android/sdk/util/Util.java | 10 +- 3 files changed, 76 insertions(+), 108 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 837caa2db..5dd4cdb2c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -405,15 +405,6 @@ public void scheduleOnWorkerThread(Runnable r) { cachedExecutor.submit(r); } - public void checkAndUpdateApptentiveConfigurations() { - // Initialize the Conversation Token, or fetch if needed. Fetch config it the token is available. - if (sessionData == null) { - asyncFetchConversationToken(); - } else { - asyncFetchAppConfigurationAndInteractions(); - } - } - public void onAppLaunch(final Context appContext) { EngagementModule.engageInternal(appContext, Event.EventLabel.app__launch.getLabelName()); } @@ -428,9 +419,6 @@ public void onActivityStarted(Activity activity) { currentTaskStackTopActivity = new WeakReference(activity); messageManager.setCurrentForegroundActivity(activity); } - - //FIXME: Kick this off on Application initialization instead. - checkAndUpdateApptentiveConfigurations(); } public void onActivityResumed(Activity activity) { @@ -523,7 +511,6 @@ public boolean init() { */ VersionHistoryItem lastVersionItemSeen = null; - String lastSeenSdkVersion = null; File internalStorage = appContext.getFilesDir(); File sessionDataFile = new File(internalStorage, "apptentive/SessionData.ser"); @@ -540,7 +527,8 @@ public boolean init() { messageManager.init(); } lastVersionItemSeen = sessionData.getVersionHistory().getLastVersionSeen(); - lastSeenSdkVersion = sessionData.getLastSeenSdkVersion(); + } else { + scheduleConversationCreation(); } apptentiveToolbarTheme = appContext.getResources().newTheme(); @@ -614,8 +602,11 @@ public boolean init() { } ApptentiveLog.i("Debug mode enabled? %b", isAppDebuggable); - if (!TextUtils.equals(lastSeenSdkVersion, Constants.APPTENTIVE_SDK_VERSION)) { - onSdkVersionChanged(appContext, lastSeenSdkVersion, Constants.APPTENTIVE_SDK_VERSION); + // TODO: Move this into a session became active handler. + if (sessionData != null) { + if (!TextUtils.equals(sessionData.getLastSeenSdkVersion(), Constants.APPTENTIVE_SDK_VERSION)) { + onSdkVersionChanged(sessionData.getLastSeenSdkVersion(), Constants.APPTENTIVE_SDK_VERSION); + } } // The apiKey can be passed in programmatically, or we can fallback to checking in the manifest. @@ -637,8 +628,6 @@ public boolean init() { ApptentiveLog.d("Apptentive API Key: %s", apiKey); // Grab app info we need to access later on. - androidId = Settings.Secure.getString(appContext.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID); - ApptentiveLog.d("Android ID: ", androidId); ApptentiveLog.d("Default Locale: %s", Locale.getDefault().toString()); return bRet; } @@ -660,8 +649,8 @@ private void onVersionChanged(Integer previousVersionCode, Integer currentVersio } // FIXME: Do this kind of thing when a session becomes active instead of at app initialization? Otherwise there is no active session to send the information to. - private void onSdkVersionChanged(Context context, String previousSdkVersion, String currentSdkVersion) { - ApptentiveLog.i("SDK version changed: %s => %s", previousSdkVersion, currentSdkVersion); + private void onSdkVersionChanged(String lastSeenSdkVersion, String currentSdkVersion) { + ApptentiveLog.i("SDK version changed: %s => %s", lastSeenSdkVersion, currentSdkVersion); Sdk sdk = SdkManager.generateCurrentSdk(); taskManager.addPayload(SdkManager.getPayload(sdk)); if (sessionData != null) { @@ -684,95 +673,58 @@ private void invalidateCaches() { config.save(); } - private synchronized void asyncFetchConversationToken() { - if (isConversationTokenFetchPending.compareAndSet(false, true)) { - AsyncTask fetchConversationTokenTask = new AsyncTask() { - private Exception e = null; + private boolean fetchConversationToken() { + try { + if (isConversationTokenFetchPending.compareAndSet(false, true)) { + ApptentiveLog.i("Fetching Configuration token task started."); - @Override - protected Boolean doInBackground(Void... params) { - try { - return fetchConversationToken(); - } catch (Exception e) { - // Hold onto the unhandled exception from fetchConversationToken() for later handling in UI thread - this.e = e; - } + // Try to fetch a new one from the server. + ConversationTokenRequest request = new ConversationTokenRequest(); + + // Send the Device and Sdk now, so they are available on the server from the start. + Device device = DeviceManager.generateNewDevice(appContext); + Sdk sdk = SdkManager.generateCurrentSdk(); + AppRelease appRelease = AppReleaseManager.generateCurrentAppRelease(appContext); + + request.setDevice(DeviceManager.getDiffPayload(null, device)); + request.setSdk(SdkManager.getPayload(sdk)); + request.setAppRelease(AppReleaseManager.getPayload(appRelease)); + + ApptentiveHttpResponse response = ApptentiveClient.getConversationToken(request); + if (response == null) { + ApptentiveLog.w("Got null response fetching ConversationToken."); return false; } - - @Override - protected void onPostExecute(Boolean successful) { - if (e == null) { - // Update pending state on UI thread after finishing the task - ApptentiveLog.d("Fetching conversation token asyncTask finished. Successful? %b", successful); - isConversationTokenFetchPending.set(false); - if (successful) { - // Once token is fetched successfully, start asyncTasks to fetch global configuration, then interaction - asyncFetchAppConfigurationAndInteractions(); + if (response.isSuccessful()) { + try { + JSONObject root = new JSONObject(response.getContent()); + String conversationToken = root.getString("token"); + ApptentiveLog.d("ConversationToken: " + conversationToken); + String conversationId = root.getString("id"); + ApptentiveLog.d("New Conversation id: %s", conversationId); + + sessionData = new SessionData(); + sessionData.setDataChangedListener(this); + if (conversationToken != null && !conversationToken.equals("")) { + sessionData.setConversationToken(conversationToken); + sessionData.setConversationId(conversationId); + sessionData.setDevice(device); + sessionData.setSdk(sdk); + sessionData.setAppRelease(appRelease); } - } else { - ApptentiveLog.w("Unhandled Exception thrown from fetching conversation token asyncTask", e); - MetricModule.sendError(e, null, null); + String personId = root.getString("person_id"); + ApptentiveLog.d("PersonId: " + personId); + sessionData.setPersonId(personId); + return true; + } catch (JSONException e) { + ApptentiveLog.e("Error parsing ConversationToken response json.", e); } } - - }; - - ApptentiveLog.i("Fetching conversation token asyncTask scheduled."); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - fetchConversationTokenTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else { - fetchConversationTokenTask.execute(); - } - } else { - ApptentiveLog.v("Fetching Configuration pending"); - } - } - - - private boolean fetchConversationToken() { - ApptentiveLog.i("Fetching Configuration token task started."); - // Try to fetch a new one from the server. - ConversationTokenRequest request = new ConversationTokenRequest(); - - // Send the Device and Sdk now, so they are available on the server from the start. - Device device = DeviceManager.generateNewDevice(); - Sdk sdk = SdkManager.generateCurrentSdk(); - AppRelease appRelease = AppReleaseManager.generateCurrentAppRelease(appContext); - - request.setDevice(DeviceManager.getDiffPayload(null, device)); - request.setSdk(SdkManager.getPayload(sdk)); - request.setAppRelease(AppReleaseManager.getPayload(appRelease)); - - ApptentiveHttpResponse response = ApptentiveClient.getConversationToken(request); - if (response == null) { - ApptentiveLog.w("Got null response fetching ConversationToken."); - return false; - } - if (response.isSuccessful()) { - try { - JSONObject root = new JSONObject(response.getContent()); - String conversationToken = root.getString("token"); - ApptentiveLog.d("ConversationToken: " + conversationToken); - String conversationId = root.getString("id"); - ApptentiveLog.d("New Conversation id: %s", conversationId); - - sessionData = new SessionData(); - sessionData.setDataChangedListener(this); - if (conversationToken != null && !conversationToken.equals("")) { - sessionData.setConversationToken(conversationToken); - sessionData.setConversationId(conversationId); - sessionData.setDevice(device); - sessionData.setSdk(sdk); - sessionData.setAppRelease(appRelease); - } - String personId = root.getString("person_id"); - ApptentiveLog.d("PersonId: " + personId); - sessionData.setPersonId(personId); - return true; - } catch (JSONException e) { - ApptentiveLog.e("Error parsing ConversationToken response json.", e); + ApptentiveLog.v("Fetching Configuration pending"); } + } finally { + isConversationTokenFetchPending.set(false); } return false; } @@ -1115,8 +1067,7 @@ public static boolean checkRegistered() { // Multi-tenancy work - public void scheduleSessionDataSave() { - ApptentiveLog.e("Schedule SessionData save?"); + private synchronized void scheduleSessionDataSave() { if (!backgroundHandler.hasMessages(MESSAGE_SAVE_SESSION_DATA)) { ApptentiveLog.e("Scheduling SessionData save."); backgroundHandler.sendEmptyMessageDelayed(MESSAGE_SAVE_SESSION_DATA, 100); @@ -1125,8 +1076,15 @@ public void scheduleSessionDataSave() { } } + private synchronized void scheduleConversationCreation() { + if (!backgroundHandler.hasMessages(MESSAGE_CREATE_CONVERSATION)) { + backgroundHandler.sendEmptyMessage(MESSAGE_CREATE_CONVERSATION); + } + } + private static final int MESSAGE_SAVE_SESSION_DATA = 0; private static final int MESSAGE_CREATE_CONVERSATION = 1; + private static final int MESSAGE_FETCH_CONFIGURATION = 2; @Override public boolean handleMessage(Message msg) { @@ -1139,7 +1097,10 @@ public boolean handleMessage(Message msg) { } break; case MESSAGE_CREATE_CONVERSATION: - // TODO: This. + fetchConversationToken(); + break; + case MESSAGE_FETCH_CONFIGURATION: + // TODO break; } return false; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java index de36062d8..a9d77fd79 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java @@ -12,6 +12,7 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.util.Util; import java.util.Locale; import java.util.TimeZone; @@ -21,7 +22,7 @@ */ public class DeviceManager { - public static Device generateNewDevice() { + public static Device generateNewDevice(Context context) { Device device = new Device(); // First, get all the information we can load from static resources. @@ -36,7 +37,7 @@ public static Device generateNewDevice() { device.setBrand(Build.BRAND); device.setCpu(Build.CPU_ABI); device.setDevice(Build.DEVICE); - device.setUuid(ApptentiveInternal.getInstance().getAndroidId()); + device.setUuid(Util.getAndroidId(context)); device.setBuildType(Build.TYPE); device.setBuildId(Build.ID); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java index 6d9975d1b..82eea2ca6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java @@ -29,6 +29,7 @@ import android.os.Build; import android.os.Environment; import android.provider.MediaStore; +import android.provider.Settings; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.text.TextUtils; @@ -36,8 +37,6 @@ import android.view.*; import android.view.inputmethod.InputMethodManager; import android.webkit.URLUtil; -import android.widget.ListAdapter; -import android.widget.ListView; import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; @@ -840,4 +839,11 @@ public static String humanReadableByteCount(long bytes, boolean si) { String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); } + + public static String getAndroidId(Context context) { + if (context == null) { + return null; + } + return Settings.Secure.getString(context.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID); + } } From 3e4615bba72dd184255b5f21ba7d6623f92bbd22 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 26 Jan 2017 16:31:47 -0800 Subject: [PATCH 039/465] Add `serialVersionUID` to all serialized classes --- .../java/com/apptentive/android/sdk/storage/CustomData.java | 2 ++ .../main/java/com/apptentive/android/sdk/storage/Device.java | 2 ++ .../main/java/com/apptentive/android/sdk/storage/EventData.java | 2 ++ .../com/apptentive/android/sdk/storage/IntegrationConfig.java | 2 ++ .../main/java/com/apptentive/android/sdk/storage/Person.java | 2 ++ .../java/com/apptentive/android/sdk/storage/VersionHistory.java | 2 ++ 6 files changed, 12 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java index 168db52a2..a9da62ff7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java @@ -14,6 +14,8 @@ public class CustomData extends HashMap implements Saveable { + private static final long serialVersionUID = 1L; + //region Listeners private transient DataChangedListener listener; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java index 0923b0e04..bf4cd9ff1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java @@ -10,6 +10,8 @@ public class Device implements Saveable, DataChangedListener { + private static final long serialVersionUID = 1L; + private String uuid; private String osName; private String osVersion; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java index 3b21ca467..7a329b825 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java @@ -16,6 +16,8 @@ */ public class EventData implements Saveable { + private static final long serialVersionUID = 1L; + private Map events; private Map interactions; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java index ab046eb80..1c8b1962f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java @@ -11,6 +11,8 @@ public class IntegrationConfig implements Saveable { + private static final long serialVersionUID = 1L; + private static final String INTEGRATION_APPTENTIVE_PUSH = "apptentive_push"; private static final String INTEGRATION_AWS_SNS = "aws_sns"; private static final String INTEGRATION_URBAN_AIRSHIP = "urban_airship"; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java index e2275310a..c368d2468 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java @@ -10,6 +10,8 @@ public class Person implements Saveable, DataChangedListener { + private static final long serialVersionUID = 1L; + private String id; private String email; private String name; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java index bd30983cd..0640be930 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java @@ -17,6 +17,8 @@ public class VersionHistory implements Saveable { + private static final long serialVersionUID = 1L; + /** * An ordered list of version history. Older versions are first, new versions are added to the end. */ From f45ed5aed05b187d06063c03643b655e331597c7 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 26 Jan 2017 16:51:18 -0800 Subject: [PATCH 040/465] Test the rest of `SessionData` Need to mock `TextUtils` to make this run on the JVM --- apptentive/build.gradle | 4 ++ .../android/sdk/storage/SessionDataTest.java | 67 +++++++++++++++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/apptentive/build.gradle b/apptentive/build.gradle index 5c03638d2..2eb356935 100644 --- a/apptentive/build.gradle +++ b/apptentive/build.gradle @@ -12,6 +12,10 @@ dependencies { compile 'com.android.support:cardview-v7:24.2.1' compile 'com.android.support:design:24.2.1' testCompile 'junit:junit:4.12' + testCompile "org.powermock:powermock-module-junit4:1.6.2" + testCompile "org.powermock:powermock-module-junit4-rule:1.6.2" + testCompile "org.powermock:powermock-api-mockito:1.6.2" + testCompile "org.powermock:powermock-classloading-xstream:1.6.2" } android { diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java index e7bb43f22..2094354a2 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java @@ -6,9 +6,18 @@ package com.apptentive.android.sdk.storage; +import android.text.TextUtils; + import com.apptentive.android.sdk.util.Util; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -17,17 +26,53 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +@RunWith(PowerMockRunner.class) +@PrepareForTest(TextUtils.class) public class SessionDataTest { + @Before + public void setup() { + PowerMockito.mockStatic(TextUtils.class); + PowerMockito.when(TextUtils.isEmpty(any(CharSequence.class))).thenAnswer(new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + CharSequence a = (CharSequence) invocation.getArguments()[0]; + return !(a != null && a.length() > 0); + } + }); + } + @Test public void testSerialization() { - String conversationId = "jvnuveanesndndnadldbj"; - String conversationToken = "watgsiovncsagjmcneiusdolnfcs"; - SessionData expected = new SessionData(); - expected.setConversationId(conversationId); - expected.setConversationToken(conversationToken); + expected.setConversationId("jvnuveanesndndnadldbj"); + expected.setConversationToken("watgsiovncsagjmcneiusdolnfcs"); + expected.setPersonId("sijngmkmvewsnblkfmsd"); + expected.setPersonEmail("nvuewnfaslvbgflkanbx"); + expected.setPersonName("fslkgkdsnbnvwasdibncksd"); + expected.setLastSeenSdkVersion("mdvnjfuoivsknbjgfaoskdl"); + expected.setMessageCenterFeatureUsed(true); + expected.setMessageCenterWhoCardPreviouslyDisplayed(false); + expected.setMessageCenterPendingMessage("`~!@#$%^&*(_+{}:\"'<>?!@#$%^&*()_+{}|:<>?"); + expected.setMessageCenterPendingAttachments("NVBUOIVKNBGFANWKSLBJK"); + expected.setTargets("MNCIUFIENVBFKDV"); + expected.setInteractions("nkjvdfikjbffasldnbnfldfmfd"); + expected.setInteractionExpiration(1234567894567890345L); + + /* + // TODO: Test these + private Device device; + private Device lastSentDevice; + private Person person; + private Person lastSentPerson; + private Sdk sdk; + private AppRelease appRelease; + private EventData eventData; + private VersionHistory versionHistory; + */ + ByteArrayOutputStream baos = null; ObjectOutputStream oos = null; @@ -46,6 +91,18 @@ public void testSerialization() { SessionData result = (SessionData) ois.readObject(); assertEquals(expected.getConversationId(), result.getConversationId()); assertEquals(expected.getConversationToken(), result.getConversationToken()); + assertEquals(expected.getPersonId(), result.getPersonId()); + assertEquals(expected.getPersonName(), result.getPersonName()); + assertEquals(expected.getPersonEmail(), result.getPersonEmail()); + assertEquals(expected.getLastSeenSdkVersion(), result.getLastSeenSdkVersion()); + assertEquals(expected.isMessageCenterFeatureUsed(), result.isMessageCenterFeatureUsed()); + assertEquals(expected.isMessageCenterWhoCardPreviouslyDisplayed(), result.isMessageCenterWhoCardPreviouslyDisplayed()); + assertEquals(expected.getMessageCenterPendingMessage(), result.getMessageCenterPendingMessage()); + assertEquals(expected.getMessageCenterPendingAttachments(), result.getMessageCenterPendingAttachments()); + assertEquals(expected.getTargets(), result.getTargets()); + assertEquals(expected.getInteractions(), result.getInteractions()); + assertEquals(expected.getInteractionExpiration(), result.getInteractionExpiration()); + } catch (Exception e) { fail(e.getMessage()); } finally { From c7a54302ee7f11aba493bd21e38d78a594a61b4b Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 26 Jan 2017 18:30:21 -0800 Subject: [PATCH 041/465] Refactor `EventData` to ease testing Stop depending on `ApptentiveInternal` --- .../android/sdk/module/engagement/EngagementModule.java | 8 ++++++-- .../com/apptentive/android/sdk/storage/EventData.java | 8 ++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java index e341182e0..5192325ac 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java @@ -56,7 +56,9 @@ public static synchronized boolean engage(Context context, String vendor, String SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { - sessionData.getEventData().storeEventForCurrentAppVersion(Util.currentTimeSeconds(), eventLabel); + String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); + int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); + sessionData.getEventData().storeEventForCurrentAppVersion(Util.currentTimeSeconds(), versionCode, versionName, eventLabel); EventManager.sendEvent(new Event(eventLabel, interactionId, data, customData, extendedData)); return doEngage(context, eventLabel); } @@ -71,7 +73,9 @@ public static boolean doEngage(Context context, String eventLabel) { if (interaction != null) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { - sessionData.getEventData().storeInteractionForCurrentAppVersion(Util.currentTimeSeconds(), interaction.getId()); + String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); + int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); + sessionData.getEventData().storeInteractionForCurrentAppVersion(Util.currentTimeSeconds(), versionCode, versionName, interaction.getId()); } launchInteraction(context, interaction); return true; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java index 7a329b825..919608154 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java @@ -45,27 +45,23 @@ public void notifyDataChanged() { // FIXME: Find all usage of this and ensure they use the same timestamp for saving events and runnign interaction queries. - public synchronized void storeEventForCurrentAppVersion(double timestamp, String eventLabel) { + public synchronized void storeEventForCurrentAppVersion(double timestamp, int versionCode, String versionName, String eventLabel) { EventRecord eventRecord = events.get(eventLabel); if (eventRecord == null) { eventRecord = new EventRecord(); events.put(eventLabel, eventRecord); } - String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); - int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); eventRecord.update(timestamp, versionName, versionCode); notifyDataChanged(); } // FIXME: Find all usage of this and ensure they use the same timestamp for saving events and runnign interaction queries. - public synchronized void storeInteractionForCurrentAppVersion(double timestamp, String interactionId) { + public synchronized void storeInteractionForCurrentAppVersion(double timestamp, int versionCode, String versionName, String interactionId) { EventRecord eventRecord = interactions.get(interactionId); if (eventRecord == null) { eventRecord = new EventRecord(); interactions.put(interactionId, eventRecord); } - String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); - int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); eventRecord.update(timestamp, versionName, versionCode); notifyDataChanged(); } From 23c2f76d2fd038f9c66c5e359ab650756c5b393f Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 26 Jan 2017 18:32:24 -0800 Subject: [PATCH 042/465] Fix listener firing in `SessionData` and `Person` --- .../main/java/com/apptentive/android/sdk/storage/Person.java | 1 + .../java/com/apptentive/android/sdk/storage/SessionData.java | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java index c368d2468..9efe02d66 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java @@ -169,6 +169,7 @@ public CustomData getCustomData() { public void setCustomData(CustomData customData) { this.customData = customData; this.customData.setDataChangedListener(this); + notifyDataChanged(); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index 16863656f..ca8d22667 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -37,6 +37,8 @@ public class SessionData implements Saveable, DataChangedListener { public SessionData() { this.device = new Device(); this.person = new Person(); + this.sdk = new Sdk(); + this.appRelease = new AppRelease(); this.eventData = new EventData(); this.versionHistory = new VersionHistory(); } @@ -139,6 +141,7 @@ public Device getLastSentDevice() { public void setLastSentDevice(Device lastSentDevice) { this.lastSentDevice = lastSentDevice; + this.lastSentDevice.setDataChangedListener(this); notifyDataChanged(); } @@ -158,6 +161,7 @@ public Person getLastSentPerson() { public void setLastSentPerson(Person lastSentPerson) { this.lastSentPerson = lastSentPerson; + this.lastSentPerson.setDataChangedListener(this); notifyDataChanged(); } From 45f1e4ec2d3ede47cd26e845d1817787f6b30a6f Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 26 Jan 2017 18:32:58 -0800 Subject: [PATCH 043/465] Add test for `SessionData` listeners --- .../android/sdk/storage/SessionDataTest.java | 234 +++++++++++++++++- 1 file changed, 225 insertions(+), 9 deletions(-) diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java index 2094354a2..d3136b93f 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java @@ -25,6 +25,8 @@ import java.io.ObjectOutputStream; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; @@ -62,15 +64,7 @@ public void testSerialization() { expected.setInteractionExpiration(1234567894567890345L); /* - // TODO: Test these - private Device device; - private Device lastSentDevice; - private Person person; - private Person lastSentPerson; - private Sdk sdk; - private AppRelease appRelease; - private EventData eventData; - private VersionHistory versionHistory; + // TODO: Test nested objects as well */ @@ -111,7 +105,229 @@ public void testSerialization() { Util.ensureClosed(bais); Util.ensureClosed(ois); } + } + + private boolean listenerFired; + + @Test + public void testListeners() { + + DataChangedListener listener = new DataChangedListener() { + @Override + public void onDataChanged() { + listenerFired = true; + } + }; + + SessionData sessionData = new SessionData(); + sessionData.setDataChangedListener(listener); + assertFalse(listenerFired); + + sessionData.setConversationToken("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setConversationId("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setPersonId("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setPersonEmail("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setPersonName("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setLastSeenSdkVersion("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setMessageCenterFeatureUsed(true); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setMessageCenterWhoCardPreviouslyDisplayed(true); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setMessageCenterPendingMessage("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setMessageCenterPendingAttachments("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setInteractions("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setTargets("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setInteractionExpiration(1000L); + assertTrue(listenerFired); + listenerFired = false; + + + sessionData.getDevice().getCustomData().put("foo", "bar"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getDevice().getCustomData().remove("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setDevice(new Device()); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getDevice().getCustomData().put("foo", "bar"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getDevice().getIntegrationConfig().setAmazonAwsSns(new IntegrationConfigItem()); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getDevice().setIntegrationConfig(new IntegrationConfig()); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getDevice().getIntegrationConfig().setAmazonAwsSns(new IntegrationConfigItem()); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getDevice().setOsApiLevel(5); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getDevice().setUuid("foo"); + assertTrue(listenerFired); + listenerFired = false; + sessionData.setLastSentDevice(new Device()); + assertTrue(listenerFired); + listenerFired = false; + sessionData.getLastSentDevice().setUuid("foo"); + assertTrue(listenerFired); + listenerFired = false; + + + sessionData.setPerson(new Person()); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getPerson().setId("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getPerson().setEmail("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getPerson().setName("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getPerson().setFacebookId("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getPerson().setPhoneNumber("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getPerson().setStreet("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getPerson().setCity("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getPerson().setZip("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getPerson().setCountry("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getPerson().setBirthday("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getPerson().setCustomData(new CustomData()); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getPerson().getCustomData().put("foo", "bar"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getPerson().getCustomData().remove("foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setLastSentPerson(new Person()); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getLastSentPerson().setId("foo"); + assertTrue(listenerFired); + listenerFired = false; + + + sessionData.setSdk(new Sdk()); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setAppRelease(new AppRelease()); + assertTrue(listenerFired); + listenerFired = false; + + + sessionData.getVersionHistory().updateVersionHistory(100D, 1, "1"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setVersionHistory(new VersionHistory()); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getVersionHistory().updateVersionHistory(100D, 1, "1"); + assertTrue(listenerFired); + listenerFired = false; + + + sessionData.getEventData().storeEventForCurrentAppVersion(100D, 10, "1.0", "foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getEventData().storeInteractionForCurrentAppVersion(100D, 10, "1.0", "foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.setEventData(new EventData()); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getEventData().storeEventForCurrentAppVersion(100D, 10, "1.0", "foo"); + assertTrue(listenerFired); + listenerFired = false; + + sessionData.getEventData().storeInteractionForCurrentAppVersion(100D, 10, "1.0", "foo"); + assertTrue(listenerFired); + listenerFired = false; } + + // TODO: Add a test for verifying that setting an existing value doesn't fire listeners. } \ No newline at end of file From 6c3853f1124c7f82fbd8ef2079fe05980958dceb Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 27 Jan 2017 13:10:08 -0800 Subject: [PATCH 044/465] Resolve merge compilation breakage --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index b5450c109..f7ac348c1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -25,7 +25,6 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Message; -import android.provider.Settings; import android.support.v4.content.ContextCompat; import android.text.TextUtils; @@ -33,8 +32,6 @@ import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.lifecycle.ApptentiveActivityLifecycleCallbacks; import com.apptentive.android.sdk.listeners.OnUserLogOutListener; -import com.apptentive.android.sdk.model.AppRelease; -import com.apptentive.android.sdk.model.CodePointStore; import com.apptentive.android.sdk.model.Configuration; import com.apptentive.android.sdk.model.ConversationTokenRequest; import com.apptentive.android.sdk.model.Event; From 6c19c5312ad3d177bea030bd6e421d740279be0e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 30 Jan 2017 12:18:58 -0800 Subject: [PATCH 045/465] Added support for private queues in DispatchQueue class --- .../sdk/util/threading/DispatchQueue.java | 15 ++++++++ .../util/threading/HandlerDispatchQueue.java | 36 ++++++++++++++++++- .../sdk/util/threading/TestDispatchQueue.java | 10 ++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java index c7cfb455a..1ead7c3ed 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java @@ -32,6 +32,14 @@ public abstract class DispatchQueue { */ public abstract void dispatchAsync(Runnable runnable); + /** + * Add {@link Runnable} task to the queue after delay milliseconds + */ + public abstract void dispatchAfter(Runnable runnable, long delay); + + /** Stops queue execution and cancels all tasks (cleanup function for private queues) */ + public abstract void stop(); + /** * A global dispatch queue associated with main thread */ @@ -39,6 +47,13 @@ public static DispatchQueue mainQueue() { return Holder.INSTANCE; } + /** + * Creates a private serial queue with specified name on a background thread + */ + public static DispatchQueue createBackgroundQueue(String name) { + return new HandlerDispatchQueue(name); + } + /** * Thread safe singleton trick */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java index afdfd3a2a..cb996e6ff 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java @@ -22,20 +22,54 @@ package com.apptentive.android.sdk.util.threading; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; -public class HandlerDispatchQueue extends DispatchQueue { +import static com.apptentive.android.sdk.debug.Assert.assertNotNull; + +/** + * {@link DispatchQueue} implementation using {@link Handler} + */ +class HandlerDispatchQueue extends DispatchQueue { private final Handler handler; + private final HandlerThread handlerThread; + + /** + * Creates a private queue with specified name + */ + public HandlerDispatchQueue(String name) { + handlerThread = new HandlerThread(name); + handlerThread.start(); + handler = new Handler(handlerThread.getLooper()); + } + /** + * Creates a queue with specified looper + */ public HandlerDispatchQueue(Looper looper) { if (looper == null) { throw new NullPointerException("Looper is null"); } handler = new Handler(looper); + handlerThread = null; // non-private queue (no explicit thread) } @Override public void dispatchAsync(Runnable runnable) { handler.post(runnable); } + + @Override + public void dispatchAfter(Runnable runnable, long delay) { + handler.postDelayed(runnable, delay); + } + + @Override + public void stop() { + assertNotNull(handlerThread, "Attempted to stop a non-private queue '%s'", handler.getLooper().getThread()); + if (handlerThread != null) { + handler.removeCallbacks(null); + handlerThread.quit(); + } + } } \ No newline at end of file diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java index 3b9126c3d..1a59404a7 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java @@ -15,6 +15,16 @@ public void dispatchAsync(Runnable runnable) { runnable.run(); } + @Override + public void dispatchAfter(Runnable runnable, long delay) { + runnable.run(); // TODO: figure out a good way of testing this case + } + + @Override + public void stop() { + // do nothing + } + public static void overrideMainQueue() { overrideMainQueue(new TestDispatchQueue()); } From 2b482e92b27173e4aeae7afdcc92bc1154eba3c5 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 30 Jan 2017 13:53:13 -0800 Subject: [PATCH 046/465] DispatchQueue refactoring Switching from Runnable to DispatchTask --- .../registry/ApptentiveComponentRegistry.java | 5 ++- .../sdk/util/threading/DispatchQueue.java | 27 +++++++++--- .../sdk/util/threading/DispatchTask.java | 44 +++++++++++++++++++ .../util/threading/HandlerDispatchQueue.java | 9 +--- .../sdk/util/threading/TestDispatchQueue.java | 7 +-- 5 files changed, 71 insertions(+), 21 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java index 5503520b2..5fb004442 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java @@ -2,6 +2,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchTask; import java.util.ArrayList; import java.util.List; @@ -117,9 +118,9 @@ private synchronized int indexOf(ApptentiveComponent component) { */ public synchronized void notifyComponents(final ComponentNotifier notifier) { // in order to avoid UI-related issues - we dispatch all the notification on main thread - DispatchQueue.mainQueue().dispatchAsync(new Runnable() { + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { @Override - public void run() { + protected void execute() { notifyComponentsSafe(notifier); } }); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java index 1ead7c3ed..639817979 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java @@ -24,20 +24,35 @@ import android.os.Looper; /** - * A class representing dispatch queue where {@link Runnable} tasks can be executed serially + * A class representing dispatch queue where {@link DispatchTask} tasks can be executed + * serially */ public abstract class DispatchQueue { + + protected abstract void dispatch(DispatchTask task); + /** - * Add {@link Runnable} task to the queue + * Add {@link DispatchTask} to the queue */ - public abstract void dispatchAsync(Runnable runnable); + public void dispatchAsync(DispatchTask task) { + task.setScheduled(true); + dispatch(task); + } /** - * Add {@link Runnable} task to the queue after delay milliseconds + * Add {@link DispatchTask} to the queue if it's not already on the queue (this + * way you can ensure only one instance of the task is scheduled at a time). After the task is + * executed you can schedule it again. */ - public abstract void dispatchAfter(Runnable runnable, long delay); + public void dispatchAsyncOnce(DispatchTask task) { + if (!task.isScheduled()) { + dispatchAsync(task); + } + } - /** Stops queue execution and cancels all tasks (cleanup function for private queues) */ + /** + * Stops queue execution and cancels all tasks (cleanup function for private queues) + */ public abstract void stop(); /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java new file mode 100644 index 000000000..e7a0b26c7 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util.threading; + +import com.apptentive.android.sdk.ApptentiveLog; + +/** + * A basic class for any dispatch runnable task + */ +public abstract class DispatchTask implements Runnable { + + /** + * True if task is already on the queue and would be executed soon. + */ + private boolean scheduled; + + /** + * Task entry point method + */ + protected abstract void execute(); + + @Override + public void run() { + try { + execute(); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while executing task"); + } finally { + setScheduled(false); + } + } + + synchronized void setScheduled(boolean scheduled) { + this.scheduled = scheduled; + } + + synchronized boolean isScheduled() { + return scheduled; + } +} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java index cb996e6ff..909205304 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java @@ -55,13 +55,8 @@ public HandlerDispatchQueue(Looper looper) { } @Override - public void dispatchAsync(Runnable runnable) { - handler.post(runnable); - } - - @Override - public void dispatchAfter(Runnable runnable, long delay) { - handler.postDelayed(runnable, delay); + protected void dispatch(DispatchTask task) { + handler.post(task); } @Override diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java index 1a59404a7..0068ca87b 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java @@ -11,15 +11,10 @@ public class TestDispatchQueue extends DispatchQueue { @Override - public void dispatchAsync(Runnable runnable) { + protected void dispatch(DispatchTask runnable) { runnable.run(); } - @Override - public void dispatchAfter(Runnable runnable, long delay) { - runnable.run(); // TODO: figure out a good way of testing this case - } - @Override public void stop() { // do nothing From 9737de9b8acaf3b20240e9051241f59d7da0b412 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 30 Jan 2017 17:08:45 -0800 Subject: [PATCH 047/465] Added Dispatch queue tests --- apptentive/build.gradle | 4 + .../apptentive/android/sdk/TestCaseBase.java | 18 +++- .../ApptentiveComponentRegistryTest.java | 12 +++ .../sdk/util/threading/DispatchQueueTest.java | 96 +++++++++++++++++++ .../sdk/util/threading/TestDispatchQueue.java | 33 ++++++- 5 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java diff --git a/apptentive/build.gradle b/apptentive/build.gradle index 2eb356935..bc9c42da9 100644 --- a/apptentive/build.gradle +++ b/apptentive/build.gradle @@ -39,6 +39,10 @@ android { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } + + testOptions { + unitTests.returnDefaultValues = true + } } apply plugin: 'maven' diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java b/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java index 74603c4ae..d2acb562e 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java @@ -17,10 +17,9 @@ public class TestCaseBase { private List result = new ArrayList<>(); + private TestDispatchQueue dispatchQueue; - static { - TestDispatchQueue.overrideMainQueue(); - } + //region Results protected void addResult(String str) { result.add(str); @@ -38,4 +37,17 @@ protected void assertResult(String... expected) { result.clear(); } + //endregion + + //region Dispatch Queue + + protected void overrideMainQueue(boolean dispatchTasksManually) { + dispatchQueue = TestDispatchQueue.overrideMainQueue(dispatchTasksManually); + } + + protected void dispatchTasks() { + dispatchQueue.dispatchTasks(); + } + + //endregion } diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java index 5785b9e97..a894ac716 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java @@ -7,12 +7,24 @@ package com.apptentive.android.sdk.util.registry; import com.apptentive.android.sdk.TestCaseBase; + +import org.junit.Before; import org.junit.Test; import static com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry.ComponentNotifier; public class ApptentiveComponentRegistryTest extends TestCaseBase { + //region Setup + + @Before + public void setUp() + { + overrideMainQueue(false); + } + + //endregion + //region Testing @Test diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java new file mode 100644 index 000000000..e8b16ef31 --- /dev/null +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util.threading; + +import com.apptentive.android.sdk.TestCaseBase; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Created by alementuev on 1/30/17. + */ +public class DispatchQueueTest extends TestCaseBase { + + @Before + public void setUp() { + overrideMainQueue(true); + } + + @Test + public void testSchedulingTasks() { + DispatchTask task = new DispatchTask() { + @Override + protected void execute() { + addResult("executed"); + } + }; + + DispatchQueue.mainQueue().dispatchAsync(task); + DispatchQueue.mainQueue().dispatchAsync(task); + dispatchTasks(); + + assertResult("executed", "executed"); + } + + @Test + public void testSchedulingTasksOnce() { + + DispatchTask task = new DispatchTask() { + @Override + protected void execute() { + addResult("executed"); + } + }; + + DispatchQueue.mainQueue().dispatchAsyncOnce(task); + DispatchQueue.mainQueue().dispatchAsyncOnce(task); + dispatchTasks(); + + assertResult("executed"); + + DispatchQueue.mainQueue().dispatchAsyncOnce(task); + DispatchQueue.mainQueue().dispatchAsyncOnce(task); + dispatchTasks(); + + assertResult("executed"); + + DispatchQueue.mainQueue().dispatchAsync(task); + DispatchQueue.mainQueue().dispatchAsync(task); + dispatchTasks(); + + assertResult("executed", "executed"); + } + + @Test + public void testSchedulingTasksWithException() { + + DispatchQueue.mainQueue().dispatchAsyncOnce(new DispatchTask() { + @Override + protected void execute() { + addResult("task-1"); + } + }); + DispatchQueue.mainQueue().dispatchAsyncOnce(new DispatchTask() { + @Override + protected void execute() { + throw new RuntimeException(); // throwing an exception should not break the queue + } + }); + DispatchQueue.mainQueue().dispatchAsyncOnce(new DispatchTask() { + @Override + protected void execute() { + addResult("task-2"); + } + }); + dispatchTasks(); + + assertResult("task-1", "task-2"); + } +} \ No newline at end of file diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java index 0068ca87b..12d40c6cc 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java @@ -8,20 +8,43 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.LinkedList; +import java.util.Queue; public class TestDispatchQueue extends DispatchQueue { + private final boolean dispatchManually; + private Queue tasks; + + public TestDispatchQueue(boolean dispatchManually) { + this.dispatchManually = dispatchManually; + this.tasks = new LinkedList<>(); + } + @Override - protected void dispatch(DispatchTask runnable) { - runnable.run(); + protected void dispatch(DispatchTask task) { + if (dispatchManually) { + tasks.add(task); + } else { + task.run(); + } } @Override public void stop() { - // do nothing + tasks.clear(); + } + + public void dispatchTasks() { + for (DispatchTask task : tasks) { + task.run(); + } + tasks.clear(); } - public static void overrideMainQueue() { - overrideMainQueue(new TestDispatchQueue()); + public static TestDispatchQueue overrideMainQueue(boolean dispatchTasksManually) { + TestDispatchQueue queue = new TestDispatchQueue(dispatchTasksManually); + overrideMainQueue(queue); + return queue; } private static void overrideMainQueue(DispatchQueue queue) { From 82d7d16e538a7099fdb43b7d3e6db988e200347d Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 30 Jan 2017 17:10:23 -0800 Subject: [PATCH 048/465] Dispatch queue refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Marked task as “not scheduled” before executing --- .../apptentive/android/sdk/util/threading/DispatchTask.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java index e7a0b26c7..e854f6dff 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java @@ -9,7 +9,7 @@ import com.apptentive.android.sdk.ApptentiveLog; /** - * A basic class for any dispatch runnable task + * A basic class for any dispatch runnable task. Tracks its "schedule" state */ public abstract class DispatchTask implements Runnable { @@ -26,11 +26,10 @@ public abstract class DispatchTask implements Runnable { @Override public void run() { try { + setScheduled(false); execute(); } catch (Exception e) { ApptentiveLog.e(e, "Exception while executing task"); - } finally { - setScheduled(false); } } From c021c030dbc9c8d1ee982965cd31c5e8fbc6fbdb Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 30 Jan 2017 17:26:00 -0800 Subject: [PATCH 049/465] =?UTF-8?q?Added=20=E2=80=9Cdelayed=E2=80=9D=20ver?= =?UTF-8?q?sions=20of=20DispatchQueue=20calls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sdk/util/threading/DispatchQueue.java | 33 ++++++++++++++++--- .../util/threading/HandlerDispatchQueue.java | 2 +- .../sdk/util/threading/DispatchQueueTest.java | 13 ++++---- .../sdk/util/threading/TestDispatchQueue.java | 2 +- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java index 639817979..101ff2e90 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java @@ -29,25 +29,50 @@ */ public abstract class DispatchQueue { - protected abstract void dispatch(DispatchTask task); + /** + * Dispatch task implementation + */ + protected abstract void dispatch(DispatchTask task, long delayMillis); /** * Add {@link DispatchTask} to the queue */ public void dispatchAsync(DispatchTask task) { + dispatchAsync(task, 0L); + } + + /** + * Add {@link DispatchTask} to the queue + */ + public void dispatchAsync(DispatchTask task, long delayMillis) { task.setScheduled(true); - dispatch(task); + dispatch(task, delayMillis); + } + + /** + * Add {@link DispatchTask} to the queue if it's not already on the queue (this + * way you can ensure only one instance of the task is scheduled at a time). After the task is + * executed you can schedule it again. + * + * @return true if task was scheduled + */ + public boolean dispatchAsyncOnce(DispatchTask task) { + return dispatchAsyncOnce(task, 0L); } /** * Add {@link DispatchTask} to the queue if it's not already on the queue (this * way you can ensure only one instance of the task is scheduled at a time). After the task is * executed you can schedule it again. + * + * @return true if task was scheduled */ - public void dispatchAsyncOnce(DispatchTask task) { + public boolean dispatchAsyncOnce(DispatchTask task, long delayMillis) { if (!task.isScheduled()) { - dispatchAsync(task); + dispatchAsync(task, delayMillis); + return true; } + return false; } /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java index 909205304..c13f45aa2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java @@ -55,7 +55,7 @@ public HandlerDispatchQueue(Looper looper) { } @Override - protected void dispatch(DispatchTask task) { + protected void dispatch(DispatchTask task, long delayMillis) { handler.post(task); } diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java index e8b16ef31..fd71531c9 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java @@ -13,9 +13,6 @@ import static org.junit.Assert.*; -/** - * Created by alementuev on 1/30/17. - */ public class DispatchQueueTest extends TestCaseBase { @Before @@ -49,14 +46,16 @@ protected void execute() { } }; - DispatchQueue.mainQueue().dispatchAsyncOnce(task); - DispatchQueue.mainQueue().dispatchAsyncOnce(task); + assertTrue(DispatchQueue.mainQueue().dispatchAsyncOnce(task)); + assertFalse(DispatchQueue.mainQueue().dispatchAsyncOnce(task)); + assertFalse(DispatchQueue.mainQueue().dispatchAsyncOnce(task)); dispatchTasks(); assertResult("executed"); - DispatchQueue.mainQueue().dispatchAsyncOnce(task); - DispatchQueue.mainQueue().dispatchAsyncOnce(task); + assertTrue(DispatchQueue.mainQueue().dispatchAsyncOnce(task)); + assertFalse(DispatchQueue.mainQueue().dispatchAsyncOnce(task)); + assertFalse(DispatchQueue.mainQueue().dispatchAsyncOnce(task)); dispatchTasks(); assertResult("executed"); diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java index 12d40c6cc..356ef58ca 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java @@ -21,7 +21,7 @@ public TestDispatchQueue(boolean dispatchManually) { } @Override - protected void dispatch(DispatchTask task) { + protected void dispatch(DispatchTask task, long delayMillis) { if (dispatchManually) { tasks.add(task); } else { From 8afc60f4ee76912b2f60b811f439269ab3b3134e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 30 Jan 2017 17:27:56 -0800 Subject: [PATCH 050/465] Refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced ‘MESSAGE_SAVE_SESSION_DATA’ with background dispatch queue --- .../android/sdk/ApptentiveInternal.java | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index f7ac348c1..ab2908c54 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -58,6 +58,8 @@ import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry; +import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchTask; import org.json.JSONException; import org.json.JSONObject; @@ -105,7 +107,8 @@ public class ApptentiveInternal implements Handler.Callback, DataChangedListener private FileSerializer fileSerializer; - Handler backgroundHandler; + Handler backgroundHandler; // TODO: get rid of the handler + private final DispatchQueue backgroundQueue; // toolbar theme specified in R.attr.apptentiveToolbarTheme Resources.Theme apptentiveToolbarTheme; @@ -152,6 +155,9 @@ public static PushAction parse(String name) { @SuppressLint("StaticFieldLeak") private static volatile ApptentiveInternal sApptentiveInternal; + private ApptentiveInternal() { + backgroundQueue = DispatchQueue.createBackgroundQueue("Apptentive Background Queue"); + } public static boolean isApptentiveRegistered() { return (sApptentiveInternal != null); @@ -1076,9 +1082,9 @@ public static boolean checkRegistered() { // Multi-tenancy work private synchronized void scheduleSessionDataSave() { - if (!backgroundHandler.hasMessages(MESSAGE_SAVE_SESSION_DATA)) { + boolean scheduled = backgroundQueue.dispatchAsyncOnce(saveSessionTask, 100L); + if (scheduled) { ApptentiveLog.e("Scheduling SessionData save."); - backgroundHandler.sendEmptyMessageDelayed(MESSAGE_SAVE_SESSION_DATA, 100); } else { ApptentiveLog.e("SessionData save already scheduled."); } @@ -1090,20 +1096,23 @@ private synchronized void scheduleConversationCreation() { } } - private static final int MESSAGE_SAVE_SESSION_DATA = 0; private static final int MESSAGE_CREATE_CONVERSATION = 1; private static final int MESSAGE_FETCH_CONFIGURATION = 2; + private final DispatchTask saveSessionTask = new DispatchTask() { + @Override + protected void execute() { + ApptentiveLog.e("Saving SessionData"); + ApptentiveLog.v("EventData: %s", sessionData.getEventData().toString()); + if (fileSerializer != null) { + fileSerializer.serialize(sessionData); + } + } + }; + @Override public boolean handleMessage(Message msg) { switch (msg.what) { - case MESSAGE_SAVE_SESSION_DATA: - ApptentiveLog.e("Saving SessionData"); - ApptentiveLog.v("EventData: %s", sessionData.getEventData().toString()); - if (fileSerializer != null) { - fileSerializer.serialize(sessionData); - } - break; case MESSAGE_CREATE_CONVERSATION: fetchConversationToken(); break; From fbd21f8172f6cdbce2677b1f374a9a542b6bdf2d Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 30 Jan 2017 17:31:02 -0800 Subject: [PATCH 051/465] Refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced ‘MESSAGE_CREATE_CONVERSATION’ with background dispatch queue --- .../android/sdk/ApptentiveInternal.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index ab2908c54..7f073fc76 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -1091,12 +1091,9 @@ private synchronized void scheduleSessionDataSave() { } private synchronized void scheduleConversationCreation() { - if (!backgroundHandler.hasMessages(MESSAGE_CREATE_CONVERSATION)) { - backgroundHandler.sendEmptyMessage(MESSAGE_CREATE_CONVERSATION); - } + backgroundQueue.dispatchAsyncOnce(createConversationTask); } - private static final int MESSAGE_CREATE_CONVERSATION = 1; private static final int MESSAGE_FETCH_CONFIGURATION = 2; private final DispatchTask saveSessionTask = new DispatchTask() { @@ -1110,12 +1107,16 @@ protected void execute() { } }; + private final DispatchTask createConversationTask = new DispatchTask() { + @Override + protected void execute() { + fetchConversationToken(); + } + }; + @Override public boolean handleMessage(Message msg) { switch (msg.what) { - case MESSAGE_CREATE_CONVERSATION: - fetchConversationToken(); - break; case MESSAGE_FETCH_CONFIGURATION: // TODO break; From 15199cc3702f2d316a255813212230ee03e8f853 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 30 Jan 2017 17:33:21 -0800 Subject: [PATCH 052/465] Refactoring Got rid off background hander in ApptentiveInternal class --- .../android/sdk/ApptentiveInternal.java | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 7f073fc76..4ea8c67a3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -80,7 +80,7 @@ /** * This class contains only internal methods. These methods should not be access directly by the host app. */ -public class ApptentiveInternal implements Handler.Callback, DataChangedListener { +public class ApptentiveInternal implements DataChangedListener { static AtomicBoolean isApptentiveInitialized = new AtomicBoolean(false); InteractionManager interactionManager; @@ -106,8 +106,7 @@ public class ApptentiveInternal implements Handler.Callback, DataChangedListener SessionData sessionData; private FileSerializer fileSerializer; - - Handler backgroundHandler; // TODO: get rid of the handler + // private background serial dispatch queue for internal SDK tasks private final DispatchQueue backgroundQueue; // toolbar theme specified in R.attr.apptentiveToolbarTheme @@ -198,12 +197,6 @@ public static ApptentiveInternal createInstance(Context context, final String ap sApptentiveInternal.cachedExecutor = Executors.newCachedThreadPool(); sApptentiveInternal.componentRegistry = componentRegistry; sApptentiveInternal.apiKey = Util.trim(apptentiveApiKey); - - - HandlerThread handlerThread = new HandlerThread("ApptentiveInternalHandlerThread"); - handlerThread.start(); - sApptentiveInternal.backgroundHandler = new Handler(handlerThread.getLooper(), sApptentiveInternal); - } } } @@ -1094,8 +1087,6 @@ private synchronized void scheduleConversationCreation() { backgroundQueue.dispatchAsyncOnce(createConversationTask); } - private static final int MESSAGE_FETCH_CONFIGURATION = 2; - private final DispatchTask saveSessionTask = new DispatchTask() { @Override protected void execute() { @@ -1114,16 +1105,6 @@ protected void execute() { } }; - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_FETCH_CONFIGURATION: - // TODO - break; - } - return false; - } - //region Listeners @Override From c57335c5afe0e95efae4c1995e4fa97924f9f9ca Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 30 Jan 2017 17:40:03 -0800 Subject: [PATCH 053/465] =?UTF-8?q?Replaced=20Session=20Data=20=E2=80=9Cer?= =?UTF-8?q?ror=E2=80=9D=20logs=20with=20=E2=80=9Cdebug=E2=80=9D=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 4ea8c67a3..e5a19e32d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -1077,9 +1077,9 @@ public static boolean checkRegistered() { private synchronized void scheduleSessionDataSave() { boolean scheduled = backgroundQueue.dispatchAsyncOnce(saveSessionTask, 100L); if (scheduled) { - ApptentiveLog.e("Scheduling SessionData save."); + ApptentiveLog.d("Scheduling SessionData save."); } else { - ApptentiveLog.e("SessionData save already scheduled."); + ApptentiveLog.d("SessionData save already scheduled."); } } @@ -1090,7 +1090,7 @@ private synchronized void scheduleConversationCreation() { private final DispatchTask saveSessionTask = new DispatchTask() { @Override protected void execute() { - ApptentiveLog.e("Saving SessionData"); + ApptentiveLog.d("Saving SessionData"); ApptentiveLog.v("EventData: %s", sessionData.getEventData().toString()); if (fileSerializer != null) { fileSerializer.serialize(sessionData); From 8080fad22001075a64bc07af1c427039fd3f9f98 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 30 Jan 2017 17:50:55 -0800 Subject: [PATCH 054/465] Added thread name to log message if logging off the main thread --- .../main/java/com/apptentive/android/sdk/ApptentiveLog.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java index e7f521ee6..d3c15781c 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java @@ -6,6 +6,7 @@ package com.apptentive.android.sdk; +import android.os.Looper; import android.util.Log; import java.util.IllegalFormatException; @@ -29,6 +30,10 @@ private static void doLog(Level level, Throwable throwable, String message, Obje level = Level.ERROR; } } + // add thread name if logging of the UI-thread + if (Looper.getMainLooper().getThread() != Thread.currentThread()) { + message = String.format("[%s] %s", Thread.currentThread().getName(), message); + } android.util.Log.println(level.getLevel(), TAG, message); if(throwable != null){ if(throwable.getMessage() != null){ From b35fe6ce77ea61bded292d64aaff68b297981e2c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 30 Jan 2017 17:54:15 -0800 Subject: [PATCH 055/465] Fixed scheduling delayed tasks on dispatch queue --- .../android/sdk/util/threading/HandlerDispatchQueue.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java index c13f45aa2..94ae5a779 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java @@ -56,7 +56,11 @@ public HandlerDispatchQueue(Looper looper) { @Override protected void dispatch(DispatchTask task, long delayMillis) { - handler.post(task); + if (delayMillis > 0) { + handler.postDelayed(task, delayMillis); + } else { + handler.post(task); + } } @Override From 844a27fe90a647f4f0d1a3ede6d7612cc7690277 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 2 Feb 2017 14:47:53 -0800 Subject: [PATCH 056/465] Added concurrent dispatch queue type --- .../android/sdk/ApptentiveInternal.java | 6 +- .../threading/ConcurrentDispatchQueue.java | 63 +++++++++++++++++++ .../sdk/util/threading/DispatchQueue.java | 13 +++- .../sdk/util/threading/DispatchQueueType.java | 21 +++++++ 4 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueue.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueueType.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index e5a19e32d..5d341d3ab 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -22,9 +22,6 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Message; import android.support.v4.content.ContextCompat; import android.text.TextUtils; @@ -59,6 +56,7 @@ import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry; import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchQueueType; import com.apptentive.android.sdk.util.threading.DispatchTask; import org.json.JSONException; @@ -155,7 +153,7 @@ public static PushAction parse(String name) { private static volatile ApptentiveInternal sApptentiveInternal; private ApptentiveInternal() { - backgroundQueue = DispatchQueue.createBackgroundQueue("Apptentive Background Queue"); + backgroundQueue = DispatchQueue.createBackgroundQueue("Apptentive Serial Queue", DispatchQueueType.Serial); } public static boolean isApptentiveRegistered() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueue.java new file mode 100644 index 000000000..6d8bbff01 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueue.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util.threading; + +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Concurrent queue implementation where each task is dispatched on a separate thread + */ +class ConcurrentDispatchQueue extends DispatchQueue implements ThreadFactory { + /* + * Gets the number of available cores + * (not always the same as the maximum number of cores) + */ + private static final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors(); + + /** + * Thread pool executor for scheduling tasks + */ + private final ScheduledThreadPoolExecutor threadPoolExecutor; + + /** The name of the queue */ + private final String name; + + /** The number of the next thread in the pool */ + private final AtomicInteger threadNumber; + + ConcurrentDispatchQueue(String name) { + this.name = name; + this.threadPoolExecutor = new ScheduledThreadPoolExecutor(NUMBER_OF_CORES, this); + this.threadNumber = new AtomicInteger(1); + } + + @Override + protected void dispatch(DispatchTask task, long delayMillis) { + if (delayMillis > 0) { + threadPoolExecutor.schedule(task, delayMillis, TimeUnit.MILLISECONDS); + } else { + threadPoolExecutor.execute(task); + } + } + + @Override + public void stop() { + threadPoolExecutor.shutdown(); + } + + //region Thread factory + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, name + "-thread-" + threadNumber.getAndIncrement()); + } + + //endregion +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java index 101ff2e90..9a9cd2427 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java @@ -88,10 +88,17 @@ public static DispatchQueue mainQueue() { } /** - * Creates a private serial queue with specified name on a background thread + * Creates a background queue with specified name and dispatch type. */ - public static DispatchQueue createBackgroundQueue(String name) { - return new HandlerDispatchQueue(name); + public static DispatchQueue createBackgroundQueue(String name, DispatchQueueType type) { + if (type == DispatchQueueType.Serial) { + return new HandlerDispatchQueue(name); + } + if (type == DispatchQueueType.Concurrent) { + return new ConcurrentDispatchQueue(name); + } + + throw new IllegalArgumentException("Unexpected queue type: " + type); } /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueueType.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueueType.java new file mode 100644 index 000000000..71c1decc3 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueueType.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util.threading; + +/** + * Describes the type of dispatch queue + */ +public enum DispatchQueueType { + /** + * Tasks are executed one after another on the same thread (no more than one task is executing at the same time + */ + Serial, + /** + * Task are executed in parallel on multiple threads (two or more tasks can be executed at the same time + */ + Concurrent +} From da374e63ba73b160bc1f6fd916e6d668f402500f Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 2 Feb 2017 14:50:37 -0800 Subject: [PATCH 057/465] Refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed ‘HandlerDispatchQueue’ to ‘SerialDispatchQueue’ --- .../android/sdk/util/threading/DispatchQueue.java | 4 ++-- ...HandlerDispatchQueue.java => SerialDispatchQueue.java} | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/util/threading/{HandlerDispatchQueue.java => SerialDispatchQueue.java} (90%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java index 9a9cd2427..d70194dd9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java @@ -92,7 +92,7 @@ public static DispatchQueue mainQueue() { */ public static DispatchQueue createBackgroundQueue(String name, DispatchQueueType type) { if (type == DispatchQueueType.Serial) { - return new HandlerDispatchQueue(name); + return new SerialDispatchQueue(name); } if (type == DispatchQueueType.Concurrent) { return new ConcurrentDispatchQueue(name); @@ -111,7 +111,7 @@ private static DispatchQueue createMainQueue() { try { // this call will fail when running a unit test // we would allow that and make test responsible for setting the implementation - return new HandlerDispatchQueue(Looper.getMainLooper()); + return new SerialDispatchQueue(Looper.getMainLooper()); } catch (Exception e) { return null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueue.java similarity index 90% rename from apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java rename to apptentive/src/main/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueue.java index 94ae5a779..21dee59bf 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/HandlerDispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueue.java @@ -28,16 +28,16 @@ import static com.apptentive.android.sdk.debug.Assert.assertNotNull; /** - * {@link DispatchQueue} implementation using {@link Handler} + * Serial dispatch queue implementation based on {@link Handler} */ -class HandlerDispatchQueue extends DispatchQueue { +class SerialDispatchQueue extends DispatchQueue { private final Handler handler; private final HandlerThread handlerThread; /** * Creates a private queue with specified name */ - public HandlerDispatchQueue(String name) { + public SerialDispatchQueue(String name) { handlerThread = new HandlerThread(name); handlerThread.start(); handler = new Handler(handlerThread.getLooper()); @@ -46,7 +46,7 @@ public HandlerDispatchQueue(String name) { /** * Creates a queue with specified looper */ - public HandlerDispatchQueue(Looper looper) { + public SerialDispatchQueue(Looper looper) { if (looper == null) { throw new NullPointerException("Looper is null"); } From 91539cce84025ee26543d2484022e5f312d2d47a Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 2 Feb 2017 14:58:32 -0800 Subject: [PATCH 058/465] Refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed ‘TestDispatchQueue’ to ‘MockDispatchQueue’ --- .../java/com/apptentive/android/sdk/TestCaseBase.java | 6 +++--- .../{TestDispatchQueue.java => MockDispatchQueue.java} | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) rename apptentive/src/test/java/com/apptentive/android/sdk/util/threading/{TestDispatchQueue.java => MockDispatchQueue.java} (86%) diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java b/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java index d2acb562e..f8164352b 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java @@ -7,7 +7,7 @@ package com.apptentive.android.sdk; import com.apptentive.android.sdk.util.StringUtils; -import com.apptentive.android.sdk.util.threading.TestDispatchQueue; +import com.apptentive.android.sdk.util.threading.MockDispatchQueue; import java.util.ArrayList; import java.util.List; @@ -17,7 +17,7 @@ public class TestCaseBase { private List result = new ArrayList<>(); - private TestDispatchQueue dispatchQueue; + private MockDispatchQueue dispatchQueue; //region Results @@ -42,7 +42,7 @@ protected void assertResult(String... expected) { //region Dispatch Queue protected void overrideMainQueue(boolean dispatchTasksManually) { - dispatchQueue = TestDispatchQueue.overrideMainQueue(dispatchTasksManually); + dispatchQueue = MockDispatchQueue.overrideMainQueue(dispatchTasksManually); } protected void dispatchTasks() { diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/MockDispatchQueue.java similarity index 86% rename from apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java rename to apptentive/src/test/java/com/apptentive/android/sdk/util/threading/MockDispatchQueue.java index 356ef58ca..c33cd96ec 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/TestDispatchQueue.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/MockDispatchQueue.java @@ -11,11 +11,11 @@ import java.util.LinkedList; import java.util.Queue; -public class TestDispatchQueue extends DispatchQueue { +public class MockDispatchQueue extends DispatchQueue { private final boolean dispatchManually; private Queue tasks; - public TestDispatchQueue(boolean dispatchManually) { + public MockDispatchQueue(boolean dispatchManually) { this.dispatchManually = dispatchManually; this.tasks = new LinkedList<>(); } @@ -41,8 +41,8 @@ public void dispatchTasks() { tasks.clear(); } - public static TestDispatchQueue overrideMainQueue(boolean dispatchTasksManually) { - TestDispatchQueue queue = new TestDispatchQueue(dispatchTasksManually); + public static MockDispatchQueue overrideMainQueue(boolean dispatchTasksManually) { + MockDispatchQueue queue = new MockDispatchQueue(dispatchTasksManually); overrideMainQueue(queue); return queue; } From 24018a3afa83ee4531a9ffc9c3c410a8894740a0 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 2 Feb 2017 16:43:47 -0800 Subject: [PATCH 059/465] Added instrumentation tests for serial and concurrent queues --- apptentive/build.gradle | 6 ++ .../sdk/test/InstrumentationBaseTestCase.java | 52 +++++++++++ .../ConcurrentDispatchQueueTest.java | 73 +++++++++++++++ .../threading/SerialDispatchQueueTest.java | 93 +++++++++++++++++++ .../threading/ConcurrentDispatchQueue.java | 2 +- .../sdk/util/threading/DispatchQueue.java | 2 +- 6 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 apptentive/src/androidTest/java/com/apptentive/android/sdk/test/InstrumentationBaseTestCase.java create mode 100644 apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueueTest.java create mode 100644 apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java diff --git a/apptentive/build.gradle b/apptentive/build.gradle index bc9c42da9..0a6ae1d82 100644 --- a/apptentive/build.gradle +++ b/apptentive/build.gradle @@ -16,6 +16,11 @@ dependencies { testCompile "org.powermock:powermock-module-junit4-rule:1.6.2" testCompile "org.powermock:powermock-api-mockito:1.6.2" testCompile "org.powermock:powermock-classloading-xstream:1.6.2" + + // Required for instrumented tests + androidTestCompile 'com.android.support:support-annotations:24.0.0' + androidTestCompile 'com.android.support.test:runner:0.5' + androidTestCompile 'com.android.support.test:rules:0.5' } android { @@ -29,6 +34,7 @@ android { versionCode System.getenv("BUILD_NUMBER") as Integer ?: 1 versionName project.version consumerProguardFiles 'consumer-proguard-rules.pro' + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } lintOptions { diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/test/InstrumentationBaseTestCase.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/test/InstrumentationBaseTestCase.java new file mode 100644 index 000000000..13a0d6c0c --- /dev/null +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/test/InstrumentationBaseTestCase.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.test; + +import android.os.SystemClock; + +import com.apptentive.android.sdk.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Created by alementuev on 2/2/17. + */ + +public class InstrumentationBaseTestCase { + private List result = new ArrayList<>(); + + //region Results + + protected void addResult(String str) { + result.add(str); + } + + protected void assertResult(String... expected) { + assertEquals("\nExpected: " + StringUtils.join(expected) + + "\nActual: " + StringUtils.join(result), expected.length, result.size()); + + for (int i = 0; i < expected.length; ++i) { + assertEquals("\nExpected: " + StringUtils.join(expected) + + "\nActual: " + StringUtils.join(result), + expected[i], result.get(i)); + } + + result.clear(); + } + //endregion + + //region Helpers + + protected void sleep(long millis) { + SystemClock.sleep(millis); + } + + //endregion +} diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueueTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueueTest.java new file mode 100644 index 000000000..2f851f961 --- /dev/null +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueueTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util.threading; + +import android.support.test.runner.AndroidJUnit4; + +import com.apptentive.android.sdk.test.InstrumentationBaseTestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +@RunWith(AndroidJUnit4.class) +public class ConcurrentDispatchQueueTest extends InstrumentationBaseTestCase { + private DispatchQueue dispatchQueue; + + @Before + public void setUp() { + dispatchQueue = DispatchQueue.createBackgroundQueue("Test Queue", DispatchQueueType.Concurrent); + } + + @After + public void tearDown() { + dispatchQueue.stop(); + } + + @Test + public void testDispatch() { + dispatchQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + sleep(500); + addResult("task-1"); + } + }); + dispatchQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + sleep(200); + addResult("task-2"); + } + }); + dispatchQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + addResult("task-3"); + } + }); + sleep(1000); // give tasks a chance to finish + assertResult("task-3", "task-2", "task-1"); // task should be executed concurrently + } + + @Test + public void testStoppingDispatchDelayed() { + dispatchQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + addResult("task"); + } + }, 100); + dispatchQueue.stop(); + sleep(1000); // wait just for the case if the task still runs + + assertResult(); // no tasks should run + } +} \ No newline at end of file diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java new file mode 100644 index 000000000..265cfb1d2 --- /dev/null +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util.threading; + +import android.support.test.runner.AndroidJUnit4; + +import com.apptentive.android.sdk.test.InstrumentationBaseTestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class SerialDispatchQueueTest extends InstrumentationBaseTestCase { + + private DispatchQueue dispatchQueue; + + @Before + public void setUp() { + dispatchQueue = DispatchQueue.createBackgroundQueue("Test Queue", DispatchQueueType.Serial); + } + + @After + public void tearDown() { + dispatchQueue.stop(); + } + + @Test + public void testDispatch() { + dispatchQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + sleep(500); + addResult("task-1"); + } + }); + dispatchQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + sleep(100); + addResult("task-2"); + } + }); + dispatchQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + addResult("task-3"); + } + }); + sleep(1000); // give tasks a chance to finish + assertResult("task-1", "task-2", "task-3"); // task should be executed serially + } + + @Test + public void testStoppingDispatch() { + dispatchQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + sleep(500); + addResult("task-1"); + } + }); + dispatchQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + addResult("task-2"); + } + }); + dispatchQueue.stop(); + sleep(1000); // wait for the first task to finish + + assertResult("task-1"); // task-2 should not run + } + + @Test + public void testStoppingDispatchDelayed() { + dispatchQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + addResult("task"); + } + }, 100); + dispatchQueue.stop(); + sleep(1000); // wait just for the case if the task still runs + + assertResult(); // no tasks should run + } +} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueue.java index 6d8bbff01..7bf13d58c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueue.java @@ -49,7 +49,7 @@ protected void dispatch(DispatchTask task, long delayMillis) { @Override public void stop() { - threadPoolExecutor.shutdown(); + threadPoolExecutor.shutdownNow(); } //region Thread factory diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java index d70194dd9..c001f1e96 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java @@ -76,7 +76,7 @@ public boolean dispatchAsyncOnce(DispatchTask task, long delayMillis) { } /** - * Stops queue execution and cancels all tasks (cleanup function for private queues) + * Stops queue execution and cancels all scheduled tasks */ public abstract void stop(); From adb1f352b75b96feae0173645a34bee5410d1c40 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 3 Feb 2017 10:01:05 -0800 Subject: [PATCH 060/465] Refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made ‘SerialDispatchQueue’ constructor only accessible inside package --- .../android/sdk/util/threading/SerialDispatchQueue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueue.java index 21dee59bf..ccacb1721 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueue.java @@ -37,7 +37,7 @@ class SerialDispatchQueue extends DispatchQueue { /** * Creates a private queue with specified name */ - public SerialDispatchQueue(String name) { + SerialDispatchQueue(String name) { handlerThread = new HandlerThread(name); handlerThread.start(); handler = new Handler(handlerThread.getLooper()); @@ -46,7 +46,7 @@ public SerialDispatchQueue(String name) { /** * Creates a queue with specified looper */ - public SerialDispatchQueue(Looper looper) { + SerialDispatchQueue(Looper looper) { if (looper == null) { throw new NullPointerException("Looper is null"); } From 55c71a3a03ce9fa7537d32e84d95d4c227f8a282 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 3 Feb 2017 10:11:28 -0800 Subject: [PATCH 061/465] Test refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed parameter name from ‘dispatchTasksManually’ to ‘runImmediately’ --- .../com/apptentive/android/sdk/TestCaseBase.java | 4 ++-- .../ApptentiveComponentRegistryTest.java | 2 +- .../sdk/util/threading/DispatchQueueTest.java | 2 +- .../sdk/util/threading/MockDispatchQueue.java | 16 ++++++++-------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java b/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java index f8164352b..70ec0ce54 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java @@ -41,8 +41,8 @@ protected void assertResult(String... expected) { //region Dispatch Queue - protected void overrideMainQueue(boolean dispatchTasksManually) { - dispatchQueue = MockDispatchQueue.overrideMainQueue(dispatchTasksManually); + protected void overrideMainQueue(boolean runImmediately) { + dispatchQueue = MockDispatchQueue.overrideMainQueue(runImmediately); } protected void dispatchTasks() { diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java index a894ac716..4ff9212dc 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java @@ -20,7 +20,7 @@ public class ApptentiveComponentRegistryTest extends TestCaseBase { @Before public void setUp() { - overrideMainQueue(false); + overrideMainQueue(true); } //endregion diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java index fd71531c9..466fe8324 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java @@ -17,7 +17,7 @@ public class DispatchQueueTest extends TestCaseBase { @Before public void setUp() { - overrideMainQueue(true); + overrideMainQueue(false); } @Test diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/MockDispatchQueue.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/MockDispatchQueue.java index c33cd96ec..c55a1bcac 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/MockDispatchQueue.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/MockDispatchQueue.java @@ -12,20 +12,20 @@ import java.util.Queue; public class MockDispatchQueue extends DispatchQueue { - private final boolean dispatchManually; + private final boolean runImmediately; private Queue tasks; - public MockDispatchQueue(boolean dispatchManually) { - this.dispatchManually = dispatchManually; + public MockDispatchQueue(boolean runImmediately) { + this.runImmediately = runImmediately; this.tasks = new LinkedList<>(); } @Override protected void dispatch(DispatchTask task, long delayMillis) { - if (dispatchManually) { - tasks.add(task); - } else { + if (runImmediately) { task.run(); + } else { + tasks.add(task); } } @@ -41,8 +41,8 @@ public void dispatchTasks() { tasks.clear(); } - public static MockDispatchQueue overrideMainQueue(boolean dispatchTasksManually) { - MockDispatchQueue queue = new MockDispatchQueue(dispatchTasksManually); + public static MockDispatchQueue overrideMainQueue(boolean runImmediately) { + MockDispatchQueue queue = new MockDispatchQueue(runImmediately); overrideMainQueue(queue); return queue; } From e0bc07efb8185f49a8f17a742457015fbd95c88f Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 3 Feb 2017 10:31:09 -0800 Subject: [PATCH 062/465] Fixed logging system to make unit tests pass --- .../src/main/java/com/apptentive/android/sdk/ApptentiveLog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java index d3c15781c..565fc0a80 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java @@ -31,7 +31,7 @@ private static void doLog(Level level, Throwable throwable, String message, Obje } } // add thread name if logging of the UI-thread - if (Looper.getMainLooper().getThread() != Thread.currentThread()) { + if (Looper.getMainLooper() != null && Looper.getMainLooper().getThread() != Thread.currentThread()) { message = String.format("[%s] %s", Thread.currentThread().getName(), message); } android.util.Log.println(level.getLevel(), TAG, message); From dd101c376f9a1fa45247f17e550fe408de87be56 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 3 Feb 2017 11:17:31 -0800 Subject: [PATCH 063/465] Created AppRelease object field inside ApptentiveInternal --- .../android/sdk/ApptentiveInternal.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 5d341d3ab..736c2c0f1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -94,8 +94,9 @@ public class ApptentiveInternal implements DataChangedListener { int currentVersionCode; String currentVersionName; + private final AppRelease appRelease; + boolean appIsInForeground; - boolean isAppDebuggable; SharedPreferences globalSharedPrefs; String apiKey; String personId; @@ -152,8 +153,9 @@ public static PushAction parse(String name) { @SuppressLint("StaticFieldLeak") private static volatile ApptentiveInternal sApptentiveInternal; - private ApptentiveInternal() { + private ApptentiveInternal(Context context) { backgroundQueue = DispatchQueue.createBackgroundQueue("Apptentive Serial Queue", DispatchQueueType.Serial); + appRelease = AppReleaseManager.generateCurrentAppRelease(context); } public static boolean isApptentiveRegistered() { @@ -177,7 +179,7 @@ public static ApptentiveInternal createInstance(Context context, final String ap if (sApptentiveInternal == null) { synchronized (ApptentiveInternal.class) { if (sApptentiveInternal == null && context != null) { - sApptentiveInternal = new ApptentiveInternal(); + sApptentiveInternal = new ApptentiveInternal(context); isApptentiveInitialized.set(false); sApptentiveInternal.appContext = context.getApplicationContext(); sApptentiveInternal.globalSharedPrefs = sApptentiveInternal.appContext.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); @@ -388,7 +390,7 @@ public String getDefaultAppDisplayName() { } public boolean isApptentiveDebuggable() { - return isAppDebuggable; + return appRelease.isDebug(); } public String getPersonId() { @@ -516,8 +518,6 @@ public boolean init() { * 3. An unreadMessageCountListener() is set up */ - VersionHistoryItem lastVersionItemSeen = null; - File internalStorage = appContext.getFilesDir(); File sessionDataFile = new File(internalStorage, "apptentive/SessionData.ser"); sessionDataFile.getParentFile().mkdirs(); @@ -532,7 +532,6 @@ public boolean init() { if (featureEverUsed) { messageManager.init(); } - lastVersionItemSeen = sessionData.getVersionHistory().getLastVersionSeen(); } else { scheduleConversationCreation(); } @@ -558,13 +557,11 @@ public boolean init() { // Used for application theme inheritance if the theme is an AppCompat theme. setApplicationDefaultTheme(ai.theme); - AppRelease appRelease = AppReleaseManager.generateCurrentAppRelease(appContext); - - isAppDebuggable = appRelease.isDebug(); currentVersionCode = appRelease.getVersionCode(); currentVersionName = appRelease.getVersionName(); + VersionHistoryItem lastVersionItemSeen = sessionData.getVersionHistory().getLastVersionSeen(); if (lastVersionItemSeen == null) { onVersionChanged(null, currentVersionCode, null, currentVersionName, appRelease); } else { @@ -602,11 +599,11 @@ public boolean init() { ApptentiveLog.i("Overriding log level: %s", logLevelOverride); setMinimumLogLevel(ApptentiveLog.Level.parse(logLevelOverride)); } else { - if (isAppDebuggable) { + if (appRelease.isDebug()) { setMinimumLogLevel(ApptentiveLog.Level.VERBOSE); } } - ApptentiveLog.i("Debug mode enabled? %b", isAppDebuggable); + ApptentiveLog.i("Debug mode enabled? %b", appRelease.isDebug()); // TODO: Move this into a session became active handler. if (sessionData != null) { @@ -623,7 +620,7 @@ public boolean init() { String errorMessage = "The Apptentive API Key is not defined. You may provide your Apptentive API Key in Apptentive.register(), or in as meta-data in your AndroidManifest.xml.\n" + ""; - if (isAppDebuggable) { + if (appRelease.isDebug()) { throw new RuntimeException(errorMessage); } else { ApptentiveLog.e(errorMessage); @@ -690,7 +687,6 @@ private boolean fetchConversationToken() { // Send the Device and Sdk now, so they are available on the server from the start. Device device = DeviceManager.generateNewDevice(appContext); Sdk sdk = SdkManager.generateCurrentSdk(); - AppRelease appRelease = AppReleaseManager.generateCurrentAppRelease(appContext); request.setDevice(DeviceManager.getDiffPayload(null, device)); request.setSdk(SdkManager.getPayload(sdk)); @@ -760,7 +756,7 @@ private void fetchAppConfiguration() { } private void asyncFetchAppConfigurationAndInteractions() { - boolean force = isAppDebuggable; + boolean force = appRelease.isDebug(); // Don't get the app configuration unless no pending fetch AND either forced, or the cache has expired. if (isConfigurationFetchPending.compareAndSet(false, true) && (force || Configuration.load().hasConfigurationCacheExpired())) { From e8dc7d46585c3ca53e507aa569721f2eb0e87cf1 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 3 Feb 2017 11:23:15 -0800 Subject: [PATCH 064/465] Refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed ‘currentVersionCode’ and ‘currentVersionName’ from ApptentiveInternal --- .../apptentive/android/sdk/ApptentiveInternal.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 736c2c0f1..6487a941e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -91,9 +91,8 @@ public class ApptentiveInternal implements DataChangedListener { // These variables are initialized in Apptentive.register(), and so they are freely thereafter. If they are unexpectedly null, then if means the host app did not register Apptentive. Context appContext; - int currentVersionCode; - String currentVersionName; + // We keep a readonly reference to AppRelease object since it won't change at runtime private final AppRelease appRelease; boolean appIsInForeground; @@ -325,11 +324,11 @@ public Context getApplicationContext() { } public int getApplicationVersionCode() { - return currentVersionCode; + return appRelease.getVersionCode(); } public String getApplicationVersionName() { - return currentVersionName; + return appRelease.getVersionName(); } public ApptentiveActivityLifecycleCallbacks getRegisteredLifecycleCallbacks() { @@ -557,9 +556,8 @@ public boolean init() { // Used for application theme inheritance if the theme is an AppCompat theme. setApplicationDefaultTheme(ai.theme); - currentVersionCode = appRelease.getVersionCode(); - currentVersionName = appRelease.getVersionName(); - + int currentVersionCode = appRelease.getVersionCode(); + String currentVersionName = appRelease.getVersionName(); VersionHistoryItem lastVersionItemSeen = sessionData.getVersionHistory().getLastVersionSeen(); if (lastVersionItemSeen == null) { From 17bef5f264e3d49d4724065c442e331721ecacda Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 3 Feb 2017 11:52:50 -0800 Subject: [PATCH 065/465] Removed some dead code --- .../com/apptentive/android/sdk/ApptentiveInternal.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 6487a941e..061dcddc2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -396,10 +396,6 @@ public String getPersonId() { return personId; } - public String getAndroidId() { - return androidId; - } - public SharedPreferences getGlobalSharedPrefs() { return globalSharedPrefs; } @@ -408,10 +404,6 @@ public void runOnWorkerThread(Runnable r) { cachedExecutor.execute(r); } - public void scheduleOnWorkerThread(Runnable r) { - cachedExecutor.submit(r); - } - public void onAppLaunch(final Context appContext) { EngagementModule.engageInternal(appContext, Event.EventLabel.app__launch.getLabelName()); } From 621fb0209a6b6513f809b4ba7457f4ed3e5d6151 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 3 Feb 2017 11:57:25 -0800 Subject: [PATCH 066/465] Refactoring Added convenience method for checking app version changes --- .../android/sdk/ApptentiveInternal.java | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 061dcddc2..64dad9ac7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -548,20 +548,8 @@ public boolean init() { // Used for application theme inheritance if the theme is an AppCompat theme. setApplicationDefaultTheme(ai.theme); - int currentVersionCode = appRelease.getVersionCode(); - String currentVersionName = appRelease.getVersionName(); + checkSendVersionChanges(); - VersionHistoryItem lastVersionItemSeen = sessionData.getVersionHistory().getLastVersionSeen(); - if (lastVersionItemSeen == null) { - onVersionChanged(null, currentVersionCode, null, currentVersionName, appRelease); - } else { - int lastSeenVersionCode = lastVersionItemSeen.getVersionCode(); - Apptentive.Version lastSeenVersionNameVersion = new Apptentive.Version(); - lastSeenVersionNameVersion.setVersion(lastVersionItemSeen.getVersionName()); - if (!(currentVersionCode == lastSeenVersionCode) || !currentVersionName.equals(lastSeenVersionNameVersion.getVersion())) { - onVersionChanged(lastVersionItemSeen.getVersionCode(), currentVersionCode, lastVersionItemSeen.getVersionName(), currentVersionName, appRelease); - } - } defaultAppDisplayName = packageManager.getApplicationLabel(packageManager.getApplicationInfo(packageInfo.packageName, 0)).toString(); // Prevent delayed run-time exception if the app upgrades from pre-2.0 and doesn't remove NetworkStateReceiver from manifest @@ -625,6 +613,23 @@ public boolean init() { return bRet; } + private void checkSendVersionChanges() { + final VersionHistoryItem lastVersionItemSeen = sessionData.getVersionHistory().getLastVersionSeen(); + final int versionCode = appRelease.getVersionCode(); + final String versionName = appRelease.getVersionName(); + + if (lastVersionItemSeen == null) { + onVersionChanged(null, versionCode, null, versionName, appRelease); + } else { + int lastSeenVersionCode = lastVersionItemSeen.getVersionCode(); + Apptentive.Version lastSeenVersionNameVersion = new Apptentive.Version(); + lastSeenVersionNameVersion.setVersion(lastVersionItemSeen.getVersionName()); + if (!(versionCode == lastSeenVersionCode) || !versionName.equals(lastSeenVersionNameVersion.getVersion())) { + onVersionChanged(lastVersionItemSeen.getVersionCode(), versionCode, lastVersionItemSeen.getVersionName(), versionName, appRelease); + } + } + } + // FIXME: Do this kind of thing when a session becomes active instead of at app initialization? Otherwise there is no active session to send the information to. private void onVersionChanged(Integer previousVersionCode, Integer currentVersionCode, String previousVersionName, String currentVersionName, AppRelease currentAppRelease) { ApptentiveLog.i("Version changed: Name: %s => %s, Code: %d => %d", previousVersionName, currentVersionName, previousVersionCode, currentVersionCode); @@ -1097,7 +1102,9 @@ public void onDataChanged() { } //endregion - /** Ends current user session */ + /** + * Ends current user session + */ public static void logout() { getInstance().getComponentRegistry() .notifyComponents(new ComponentNotifier(OnUserLogOutListener.class) { From 519075367d4a111d30714fc20a69904b61c7043a Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 3 Feb 2017 12:24:40 -0800 Subject: [PATCH 067/465] Added SdkAndAppReleasePayload class --- .../android/sdk/ApptentiveInternal.java | 17 +- .../android/sdk/comm/ApptentiveClient.java | 4 + .../apptentive/android/sdk/model/Payload.java | 3 +- .../sdk/model/SdkAndAppReleasePayload.java | 370 ++++++++++++++++++ .../sdk/storage/PayloadSendWorker.java | 3 + 5 files changed, 389 insertions(+), 8 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 64dad9ac7..a8122376a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -583,13 +583,6 @@ public boolean init() { } ApptentiveLog.i("Debug mode enabled? %b", appRelease.isDebug()); - // TODO: Move this into a session became active handler. - if (sessionData != null) { - if (!TextUtils.equals(sessionData.getLastSeenSdkVersion(), Constants.APPTENTIVE_SDK_VERSION)) { - onSdkVersionChanged(sessionData.getLastSeenSdkVersion(), Constants.APPTENTIVE_SDK_VERSION); - } - } - // The apiKey can be passed in programmatically, or we can fallback to checking in the manifest. if (TextUtils.isEmpty(apiKey) && !TextUtils.isEmpty(manifestApiKey)) { apiKey = manifestApiKey; @@ -614,6 +607,11 @@ public boolean init() { } private void checkSendVersionChanges() { + if (sessionData == null) { + ApptentiveLog.e("Can't check session data changes: session data is not initialized"); + return; + } + final VersionHistoryItem lastVersionItemSeen = sessionData.getVersionHistory().getLastVersionSeen(); final int versionCode = appRelease.getVersionCode(); final String versionName = appRelease.getVersionName(); @@ -628,6 +626,11 @@ private void checkSendVersionChanges() { onVersionChanged(lastVersionItemSeen.getVersionCode(), versionCode, lastVersionItemSeen.getVersionName(), versionName, appRelease); } } + + // TODO: Move this into a session became active handler. + if (!TextUtils.equals(sessionData.getLastSeenSdkVersion(), Constants.APPTENTIVE_SDK_VERSION)) { + onSdkVersionChanged(sessionData.getLastSeenSdkVersion(), Constants.APPTENTIVE_SDK_VERSION); + } } // FIXME: Do this kind of thing when a session becomes active instead of at app initialization? Otherwise there is no active session to send the information to. diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index ca52ad894..581eb7d31 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -98,6 +98,10 @@ public static ApptentiveHttpResponse putAppRelease(AppRelease appRelease) { return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, appRelease.marshallForSending()); } + public static ApptentiveHttpResponse putSdkAndAppRelease(SdkAndAppReleasePayload payload) { + return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, payload.marshallForSending()); + } + public static ApptentiveHttpResponse putPerson(Person person) { return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_PEOPLE, Method.PUT, person.marshallForSending()); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index d6b4494c6..d9b7d0a1c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -66,12 +66,13 @@ protected void setBaseType(BaseType baseType) { this.baseType = baseType; } - public static enum BaseType { + public enum BaseType { message, event, device, sdk, app_release, + sdk_and_app_release, person, unknown, // Legacy diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java new file mode 100644 index 000000000..040919604 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.model; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; + +import com.apptentive.android.sdk.ApptentiveInternal; +import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.util.Util; + +import org.json.JSONException; + +/** + * A combined payload of {@link Sdk} and {@link AppRelease} payloads. + * + * This class effectively contains the source code from both {@link Sdk} + * and {@link AppRelease} payloads (which still kept for backward compatibility + * purposes). + */ +public class SdkAndAppReleasePayload extends Payload { + + //region Sdk payload keys + private static final String KEY_VERSION = "version"; + private static final String KEY_PROGRAMMING_LANGUAGE = "programming_language"; + private static final String KEY_AUTHOR_NAME = "author_name"; + private static final String KEY_AUTHOR_EMAIL = "author_email"; + private static final String KEY_PLATFORM = "platform"; + private static final String KEY_DISTRIBUTION = "distribution"; + private static final String KEY_DISTRIBUTION_VERSION = "distribution_version"; + //endregion + + //region AppRelease keys + private static final String KEY_TYPE = "type"; + private static final String KEY_VERSION_NAME = "version_name"; + private static final String KEY_VERSION_CODE = "version_code"; + private static final String KEY_IDENTIFIER = "identifier"; + private static final String KEY_TARGET_SDK_VERSION = "target_sdk_version"; + private static final String KEY_APP_STORE = "app_store"; + private static final String KEY_STYLE_INHERIT = "inheriting_styles"; + private static final String KEY_STYLE_OVERRIDE = "overriding_styles"; + private static final String KEY_DEBUG = "debug"; + //endregion + + public SdkAndAppReleasePayload(String json) throws JSONException { + super(json); + } + + public SdkAndAppReleasePayload() { + super(); + } + + //region Inheritance + public void initBaseType() { + setBaseType(BaseType.sdk_and_app_release); + } + //endregion + + //region Sdk getters/setters + public String getVersion() { + try { + if(!isNull(KEY_VERSION)) { + return getString(KEY_VERSION); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setVersion(String version) { + try { + put(KEY_VERSION, version); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_VERSION); + } + } + + public String getProgrammingLanguage() { + try { + if(!isNull(KEY_PROGRAMMING_LANGUAGE)) { + return getString(KEY_PROGRAMMING_LANGUAGE); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setProgrammingLanguage(String programmingLanguage) { + try { + put(KEY_PROGRAMMING_LANGUAGE, programmingLanguage); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_PROGRAMMING_LANGUAGE); + } + } + + public String getAuthorName() { + try { + if(!isNull(KEY_AUTHOR_NAME)) { + return getString(KEY_AUTHOR_NAME); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setAuthorName(String authorName) { + try { + put(KEY_AUTHOR_NAME, authorName); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_AUTHOR_NAME); + } + } + + public String getAuthorEmail() { + try { + if(!isNull(KEY_AUTHOR_EMAIL)) { + return getString(KEY_AUTHOR_EMAIL); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setAuthorEmail(String authorEmail) { + try { + put(KEY_AUTHOR_EMAIL, authorEmail); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_AUTHOR_EMAIL); + } + } + + public String getPlatform() { + try { + if(!isNull(KEY_PLATFORM)) { + return getString(KEY_PLATFORM); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setPlatform(String platform) { + try { + put(KEY_PLATFORM, platform); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_PLATFORM); + } + } + + public String getDistribution() { + try { + if(!isNull(KEY_DISTRIBUTION)) { + return getString(KEY_DISTRIBUTION); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setDistribution(String distribution) { + try { + put(KEY_DISTRIBUTION, distribution); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_DISTRIBUTION); + } + } + + public String getDistributionVersion() { + try { + if(!isNull(KEY_DISTRIBUTION_VERSION)) { + return getString(KEY_DISTRIBUTION_VERSION); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setDistributionVersion(String distributionVersion) { + try { + put(KEY_DISTRIBUTION_VERSION, distributionVersion); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_DISTRIBUTION_VERSION); + } + } + //endregion + + //region AppRelease getters/setters + public String getType() { + if (!isNull(KEY_TYPE)) { + return optString(KEY_TYPE, null); + } + return null; + } + + public void setType(String type) { + try { + put(KEY_TYPE, type); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_TYPE); + } + } + + public String getVersionName() { + if (!isNull(KEY_VERSION_NAME)) { + return optString(KEY_VERSION_NAME, null); + } + return null; + } + + public void setVersionName(String versionName) { + try { + put(KEY_VERSION_NAME, versionName); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_VERSION_NAME); + } + } + + public int getVersionCode() { + if (!isNull(KEY_VERSION_CODE)) { + return optInt(KEY_VERSION_CODE, -1); + } + return -1; + } + + public void setVersionCode(int versionCode) { + try { + put(KEY_VERSION_CODE, versionCode); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_VERSION_CODE); + } + } + + public String getIdentifier() { + if (!isNull(KEY_IDENTIFIER)) { + return optString(KEY_IDENTIFIER, null); + } + return null; + } + + public void setIdentifier(String identifier) { + try { + put(KEY_IDENTIFIER, identifier); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_IDENTIFIER); + } + } + + public String getTargetSdkVersion() { + if (!isNull(KEY_TARGET_SDK_VERSION)) { + return optString(KEY_TARGET_SDK_VERSION); + } + return null; + } + + public void setTargetSdkVersion(String targetSdkVersion) { + try { + put(KEY_TARGET_SDK_VERSION, targetSdkVersion); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_TARGET_SDK_VERSION); + } + } + + public String getAppStore() { + if (!isNull(KEY_APP_STORE)) { + return optString(KEY_APP_STORE, null); + } + return null; + } + + public void setAppStore(String appStore) { + try { + put(KEY_APP_STORE, appStore); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_APP_STORE); + } + } + + // Flag for whether the apptentive is inheriting styles from the host app + public boolean getInheritStyle() { + return optBoolean(KEY_STYLE_INHERIT); + } + + public void setInheritStyle(boolean inheritStyle) { + try { + put(KEY_STYLE_INHERIT, inheritStyle); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_STYLE_INHERIT); + } + } + + // Flag for whether the app is overriding any Apptentive Styles + public boolean getOverrideStyle() { + return optBoolean(KEY_STYLE_OVERRIDE); + } + + public void setOverrideStyle(boolean overrideStyle) { + try { + put(KEY_STYLE_OVERRIDE, overrideStyle); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_STYLE_OVERRIDE); + } + } + + public boolean getDebug() { + return optBoolean(KEY_DEBUG); + } + + public void setDebug(boolean debug) { + try { + put(KEY_DEBUG, debug); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_DEBUG); + } + } + + public static AppRelease generateCurrentAppRelease(Context context) { + + AppRelease appRelease = new AppRelease(); + + String appPackageName = context.getPackageName(); + PackageManager packageManager = context.getPackageManager(); + + int currentVersionCode = 0; + String currentVersionName = "0"; + int targetSdkVersion = 0; + boolean isAppDebuggable = false; + try { + PackageInfo packageInfo = packageManager.getPackageInfo(appPackageName, PackageManager.GET_META_DATA | PackageManager.GET_RECEIVERS); + ApplicationInfo ai = packageInfo.applicationInfo; + currentVersionCode = packageInfo.versionCode; + currentVersionName = packageInfo.versionName; + targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion; + Bundle metaData = ai.metaData; + if (metaData != null) { + isAppDebuggable = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } + } catch (PackageManager.NameNotFoundException e) { + ApptentiveLog.e("Failed to read app's PackageInfo."); + } + + int themeOverrideResId = context.getResources().getIdentifier("ApptentiveThemeOverride", "style", appPackageName); + + appRelease.setType("android"); + appRelease.setVersionName(currentVersionName); + appRelease.setIdentifier(appPackageName); + appRelease.setVersionCode(currentVersionCode); + appRelease.setTargetSdkVersion(String.valueOf(targetSdkVersion)); + appRelease.setAppStore(Util.getInstallerPackageName(context)); + appRelease.setInheritStyle(ApptentiveInternal.getInstance().isAppUsingAppCompatTheme()); + appRelease.setOverrideStyle(themeOverrideResId != 0); + appRelease.setDebug(isAppDebuggable); + + return appRelease; + } + //endregion +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java index 44a719d23..8e41b5480 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java @@ -170,6 +170,9 @@ public void run() { case app_release: response = ApptentiveClient.putAppRelease((com.apptentive.android.sdk.model.AppRelease) payload); break; + case sdk_and_app_release: + response = ApptentiveClient.putSdkAndAppRelease((com.apptentive.android.sdk.model.SdkAndAppReleasePayload) payload); + break; case person: response = ApptentiveClient.putPerson((com.apptentive.android.sdk.model.Person) payload); break; From 50e61881a76d5629ef6891ffd5ebbbbeb8339cbc Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 3 Feb 2017 13:16:20 -0800 Subject: [PATCH 068/465] Combined AppRelease and Sdk payloads when data changes --- .../android/sdk/ApptentiveInternal.java | 61 ++++++++++--------- .../sdk/storage/AppReleaseManager.java | 31 ++++++++++ 2 files changed, 63 insertions(+), 29 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index a8122376a..8885ff384 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -612,53 +612,56 @@ private void checkSendVersionChanges() { return; } + boolean appReleaseChanged = false; + boolean sdkChanged = false; + final VersionHistoryItem lastVersionItemSeen = sessionData.getVersionHistory().getLastVersionSeen(); - final int versionCode = appRelease.getVersionCode(); - final String versionName = appRelease.getVersionName(); + final int currentVersionCode = appRelease.getVersionCode(); + final String currentVersionName = appRelease.getVersionName(); + + Integer previousVersionCode = null; + String previousVersionName = null; if (lastVersionItemSeen == null) { - onVersionChanged(null, versionCode, null, versionName, appRelease); - } else { - int lastSeenVersionCode = lastVersionItemSeen.getVersionCode(); + appReleaseChanged = true; + } + else { + previousVersionCode = lastVersionItemSeen.getVersionCode(); Apptentive.Version lastSeenVersionNameVersion = new Apptentive.Version(); - lastSeenVersionNameVersion.setVersion(lastVersionItemSeen.getVersionName()); - if (!(versionCode == lastSeenVersionCode) || !versionName.equals(lastSeenVersionNameVersion.getVersion())) { - onVersionChanged(lastVersionItemSeen.getVersionCode(), versionCode, lastVersionItemSeen.getVersionName(), versionName, appRelease); + + previousVersionName = lastVersionItemSeen.getVersionName(); + lastSeenVersionNameVersion.setVersion(previousVersionName); + if (!(currentVersionCode == previousVersionCode) || !currentVersionName.equals(lastSeenVersionNameVersion.getVersion())) { + appReleaseChanged = true; } } // TODO: Move this into a session became active handler. - if (!TextUtils.equals(sessionData.getLastSeenSdkVersion(), Constants.APPTENTIVE_SDK_VERSION)) { - onSdkVersionChanged(sessionData.getLastSeenSdkVersion(), Constants.APPTENTIVE_SDK_VERSION); + final String lastSeenSdkVersion = sessionData.getLastSeenSdkVersion(); + final String currentSdkVersion = Constants.APPTENTIVE_SDK_VERSION; + if (!TextUtils.equals(lastSeenSdkVersion, currentSdkVersion)) { + sdkChanged = true; } - } - // FIXME: Do this kind of thing when a session becomes active instead of at app initialization? Otherwise there is no active session to send the information to. - private void onVersionChanged(Integer previousVersionCode, Integer currentVersionCode, String previousVersionName, String currentVersionName, AppRelease currentAppRelease) { - ApptentiveLog.i("Version changed: Name: %s => %s, Code: %d => %d", previousVersionName, currentVersionName, previousVersionCode, currentVersionCode); - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getVersionHistory().updateVersionHistory(Util.currentTimeSeconds(), currentVersionCode, currentVersionName); - } - if (previousVersionCode != null) { - taskManager.addPayload(AppReleaseManager.getPayload(currentAppRelease)); + if (appReleaseChanged) { + ApptentiveLog.i("Version changed: Name: %s => %s, Code: %d => %d", previousVersionName, currentVersionName, previousVersionCode, currentVersionCode); + SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { - sessionData.setAppRelease(currentAppRelease); + sessionData.getVersionHistory().updateVersionHistory(Util.currentTimeSeconds(), currentVersionCode, currentVersionName); } } - invalidateCaches(); - } - // FIXME: Do this kind of thing when a session becomes active instead of at app initialization? Otherwise there is no active session to send the information to. - private void onSdkVersionChanged(String lastSeenSdkVersion, String currentSdkVersion) { - ApptentiveLog.i("SDK version changed: %s => %s", lastSeenSdkVersion, currentSdkVersion); Sdk sdk = SdkManager.generateCurrentSdk(); - taskManager.addPayload(SdkManager.getPayload(sdk)); - if (sessionData != null) { + if (sdkChanged) { + ApptentiveLog.i("SDK version changed: %s => %s", lastSeenSdkVersion, currentSdkVersion); sessionData.setLastSeenSdkVersion(currentSdkVersion); sessionData.setSdk(sdk); } - invalidateCaches(); + + if (appReleaseChanged || sdkChanged) { + taskManager.addPayload(AppReleaseManager.getPayload(sdk, appRelease)); + invalidateCaches(); + } } /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java index 88566051c..29e186e56 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java @@ -14,6 +14,7 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.model.*; import com.apptentive.android.sdk.util.Util; public class AppReleaseManager { @@ -75,4 +76,34 @@ public static com.apptentive.android.sdk.model.AppRelease getPayload(AppRelease ret.setVersionName(appRelease.getVersionName()); return ret; } + + // TODO: this method might not belong here + public static com.apptentive.android.sdk.model.SdkAndAppReleasePayload getPayload(Sdk sdk, AppRelease appRelease) { + com.apptentive.android.sdk.model.SdkAndAppReleasePayload ret = new com.apptentive.android.sdk.model.SdkAndAppReleasePayload(); + if (appRelease == null) { + return ret; + } + + // sdk data + ret.setAuthorEmail(sdk.getAuthorEmail()); + ret.setAuthorName(sdk.getAuthorName()); + ret.setDistribution(sdk.getDistribution()); + ret.setDistributionVersion(sdk.getDistributionVersion()); + ret.setPlatform(sdk.getPlatform()); + ret.setProgrammingLanguage(sdk.getProgrammingLanguage()); + ret.setVersion(sdk.getVersion()); + + + // app release data + ret.setAppStore(appRelease.getAppStore()); + ret.setDebug(appRelease.isDebug()); + ret.setIdentifier(appRelease.getIdentifier()); + ret.setInheritStyle(appRelease.isInheritStyle()); + ret.setOverrideStyle(appRelease.isOverrideStyle()); + ret.setTargetSdkVersion(appRelease.getTargetSdkVersion()); + ret.setType(appRelease.getType()); + ret.setVersionCode(appRelease.getVersionCode()); + ret.setVersionName(appRelease.getVersionName()); + return ret; + } } From fdf752dfe141d1055186758d6c8f78a060b8a069 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 3 Feb 2017 13:22:38 -0800 Subject: [PATCH 069/465] Fixed creating SdkAndAppReleasePayload from json --- .../apptentive/android/sdk/model/PayloadFactory.java | 2 ++ .../android/sdk/model/SdkAndAppReleasePayload.java | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java index c39f58ecb..e55a230fc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java @@ -28,6 +28,8 @@ public static Payload fromJson(String json, Payload.BaseType baseType) { return SdkFactory.fromJson(json); case app_release: return AppReleaseFactory.fromJson(json); + case sdk_and_app_release: + return SdkAndAppReleasePayload.fromJson(json); case person: return PersonFactory.fromJson(json); case survey: diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index 040919604..a07ca18b5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -49,6 +49,17 @@ public class SdkAndAppReleasePayload extends Payload { private static final String KEY_DEBUG = "debug"; //endregion + public static SdkAndAppReleasePayload fromJson(String json) { + try { + return new SdkAndAppReleasePayload(json); + } catch (JSONException e) { + ApptentiveLog.v("Error parsing json as SdkAndAppReleasePayload: %s", e, json); + } catch (IllegalArgumentException e) { + // Unknown unknown #rumsfeld + } + return null; + } + public SdkAndAppReleasePayload(String json) throws JSONException { super(json); } From 4ea8a331a233c5117a915e3985ca6e9f4cabf4aa Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Sat, 4 Feb 2017 14:42:18 -0800 Subject: [PATCH 070/465] Fixed null pointer crash on startup --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 2 +- .../com/apptentive/android/sdk/storage/AppReleaseManager.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 8885ff384..d9acd3bfd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -154,7 +154,7 @@ public static PushAction parse(String name) { private ApptentiveInternal(Context context) { backgroundQueue = DispatchQueue.createBackgroundQueue("Apptentive Serial Queue", DispatchQueueType.Serial); - appRelease = AppReleaseManager.generateCurrentAppRelease(context); + appRelease = AppReleaseManager.generateCurrentAppRelease(context, this); } public static boolean isApptentiveRegistered() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java index 29e186e56..aab2f776e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java @@ -19,7 +19,7 @@ public class AppReleaseManager { - public static AppRelease generateCurrentAppRelease(Context context) { + public static AppRelease generateCurrentAppRelease(Context context, ApptentiveInternal apptentiveInternal) { AppRelease appRelease = new AppRelease(); @@ -49,7 +49,7 @@ public static AppRelease generateCurrentAppRelease(Context context) { appRelease.setAppStore(Util.getInstallerPackageName(context)); appRelease.setDebug(isAppDebuggable); appRelease.setIdentifier(appPackageName); - appRelease.setInheritStyle(ApptentiveInternal.getInstance().isAppUsingAppCompatTheme()); + appRelease.setInheritStyle(apptentiveInternal.isAppUsingAppCompatTheme()); appRelease.setOverrideStyle(themeOverrideResId != 0); appRelease.setTargetSdkVersion(String.valueOf(targetSdkVersion)); appRelease.setType("android"); From 8310fc47aef2acc74ede23de33922d0335b711e8 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Sat, 4 Feb 2017 17:20:13 -0800 Subject: [PATCH 071/465] =?UTF-8?q?Added=20=E2=80=98Tester=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apptentive/android/sdk/debug/Tester.java | 76 +++++++++++++++++++ .../android/sdk/debug/TesterEvent.java | 6 ++ .../sdk/debug/TesterEventListener.java | 6 ++ 3 files changed, 88 insertions(+) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEventListener.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java new file mode 100644 index 000000000..194fe8552 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java @@ -0,0 +1,76 @@ +package com.apptentive.android.sdk.debug; + +import com.apptentive.android.sdk.ApptentiveLog; + +import static com.apptentive.android.sdk.debug.TesterEvent.*; + +public class Tester +{ + private static Tester instance; + + private TesterEventListener listener; + + private Tester(TesterEventListener listener) + { + this.listener = listener; + } + + //////////////////////////////////////////////////////////////// + // Instance + + public static void init(TesterEventListener delegate) + { + instance = new Tester(delegate); + } + + public static void destroy() + { + instance = null; + } + + //////////////////////////////////////////////////////////////// + // Events + + public static boolean isListeningForDebugEvents() + { + return instance != null && instance.listener != null; + } + + public static void dispatchDebugEvent(String name) + { + if (isListeningForDebugEvents()) + { + notifyEvent(name); + } + } + + public static void dispatchException(Throwable e) + { + if (isListeningForDebugEvents() && e != null) + { + StringBuilder stackTrace = new StringBuilder(); + StackTraceElement[] elements = e.getStackTrace(); + for (int i = 0; i < elements.length; ++i) + { + stackTrace.append(elements[i]); + if (i < elements.length-1) + { + stackTrace.append('\n'); + } + } + notifyEvent(EVT_EXCEPTION, e.getClass().getName(), e.getMessage(), stackTrace.toString()); + } + } + + private static void notifyEvent(String name, Object... args) + { + try + { + instance.listener.onDebugEvent(name, args); + } + catch (Exception e) + { + ApptentiveLog.e(e, "Error while dispatching debug event: %s", name); + } + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java new file mode 100644 index 000000000..547f70783 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java @@ -0,0 +1,6 @@ +package com.apptentive.android.sdk.debug; + +public class TesterEvent +{ + public static final String EVT_EXCEPTION = "exception"; // { class:String, message:String, stackTrace:String } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEventListener.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEventListener.java new file mode 100644 index 000000000..b262939a6 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEventListener.java @@ -0,0 +1,6 @@ +package com.apptentive.android.sdk.debug; + +public interface TesterEventListener +{ + void onDebugEvent(String name, Object... params); +} From 8f703db84e6040e46bcd4876dc36e515cd55e8c9 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Sat, 4 Feb 2017 21:12:53 -0800 Subject: [PATCH 072/465] Progress --- .../android/sdk/ApptentiveInternal.java | 10 +- .../apptentive/android/sdk/debug/Tester.java | 127 ++++++++---------- .../android/sdk/debug/TesterEvent.java | 7 +- 3 files changed, 72 insertions(+), 72 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index d9acd3bfd..bca97c339 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -73,6 +73,9 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; +import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; +import static com.apptentive.android.sdk.debug.TesterEvent.EVT_FETCH_CONVERSATION_TOKEN; +import static com.apptentive.android.sdk.debug.TesterEvent.EVT_INSTANCE_CREATED; import static com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry.ComponentNotifier; /** @@ -224,7 +227,11 @@ public static ApptentiveInternal getInstance() { synchronized (ApptentiveInternal.class) { if (sApptentiveInternal != null && !isApptentiveInitialized.get()) { isApptentiveInitialized.set(true); - if (!sApptentiveInternal.init()) { + + final boolean successful = sApptentiveInternal.init(); + dispatchDebugEvent(EVT_INSTANCE_CREATED, successful); + + if (!successful) { ApptentiveLog.e("Apptentive init() failed"); } } @@ -1079,6 +1086,7 @@ private synchronized void scheduleSessionDataSave() { } private synchronized void scheduleConversationCreation() { + dispatchDebugEvent(EVT_FETCH_CONVERSATION_TOKEN); backgroundQueue.dispatchAsyncOnce(createConversationTask); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java index 194fe8552..45eb2f587 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java @@ -4,73 +4,64 @@ import static com.apptentive.android.sdk.debug.TesterEvent.*; -public class Tester -{ - private static Tester instance; - - private TesterEventListener listener; - - private Tester(TesterEventListener listener) - { - this.listener = listener; - } - - //////////////////////////////////////////////////////////////// - // Instance - - public static void init(TesterEventListener delegate) - { - instance = new Tester(delegate); - } - - public static void destroy() - { - instance = null; - } - - //////////////////////////////////////////////////////////////// - // Events - - public static boolean isListeningForDebugEvents() - { - return instance != null && instance.listener != null; - } - - public static void dispatchDebugEvent(String name) - { - if (isListeningForDebugEvents()) - { - notifyEvent(name); - } - } +public class Tester { + private static Tester instance; - public static void dispatchException(Throwable e) - { - if (isListeningForDebugEvents() && e != null) - { - StringBuilder stackTrace = new StringBuilder(); - StackTraceElement[] elements = e.getStackTrace(); - for (int i = 0; i < elements.length; ++i) - { - stackTrace.append(elements[i]); - if (i < elements.length-1) - { - stackTrace.append('\n'); - } - } - notifyEvent(EVT_EXCEPTION, e.getClass().getName(), e.getMessage(), stackTrace.toString()); - } - } - - private static void notifyEvent(String name, Object... args) - { - try - { - instance.listener.onDebugEvent(name, args); - } - catch (Exception e) - { - ApptentiveLog.e(e, "Error while dispatching debug event: %s", name); - } - } + private TesterEventListener listener; + + private Tester(TesterEventListener listener) { + this.listener = listener; + } + + //////////////////////////////////////////////////////////////// + // Instance + + public static void init(TesterEventListener delegate) { + instance = new Tester(delegate); + } + + public static void destroy() { + instance = null; + } + + //////////////////////////////////////////////////////////////// + // Events + + public static boolean isListeningForDebugEvents() { + return instance != null && instance.listener != null; + } + + public static void dispatchDebugEvent(String name) { + if (isListeningForDebugEvents()) { + notifyEvent(name); + } + } + + public static void dispatchDebugEvent(String name, boolean arg) { + if (isListeningForDebugEvents()) { + notifyEvent(name, arg); + } + } + + public static void dispatchException(Throwable e) { + if (isListeningForDebugEvents() && e != null) { + StringBuilder stackTrace = new StringBuilder(); + StackTraceElement[] elements = e.getStackTrace(); + for (int i = 0; i < elements.length; ++i) { + stackTrace.append(elements[i]); + if (i < elements.length - 1) { + stackTrace.append('\n'); + } + } + notifyEvent(EVT_EXCEPTION, e.getClass().getName(), e.getMessage(), stackTrace.toString()); + } + } + + private static void notifyEvent(String name, Object... args) { + try { + instance.listener.onDebugEvent(name, args); + } catch (Exception e) { + ApptentiveLog.e(e, "Error while dispatching debug event: %s", name); + } + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java index 547f70783..2ea29eca6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java @@ -1,6 +1,7 @@ package com.apptentive.android.sdk.debug; -public class TesterEvent -{ - public static final String EVT_EXCEPTION = "exception"; // { class:String, message:String, stackTrace:String } +public class TesterEvent { + public static final String EVT_FETCH_CONVERSATION_TOKEN = "fetch_conversation_token"; + public static final String EVT_INSTANCE_CREATED = "instance_created"; // { successful:boolean } + public static final String EVT_EXCEPTION = "exception"; // { class:String, message:String, stackTrace:String } } From 3e27630bf6022691c520b67867e2772f5b6f093f Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 6 Feb 2017 16:53:44 -0800 Subject: [PATCH 073/465] Added classes for async request handling --- .../apptentive/android/sdk/ApptentiveLog.java | 6 + .../android/sdk/ApptentiveLogTag.java | 7 + .../apptentive/android/sdk/debug/Assert.java | 21 + .../android/sdk/network/HttpRequest.java | 431 ++++++++++++++++++ .../sdk/network/HttpRequestManager.java | 182 ++++++++ .../android/sdk/util/StringUtils.java | 39 ++ 6 files changed, 686 insertions(+) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java index 565fc0a80..e0f6db26b 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java @@ -58,6 +58,12 @@ public static void v(String message, Throwable throwable, Object... args){ doLog(Level.VERBOSE, throwable, message, args); } + public static void d(ApptentiveLogTag tag, String message, Object... args){ + if (tag.enabled) { + doLog(Level.DEBUG, null, message, args); + } + } + public static void d(String message, Object... args){ doLog(Level.DEBUG, null, message, args); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java new file mode 100644 index 000000000..98d72beae --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java @@ -0,0 +1,7 @@ +package com.apptentive.android.sdk; + +public enum ApptentiveLogTag { + NETWORK; + + public boolean enabled; +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java index 593d08835..edcb548b0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -76,6 +76,27 @@ public static void assertFalse(boolean condition, String format, Object... args) // FIXME: implement me } + /** + * Asserts that an object is null + */ + public static void assertNull(Object object) { + // FIXME: implement me + } + + /** + * Asserts that an object is null + */ + public static void assertNull(Object object, String message) { + // FIXME: implement me + } + + /** + * Asserts that an object is null + */ + public static void assertNull(Object object, String format, Object... args) { + // FIXME: implement me + } + /** * Asserts that an object isn't null */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java new file mode 100644 index 000000000..76edb1eb5 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -0,0 +1,431 @@ +package com.apptentive.android.sdk.network; + +import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.util.StringUtils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import static com.apptentive.android.sdk.ApptentiveLogTag.NETWORK; + +/** + * Class representing async HTTP request + */ +public class HttpRequest { + + public static final String METHOD_GET = "GET"; + + public static final String METHOD_POST = "POST"; + + public static final String METHOD_PUT = "PUT"; + + /** + * Default connection timeout + */ + private static final long DEFAULT_CONNECTION_TIMEOUT_MILLIS = 45 * 1000L; + + /** + * Default read timeout + */ + private static final long DEFAULT_READ_TIMEOUT_MILLIS = 45 * 1000L; + + /** + * Id-number of the next request + */ + private static int nextRequestId; + + /** + * Url-connection for network communications + */ + private HttpURLConnection connection; + + /** + * Optional name of the request (for logging purposes) + */ + private String name; + + /** + * Request id (used for testing) + */ + private final int id; + + /** + * URL string of the request + */ + private String urlString; + + /** + * Optional request URL query params + */ + private Map queryParams; + + /** + * Request HTTP httpHeaders + */ + private Map httpHeaders; + + /** + * Request method (GET, POST, PUT) + */ + private String method = METHOD_GET; + + /** + * Connection timeout in milliseconds + */ + private long connectionTimeoutMillis = DEFAULT_CONNECTION_TIMEOUT_MILLIS; + + /** + * Read timeout in milliseconds + */ + private long readTimeoutMillis = DEFAULT_READ_TIMEOUT_MILLIS; + + /** + * The status code from an HTTP response message + */ + private int responseCode; + + /** + * The status message from an HTTP response + */ + private String responseMessage; + + /** + * Optional body data + */ + private byte[] body; + + /** + * Http-response data + */ + private byte[] responseData; + + /** + * Cancelled flag (not thread safe) + */ + private boolean cancelled; + + /** + * Inner exception thrown on a background thread + */ + private Exception thrownException; + + @SuppressWarnings("rawtypes") + private Listener listener; + + /** + * Total request durationMillis in milliseconds + */ + private long durationMillis; + + public HttpRequest(String urlString) { + if (urlString == null || urlString.length() == 0) { + throw new IllegalArgumentException("Invalid URL string '" + urlString + "'"); + } + + this.id = nextRequestId++; + this.urlString = urlString; + this.method = METHOD_GET; + this.connectionTimeoutMillis = DEFAULT_CONNECTION_TIMEOUT_MILLIS; + } + + //////////////////////////////////////////////////////////////// + // Lifecycle + + @SuppressWarnings("unchecked") + private void finishRequest() { + try { + if (listener != null) { + listener.onFinish(this); + } + } catch (Exception e) { + ApptentiveLog.e(e, "Exception in request finish listener"); + } + } + + /** Override this method in a subclass to create data from response bytes */ + protected void handleResponse(byte[] responseData) throws IOException { + } + + //////////////////////////////////////////////////////////////// + // Request async task + + void dispatchSync() { + try { + sendRequestSync(); + } catch (Exception e) { + thrownException = e; + if (!isCancelled()) { + ApptentiveLog.e(e, "Unable to perform request"); + } + } + + finishRequest(); + } + + private void sendRequestSync() throws IOException { + try { + long startTimestamp = System.currentTimeMillis(); + + URL url = createUrl(urlString); + ApptentiveLog.d(NETWORK, "Performing request: %s", url); + + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod(method); + connection.setConnectTimeout((int) connectionTimeoutMillis); + connection.setReadTimeout((int) readTimeoutMillis); + + if (isCancelled()) { + return; + } + + if (httpHeaders != null && httpHeaders.size() > 0) { + setupHeaders(connection, httpHeaders); + } + + if (METHOD_POST.equals(method) || METHOD_PUT.equals(method)) { + connection.setDoInput(true); + connection.setDoOutput(true); + connection.setUseCaches(false); + + if (body != null && body.length > 0) { + OutputStream outputStream = null; + try { + outputStream = connection.getOutputStream(); + outputStream.write(body); + } finally { + if (outputStream != null) { + outputStream.close(); + } + } + } + } + + responseCode = connection.getResponseCode(); + responseMessage = connection.getResponseMessage(); + + if (isCancelled()) { + return; + } + + if (responseCode == HttpURLConnection.HTTP_OK) { + InputStream is = this.connection.getInputStream(); + responseData = readResponse(is); + + if (isCancelled()) { + return; + } + + handleResponse(responseData); + + durationMillis = System.currentTimeMillis() - startTimestamp; + } else { + throw new IOException("Unexpected response code " + responseCode + " for URL " + url); + } + } finally { + closeConnection(); + } + } + + //region Connection + + private void setupHeaders(HttpURLConnection connection, Map headers) { + if (headers == null) { + throw new IllegalArgumentException("Headers are null"); + } + + Set> entries = headers.entrySet(); + for (Entry e : entries) { + String name = e.getKey(); + Object value = e.getValue(); + + if (name != null && value != null) { + connection.setRequestProperty(name, value.toString()); + } + } + } + + private void closeConnection() { + if (connection != null) { + connection.disconnect(); + connection = null; + } + } + + private byte[] readResponse(InputStream is) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int bytesRead; + + while ((bytesRead = is.read(buffer)) != -1) { + bos.write(buffer, 0, bytesRead); + } + + return bos.toByteArray(); + } + + //endregion + + //region Cancellation + + /** + * Returns true if request is cancelled + */ + public synchronized boolean isCancelled() { + return cancelled; + } + + /** + * Marks request as cancelled + */ + public synchronized void cancel() { + cancelled = true; + } + + //endregion + + //region Query + + /** + * Sets a URL-parameter. The actual usage depends on request method (GET, POST, etc) + */ + public void setQueryParam(String key, Object value) { + if (value != null) { + if (queryParams == null) { + queryParams = new HashMap<>(); + } + queryParams.put(key, value); + } + } + + /** + * Sets URL-parameters. The actual usage depends on request method (GET, POST, etc) + */ + public void setQueryParams(Map queryParams) { + if (queryParams != null && queryParams.size() > 0) { + Set> entries = queryParams.entrySet(); + for (Entry e : entries) { + String key = e.getKey(); + if (key == null) { + throw new IllegalArgumentException("Can't add request param: key is null"); + } + + Object value = e.getValue(); + if (value != null) { + setQueryParam(key, value); + } + } + } + } + + //endregion + + //region HTTP header + + /** + * Sets HTTP header for request + */ + public void setHttpHeader(String key, Object value) { + if (value != null) { + if (httpHeaders == null) { + httpHeaders = new HashMap<>(); + } + httpHeaders.put(key, value); + } + } + + /** + * Sets HTTP headers for request + */ + public void setHttpHeaders(Map headers) { + if (headers != null && headers.size() > 0) { + Set> entries = headers.entrySet(); + for (Entry e : entries) { + String key = e.getKey(); + if (key == null) { + throw new IllegalArgumentException("Can't add httpHeaders to request: map contains a null key"); + } + + Object value = e.getValue(); + if (value != null) { + setHttpHeader(key, value); + } + } + } + } + + //endregion + + //region Helpers + + private URL createUrl(String baseUrl) throws IOException { + if (METHOD_GET.equals(method)) { + if (queryParams != null && queryParams.size() > 0) { + String query = StringUtils.createQueryString(queryParams); + if (baseUrl.endsWith("/")) { + return new URL(baseUrl + query); + } + return new URL(baseUrl + "/" + query); + } + } + return new URL(baseUrl); + } + + //endregion + + //region String representation + + public String toString() { + return urlString; + } + + //endregion + + //region Getters/Setters + + public int getId() { + return id; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setListener(Listener listener) { + this.listener = listener; + } + + public byte[] responseData() { + return responseData; + } + + public long duration() { + return durationMillis; + } + + /** Inner exception which caused request to fail */ + public Exception getThrownException() { + return thrownException; + } + + //endregion + + //region Listener + + public interface Listener { + void onFinish(T request); + } + + //endregion +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java new file mode 100644 index 000000000..728fc4dd0 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java @@ -0,0 +1,182 @@ +package com.apptentive.android.sdk.network; + +import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchQueueType; +import com.apptentive.android.sdk.util.threading.DispatchTask; + +import java.util.ArrayList; +import java.util.List; + +import static com.apptentive.android.sdk.debug.Assert.assertTrue; + +/** + * Class for asynchronous HTTP requests handling + */ +public class HttpRequestManager { + /** + * List of active requests (started but not yet finished) + */ + private List activeRequests; + + /** + * Dispatch queue for blocking network operations + */ + private final DispatchQueue networkQueue; + + /** + * Dispatch queue for listener callbacks + */ + private final DispatchQueue callbackQueue; + + private Listener listener; + + /** + * Creates a request manager with a default concurrent "network" queue. All listener callbacks are + * dispatched on the main queue. + */ + public HttpRequestManager() { + this(DispatchQueue.createBackgroundQueue("Apptentive Network Queue", DispatchQueueType.Concurrent), DispatchQueue.mainQueue()); + } + + /** + * Creates a request manager with custom "network" and "callback" queues + * + * @param networkQueue - dispatch queue for blocking network operations + * @param callbackQueue - dispatch queue for listener callbacks + * @throws IllegalArgumentException if any of specified queues is null + */ + public HttpRequestManager(DispatchQueue networkQueue, DispatchQueue callbackQueue) { + if (networkQueue == null) { + throw new IllegalArgumentException("Network queue is null"); + } + if (callbackQueue == null) { + throw new IllegalArgumentException("Callback queue is null"); + } + + this.networkQueue = networkQueue; + this.callbackQueue = callbackQueue; + this.activeRequests = new ArrayList<>(); + } + + //region Requests + + /** + * Starts network request on the network queue (method returns immediately) + */ + public synchronized void startRequest(final HttpRequest request) { + if (request == null) { + throw new IllegalArgumentException("Request is null"); + } + + registerRequest(request); + + networkQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + try { + request.dispatchSync(); + } finally { + unregisterRequest(request); + } + } + }); + + notifyRequestStarted(request); + } + + /** + * Cancel all active requests + */ + public synchronized void cancelAll() { + if (activeRequests.size() > 0) { + List temp = new ArrayList<>(activeRequests); + for (HttpRequest request : temp) { + request.cancel(); + } + activeRequests.clear(); + notifyCancelledAllRequests(); + } + } + + /** + * Register active request + */ + synchronized void registerRequest(HttpRequest request) { + activeRequests.add(request); + } + + /** + * Unregisters active request + */ + synchronized void unregisterRequest(HttpRequest request) { + boolean removed = activeRequests.remove(request); + assertTrue(removed, "Attempted to unregister missing request: %s", request); + + if (removed) { + notifyRequestFinished(request); + } + } + + //endregion + + //region Listener callbacks + + private void notifyRequestStarted(final HttpRequest request) { + callbackQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + if (listener != null) { + listener.onRequestStart(HttpRequestManager.this, request); + } + } + }); + } + + private void notifyRequestFinished(final HttpRequest request) { + callbackQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + if (listener != null) { + listener.onRequestFinish(HttpRequestManager.this, request); + } + } + }); + } + + private void notifyCancelledAllRequests() { + callbackQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + if (listener != null) { + listener.onRequestsCancel(HttpRequestManager.this); + } + } + }); + } + + //endregion + + //region Getters/Setters + + public Listener getListener() { + return listener; + } + + public void setListener(Listener listener) { + this.listener = listener; + } + + //endregion + + //region Listener + + public interface Listener { + void onRequestStart(HttpRequestManager manager, HttpRequest request); + + void onRequestFinish(HttpRequestManager manager, HttpRequest request); + + void onRequestsCancel(HttpRequestManager manager); + } + + //endregion +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java index d89e40423..b0aba87f9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java @@ -21,7 +21,9 @@ package com.apptentive.android.sdk.util; +import java.net.URLEncoder; import java.util.List; +import java.util.Map; /** * A collection of useful string-related functions @@ -88,4 +90,41 @@ public static String join(List list, String separator) { } return builder.toString(); } + + /** + * Create URL encoded params string from the map of key-value pairs + * + * @throws IllegalArgumentException if map, any key or value appears to be null + */ + public static String createQueryString(Map params) { // FIXME: unit tests (DO NOT ACCEPT PULL REQUEST IF YOU SEE THIS COMMENT) + if (params == null) { + throw new IllegalArgumentException("Params are null"); + } + + StringBuilder result = new StringBuilder(); + for (Map.Entry e : params.entrySet()) { + String key = e.getKey(); + if (key == null) { + throw new IllegalArgumentException("key is null"); + } + + Object valueObj = e.getValue(); + if (valueObj == null) { + throw new IllegalArgumentException("value is null for key '" + key + "'"); + } + + String value = valueObj.toString(); + + @SuppressWarnings("deprecation") + String encodedKey = URLEncoder.encode(key); + @SuppressWarnings("deprecation") + String encodedValue = URLEncoder.encode(value); + + result.append(result.length() == 0 ? "?" : "&"); + result.append(encodedKey); + result.append("="); + result.append(encodedValue); + } + return result.toString(); + } } From 77fe64d2c99c35f01f8866a106a3971ada403a06 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 7 Feb 2017 11:15:13 -0800 Subject: [PATCH 074/465] Added HttpJsonRequest class + some HttpRequestManager refactoring --- .../android/sdk/network/HttpJsonRequest.java | 19 +++++++++++ .../android/sdk/network/HttpRequest.java | 32 +++++++++++-------- .../sdk/network/HttpRequestMethod.java | 7 ++++ 3 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonRequest.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestMethod.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonRequest.java new file mode 100644 index 000000000..cde555f6b --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonRequest.java @@ -0,0 +1,19 @@ +package com.apptentive.android.sdk.network; + +import org.json.JSONObject; + +/** + * Class representing HTTP request with Json POST body + */ +public class HttpJsonRequest extends HttpRequest { + private final JSONObject jsonObject; + + public HttpJsonRequest(String urlString, JSONObject jsonObject) { + super(urlString); + + if (jsonObject == null) { + throw new IllegalArgumentException("Json object is null"); + } + this.jsonObject = jsonObject; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 76edb1eb5..7079130cc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -21,12 +21,6 @@ */ public class HttpRequest { - public static final String METHOD_GET = "GET"; - - public static final String METHOD_POST = "POST"; - - public static final String METHOD_PUT = "PUT"; - /** * Default connection timeout */ @@ -75,7 +69,7 @@ public class HttpRequest { /** * Request method (GET, POST, PUT) */ - private String method = METHOD_GET; + private HttpRequestMethod method = HttpRequestMethod.GET; /** * Connection timeout in milliseconds @@ -132,8 +126,6 @@ public HttpRequest(String urlString) { this.id = nextRequestId++; this.urlString = urlString; - this.method = METHOD_GET; - this.connectionTimeoutMillis = DEFAULT_CONNECTION_TIMEOUT_MILLIS; } //////////////////////////////////////////////////////////////// @@ -150,7 +142,9 @@ private void finishRequest() { } } - /** Override this method in a subclass to create data from response bytes */ + /** + * Override this method in a subclass to create data from response bytes + */ protected void handleResponse(byte[] responseData) throws IOException { } @@ -178,7 +172,7 @@ private void sendRequestSync() throws IOException { ApptentiveLog.d(NETWORK, "Performing request: %s", url); connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod(method); + connection.setRequestMethod(method.toString()); connection.setConnectTimeout((int) connectionTimeoutMillis); connection.setReadTimeout((int) readTimeoutMillis); @@ -190,7 +184,7 @@ private void sendRequestSync() throws IOException { setupHeaders(connection, httpHeaders); } - if (METHOD_POST.equals(method) || METHOD_PUT.equals(method)) { + if (HttpRequestMethod.POST.equals(method) || HttpRequestMethod.PUT.equals(method)) { connection.setDoInput(true); connection.setDoOutput(true); connection.setUseCaches(false); @@ -366,7 +360,7 @@ public void setHttpHeaders(Map headers) { //region Helpers private URL createUrl(String baseUrl) throws IOException { - if (METHOD_GET.equals(method)) { + if (HttpRequestMethod.GET.equals(method)) { if (queryParams != null && queryParams.size() > 0) { String query = StringUtils.createQueryString(queryParams); if (baseUrl.endsWith("/")) { @@ -390,6 +384,14 @@ public String toString() { //region Getters/Setters + public void setMethod(HttpRequestMethod method) { + if (method == null) { + throw new IllegalArgumentException("Method is null"); + } + + this.method = method; + } + public int getId() { return id; } @@ -414,7 +416,9 @@ public long duration() { return durationMillis; } - /** Inner exception which caused request to fail */ + /** + * Inner exception which caused request to fail + */ public Exception getThrownException() { return thrownException; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestMethod.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestMethod.java new file mode 100644 index 000000000..f8cafdfed --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestMethod.java @@ -0,0 +1,7 @@ +package com.apptentive.android.sdk.network; + +public enum HttpRequestMethod { + GET, + POST, + PUT +} From 067cfbbb38740b673ac0ded4829ac5715998db1b Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 7 Feb 2017 11:23:51 -0800 Subject: [PATCH 075/465] HttpRequest refactoring --- .../android/sdk/network/HttpRequest.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 7079130cc..a1e945f69 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -24,7 +24,7 @@ public class HttpRequest { /** * Default connection timeout */ - private static final long DEFAULT_CONNECTION_TIMEOUT_MILLIS = 45 * 1000L; + private static final long DEFAULT_CONNECT_TIMEOUT_MILLIS = 45 * 1000L; /** * Default read timeout @@ -74,12 +74,12 @@ public class HttpRequest { /** * Connection timeout in milliseconds */ - private long connectionTimeoutMillis = DEFAULT_CONNECTION_TIMEOUT_MILLIS; + private long connectTimeout = DEFAULT_CONNECT_TIMEOUT_MILLIS; /** * Read timeout in milliseconds */ - private long readTimeoutMillis = DEFAULT_READ_TIMEOUT_MILLIS; + private long readTimeout = DEFAULT_READ_TIMEOUT_MILLIS; /** * The status code from an HTTP response message @@ -173,8 +173,8 @@ private void sendRequestSync() throws IOException { connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod(method.toString()); - connection.setConnectTimeout((int) connectionTimeoutMillis); - connection.setReadTimeout((int) readTimeoutMillis); + connection.setConnectTimeout((int) connectTimeout); + connection.setReadTimeout((int) readTimeout); if (isCancelled()) { return; @@ -392,6 +392,14 @@ public void setMethod(HttpRequestMethod method) { this.method = method; } + public void setConnectTimeout(long connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public void setReadTimeout(long readTimeout) { + this.readTimeout = readTimeout; + } + public int getId() { return id; } From 8acda119dd90399688f1721087957786a97be7e6 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 7 Feb 2017 11:34:52 -0800 Subject: [PATCH 076/465] HttpRequest refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed ‘headers’ to ‘request properties’ --- .../android/sdk/network/HttpRequest.java | 48 +++++-------------- 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index a1e945f69..28e1b9f04 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -62,9 +62,9 @@ public class HttpRequest { private Map queryParams; /** - * Request HTTP httpHeaders + * Request HTTP properties (will be added to a connection) */ - private Map httpHeaders; + private Map requestProperties; /** * Request method (GET, POST, PUT) @@ -180,8 +180,8 @@ private void sendRequestSync() throws IOException { return; } - if (httpHeaders != null && httpHeaders.size() > 0) { - setupHeaders(connection, httpHeaders); + if (requestProperties != null && requestProperties.size() > 0) { + setupRequestProperties(connection, requestProperties); } if (HttpRequestMethod.POST.equals(method) || HttpRequestMethod.PUT.equals(method)) { @@ -230,12 +230,8 @@ private void sendRequestSync() throws IOException { //region Connection - private void setupHeaders(HttpURLConnection connection, Map headers) { - if (headers == null) { - throw new IllegalArgumentException("Headers are null"); - } - - Set> entries = headers.entrySet(); + private void setupRequestProperties(HttpURLConnection connection, Map properties) { + Set> entries = properties.entrySet(); for (Entry e : entries) { String name = e.getKey(); Object value = e.getValue(); @@ -321,37 +317,17 @@ public void setQueryParams(Map queryParams) { //endregion - //region HTTP header + //region HTTP request properties /** - * Sets HTTP header for request + * Sets HTTP request property */ - public void setHttpHeader(String key, Object value) { + public void setRequestProperty(String key, Object value) { if (value != null) { - if (httpHeaders == null) { - httpHeaders = new HashMap<>(); - } - httpHeaders.put(key, value); - } - } - - /** - * Sets HTTP headers for request - */ - public void setHttpHeaders(Map headers) { - if (headers != null && headers.size() > 0) { - Set> entries = headers.entrySet(); - for (Entry e : entries) { - String key = e.getKey(); - if (key == null) { - throw new IllegalArgumentException("Can't add httpHeaders to request: map contains a null key"); - } - - Object value = e.getValue(); - if (value != null) { - setHttpHeader(key, value); - } + if (requestProperties == null) { + requestProperties = new HashMap<>(); } + requestProperties.put(key, value); } } From 910fa25b5cfbd637280a5b89f52d5bf822b5b01b Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 7 Feb 2017 12:18:05 -0800 Subject: [PATCH 077/465] HttpRequest refactoring Added support for g-zip content encoding --- .../apptentive/android/sdk/ApptentiveLog.java | 11 +++ .../android/sdk/network/HttpRequest.java | 90 ++++++++++++------- 2 files changed, 71 insertions(+), 30 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java index e0f6db26b..f23294d8b 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java @@ -51,6 +51,12 @@ public static boolean canLog(Level level) { return logLevel.canLog(level); } + public static void v(ApptentiveLogTag tag, String message, Object... args) { + if (tag.enabled) { + doLog(Level.VERBOSE, null, message, args); + } + } + public static void v(String message, Object... args){ doLog(Level.VERBOSE, null, message, args); } @@ -78,6 +84,11 @@ public static void i(String message, Throwable throwable, Object... args){ doLog(Level.INFO, throwable, message, args); } + public static void w(ApptentiveLogTag tag, String message, Object... args){ + if (tag.enabled) { + doLog(Level.WARN, null, message, args); + } + } public static void w(String message, Object... args){ doLog(Level.WARN, null, message, args); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 28e1b9f04..c1c241238 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -2,17 +2,19 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.util.Util; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.zip.GZIPInputStream; import static com.apptentive.android.sdk.ApptentiveLogTag.NETWORK; @@ -82,7 +84,7 @@ public class HttpRequest { private long readTimeout = DEFAULT_READ_TIMEOUT_MILLIS; /** - * The status code from an HTTP response message + * The status code from an HTTP response */ private int responseCode; @@ -97,9 +99,14 @@ public class HttpRequest { private byte[] body; /** - * Http-response data + * HTTP response content string */ - private byte[] responseData; + private String responseContent; + + /** + * Map of connection response headers + */ + private Map responseHeaders; /** * Cancelled flag (not thread safe) @@ -145,7 +152,7 @@ private void finishRequest() { /** * Override this method in a subclass to create data from response bytes */ - protected void handleResponse(byte[] responseData) throws IOException { + protected void handleResponse(String response) throws IOException { } //////////////////////////////////////////////////////////////// @@ -195,13 +202,12 @@ private void sendRequestSync() throws IOException { outputStream = connection.getOutputStream(); outputStream.write(body); } finally { - if (outputStream != null) { - outputStream.close(); - } + Util.ensureClosed(outputStream); } } } + // send request responseCode = connection.getResponseCode(); responseMessage = connection.getResponseMessage(); @@ -209,20 +215,28 @@ private void sendRequestSync() throws IOException { return; } - if (responseCode == HttpURLConnection.HTTP_OK) { - InputStream is = this.connection.getInputStream(); - responseData = readResponse(is); - - if (isCancelled()) { - return; - } - - handleResponse(responseData); + // get HTTP headers + responseHeaders = getResponseHeaders(connection); - durationMillis = System.currentTimeMillis() - startTimestamp; + // TODO: figure out a better way of handling response codes + boolean gzipped = isGzipContentEncoding(responseHeaders); + if (responseCode >= HttpURLConnection.HTTP_OK && responseCode < HttpURLConnection.HTTP_MULT_CHOICE) { + responseContent = readResponse(connection.getInputStream(), gzipped); + ApptentiveLog.v(NETWORK, "Response: %s", responseContent); } else { - throw new IOException("Unexpected response code " + responseCode + " for URL " + url); + responseContent = readResponse(connection.getErrorStream(), gzipped); + ApptentiveLog.w(NETWORK, "Response: %s", responseContent); } + + if (isCancelled()) { + return; + } + + // optionally handle response data (should be overridden in a sub class) + handleResponse(responseContent); + + // calculate total duration + durationMillis = System.currentTimeMillis() - startTimestamp; } finally { closeConnection(); } @@ -249,16 +263,36 @@ private void closeConnection() { } } - private byte[] readResponse(InputStream is) throws IOException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int bytesRead; + private static Map getResponseHeaders(HttpURLConnection connection) { + Map headers = new HashMap<>(); + Map> map = connection.getHeaderFields(); + for (Entry> entry : map.entrySet()) { + headers.put(entry.getKey(), entry.getValue().toString()); + } + return headers; + } + + private static boolean isGzipContentEncoding(Map responseHeaders) { + if (responseHeaders != null) { + String contentEncoding = responseHeaders.get("Content-Encoding"); + return contentEncoding != null && contentEncoding.equalsIgnoreCase("[gzip]"); + } + return false; + } - while ((bytesRead = is.read(buffer)) != -1) { - bos.write(buffer, 0, bytesRead); + private static String readResponse(InputStream is, boolean gzipped) throws IOException { + if (is == null) { + return null; } - return bos.toByteArray(); + try { + if (gzipped) { + is = new GZIPInputStream(is); + } + return Util.readStringFromInputStream(is, "UTF-8"); + } finally { + Util.ensureClosed(is); + } } //endregion @@ -392,10 +426,6 @@ public void setListener(Listener listener) { this.listener = listener; } - public byte[] responseData() { - return responseData; - } - public long duration() { return durationMillis; } From 2c237b0000f07be5201865ad205a28b72b882d73 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 7 Feb 2017 13:52:51 -0800 Subject: [PATCH 078/465] HttpJsonRequest progress --- .../android/sdk/network/HttpJsonRequest.java | 31 ++++++++++++++++--- .../android/sdk/network/HttpRequest.java | 15 ++++----- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonRequest.java index cde555f6b..f93bb41ed 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonRequest.java @@ -1,19 +1,42 @@ package com.apptentive.android.sdk.network; +import org.json.JSONException; import org.json.JSONObject; +import java.io.IOException; + /** * Class representing HTTP request with Json POST body */ public class HttpJsonRequest extends HttpRequest { - private final JSONObject jsonObject; + private final JSONObject requestObject; + private JSONObject responseObject; - public HttpJsonRequest(String urlString, JSONObject jsonObject) { + public HttpJsonRequest(String urlString, JSONObject requestObject) { super(urlString); - if (jsonObject == null) { + if (requestObject == null) { throw new IllegalArgumentException("Json object is null"); } - this.jsonObject = jsonObject; + this.requestObject = requestObject; + } + + @Override + protected byte[] createRequestData() throws IOException { + String json = requestObject.toString(); + return json.getBytes("UTF-8"); + } + + @Override + protected void handleResponse(String response) throws IOException { + try { + responseObject = new JSONObject(response); + } catch (JSONException e) { + throw new IOException(e); + } + } + + public JSONObject getResponseObject() { + return responseObject; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index c1c241238..b73184dd7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -93,11 +93,6 @@ public class HttpRequest { */ private String responseMessage; - /** - * Optional body data - */ - private byte[] body; - /** * HTTP response content string */ @@ -149,6 +144,11 @@ private void finishRequest() { } } + /** Override this method to create request data on a background thread */ + protected byte[] createRequestData() throws IOException { + return null; + } + /** * Override this method in a subclass to create data from response bytes */ @@ -196,11 +196,12 @@ private void sendRequestSync() throws IOException { connection.setDoOutput(true); connection.setUseCaches(false); - if (body != null && body.length > 0) { + byte[] requestData = createRequestData(); + if (requestData != null && requestData.length > 0) { OutputStream outputStream = null; try { outputStream = connection.getOutputStream(); - outputStream.write(body); + outputStream.write(requestData); } finally { Util.ensureClosed(outputStream); } From bd4f8a95eab7d1dbe3bbfd54082bcfc18d296a2d Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 7 Feb 2017 13:57:27 -0800 Subject: [PATCH 079/465] Added async version of ApptentiveClien --- .../sdk/comm/ApptentiveHttpClient.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java new file mode 100644 index 000000000..6ecfa9ab7 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -0,0 +1,84 @@ +package com.apptentive.android.sdk.comm; + +import com.apptentive.android.sdk.model.ConversationTokenRequest; +import com.apptentive.android.sdk.network.HttpJsonRequest; +import com.apptentive.android.sdk.network.HttpRequest; +import com.apptentive.android.sdk.network.HttpRequestManager; +import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.util.Constants; + +import org.json.JSONObject; + +import static android.text.TextUtils.isEmpty; + +/** + * Class responsible for all client-server network communications using asynchronous HTTP requests + */ +public class ApptentiveHttpClient { + public static final String API_VERSION = "7"; + + private static final String USER_AGENT_STRING = "Apptentive/%s (Android)"; // Format with SDK version string. + + public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 45000; + public static final int DEFAULT_HTTP_SOCKET_TIMEOUT = 45000; + + // Active API + private static final String ENDPOINT_CONVERSATION = "/conversation"; + + private final String oauthToken; + private final String serverURL; + private final String userAgentString; + private final HttpRequestManager httpRequestManager; + + public ApptentiveHttpClient(String oauthToken, String serverURL) { + if (isEmpty(oauthToken)) { + throw new IllegalArgumentException("Illegal OAuth Token: '" + oauthToken + "'"); + } + + if (isEmpty(serverURL)) { + throw new IllegalArgumentException("Illegal server URL: '" + serverURL + "'"); + } + + this.httpRequestManager = new HttpRequestManager(); + this.oauthToken = oauthToken; + this.serverURL = serverURL; + this.userAgentString = String.format(USER_AGENT_STRING, Constants.APPTENTIVE_SDK_VERSION); + } + + //region API Requests + + public void getConversationToken(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { + HttpJsonRequest request = createJsonRequest(ENDPOINT_CONVERSATION, conversationTokenRequest); + request.setListener(listener); + httpRequestManager.startRequest(request); + } + + //endregion + + //region Helpers + + private HttpJsonRequest createJsonRequest(String uri, JSONObject jsonObject) { + String url = createEndpointURL(uri); + HttpJsonRequest request = new HttpJsonRequest(url, jsonObject); + setupRequestDefaults(request); + request.setMethod(HttpRequestMethod.POST); + return request; + } + + private void setupRequestDefaults(HttpRequest request) { + request.setRequestProperty("User-Agent", userAgentString); + request.setRequestProperty("Connection", "Keep-Alive"); + request.setRequestProperty("Authorization", "OAuth " + oauthToken); + request.setRequestProperty("Accept-Encoding", "gzip"); + request.setRequestProperty("Accept", "application/json"); + request.setRequestProperty("X-API-Version", API_VERSION); + request.setConnectTimeout(DEFAULT_HTTP_CONNECT_TIMEOUT); + request.setReadTimeout(DEFAULT_HTTP_SOCKET_TIMEOUT); + } + + private String createEndpointURL(String uri) { + return serverURL + uri; + } + + //endregion +} From 877494df870d7b3304a074b47cfa1a6a209677e4 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 7 Feb 2017 14:30:45 -0800 Subject: [PATCH 080/465] Refactoring Initialized fields of class ApptentiveInternal inside constructor --- .../android/sdk/ApptentiveInternal.java | 124 ++++++++++-------- 1 file changed, 72 insertions(+), 52 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index bca97c339..8f1594fee 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -26,6 +26,7 @@ import android.text.TextUtils; import com.apptentive.android.sdk.comm.ApptentiveClient; +import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.lifecycle.ApptentiveActivityLifecycleCallbacks; import com.apptentive.android.sdk.listeners.OnUserLogOutListener; @@ -43,6 +44,7 @@ import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.AppReleaseManager; import com.apptentive.android.sdk.storage.ApptentiveTaskManager; +import com.apptentive.android.sdk.storage.DataChangedListener; import com.apptentive.android.sdk.storage.Device; import com.apptentive.android.sdk.storage.DeviceManager; import com.apptentive.android.sdk.storage.FileSerializer; @@ -50,7 +52,6 @@ import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.storage.SessionData; -import com.apptentive.android.sdk.storage.DataChangedListener; import com.apptentive.android.sdk.storage.VersionHistoryItem; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -84,23 +85,24 @@ public class ApptentiveInternal implements DataChangedListener { static AtomicBoolean isApptentiveInitialized = new AtomicBoolean(false); - InteractionManager interactionManager; - MessageManager messageManager; - PayloadSendWorker payloadWorker; - ApptentiveTaskManager taskManager; + private final InteractionManager interactionManager; + private final MessageManager messageManager; + private final PayloadSendWorker payloadWorker; + private final ApptentiveTaskManager taskManager; ApptentiveActivityLifecycleCallbacks lifecycleCallbacks; - ApptentiveComponentRegistry componentRegistry; + private final ApptentiveComponentRegistry componentRegistry; + private final ApptentiveHttpClient apptentiveHttpClient; // These variables are initialized in Apptentive.register(), and so they are freely thereafter. If they are unexpectedly null, then if means the host app did not register Apptentive. - Context appContext; + private final Context appContext; // We keep a readonly reference to AppRelease object since it won't change at runtime private final AppRelease appRelease; boolean appIsInForeground; - SharedPreferences globalSharedPrefs; - String apiKey; + private final SharedPreferences globalSharedPrefs; + private final String apiKey; String personId; String androidId; String appPackageName; @@ -128,7 +130,7 @@ public class ApptentiveInternal implements DataChangedListener { final LinkedBlockingQueue interactionUpdateListeners = new LinkedBlockingQueue(); - ExecutorService cachedExecutor; + private final ExecutorService cachedExecutor; // Holds reference to the current foreground activity of the host app private WeakReference currentTaskStackTopActivity; @@ -155,9 +157,22 @@ public static PushAction parse(String name) { @SuppressLint("StaticFieldLeak") private static volatile ApptentiveInternal sApptentiveInternal; - private ApptentiveInternal(Context context) { + private ApptentiveInternal(Context context, String apiKey) { + this.apiKey = apiKey; + + appContext = context.getApplicationContext(); + + globalSharedPrefs = context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); backgroundQueue = DispatchQueue.createBackgroundQueue("Apptentive Serial Queue", DispatchQueueType.Serial); + componentRegistry = new ApptentiveComponentRegistry(); + apptentiveHttpClient = new ApptentiveHttpClient(apiKey, getEndpointBase(globalSharedPrefs)); + appRelease = AppReleaseManager.generateCurrentAppRelease(context, this); + messageManager = new MessageManager(); + payloadWorker = new PayloadSendWorker(); + interactionManager = new InteractionManager(); + taskManager = new ApptentiveTaskManager(appContext); + cachedExecutor = Executors.newCachedThreadPool(); } public static boolean isApptentiveRegistered() { @@ -177,34 +192,48 @@ public static boolean isApptentiveRegistered() { * @param context the context of the app that is creating the instance * @return An non-null instance of the Apptentive SDK */ - public static ApptentiveInternal createInstance(Context context, final String apptentiveApiKey) { + public static ApptentiveInternal createInstance(Context context, String apptentiveApiKey) { if (sApptentiveInternal == null) { synchronized (ApptentiveInternal.class) { if (sApptentiveInternal == null && context != null) { - sApptentiveInternal = new ApptentiveInternal(context); + + // trim spaces + apptentiveApiKey = Util.trim(apptentiveApiKey); + + // if API key is not defined - try loading from AndroidManifest.xml + if (TextUtils.isEmpty(apptentiveApiKey)) { + apptentiveApiKey = resolveManifestApiKey(context); + } + + sApptentiveInternal = new ApptentiveInternal(context, apptentiveApiKey); isApptentiveInitialized.set(false); - sApptentiveInternal.appContext = context.getApplicationContext(); - sApptentiveInternal.globalSharedPrefs = sApptentiveInternal.appContext.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); - - MessageManager msgManager = new MessageManager(); - PayloadSendWorker payloadWorker = new PayloadSendWorker(); - InteractionManager interactionMgr = new InteractionManager(); - ApptentiveTaskManager worker = new ApptentiveTaskManager(sApptentiveInternal.appContext); - ApptentiveComponentRegistry componentRegistry = new ApptentiveComponentRegistry(); - - sApptentiveInternal.messageManager = msgManager; - sApptentiveInternal.payloadWorker = payloadWorker; - sApptentiveInternal.interactionManager = interactionMgr; - sApptentiveInternal.taskManager = worker; - sApptentiveInternal.cachedExecutor = Executors.newCachedThreadPool(); - sApptentiveInternal.componentRegistry = componentRegistry; - sApptentiveInternal.apiKey = Util.trim(apptentiveApiKey); } } } return sApptentiveInternal; } + /** + * Helper method for resolving API key from AndroidManifest.xml + * + * @return null if API key is missing or exception is thrown + */ + private static String resolveManifestApiKey(Context context) { + try { + String appPackageName = context.getPackageName(); + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(appPackageName, PackageManager.GET_META_DATA | PackageManager.GET_RECEIVERS); + Bundle metaData = packageInfo.applicationInfo.metaData; + if (metaData != null) { + return Util.trim(metaData.getString(Constants.MANIFEST_KEY_APPTENTIVE_API_KEY)); + } + } catch (Exception e) { + ApptentiveLog.e("Unexpected error while reading application or package info.", e); + } + + return null; + } + /** * Retrieve the existing instance of the Apptentive class. If {@link Apptentive#register(Application)} is * not called prior to this, it will only return null if context is null @@ -251,21 +280,6 @@ public static void setInstance(ApptentiveInternal instance) { isApptentiveInitialized.set(false); } - /** - * Use this method to set or clear the internal app context (pass in null) - * Note: designed to be used for unit testing only - * - * @param appContext the new application context to be set to - */ - public static void setApplicationContext(Context appContext) { - synchronized (ApptentiveInternal.class) { - ApptentiveInternal internal = ApptentiveInternal.getInstance(); - if (internal != null) { - internal.appContext = appContext; - } - } - } - /* Called by {@link #Apptentive.register()} to register global lifecycle * callbacks, only if the callback hasn't been set yet. */ @@ -281,7 +295,6 @@ static void setLifeCycleCallback() { } } - /* * Set default theme whom Apptentive UI will inherit theme attributes from. Apptentive will only * inherit from an AppCompat theme @@ -538,7 +551,6 @@ public boolean init() { boolean apptentiveDebug = false; String logLevelOverride = null; - String manifestApiKey = null; try { appPackageName = appContext.getPackageName(); PackageManager packageManager = appContext.getPackageManager(); @@ -547,7 +559,6 @@ public boolean init() { Bundle metaData = ai.metaData; if (metaData != null) { - manifestApiKey = Util.trim(metaData.getString(Constants.MANIFEST_KEY_APPTENTIVE_API_KEY)); logLevelOverride = Util.trim(metaData.getString(Constants.MANIFEST_KEY_APPTENTIVE_LOG_LEVEL)); apptentiveDebug = metaData.getBoolean(Constants.MANIFEST_KEY_APPTENTIVE_DEBUG); } @@ -591,9 +602,6 @@ public boolean init() { ApptentiveLog.i("Debug mode enabled? %b", appRelease.isDebug()); // The apiKey can be passed in programmatically, or we can fallback to checking in the manifest. - if (TextUtils.isEmpty(apiKey) && !TextUtils.isEmpty(manifestApiKey)) { - apiKey = manifestApiKey; - } if (TextUtils.isEmpty(apiKey) || apiKey.contains(Constants.EXAMPLE_API_KEY_VALUE)) { String errorMessage = "The Apptentive API Key is not defined. You may provide your Apptentive API Key in Apptentive.register(), or in as meta-data in your AndroidManifest.xml.\n" + " Date: Tue, 7 Feb 2017 15:12:17 -0800 Subject: [PATCH 081/465] HttpRequest refactoring --- .../android/sdk/network/HttpRequest.java | 61 ++++++++++++++++++- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index b73184dd7..5df25a75f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -3,6 +3,8 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; +import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchTask; import java.io.IOException; import java.io.InputStream; @@ -121,6 +123,11 @@ public class HttpRequest { */ private long durationMillis; + /** + * Optional dispatch queue for listener callbacks + */ + private DispatchQueue callbackQueue; + public HttpRequest(String urlString) { if (urlString == null || urlString.length() == 0) { throw new IllegalArgumentException("Invalid URL string '" + urlString + "'"); @@ -137,14 +144,22 @@ public HttpRequest(String urlString) { private void finishRequest() { try { if (listener != null) { - listener.onFinish(this); + if (isSuccessful()) { + listener.onFinish(this); + } else if (isCancelled()) { + listener.onCancel(this); + } else { + listener.onFail(this, thrownException != null ? thrownException.getMessage() : null); + } } } catch (Exception e) { ApptentiveLog.e(e, "Exception in request finish listener"); } } - /** Override this method to create request data on a background thread */ + /** + * Override this method to create request data on a background thread + */ protected byte[] createRequestData() throws IOException { return null; } @@ -168,7 +183,17 @@ void dispatchSync() { } } - finishRequest(); + // use custom callback queue (if any) + if (callbackQueue != null) { + callbackQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + finishRequest(); + } + }); + } else { + finishRequest(); // we don't care where the callback is dispatched until it's on a background queue + } } private void sendRequestSync() throws IOException { @@ -411,6 +436,10 @@ public void setReadTimeout(long readTimeout) { this.readTimeout = readTimeout; } + public boolean isSuccessful() { + return responseCode >= 200 && responseCode < 300; + } + public int getId() { return id; } @@ -427,6 +456,10 @@ public void setListener(Listener listener) { this.listener = listener; } + public void setCallbackQueue(DispatchQueue callbackQueue) { + this.callbackQueue = callbackQueue; + } + public long duration() { return durationMillis; } @@ -444,6 +477,28 @@ public Exception getThrownException() { public interface Listener { void onFinish(T request); + + void onCancel(T request); + + void onFail(T request, String reason); + } + + public static abstract class Adapter implements Listener { + + @Override + public void onFinish(T request) { + + } + + @Override + public void onCancel(T request) { + + } + + @Override + public void onFail(T request, String reason) { + + } } //endregion From 673847345654039ed0d08c4974393f59271a42cc Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 7 Feb 2017 15:13:02 -0800 Subject: [PATCH 082/465] Replaced sync call for conversation fetch with an async one --- .../android/sdk/ApptentiveInternal.java | 90 +++++++++---------- .../apptentive/android/sdk/ApptentiveLog.java | 5 ++ .../android/sdk/ApptentiveLogTag.java | 5 +- 3 files changed, 50 insertions(+), 50 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 8f1594fee..08e05a74d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -41,6 +41,8 @@ import com.apptentive.android.sdk.module.rating.IRatingProvider; import com.apptentive.android.sdk.module.rating.impl.GooglePlayRatingProvider; import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; +import com.apptentive.android.sdk.network.HttpJsonRequest; +import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.AppReleaseManager; import com.apptentive.android.sdk.storage.ApptentiveTaskManager; @@ -74,9 +76,9 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; -import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; -import static com.apptentive.android.sdk.debug.TesterEvent.EVT_FETCH_CONVERSATION_TOKEN; -import static com.apptentive.android.sdk.debug.TesterEvent.EVT_INSTANCE_CREATED; +import static com.apptentive.android.sdk.ApptentiveLogTag.*; +import static com.apptentive.android.sdk.debug.Tester.*; +import static com.apptentive.android.sdk.debug.TesterEvent.*; import static com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry.ComponentNotifier; /** @@ -544,7 +546,7 @@ public boolean init() { messageManager.init(); } } else { - scheduleConversationCreation(); + fetchConversationToken(); } apptentiveToolbarTheme = appContext.getResources().newTheme(); @@ -691,37 +693,34 @@ private void invalidateCaches() { config.save(); } - private boolean fetchConversationToken() { - try { - if (isConversationTokenFetchPending.compareAndSet(false, true)) { - ApptentiveLog.i("Fetching Configuration token task started."); + private void fetchConversationToken() { + if (isConversationTokenFetchPending.compareAndSet(false, true)) { + ApptentiveLog.i(CONVERSATION, "Fetching Configuration token task started."); + dispatchDebugEvent(EVT_FETCH_CONVERSATION_TOKEN); - // Try to fetch a new one from the server. - ConversationTokenRequest request = new ConversationTokenRequest(); + // Try to fetch a new one from the server. + ConversationTokenRequest request = new ConversationTokenRequest(); - // Send the Device and Sdk now, so they are available on the server from the start. - Device device = DeviceManager.generateNewDevice(appContext); - Sdk sdk = SdkManager.generateCurrentSdk(); + // Send the Device and Sdk now, so they are available on the server from the start. + final Device device = DeviceManager.generateNewDevice(appContext); + final Sdk sdk = SdkManager.generateCurrentSdk(); - request.setDevice(DeviceManager.getDiffPayload(null, device)); - request.setSdk(SdkManager.getPayload(sdk)); - request.setAppRelease(AppReleaseManager.getPayload(appRelease)); + request.setDevice(DeviceManager.getDiffPayload(null, device)); + request.setSdk(SdkManager.getPayload(sdk)); + request.setAppRelease(AppReleaseManager.getPayload(appRelease)); - ApptentiveHttpResponse response = ApptentiveClient.getConversationToken(request); - if (response == null) { - ApptentiveLog.w("Got null response fetching ConversationToken."); - return false; - } - if (response.isSuccessful()) { + apptentiveHttpClient.getConversationToken(request, new HttpRequest.Listener() { + @Override + public void onFinish(HttpJsonRequest request) { try { - JSONObject root = new JSONObject(response.getContent()); + JSONObject root = request.getResponseObject(); String conversationToken = root.getString("token"); - ApptentiveLog.d("ConversationToken: " + conversationToken); + ApptentiveLog.d(CONVERSATION, "ConversationToken: " + conversationToken); String conversationId = root.getString("id"); - ApptentiveLog.d("New Conversation id: %s", conversationId); + ApptentiveLog.d(CONVERSATION, "New Conversation id: %s", conversationId); sessionData = new SessionData(); - sessionData.setDataChangedListener(this); + sessionData.setDataChangedListener(ApptentiveInternal.this); if (conversationToken != null && !conversationToken.equals("")) { sessionData.setConversationToken(conversationToken); sessionData.setConversationId(conversationId); @@ -730,20 +729,27 @@ private boolean fetchConversationToken() { sessionData.setAppRelease(appRelease); } String personId = root.getString("person_id"); - ApptentiveLog.d("PersonId: " + personId); + ApptentiveLog.d(CONVERSATION, "PersonId: " + personId); sessionData.setPersonId(personId); - return true; - } catch (JSONException e) { - ApptentiveLog.e("Error parsing ConversationToken response json.", e); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while handling conversation token"); + } finally { + isConversationTokenFetchPending.set(false); } } - } else { - ApptentiveLog.v("Fetching Configuration pending"); - } - } finally { - isConversationTokenFetchPending.set(false); + + @Override + public void onCancel(HttpJsonRequest request) { + isConversationTokenFetchPending.set(false); + } + + @Override + public void onFail(HttpJsonRequest request, String reason) { + ApptentiveLog.w("Failed to fetch conversation token: %s", reason); + isConversationTokenFetchPending.set(false); + } + }); } - return false; } /** @@ -1092,11 +1098,6 @@ private synchronized void scheduleSessionDataSave() { } } - private synchronized void scheduleConversationCreation() { - dispatchDebugEvent(EVT_FETCH_CONVERSATION_TOKEN); - backgroundQueue.dispatchAsyncOnce(createConversationTask); - } - private final DispatchTask saveSessionTask = new DispatchTask() { @Override protected void execute() { @@ -1108,13 +1109,6 @@ protected void execute() { } }; - private final DispatchTask createConversationTask = new DispatchTask() { - @Override - protected void execute() { - fetchConversationToken(); - } - }; - //region Helpers private String getEndpointBase(SharedPreferences prefs) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java index f23294d8b..ab3d5c349 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java @@ -77,6 +77,11 @@ public static void d(String message, Throwable throwable, Object... args){ doLog(Level.DEBUG, throwable, message, args); } + public static void i(ApptentiveLogTag tag, String message, Object... args){ + if (tag.enabled) { + doLog(Level.INFO, null, message, args); + } + } public static void i(String message, Object... args){ doLog(Level.INFO, null, message, args); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java index 98d72beae..d9d5b0339 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java @@ -1,7 +1,8 @@ package com.apptentive.android.sdk; public enum ApptentiveLogTag { - NETWORK; + NETWORK, + CONVERSATION; - public boolean enabled; + public boolean enabled = true; } From 6073512a17be71c7abf25e656ac094b91a225e63 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 7 Feb 2017 15:51:39 -0800 Subject: [PATCH 083/465] HttpRequest refactoring Removed some unused code and added more traces --- .../android/sdk/network/HttpRequest.java | 79 ++----------------- 1 file changed, 5 insertions(+), 74 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 5df25a75f..a122c5969 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -1,7 +1,6 @@ package com.apptentive.android.sdk.network; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -60,11 +59,6 @@ public class HttpRequest { */ private String urlString; - /** - * Optional request URL query params - */ - private Map queryParams; - /** * Request HTTP properties (will be added to a connection) */ @@ -118,11 +112,6 @@ public class HttpRequest { @SuppressWarnings("rawtypes") private Listener listener; - /** - * Total request durationMillis in milliseconds - */ - private long durationMillis; - /** * Optional dispatch queue for listener callbacks */ @@ -174,6 +163,8 @@ protected void handleResponse(String response) throws IOException { // Request async task void dispatchSync() { + long requestStartTime = System.currentTimeMillis(); + try { sendRequestSync(); } catch (Exception e) { @@ -183,6 +174,8 @@ void dispatchSync() { } } + ApptentiveLog.d(NETWORK, "Request finished in %d ms", System.currentTimeMillis() - requestStartTime); + // use custom callback queue (if any) if (callbackQueue != null) { callbackQueue.dispatchAsync(new DispatchTask() { @@ -198,9 +191,7 @@ protected void execute() { private void sendRequestSync() throws IOException { try { - long startTimestamp = System.currentTimeMillis(); - - URL url = createUrl(urlString); + URL url = new URL(urlString); ApptentiveLog.d(NETWORK, "Performing request: %s", url); connection = (HttpURLConnection) url.openConnection(); @@ -260,9 +251,6 @@ private void sendRequestSync() throws IOException { // optionally handle response data (should be overridden in a sub class) handleResponse(responseContent); - - // calculate total duration - durationMillis = System.currentTimeMillis() - startTimestamp; } finally { closeConnection(); } @@ -341,42 +329,6 @@ public synchronized void cancel() { //endregion - //region Query - - /** - * Sets a URL-parameter. The actual usage depends on request method (GET, POST, etc) - */ - public void setQueryParam(String key, Object value) { - if (value != null) { - if (queryParams == null) { - queryParams = new HashMap<>(); - } - queryParams.put(key, value); - } - } - - /** - * Sets URL-parameters. The actual usage depends on request method (GET, POST, etc) - */ - public void setQueryParams(Map queryParams) { - if (queryParams != null && queryParams.size() > 0) { - Set> entries = queryParams.entrySet(); - for (Entry e : entries) { - String key = e.getKey(); - if (key == null) { - throw new IllegalArgumentException("Can't add request param: key is null"); - } - - Object value = e.getValue(); - if (value != null) { - setQueryParam(key, value); - } - } - } - } - - //endregion - //region HTTP request properties /** @@ -393,23 +345,6 @@ public void setRequestProperty(String key, Object value) { //endregion - //region Helpers - - private URL createUrl(String baseUrl) throws IOException { - if (HttpRequestMethod.GET.equals(method)) { - if (queryParams != null && queryParams.size() > 0) { - String query = StringUtils.createQueryString(queryParams); - if (baseUrl.endsWith("/")) { - return new URL(baseUrl + query); - } - return new URL(baseUrl + "/" + query); - } - } - return new URL(baseUrl); - } - - //endregion - //region String representation public String toString() { @@ -460,10 +395,6 @@ public void setCallbackQueue(DispatchQueue callbackQueue) { this.callbackQueue = callbackQueue; } - public long duration() { - return durationMillis; - } - /** * Inner exception which caused request to fail */ From 08df0659d19dcea022e352fa4abb72942eee7e71 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 7 Feb 2017 15:58:48 -0800 Subject: [PATCH 084/465] HttpRequestManager refactoring Removed callback dispatch queue --- .../sdk/network/HttpRequestManager.java | 51 +++++-------------- 1 file changed, 12 insertions(+), 39 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java index 728fc4dd0..a00000238 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java @@ -10,7 +10,7 @@ import static com.apptentive.android.sdk.debug.Assert.assertTrue; /** - * Class for asynchronous HTTP requests handling + * Class for asynchronous HTTP requests handling. */ public class HttpRequestManager { /** @@ -23,38 +23,26 @@ public class HttpRequestManager { */ private final DispatchQueue networkQueue; - /** - * Dispatch queue for listener callbacks - */ - private final DispatchQueue callbackQueue; - private Listener listener; /** - * Creates a request manager with a default concurrent "network" queue. All listener callbacks are - * dispatched on the main queue. + * Creates a request manager with a default concurrent "network" queue. */ public HttpRequestManager() { - this(DispatchQueue.createBackgroundQueue("Apptentive Network Queue", DispatchQueueType.Concurrent), DispatchQueue.mainQueue()); + this(DispatchQueue.createBackgroundQueue("Apptentive Network Queue", DispatchQueueType.Concurrent)); } /** - * Creates a request manager with custom "network" and "callback" queues + * Creates a request manager with custom network dispatch queue * * @param networkQueue - dispatch queue for blocking network operations - * @param callbackQueue - dispatch queue for listener callbacks - * @throws IllegalArgumentException if any of specified queues is null + * @throws IllegalArgumentException if queue is null */ - public HttpRequestManager(DispatchQueue networkQueue, DispatchQueue callbackQueue) { + public HttpRequestManager(DispatchQueue networkQueue) { if (networkQueue == null) { throw new IllegalArgumentException("Network queue is null"); } - if (callbackQueue == null) { - throw new IllegalArgumentException("Callback queue is null"); - } - this.networkQueue = networkQueue; - this.callbackQueue = callbackQueue; this.activeRequests = new ArrayList<>(); } @@ -122,36 +110,21 @@ synchronized void unregisterRequest(HttpRequest request) { //region Listener callbacks private void notifyRequestStarted(final HttpRequest request) { - callbackQueue.dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - if (listener != null) { - listener.onRequestStart(HttpRequestManager.this, request); - } + if (listener != null) { + listener.onRequestStart(HttpRequestManager.this, request); } - }); } private void notifyRequestFinished(final HttpRequest request) { - callbackQueue.dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - if (listener != null) { - listener.onRequestFinish(HttpRequestManager.this, request); - } + if (listener != null) { + listener.onRequestFinish(HttpRequestManager.this, request); } - }); } private void notifyCancelledAllRequests() { - callbackQueue.dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - if (listener != null) { - listener.onRequestsCancel(HttpRequestManager.this); - } + if (listener != null) { + listener.onRequestsCancel(HttpRequestManager.this); } - }); } //endregion From 2c810afab2e5df3f6db8f315b169d1ef56b9237c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 7 Feb 2017 16:00:26 -0800 Subject: [PATCH 085/465] Removed some unused code from ApptentiveClient class --- .../com/apptentive/android/sdk/comm/ApptentiveClient.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 581eb7d31..3aeda6d01 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -51,10 +51,6 @@ public class ApptentiveClient { // private static final String ENDPOINT_RECORDS = ENDPOINT_BASE + "/records"; // private static final String ENDPOINT_SURVEYS_FETCH = ENDPOINT_BASE + "/surveys"; - public static ApptentiveHttpResponse getConversationToken(ConversationTokenRequest conversationTokenRequest) { - return performHttpRequest(ApptentiveInternal.getInstance().getApptentiveApiKey(), ENDPOINT_CONVERSATION, Method.POST, conversationTokenRequest.toString()); - } - public static ApptentiveHttpResponse getAppConfiguration() { return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_CONFIGURATION, Method.GET, null); } From 3f58f9bc8bc824326a947517697b6a181054ad0c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 7 Feb 2017 17:39:28 -0800 Subject: [PATCH 086/465] Fixed handling exceptions in HTTP requests + some unit tests --- .../android/sdk/network/HttpRequest.java | 35 +++-- .../sdk/network/HttpRequestManagerTest.java | 76 +++++++++++ .../android/sdk/network/MockHttpRequest.java | 123 ++++++++++++++++++ 3 files changed, 216 insertions(+), 18 deletions(-) create mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java create mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/network/MockHttpRequest.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index a122c5969..09f4000f6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -84,11 +84,6 @@ public class HttpRequest { */ private int responseCode; - /** - * The status message from an HTTP response - */ - private String responseMessage; - /** * HTTP response content string */ @@ -105,9 +100,9 @@ public class HttpRequest { private boolean cancelled; /** - * Inner exception thrown on a background thread + * Error message for the failed request */ - private Exception thrownException; + private String errorMessage; @SuppressWarnings("rawtypes") private Listener listener; @@ -138,7 +133,7 @@ private void finishRequest() { } else if (isCancelled()) { listener.onCancel(this); } else { - listener.onFail(this, thrownException != null ? thrownException.getMessage() : null); + listener.onFail(this, errorMessage); } } } catch (Exception e) { @@ -168,7 +163,8 @@ void dispatchSync() { try { sendRequestSync(); } catch (Exception e) { - thrownException = e; + responseCode = -1; // indicates failure + errorMessage = e.getMessage(); if (!isCancelled()) { ApptentiveLog.e(e, "Unable to perform request"); } @@ -189,12 +185,12 @@ protected void execute() { } } - private void sendRequestSync() throws IOException { + protected void sendRequestSync() throws IOException { try { URL url = new URL(urlString); ApptentiveLog.d(NETWORK, "Performing request: %s", url); - connection = (HttpURLConnection) url.openConnection(); + connection = openConnection(url); connection.setRequestMethod(method.toString()); connection.setConnectTimeout((int) connectTimeout); connection.setReadTimeout((int) readTimeout); @@ -226,7 +222,6 @@ private void sendRequestSync() throws IOException { // send request responseCode = connection.getResponseCode(); - responseMessage = connection.getResponseMessage(); if (isCancelled()) { return; @@ -241,6 +236,7 @@ private void sendRequestSync() throws IOException { responseContent = readResponse(connection.getInputStream(), gzipped); ApptentiveLog.v(NETWORK, "Response: %s", responseContent); } else { + errorMessage = String.format("Unexpected response code: %d (%s)", responseCode, connection.getResponseMessage()); responseContent = readResponse(connection.getErrorStream(), gzipped); ApptentiveLog.w(NETWORK, "Response: %s", responseContent); } @@ -270,6 +266,11 @@ private void setupRequestProperties(HttpURLConnection connection, Map { void onFail(T request, String reason); } - public static abstract class Adapter implements Listener { + public static class Adapter implements Listener { @Override public void onFinish(T request) { diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java new file mode 100644 index 000000000..4717b41a9 --- /dev/null +++ b/apptentive/src/test/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java @@ -0,0 +1,76 @@ +package com.apptentive.android.sdk.network; + +import com.apptentive.android.sdk.TestCaseBase; +import com.apptentive.android.sdk.util.threading.MockDispatchQueue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +public class HttpRequestManagerTest extends TestCaseBase { + + private HttpRequestManager requestManager; + private MockDispatchQueue networkQueue; + + @Before + public void setUp() { + networkQueue = new MockDispatchQueue(false); + requestManager = new HttpRequestManager(networkQueue); + } + + @After + public void tearDown() { + requestManager.cancelAll(); + } + + @Test + public void testStartRequest() { + startRequest(new MockHttpRequest("1")); + startRequest(new MockHttpRequest("2").setMockResponseCode(204)); + startRequest(new MockHttpRequest("3").setMockResponseCode(500)); + startRequest(new MockHttpRequest("4").setThrowsExceptionOnConnect(true)); + startRequest(new MockHttpRequest("5").setThrowsExceptionOnDisconnect(true)); + dispatchRequests(); + + assertResult( + "finished: 1", + "finished: 2", + "failed: 3 Unexpected response code: 500 (Internal Server Error)", + "failed: 4 Connection error", + "failed: 5 Disconnection error" + ); + } + + //region Helpers + + private void startRequest(HttpRequest request) { + request.setListener(new HttpRequest.Listener() { + @Override + public void onFinish(MockHttpRequest request) { + addResult("finished: " + request); + } + + @Override + public void onCancel(MockHttpRequest request) { + addResult("cancelled: " + request); + } + + @Override + public void onFail(MockHttpRequest request, String reason) { + addResult("failed: " + request + " " + reason); + } + }); + requestManager.startRequest(request); + } + + private void dispatchRequests() { + networkQueue.dispatchTasks(); + } + + //endregion +} \ No newline at end of file diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/network/MockHttpRequest.java b/apptentive/src/test/java/com/apptentive/android/sdk/network/MockHttpRequest.java new file mode 100644 index 000000000..8238ed428 --- /dev/null +++ b/apptentive/src/test/java/com/apptentive/android/sdk/network/MockHttpRequest.java @@ -0,0 +1,123 @@ +package com.apptentive.android.sdk.network; + +import android.util.SparseArray; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.ProtocolException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +class MockHttpRequest extends HttpRequest { + + private static final Map statusLookup; + + static { + statusLookup = new HashMap<>(); + statusLookup.put(200, "OK"); + statusLookup.put(204, "No Content"); + statusLookup.put(500, "Internal Server Error"); + } + + private boolean throwsExceptionOnConnect; + private boolean throwsExceptionOnDisconnect; + private int mockResponseCode = 200; // HTTP OK by default + private String mockResponseMessage = "OK"; + private String responseData = ""; + private String errorData = ""; + + MockHttpRequest(String name) { + super("https://abc.com"); + setName(name); + } + + @Override + protected HttpURLConnection openConnection(URL url) throws IOException { + return new HttpURLConnection(url) { + + @Override + public boolean usingProxy() { + return false; + } + + @Override + public void connect() throws IOException { + connected = true; + } + + @Override + public void disconnect() { + connected = false; + if (throwsExceptionOnDisconnect) { + throw new RuntimeException("Disconnection error"); + } + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(responseData.getBytes("UTF-8")); + } + + @Override + public InputStream getErrorStream() { + try { + return new ByteArrayInputStream(errorData.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + } + + @Override + public int getResponseCode() throws IOException { + if (throwsExceptionOnConnect) { + throw new IOException("Connection error"); + } + return mockResponseCode; + } + + @Override + public String getResponseMessage() throws IOException { + return mockResponseMessage; + } + + @Override + public void setRequestMethod(String method) throws ProtocolException { + } + }; + } + + public MockHttpRequest setThrowsExceptionOnConnect(boolean throwsExceptionOnConnect) { + this.throwsExceptionOnConnect = throwsExceptionOnConnect; + return this; + } + + public MockHttpRequest setThrowsExceptionOnDisconnect(boolean throwsExceptionOnDisconnect) { + this.throwsExceptionOnDisconnect = throwsExceptionOnDisconnect; + return this; + } + + public MockHttpRequest setMockResponseCode(int mockResponseCode) { + this.mockResponseCode = mockResponseCode; + this.mockResponseMessage = statusLookup.get(mockResponseCode); + return this; + } + + public MockHttpRequest setResponseData(String responseData) { + this.responseData = responseData; + return this; + } + + public MockHttpRequest setErrorData(String errorData) { + this.errorData = errorData; + return this; + } + + @Override + public String toString() { + return getName(); + } +} From 141d1badbbdfffe6f05df3472b4d94769dcd02ef Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 8 Feb 2017 14:17:08 -0800 Subject: [PATCH 087/465] Fix Interaction fetching 1. Make sure to check and fetch Interactions when a Conversation becomes current 2. Refactor Interactions check into ApptentiveInternal 3. Place InteractionManager instance inside SessionData --- .../android/sdk/ApptentiveInternal.java | 93 +++++------- .../module/engagement/EngagementModule.java | 4 +- .../interaction/InteractionManager.java | 136 +++++++----------- .../android/sdk/storage/SessionData.java | 19 ++- 4 files changed, 107 insertions(+), 145 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index d9acd3bfd..d3eb51e06 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -19,7 +19,6 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.support.v4.content.ContextCompat; @@ -43,6 +42,7 @@ import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.AppReleaseManager; import com.apptentive.android.sdk.storage.ApptentiveTaskManager; +import com.apptentive.android.sdk.storage.DataChangedListener; import com.apptentive.android.sdk.storage.Device; import com.apptentive.android.sdk.storage.DeviceManager; import com.apptentive.android.sdk.storage.FileSerializer; @@ -50,7 +50,6 @@ import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.storage.SessionData; -import com.apptentive.android.sdk.storage.DataChangedListener; import com.apptentive.android.sdk.storage.VersionHistoryItem; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -81,7 +80,6 @@ public class ApptentiveInternal implements DataChangedListener { static AtomicBoolean isApptentiveInitialized = new AtomicBoolean(false); - InteractionManager interactionManager; MessageManager messageManager; PayloadSendWorker payloadWorker; ApptentiveTaskManager taskManager; @@ -185,13 +183,11 @@ public static ApptentiveInternal createInstance(Context context, final String ap MessageManager msgManager = new MessageManager(); PayloadSendWorker payloadWorker = new PayloadSendWorker(); - InteractionManager interactionMgr = new InteractionManager(); ApptentiveTaskManager worker = new ApptentiveTaskManager(sApptentiveInternal.appContext); ApptentiveComponentRegistry componentRegistry = new ApptentiveComponentRegistry(); sApptentiveInternal.messageManager = msgManager; sApptentiveInternal.payloadWorker = payloadWorker; - sApptentiveInternal.interactionManager = interactionMgr; sApptentiveInternal.taskManager = worker; sApptentiveInternal.cachedExecutor = Executors.newCachedThreadPool(); sApptentiveInternal.componentRegistry = componentRegistry; @@ -356,10 +352,6 @@ public MessageManager getMessageManager() { return messageManager; } - public InteractionManager getInteractionManager() { - return interactionManager; - } - public PayloadSendWorker getPayloadWorker() { return payloadWorker; } @@ -518,11 +510,14 @@ public boolean init() { sessionData.setDataChangedListener(this); ApptentiveLog.d("Restored existing SessionData"); ApptentiveLog.v("Restored EventData: %s", sessionData.getEventData()); - // FIXME: Move this to whereever the sessions first comes online? + // FIXME: Move this to wherever the sessions first comes online? boolean featureEverUsed = sessionData != null && sessionData.isMessageCenterFeatureUsed(); if (featureEverUsed) { messageManager.init(); } + sessionData.setInteractionManager(new InteractionManager(sessionData)); + // TODO: Make a callback like conversationBecameCurrent(), and call this there + scheduleInteractionFetch(); } else { scheduleConversationCreation(); } @@ -714,10 +709,14 @@ private boolean fetchConversationToken() { sessionData.setDevice(device); sessionData.setSdk(sdk); sessionData.setAppRelease(appRelease); + sessionData.setInteractionManager(new InteractionManager(sessionData)); } String personId = root.getString("person_id"); ApptentiveLog.d("PersonId: " + personId); sessionData.setPersonId(personId); + + // TODO: Make a callback like sessionBecameCurrent(), and call this there + scheduleInteractionFetch(); return true; } catch (JSONException e) { ApptentiveLog.e("Error parsing ConversationToken response json.", e); @@ -756,53 +755,6 @@ private void fetchAppConfiguration() { } } - private void asyncFetchAppConfigurationAndInteractions() { - boolean force = appRelease.isDebug(); - - // Don't get the app configuration unless no pending fetch AND either forced, or the cache has expired. - if (isConfigurationFetchPending.compareAndSet(false, true) && (force || Configuration.load().hasConfigurationCacheExpired())) { - AsyncTask fetchConfigurationTask = new AsyncTask() { - // Hold onto the exception from the AsyncTask instance for later handling in UI thread - private Exception e = null; - - @Override - protected Void doInBackground(Void... params) { - try { - fetchAppConfiguration(); - } catch (Exception e) { - this.e = e; - } - return null; - } - - @Override - protected void onPostExecute(Void v) { - // Update pending state on UI thread after finishing the task - ApptentiveLog.i("Fetching new Configuration asyncTask finished."); - isConfigurationFetchPending.set(false); - if (e != null) { - ApptentiveLog.w("Unhandled Exception thrown from fetching configuration asyncTask", e); - MetricModule.sendError(e, null, null); - } else { - // Check if need to start another asyncTask to fetch interaction after successfully fetching configuration - interactionManager.asyncFetchAndStoreInteractions(); - } - } - }; - - ApptentiveLog.i("Fetching new Configuration asyncTask scheduled."); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - fetchConfigurationTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - fetchConfigurationTask.execute(); - } - } else { - ApptentiveLog.v("Using cached Configuration."); - // If configuration hasn't expire, then check if need to start another asyncTask to fetch interaction - interactionManager.asyncFetchAndStoreInteractions(); - } - } - public IRatingProvider getRatingProvider() { if (ratingProvider == null) { ratingProvider = new GooglePlayRatingProvider(); @@ -1082,6 +1034,23 @@ private synchronized void scheduleConversationCreation() { backgroundQueue.dispatchAsyncOnce(createConversationTask); } + private synchronized void scheduleInteractionFetch() { + if (sessionData != null) { + InteractionManager interactionManager = sessionData.getInteractionManager(); + if (interactionManager != null) { + if (interactionManager.isPollForInteractions()) { + boolean cacheExpired = sessionData.getInteractionExpiration() > Util.currentTimeSeconds(); + boolean force = appRelease != null && appRelease.isDebug(); + if (cacheExpired || force) { + backgroundQueue.dispatchAsyncOnce(fetchInteractionsTask); + } + } else { + ApptentiveLog.v("Interaction polling is disabled."); + } + } + } + } + private final DispatchTask saveSessionTask = new DispatchTask() { @Override protected void execute() { @@ -1100,6 +1069,16 @@ protected void execute() { } }; + private final DispatchTask fetchInteractionsTask = new DispatchTask() { + @Override + protected void execute() { + SessionData sessionData = getSessionData(); + if (sessionData != null) { + sessionData.getInteractionManager().fetchInteractions(); + } + } + }; + //region Listeners @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java index 5192325ac..88f2040fa 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java @@ -69,7 +69,7 @@ public static synchronized boolean engage(Context context, String vendor, String } public static boolean doEngage(Context context, String eventLabel) { - Interaction interaction = ApptentiveInternal.getInstance().getInteractionManager().getApplicableInteraction(eventLabel); + Interaction interaction = ApptentiveInternal.getInstance().getSessionData().getInteractionManager().getApplicableInteraction(eventLabel); if (interaction != null) { SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); if (sessionData != null) { @@ -126,7 +126,7 @@ public static boolean canShowInteraction(String vendor, String interaction, Stri } private static boolean canShowInteraction(String eventLabel) { - Interaction interaction = ApptentiveInternal.getInstance().getInteractionManager().getApplicableInteraction(eventLabel); + Interaction interaction = ApptentiveInternal.getInstance().getSessionData().getInteractionManager().getApplicableInteraction(eventLabel); return interaction != null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java index 761621792..fa66808e7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java @@ -33,6 +33,12 @@ public class InteractionManager { // boolean to prevent multiple fetching threads private AtomicBoolean isFetchPending = new AtomicBoolean(false); + private SessionData sessionData; + + public InteractionManager(SessionData sessionData) { + this.sessionData = sessionData; + } + public interface InteractionUpdateListener { void onInteractionUpdated(boolean successful); } @@ -62,95 +68,59 @@ public Interaction getApplicableInteraction(String eventLabel) { return null; } - public void asyncFetchAndStoreInteractions() { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData == null) { - return; - } - if (!isPollForInteractions()) { - ApptentiveLog.v("Interaction polling is disabled."); - return; - } - - boolean cacheExpired = sessionData.isMessageCenterFeatureUsed(); - boolean force = ApptentiveInternal.getInstance().isApptentiveDebuggable(); - // Check isFetchPending to only allow one asyncTask at a time when fetching interaction - if (isFetchPending.compareAndSet(false, true) && (force || cacheExpired)) { - AsyncTask task = new AsyncTask() { - // Hold onto the exception from the AsyncTask instance for later handling in UI thread - private Exception e = null; - - @Override - protected Boolean doInBackground(Void... params) { - try { - return fetchAndStoreInteractions(); - } catch (Exception e) { - this.e = e; - } - return false; + // TODO: Refactor this class to dispatch to its own queue. + public void fetchInteractions() { + ApptentiveLog.v("Fetching Interactions"); + if (sessionData != null) { + InteractionManager interactionManager = sessionData.getInteractionManager(); + if (interactionManager != null) { + ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); + + // TODO: Move this to global config + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); + boolean updateSuccessful = true; + + // We weren't able to connect to the internet. + if (response.isException()) { + prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, false).apply(); + updateSuccessful = false; } - - @Override - protected void onPostExecute(Boolean successful) { - isFetchPending.set(false); - if (e == null) { - ApptentiveLog.d("Fetching new Interactions asyncTask finished. Successful? %b", successful); - // Update pending state on UI thread after finishing the task - ApptentiveInternal.getInstance().notifyInteractionUpdated(successful); - } else { - ApptentiveLog.w("Unhandled Exception thrown from fetching new Interactions asyncTask", e); - MetricModule.sendError(e, null, null); - } + // We got a server error. + else if (!response.isSuccessful()) { + prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, true).apply(); + updateSuccessful = false; } - }; - ApptentiveLog.i("Fetching new Interactions asyncTask scheduled"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - task.execute(); - } - } else { - ApptentiveLog.v("Using cached Interactions."); - } - } - - // This method will be run from a worker thread created by asyncTask - private boolean fetchAndStoreInteractions() { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData == null) { - return false; - } - ApptentiveLog.i("Fetching new Interactions asyncTask started"); - ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); - - // We weren't able to connect to the internet. - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - boolean updateSuccessful = true; - if (response.isException()) { - prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, false).apply(); - updateSuccessful = false; - } - // We got a server error. - else if (!response.isSuccessful()) { - prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, true).apply(); - updateSuccessful = false; - } - - if (updateSuccessful) { - String interactionsPayloadString = response.getContent(); + if (updateSuccessful) { + String interactionsPayloadString = response.getContent(); - // Store new integration cache expiration. - String cacheControl = response.getHeaders().get("Cache-Control"); - Integer cacheSeconds = Util.parseCacheControlHeader(cacheControl); - if (cacheSeconds == null) { - cacheSeconds = Constants.CONFIG_DEFAULT_INTERACTION_CACHE_EXPIRATION_DURATION_SECONDS; + // Store new integration cache expiration. + String cacheControl = response.getHeaders().get("Cache-Control"); + Integer cacheSeconds = Util.parseCacheControlHeader(cacheControl); + if (cacheSeconds == null) { + cacheSeconds = Constants.CONFIG_DEFAULT_INTERACTION_CACHE_EXPIRATION_DURATION_SECONDS; + } + sessionData.setInteractionExpiration(Util.currentTimeSeconds() + cacheSeconds); + try { + InteractionManifest payload = new InteractionManifest(interactionsPayloadString); + Interactions interactions = payload.getInteractions(); + Targets targets = payload.getTargets(); + if (interactions != null && targets != null) { + sessionData.setTargets(targets.toString()); + sessionData.setInteractions(interactions.toString()); + } else { + ApptentiveLog.e("Unable to save interactionManifest."); + } + } catch (JSONException e) { + ApptentiveLog.w("Invalid InteractionManifest received."); + } } + ApptentiveLog.d("Fetching new Interactions asyncTask finished. Successful? %b", updateSuccessful); + // Update pending state on UI thread after finishing the task + ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); } - sessionData.setInteractionExpiration(System.currentTimeMillis() + (cacheSeconds * 1000)); - storeInteractionManifest(interactionsPayloadString); + } else { + ApptentiveLog.v("Cancelled Interaction fetch due to null SessionData."); } - - return updateSuccessful; } /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java index ca8d22667..34e08968e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java @@ -8,6 +8,8 @@ import android.text.TextUtils; +import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; + public class SessionData implements Saveable, DataChangedListener { private static final long serialVersionUID = 1L; @@ -32,7 +34,10 @@ public class SessionData implements Saveable, DataChangedListener { private String messageCenterPendingAttachments; private String targets; private String interactions; - private long interactionExpiration; + private double interactionExpiration; + + // TODO: Maybe move this up to a wrapping Conversation class? + private transient InteractionManager interactionManager; public SessionData() { this.device = new Device(); @@ -278,16 +283,24 @@ public void setInteractions(String interactions) { } } - public long getInteractionExpiration() { + public double getInteractionExpiration() { return interactionExpiration; } - public void setInteractionExpiration(long interactionExpiration) { + public void setInteractionExpiration(double interactionExpiration) { if (this.interactionExpiration != interactionExpiration) { this.interactionExpiration = interactionExpiration; notifyDataChanged(); } } + public InteractionManager getInteractionManager() { + return interactionManager; + } + + public void setInteractionManager(InteractionManager interactionManager) { + this.interactionManager = interactionManager; + } + //endregion } From 5a5f485d9891d7dcd0afe4cd0ba1051d82f5f670 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 8 Feb 2017 14:34:52 -0800 Subject: [PATCH 088/465] HttpRequestManager testing progress --- .../sdk/network/HttpRequestManagerTest.java | 62 ++++++++- .../sdk/network/MockHttpJsonRequest.java | 39 ++++++ .../android/sdk/network/MockHttpRequest.java | 47 +++++++ .../sdk/network/MockHttpURLConnection.java | 86 ++++++++++++ .../android/sdk/network/HttpRequest.java | 16 ++- .../android/sdk/network/MockHttpRequest.java | 123 ------------------ 6 files changed, 240 insertions(+), 133 deletions(-) rename apptentive/src/{test => androidTest}/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java (52%) create mode 100644 apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java create mode 100644 apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpRequest.java create mode 100644 apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java delete mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/network/MockHttpRequest.java diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java similarity index 52% rename from apptentive/src/test/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java rename to apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java index 4717b41a9..ed42df67a 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java @@ -3,14 +3,15 @@ import com.apptentive.android.sdk.TestCaseBase; import com.apptentive.android.sdk.util.threading.MockDispatchQueue; +import junit.framework.Assert; + +import org.json.JSONException; +import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Test; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; +import java.util.concurrent.atomic.AtomicBoolean; public class HttpRequestManagerTest extends TestCaseBase { @@ -46,6 +47,59 @@ public void testStartRequest() { ); } + @Test + public void testRequestData() { + final String expected = "Some test data with Unicode chars 文字"; + + final AtomicBoolean finished = new AtomicBoolean(false); + + HttpRequest request = new MockHttpRequest("request").setResponseData(expected); + request.setListener(new HttpRequest.Adapter() { + @Override + public void onFinish(HttpRequest request) { + Assert.assertEquals(expected, request.getResponseData()); + finished.set(true); + } + }); + requestManager.startRequest(request); + dispatchRequests(); + + Assert.assertTrue(finished.get()); + } + + @Test + public void testJsonRequestData() throws JSONException { + final JSONObject expected = new JSONObject(); + expected.put("int", 10); + expected.put("string", "value"); + expected.put("boolean", true); + expected.put("float", 3.14f); + + JSONObject inner = new JSONObject(); + inner.put("key", "value"); + expected.put("inner", inner); + + final AtomicBoolean finished = new AtomicBoolean(false); + + HttpJsonRequest request = new MockHttpJsonRequest("request", expected).setMockResponseData(expected); + request.setListener(new HttpRequest.Adapter() { + @Override + public void onFinish(HttpJsonRequest request) { + Assert.assertEquals(expected, request.getResponseObject()); + finished.set(true); + } + + @Override + public void onFail(HttpJsonRequest request, String reason) { + Assert.fail(reason); + } + }); + requestManager.startRequest(request); + dispatchRequests(); + + Assert.assertTrue(finished.get()); + } + //region Helpers private void startRequest(HttpRequest request) { diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java new file mode 100644 index 000000000..eaf46d939 --- /dev/null +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java @@ -0,0 +1,39 @@ +package com.apptentive.android.sdk.network; + +import org.json.JSONObject; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +class MockHttpJsonRequest extends HttpJsonRequest { + + private final MockHttpURLConnection connection; + + MockHttpJsonRequest(String name, JSONObject requestObject) { + super("https://abc.com", requestObject); + connection = new MockHttpURLConnection(); + connection.setMockResponseCode(200); + setName(name); + setMethod(HttpRequestMethod.POST); + } + + @Override + protected HttpURLConnection openConnection(URL url) throws IOException { + return connection; + } + + @Override + public String toString() { + return getName(); + } + + public MockHttpJsonRequest setMockResponseData(JSONObject responseData) { + return setMockResponseData(responseData.toString()); + } + + public MockHttpJsonRequest setMockResponseData(String responseData) { + connection.responseData = responseData; + return this; + } +} diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpRequest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpRequest.java new file mode 100644 index 000000000..b7c2270be --- /dev/null +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpRequest.java @@ -0,0 +1,47 @@ +package com.apptentive.android.sdk.network; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +class MockHttpRequest extends HttpRequest { + + private final MockHttpURLConnection connection; + + MockHttpRequest(String name) { + super("https://abc.com"); + connection = new MockHttpURLConnection(); + connection.setMockResponseCode(200); + setName(name); + } + + @Override + protected HttpURLConnection openConnection(URL url) throws IOException { + return connection; + } + + public MockHttpRequest setThrowsExceptionOnConnect(boolean throwsExceptionOnConnect) { + connection.throwsExceptionOnConnect = throwsExceptionOnConnect; + return this; + } + + public MockHttpRequest setThrowsExceptionOnDisconnect(boolean throwsExceptionOnDisconnect) { + connection.throwsExceptionOnDisconnect = throwsExceptionOnDisconnect; + return this; + } + + public MockHttpRequest setMockResponseCode(int mockResponseCode) { + connection.setMockResponseCode(mockResponseCode); + return this; + } + + public MockHttpRequest setResponseData(String responseData) { + connection.responseData = responseData; + return this; + } + + @Override + public String toString() { + return getName(); + } +} diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java new file mode 100644 index 000000000..bbd7ff5dd --- /dev/null +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java @@ -0,0 +1,86 @@ +package com.apptentive.android.sdk.network; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.ProtocolException; +import java.util.HashMap; +import java.util.Map; + +class MockHttpURLConnection extends HttpURLConnection { + private static final Map statusLookup; + + static { + statusLookup = new HashMap<>(); + statusLookup.put(200, "OK"); + statusLookup.put(204, "No Content"); + statusLookup.put(500, "Internal Server Error"); + } + + boolean throwsExceptionOnConnect; + boolean throwsExceptionOnDisconnect; + int mockResponseCode = 200; // HTTP OK by default + String mockResponseMessage = "OK"; + String responseData = ""; + String errorData = ""; + + protected MockHttpURLConnection() { + super(null); + } + + @Override + public boolean usingProxy() { + return false; + } + + @Override + public void connect() throws IOException { + connected = true; + } + + @Override + public void disconnect() { + connected = false; + if (throwsExceptionOnDisconnect) { + throw new RuntimeException("Disconnection error"); + } + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(responseData.getBytes("UTF-8")); + } + + @Override + public InputStream getErrorStream() { + try { + return new ByteArrayInputStream(errorData.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + } + + @Override + public int getResponseCode() throws IOException { + if (throwsExceptionOnConnect) { + throw new IOException("Connection error"); + } + return mockResponseCode; + } + + @Override + public String getResponseMessage() throws IOException { + return mockResponseMessage; + } + + @Override + public void setRequestMethod(String method) throws ProtocolException { + } + + public void setMockResponseCode(int mockResponseCode) { + this.mockResponseCode = mockResponseCode; + this.mockResponseMessage = statusLookup.get(mockResponseCode); + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 09f4000f6..8f08636a6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -87,7 +87,7 @@ public class HttpRequest { /** * HTTP response content string */ - private String responseContent; + private String responseData; /** * Map of connection response headers @@ -233,12 +233,12 @@ protected void sendRequestSync() throws IOException { // TODO: figure out a better way of handling response codes boolean gzipped = isGzipContentEncoding(responseHeaders); if (responseCode >= HttpURLConnection.HTTP_OK && responseCode < HttpURLConnection.HTTP_MULT_CHOICE) { - responseContent = readResponse(connection.getInputStream(), gzipped); - ApptentiveLog.v(NETWORK, "Response: %s", responseContent); + responseData = readResponse(connection.getInputStream(), gzipped); + ApptentiveLog.v(NETWORK, "Response: %s", responseData); } else { errorMessage = String.format("Unexpected response code: %d (%s)", responseCode, connection.getResponseMessage()); - responseContent = readResponse(connection.getErrorStream(), gzipped); - ApptentiveLog.w(NETWORK, "Response: %s", responseContent); + responseData = readResponse(connection.getErrorStream(), gzipped); + ApptentiveLog.w(NETWORK, "Response: %s", responseData); } if (isCancelled()) { @@ -246,7 +246,7 @@ protected void sendRequestSync() throws IOException { } // optionally handle response data (should be overridden in a sub class) - handleResponse(responseContent); + handleResponse(responseData); } finally { closeConnection(); } @@ -396,6 +396,10 @@ public void setCallbackQueue(DispatchQueue callbackQueue) { this.callbackQueue = callbackQueue; } + public String getResponseData() { + return responseData; + } + /* For unit testing */ protected void setResponseCode(int code) { responseCode = code; diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/network/MockHttpRequest.java b/apptentive/src/test/java/com/apptentive/android/sdk/network/MockHttpRequest.java deleted file mode 100644 index 8238ed428..000000000 --- a/apptentive/src/test/java/com/apptentive/android/sdk/network/MockHttpRequest.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.apptentive.android.sdk.network; - -import android.util.SparseArray; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.ProtocolException; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; - -class MockHttpRequest extends HttpRequest { - - private static final Map statusLookup; - - static { - statusLookup = new HashMap<>(); - statusLookup.put(200, "OK"); - statusLookup.put(204, "No Content"); - statusLookup.put(500, "Internal Server Error"); - } - - private boolean throwsExceptionOnConnect; - private boolean throwsExceptionOnDisconnect; - private int mockResponseCode = 200; // HTTP OK by default - private String mockResponseMessage = "OK"; - private String responseData = ""; - private String errorData = ""; - - MockHttpRequest(String name) { - super("https://abc.com"); - setName(name); - } - - @Override - protected HttpURLConnection openConnection(URL url) throws IOException { - return new HttpURLConnection(url) { - - @Override - public boolean usingProxy() { - return false; - } - - @Override - public void connect() throws IOException { - connected = true; - } - - @Override - public void disconnect() { - connected = false; - if (throwsExceptionOnDisconnect) { - throw new RuntimeException("Disconnection error"); - } - } - - @Override - public InputStream getInputStream() throws IOException { - return new ByteArrayInputStream(responseData.getBytes("UTF-8")); - } - - @Override - public InputStream getErrorStream() { - try { - return new ByteArrayInputStream(errorData.getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); - } - } - - @Override - public int getResponseCode() throws IOException { - if (throwsExceptionOnConnect) { - throw new IOException("Connection error"); - } - return mockResponseCode; - } - - @Override - public String getResponseMessage() throws IOException { - return mockResponseMessage; - } - - @Override - public void setRequestMethod(String method) throws ProtocolException { - } - }; - } - - public MockHttpRequest setThrowsExceptionOnConnect(boolean throwsExceptionOnConnect) { - this.throwsExceptionOnConnect = throwsExceptionOnConnect; - return this; - } - - public MockHttpRequest setThrowsExceptionOnDisconnect(boolean throwsExceptionOnDisconnect) { - this.throwsExceptionOnDisconnect = throwsExceptionOnDisconnect; - return this; - } - - public MockHttpRequest setMockResponseCode(int mockResponseCode) { - this.mockResponseCode = mockResponseCode; - this.mockResponseMessage = statusLookup.get(mockResponseCode); - return this; - } - - public MockHttpRequest setResponseData(String responseData) { - this.responseData = responseData; - return this; - } - - public MockHttpRequest setErrorData(String errorData) { - this.errorData = errorData; - return this; - } - - @Override - public String toString() { - return getName(); - } -} From 677afca1659668083389aa541f277ef203145033 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 8 Feb 2017 14:50:25 -0800 Subject: [PATCH 089/465] Reorganized test source code --- apptentive/build.gradle | 9 ++++ .../sdk/test/InstrumentationBaseTestCase.java | 52 ------------------- .../ConcurrentDispatchQueueTest.java | 6 +-- .../threading/SerialDispatchQueueTest.java | 4 +- .../android/sdk}/MockDispatchQueue.java | 0 .../apptentive/android/sdk/TestCaseBase.java | 10 ++++ 6 files changed, 23 insertions(+), 58 deletions(-) delete mode 100644 apptentive/src/androidTest/java/com/apptentive/android/sdk/test/InstrumentationBaseTestCase.java rename apptentive/src/{test/java/com/apptentive/android/sdk/util/threading => testCommon/java/com/apptentive/android/sdk}/MockDispatchQueue.java (100%) rename apptentive/src/{test => testCommon}/java/com/apptentive/android/sdk/TestCaseBase.java (90%) diff --git a/apptentive/build.gradle b/apptentive/build.gradle index 0a6ae1d82..d7c385026 100644 --- a/apptentive/build.gradle +++ b/apptentive/build.gradle @@ -41,6 +41,15 @@ android { abortOnError false } + sourceSets { + test { + java.srcDirs = ['src/test/java', 'src/testCommon/java'] + } + androidTest { + java.srcDirs = ['src/androidTest/java', 'src/testCommon/java'] + } + } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/test/InstrumentationBaseTestCase.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/test/InstrumentationBaseTestCase.java deleted file mode 100644 index 13a0d6c0c..000000000 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/test/InstrumentationBaseTestCase.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.test; - -import android.os.SystemClock; - -import com.apptentive.android.sdk.util.StringUtils; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.assertEquals; - -/** - * Created by alementuev on 2/2/17. - */ - -public class InstrumentationBaseTestCase { - private List result = new ArrayList<>(); - - //region Results - - protected void addResult(String str) { - result.add(str); - } - - protected void assertResult(String... expected) { - assertEquals("\nExpected: " + StringUtils.join(expected) + - "\nActual: " + StringUtils.join(result), expected.length, result.size()); - - for (int i = 0; i < expected.length; ++i) { - assertEquals("\nExpected: " + StringUtils.join(expected) + - "\nActual: " + StringUtils.join(result), - expected[i], result.get(i)); - } - - result.clear(); - } - //endregion - - //region Helpers - - protected void sleep(long millis) { - SystemClock.sleep(millis); - } - - //endregion -} diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueueTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueueTest.java index 2f851f961..fb405b4e2 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueueTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueueTest.java @@ -8,17 +8,15 @@ import android.support.test.runner.AndroidJUnit4; -import com.apptentive.android.sdk.test.InstrumentationBaseTestCase; +import com.apptentive.android.sdk.TestCaseBase; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.*; - @RunWith(AndroidJUnit4.class) -public class ConcurrentDispatchQueueTest extends InstrumentationBaseTestCase { +public class ConcurrentDispatchQueueTest extends TestCaseBase { private DispatchQueue dispatchQueue; @Before diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java index 265cfb1d2..dd5cca34a 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java @@ -8,7 +8,7 @@ import android.support.test.runner.AndroidJUnit4; -import com.apptentive.android.sdk.test.InstrumentationBaseTestCase; +import com.apptentive.android.sdk.TestCaseBase; import org.junit.After; import org.junit.Before; @@ -16,7 +16,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) -public class SerialDispatchQueueTest extends InstrumentationBaseTestCase { +public class SerialDispatchQueueTest extends TestCaseBase { private DispatchQueue dispatchQueue; diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/MockDispatchQueue.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/MockDispatchQueue.java similarity index 100% rename from apptentive/src/test/java/com/apptentive/android/sdk/util/threading/MockDispatchQueue.java rename to apptentive/src/testCommon/java/com/apptentive/android/sdk/MockDispatchQueue.java diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java similarity index 90% rename from apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java rename to apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java index 70ec0ce54..12c27b560 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/TestCaseBase.java +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java @@ -6,6 +6,8 @@ package com.apptentive.android.sdk; +import android.os.SystemClock; + import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.threading.MockDispatchQueue; @@ -50,4 +52,12 @@ protected void dispatchTasks() { } //endregion + + //region Helpers + + protected void sleep(long millis) { + SystemClock.sleep(millis); + } + + //endregion } From fbae8daefada1aec4aade54f977224ea5bae10d0 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 8 Feb 2017 14:58:11 -0800 Subject: [PATCH 090/465] HttpJsonRequest test --- .../sdk/network/HttpRequestManagerTest.java | 33 +++++++++++++++++-- .../sdk/network/MockHttpURLConnection.java | 7 ++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java index ed42df67a..0861a1017 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java @@ -69,6 +69,11 @@ public void onFinish(HttpRequest request) { @Test public void testJsonRequestData() throws JSONException { + final JSONObject requestObject = new JSONObject(); + requestObject.put("key1", "value1"); + requestObject.put("key2", "value2"); + requestObject.put("key3", "value3"); + final JSONObject expected = new JSONObject(); expected.put("int", 10); expected.put("string", "value"); @@ -81,11 +86,11 @@ public void testJsonRequestData() throws JSONException { final AtomicBoolean finished = new AtomicBoolean(false); - HttpJsonRequest request = new MockHttpJsonRequest("request", expected).setMockResponseData(expected); + HttpJsonRequest request = new MockHttpJsonRequest("request", requestObject).setMockResponseData(expected); request.setListener(new HttpRequest.Adapter() { @Override public void onFinish(HttpJsonRequest request) { - Assert.assertEquals(expected, request.getResponseObject()); + Assert.assertEquals(expected.toString(), request.getResponseObject().toString()); finished.set(true); } @@ -100,6 +105,30 @@ public void onFail(HttpJsonRequest request, String reason) { Assert.assertTrue(finished.get()); } + @Test + public void testJsonRequestCorruptedData() throws JSONException { + final JSONObject requestObject = new JSONObject(); + requestObject.put("key1", "value1"); + requestObject.put("key2", "value2"); + requestObject.put("key3", "value3"); + + String invalidJson = "{ key1 : value key2 : value2 }"; + + final AtomicBoolean finished = new AtomicBoolean(false); + + HttpJsonRequest request = new MockHttpJsonRequest("request", requestObject).setMockResponseData(invalidJson); + request.setListener(new HttpRequest.Adapter() { + @Override + public void onFail(HttpJsonRequest request, String reason) { + finished.set(true); + } + }); + requestManager.startRequest(request); + dispatchRequests(); + + Assert.assertTrue(finished.get()); + } + //region Helpers private void startRequest(HttpRequest request) { diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java index bbd7ff5dd..3c2dc933a 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java @@ -1,8 +1,10 @@ package com.apptentive.android.sdk.network; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.ProtocolException; @@ -62,6 +64,11 @@ public InputStream getErrorStream() { } } + @Override + public OutputStream getOutputStream() throws IOException { + return new ByteArrayOutputStream(); + } + @Override public int getResponseCode() throws IOException { if (throwsExceptionOnConnect) { From 333db843507dc4f7b77d2418cdcd87fe62d43d61 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 8 Feb 2017 15:18:09 -0800 Subject: [PATCH 091/465] HttpRequestManager tests --- .../sdk/network/HttpRequestManagerTest.java | 73 +++++++++++++++++++ .../sdk/network/HttpRequestManager.java | 7 +- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java index 0861a1017..737dabb4f 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java @@ -129,6 +129,79 @@ public void onFail(HttpJsonRequest request, String reason) { Assert.assertTrue(finished.get()); } + @Test + public void testListener() { + requestManager.setListener(new HttpRequestManager.Listener() { + @Override + public void onRequestStart(HttpRequestManager manager, HttpRequest request) { + addResult("start: " + request); + } + + @Override + public void onRequestFinish(HttpRequestManager manager, HttpRequest request) { + if (request.isSuccessful()) { + addResult("finish: " + request); + } else if (request.isCancelled()) { + addResult("cancel: " + request); + } else { + addResult("fail: " + request); + } + } + + @Override + public void onRequestsCancel(HttpRequestManager manager) { + addResult("cancel all"); + } + }); + + // start requests and let them finish + requestManager.startRequest(new MockHttpRequest("1")); + requestManager.startRequest(new MockHttpRequest("2").setMockResponseCode(500)); + requestManager.startRequest(new MockHttpRequest("3").setThrowsExceptionOnConnect(true)); + dispatchRequests(); + + assertResult( + "start: 1", + "start: 2", + "start: 3", + "finish: 1", + "fail: 2", + "fail: 3" + ); + + // start requests and cancel some + requestManager.startRequest(new MockHttpRequest("4")); + requestManager.startRequest(new MockHttpRequest("5")).cancel(); + requestManager.startRequest(new MockHttpRequest("6")); + dispatchRequests(); + + assertResult( + "start: 4", + "start: 5", + "start: 6", + "finish: 4", + "cancel: 5", + "finish: 6" + ); + + // start requests and cancel them all + requestManager.startRequest(new MockHttpRequest("4")); + requestManager.startRequest(new MockHttpRequest("5")); + requestManager.startRequest(new MockHttpRequest("6")); + requestManager.cancelAll(); + dispatchRequests(); + + assertResult( + "start: 4", + "start: 5", + "start: 6", + "cancel all", + "cancel: 4", + "cancel: 5", + "cancel: 6" + ); + } + //region Helpers private void startRequest(HttpRequest request) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java index a00000238..ef4334edf 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java @@ -51,7 +51,7 @@ public HttpRequestManager(DispatchQueue networkQueue) { /** * Starts network request on the network queue (method returns immediately) */ - public synchronized void startRequest(final HttpRequest request) { + public synchronized HttpRequest startRequest(final HttpRequest request) { if (request == null) { throw new IllegalArgumentException("Request is null"); } @@ -70,6 +70,8 @@ protected void execute() { }); notifyRequestStarted(request); + + return request; } /** @@ -81,9 +83,8 @@ public synchronized void cancelAll() { for (HttpRequest request : temp) { request.cancel(); } - activeRequests.clear(); - notifyCancelledAllRequests(); } + notifyCancelledAllRequests(); } /** From 7a0f60e01594da5eba65916d87a0291513217e2e Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 9 Feb 2017 10:56:53 -0800 Subject: [PATCH 092/465] Auto-mirror icons Pick three icons in Message Center that are not symmetrical, and set them up to be auto-mirrored when using a RTL language. Available on API 19+ --- .../apptentive_ic_action_attach_auto_mirror.xml | 10 ++++++++++ .../drawable/apptentive_ic_action_send_auto_mirror.xml | 10 ++++++++++ .../res/drawable/apptentive_ic_compose_auto_mirror.xml | 10 ++++++++++ .../src/main/res/layout/apptentive_message_center.xml | 2 +- .../res/layout/apptentive_message_center_composer.xml | 4 ++-- 5 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 apptentive/src/main/res/drawable/apptentive_ic_action_attach_auto_mirror.xml create mode 100644 apptentive/src/main/res/drawable/apptentive_ic_action_send_auto_mirror.xml create mode 100644 apptentive/src/main/res/drawable/apptentive_ic_compose_auto_mirror.xml diff --git a/apptentive/src/main/res/drawable/apptentive_ic_action_attach_auto_mirror.xml b/apptentive/src/main/res/drawable/apptentive_ic_action_attach_auto_mirror.xml new file mode 100644 index 000000000..e51201143 --- /dev/null +++ b/apptentive/src/main/res/drawable/apptentive_ic_action_attach_auto_mirror.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/apptentive/src/main/res/drawable/apptentive_ic_action_send_auto_mirror.xml b/apptentive/src/main/res/drawable/apptentive_ic_action_send_auto_mirror.xml new file mode 100644 index 000000000..de4ebe320 --- /dev/null +++ b/apptentive/src/main/res/drawable/apptentive_ic_action_send_auto_mirror.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/apptentive/src/main/res/drawable/apptentive_ic_compose_auto_mirror.xml b/apptentive/src/main/res/drawable/apptentive_ic_compose_auto_mirror.xml new file mode 100644 index 000000000..852f38834 --- /dev/null +++ b/apptentive/src/main/res/drawable/apptentive_ic_compose_auto_mirror.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/apptentive/src/main/res/layout/apptentive_message_center.xml b/apptentive/src/main/res/layout/apptentive_message_center.xml index e6f3d4442..805b89166 100644 --- a/apptentive/src/main/res/layout/apptentive_message_center.xml +++ b/apptentive/src/main/res/layout/apptentive_message_center.xml @@ -25,6 +25,6 @@ android:layout_height="wrap_content" style="?attr/apptentiveFabStyle" android:visibility="gone" - android:src="@drawable/apptentive_ic_compose" + android:src="@drawable/apptentive_ic_compose_auto_mirror" android:contentDescription="@string/apptentive_message_center_fab_content_description"/> \ No newline at end of file diff --git a/apptentive/src/main/res/layout/apptentive_message_center_composer.xml b/apptentive/src/main/res/layout/apptentive_message_center_composer.xml index c6c4fff13..eb3e965fb 100644 --- a/apptentive/src/main/res/layout/apptentive_message_center_composer.xml +++ b/apptentive/src/main/res/layout/apptentive_message_center_composer.xml @@ -50,7 +50,7 @@ android:layout_alignParentEnd="true" style="@style/Apptentive.Style.Widget.ImageButton.Composing" android:minWidth="48dp" - android:src="@drawable/apptentive_ic_action_send" + android:src="@drawable/apptentive_ic_action_send_auto_mirror" android:scaleType="center" android:contentDescription="@string/apptentive_message_center_content_description_send_button"/> From 37776acd92bf4da7687f47abb7f80ad28420ef94 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 10 Feb 2017 15:36:29 -0800 Subject: [PATCH 093/465] Added binary object serialization --- .../serialization/ObjectSerialization.java | 51 +++++++++++++++ .../sdk/serialization/SerializableObject.java | 32 +++++++++ .../ObjectSerializationTest.java | 65 +++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/serialization/ObjectSerialization.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/serialization/SerializableObject.java create mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/serialization/ObjectSerializationTest.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/serialization/ObjectSerialization.java b/apptentive/src/main/java/com/apptentive/android/sdk/serialization/ObjectSerialization.java new file mode 100644 index 000000000..d5aa68b67 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/serialization/ObjectSerialization.java @@ -0,0 +1,51 @@ +package com.apptentive.android.sdk.serialization; + +import com.apptentive.android.sdk.util.Util; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Constructor; + +/** + * Helper class for a binary file-based object serialization. + */ +public class ObjectSerialization { + /** + * Writes an object ot a file + */ + public static void serialize(File file, SerializableObject object) throws IOException { + FileOutputStream stream = null; + try { + stream = new FileOutputStream(file); + DataOutputStream out = new DataOutputStream(stream); + object.writeExternal(out); + } finally { + Util.ensureClosed(stream); + } + } + + /** + * Reads an object from a file + */ + public static T deserialize(File file, Class cls) throws IOException { + FileInputStream stream = null; + try { + stream = new FileInputStream(file); + DataInputStream in = new DataInputStream(stream); + + try { + Constructor constructor = cls.getDeclaredConstructor(DataInput.class); + return constructor.newInstance(in); + } catch (Exception e) { + throw new IOException("Unable to instantiate class: " + cls, e); + } + } finally { + Util.ensureClosed(stream); + } + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/serialization/SerializableObject.java b/apptentive/src/main/java/com/apptentive/android/sdk/serialization/SerializableObject.java new file mode 100644 index 000000000..33d255eaa --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/serialization/SerializableObject.java @@ -0,0 +1,32 @@ +package com.apptentive.android.sdk.serialization; + +import java.io.DataOutput; +import java.io.IOException; + +/** + * Only the identity of the class of an SerializableObject instance + * is written in the serialization stream and it is the responsibility + * of the class to save and restore the contents of its instances. + * The writeExternal and a single arg constructor of the SerializableObject + * interface are implemented by a class to give the class complete control + * over the format and contents of the stream for an object and its + * supertypes. These methods must explicitly coordinate with the supertype + * to save its state. + */ +public interface SerializableObject { + + /** + * The object should have a public single arg constructor accepting + * DataInput to restore its contents by calling the methods + * of DataInput for primitive types. The constructor must read the + * values in the same sequence and with the same types as were written + * by writeExternal. + */ + /* SerializableObject(DataInput in) throws IOException; */ + + /** + * The object implements the writeExternal method to save its contents + * by calling the methods of DataOutput for its primitive values. + */ + void writeExternal(DataOutput out) throws IOException; +} diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/serialization/ObjectSerializationTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/serialization/ObjectSerializationTest.java new file mode 100644 index 000000000..75c46d491 --- /dev/null +++ b/apptentive/src/test/java/com/apptentive/android/sdk/serialization/ObjectSerializationTest.java @@ -0,0 +1,65 @@ +package com.apptentive.android.sdk.serialization; + +import org.junit.Before; +import org.junit.Test; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +public class ObjectSerializationTest { + + private File file; + + @Before + public void setUp() throws IOException { + file = File.createTempFile("dummy", ".bin"); + file.deleteOnExit(); + } + + @Test + public void testSerialization() throws IOException { + + Dummy expected = new Dummy("Some value"); + ObjectSerialization.serialize(file, expected); + Dummy actual = ObjectSerialization.deserialize(file, Dummy.class); + assertEquals(expected, actual); + } + + static class Dummy implements SerializableObject { + + private final String value; + + public Dummy(String value) { + this.value = value; + } + + public Dummy(DataInput in) throws IOException { + value = in.readUTF(); + } + + @Override + public void writeExternal(DataOutput out) throws IOException { + out.writeUTF(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Dummy dummy = (Dummy) o; + + return value.equals(dummy.value); + + } + + @Override + public int hashCode() { + return value.hashCode(); + } + } +} \ No newline at end of file From d646ad59a69703599f59f90f2793eee0e255c96e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 10 Feb 2017 16:15:03 -0800 Subject: [PATCH 094/465] Added basic conversation manager and conversation metadata --- .../sdk/conversation/Conversation.java | 4 + .../sdk/conversation/ConversationManager.java | 112 ++++++++++++++++++ .../conversation/ConversationMetadata.java | 56 +++++++++ .../ConversationMetadataItem.java | 32 +++++ 4 files changed, 204 insertions(+) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java new file mode 100644 index 000000000..9fe5561e2 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -0,0 +1,4 @@ +package com.apptentive.android.sdk.conversation; + +public class Conversation { +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java new file mode 100644 index 000000000..fed1a38c3 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -0,0 +1,112 @@ +package com.apptentive.android.sdk.conversation; + +import com.apptentive.android.sdk.serialization.ObjectSerialization; +import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchTask; + +import java.io.File; +import java.io.IOException; + +/** + * Class responsible for managing conversations. + *
+ *   - Saving/Loading conversations from/to files.
+ *   - Switching conversations when users login/logout.
+ *   - Creating default conversation.
+ *   - Migrating legacy conversation data.
+ * 
+ */ +public class ConversationManager { + /** + * Private serial dispatch queue for background operations + */ + private final DispatchQueue operationQueue; + + /** + * Current state of conversation metadata. + */ + private ConversationMetadata conversationMetadata; + + public ConversationManager(DispatchQueue operationQueue) { + if (operationQueue == null) { + throw new IllegalArgumentException("Operation queue is null"); + } + this.operationQueue = operationQueue; + } + + /** + * Loads current conversation asynchronously. + */ + public void loadCurrentConversation(Callback callback) { + loadConversation(new Filter() { + @Override + public boolean accept(ConversationMetadataItem metadata) { + return false; + } + }, callback); + } + + /** + * Helper method for async conversation loading with specified filter. + */ + private void loadConversation(final Filter filter, final Callback callback) { + operationQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + Conversation conversation = null; + String errorMessage = null; + try { + conversation = loadConversationSync(filter); + } catch (Exception e) { + errorMessage = e.getMessage(); + } + + if (callback != null) { + callback.onFinishLoading(conversation, errorMessage); + } + } + }); + } + + /** + * Loads selected conversation on a background queue + */ + private Conversation loadConversationSync(Filter filter) throws IOException { + if (conversationMetadata == null) { + conversationMetadata = ObjectSerialization.deserialize(new File(""), ConversationMetadata.class); + } + + ConversationMetadataItem matchingItem = null; + for (ConversationMetadataItem item : conversationMetadata.getItems()) { + if (filter.accept(item)) { + matchingItem = item; + } + } + + if (matchingItem == null) { + return null; + } + + return null; + } + + /** + * Callback listener interface + */ + public interface Callback { + /** + * Called when conversation loading is finished. + * + * @param conversation - null if loading failed + * @param errorMessage - error description in case if loading failed (null is succeed) + */ + void onFinishLoading(Conversation conversation, String errorMessage); + } + + /** + * Interface which encapsulates conversation metadata filtering (visitor pattern) + */ + interface Filter { + boolean accept(ConversationMetadataItem item); + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java new file mode 100644 index 000000000..c89e11128 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java @@ -0,0 +1,56 @@ +package com.apptentive.android.sdk.conversation; + +import com.apptentive.android.sdk.serialization.SerializableObject; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Class which represents all conversation entries stored on the disk + */ +class ConversationMetadata implements SerializableObject { + private static final byte VERSION = 1; + + private final List items; + + public ConversationMetadata() { + items = new ArrayList<>(); + } + + //region Serialization + + public ConversationMetadata(DataInput in) throws IOException { + byte version = in.readByte(); + if (version != VERSION) { + throw new IOException("Expected version " + VERSION + " but was " + version); + } + + int count = in.readByte(); + items = new ArrayList<>(count); + for (int i = 0; i < count; ++i) { + items.add(new ConversationMetadataItem(in)); + } + } + + @Override + public void writeExternal(DataOutput out) throws IOException { + out.writeByte(VERSION); + out.write(items.size()); + for (int i = 0; i < items.size(); ++i) { + items.get(i).writeExternal(out); + } + } + + //endregion + + //region Getters/Setters + + public List getItems() { + return items; + } + + //endregion +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java new file mode 100644 index 000000000..8413a8666 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -0,0 +1,32 @@ +package com.apptentive.android.sdk.conversation; + +import com.apptentive.android.sdk.serialization.SerializableObject; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * A light weight representation of the conversation object stored on the disk. + */ +class ConversationMetadataItem implements SerializableObject { + String userId; + String filename; + String key; + boolean active; + + public ConversationMetadataItem(DataInput in) throws IOException { + userId = in.readUTF(); + filename = in.readUTF(); + key = in.readUTF(); + active = in.readBoolean(); + } + + @Override + public void writeExternal(DataOutput out) throws IOException { + out.writeUTF(userId); + out.writeUTF(filename); + out.writeUTF(key); + out.writeBoolean(active); + } +} From 18d06b9c7e37aefae727867494dc15eb6b8dfcd4 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 10 Feb 2017 17:09:51 -0800 Subject: [PATCH 095/465] Conversation manager progress --- .../sdk/conversation/ConversationManager.java | 15 +++++-- .../ConversationMetadataItem.java | 40 ++++++++++++++++--- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index fed1a38c3..db35ffeb5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -22,26 +22,33 @@ public class ConversationManager { */ private final DispatchQueue operationQueue; + /** + * A basic directory for storing conversation-related data. + */ + private final File storageDir; + /** * Current state of conversation metadata. */ private ConversationMetadata conversationMetadata; - public ConversationManager(DispatchQueue operationQueue) { + public ConversationManager(DispatchQueue operationQueue, File storageDir) { if (operationQueue == null) { throw new IllegalArgumentException("Operation queue is null"); } + this.operationQueue = operationQueue; + this.storageDir = storageDir; } /** - * Loads current conversation asynchronously. + * Attempts to load an active conversation asynchronously. */ - public void loadCurrentConversation(Callback callback) { + public void loadActiveConversation(Callback callback) { loadConversation(new Filter() { @Override public boolean accept(ConversationMetadataItem metadata) { - return false; + return metadata.isActive(); } }, callback); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index 8413a8666..09796b2a0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -10,23 +10,51 @@ * A light weight representation of the conversation object stored on the disk. */ class ConversationMetadataItem implements SerializableObject { + /** + * Conversation state is not known + */ + public static byte CONVERSATION_STATE_UNDEFINED = 0; + + /** + * No users have logged-in yet (guest mode) + */ + public static byte CONVERSATION_STATE_DEFAULT = 1; + + /** + * The conversation belongs to the currently logged-in user. + */ + public static byte CONVERSATION_STATE_ACTIVE = 2; + + /** + * The conversation belongs to a logged-out user. + */ + public static byte CONVERSATION_STATE_INACTIVE = 3; + + byte state = CONVERSATION_STATE_UNDEFINED; String userId; String filename; - String key; - boolean active; + String keyId; public ConversationMetadataItem(DataInput in) throws IOException { userId = in.readUTF(); filename = in.readUTF(); - key = in.readUTF(); - active = in.readBoolean(); + keyId = in.readUTF(); + state = in.readByte(); } @Override public void writeExternal(DataOutput out) throws IOException { out.writeUTF(userId); out.writeUTF(filename); - out.writeUTF(key); - out.writeBoolean(active); + out.writeUTF(keyId); + out.writeByte(state); + } + + public boolean isActive() { + return state == CONVERSATION_STATE_ACTIVE; + } + + public boolean isDefault() { + return state == CONVERSATION_STATE_DEFAULT; } } From d08f211d8361187862caabd9b615a9765f557e6a Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 16 Feb 2017 13:17:39 -0800 Subject: [PATCH 096/465] Modify the SDK to take server URL from the test server. --- .../com/apptentive/android/sdk/Apptentive.java | 8 +++++++- .../android/sdk/ApptentiveInternal.java | 17 ++++++++++++++--- .../android/sdk/comm/ApptentiveClient.java | 2 +- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index a24038135..6901ccab0 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -58,7 +58,13 @@ public static void register(Application application) { public static void register(Application application, String apptentiveApiKey) { ApptentiveLog.i("Registering Apptentive."); - ApptentiveInternal.createInstance(application, apptentiveApiKey); + ApptentiveInternal.createInstance(application, apptentiveApiKey, null); + ApptentiveInternal.setLifeCycleCallback(); + } + + public static void register(Application application, String apptentiveApiKey, String serverUrl) { + ApptentiveLog.i("Registering Apptentive."); + ApptentiveInternal.createInstance(application, apptentiveApiKey, serverUrl); ApptentiveInternal.setLifeCycleCallback(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index bca97c339..a9d170c58 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -101,6 +101,7 @@ public class ApptentiveInternal implements DataChangedListener { boolean appIsInForeground; SharedPreferences globalSharedPrefs; String apiKey; + String serverUrl; String personId; String androidId; String appPackageName; @@ -177,12 +178,16 @@ public static boolean isApptentiveRegistered() { * @param context the context of the app that is creating the instance * @return An non-null instance of the Apptentive SDK */ - public static ApptentiveInternal createInstance(Context context, final String apptentiveApiKey) { + public static ApptentiveInternal createInstance(Context context, final String apptentiveApiKey, final String serverUrl) { if (sApptentiveInternal == null) { synchronized (ApptentiveInternal.class) { if (sApptentiveInternal == null && context != null) { sApptentiveInternal = new ApptentiveInternal(context); isApptentiveInitialized.set(false); + + sApptentiveInternal.apiKey = Util.trim(apptentiveApiKey); + sApptentiveInternal.serverUrl = serverUrl; + sApptentiveInternal.appContext = context.getApplicationContext(); sApptentiveInternal.globalSharedPrefs = sApptentiveInternal.appContext.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); @@ -198,7 +203,6 @@ public static ApptentiveInternal createInstance(Context context, final String ap sApptentiveInternal.taskManager = worker; sApptentiveInternal.cachedExecutor = Executors.newCachedThreadPool(); sApptentiveInternal.componentRegistry = componentRegistry; - sApptentiveInternal.apiKey = Util.trim(apptentiveApiKey); } } } @@ -212,7 +216,7 @@ public static ApptentiveInternal createInstance(Context context, final String ap * @return the existing instance of the Apptentive SDK fully initialized with API key, or a new instance if context is not null */ public static ApptentiveInternal getInstance(Context context) { - return createInstance((context == null) ? null : context, null); + return createInstance((context == null) ? null : context, null, null); } /** @@ -391,6 +395,13 @@ public String getApptentiveApiKey() { return apiKey; } + public String getServerUrl() { + if (serverUrl == null) { + return Constants.CONFIG_DEFAULT_SERVER_URL; + } + return serverUrl; + } + public String getDefaultAppDisplayName() { return defaultAppDisplayName; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 581eb7d31..44579feff 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -402,7 +402,7 @@ private static String getEndpointBase() { SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); String url = prefs.getString(Constants.PREF_KEY_SERVER_URL, null); if (url == null) { - url = Constants.CONFIG_DEFAULT_SERVER_URL; + url = ApptentiveInternal.getInstance().getServerUrl(); prefs.edit().putString(Constants.PREF_KEY_SERVER_URL, url).apply(); } return url; From 2ad30eda23a23f6800ff07f2ec666ab453e4253f Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 16 Feb 2017 18:08:49 -0800 Subject: [PATCH 097/465] Add TesterEvent for Apptentive Events --- .../main/java/com/apptentive/android/sdk/debug/Tester.java | 6 ++++++ .../java/com/apptentive/android/sdk/debug/TesterEvent.java | 1 + .../main/java/com/apptentive/android/sdk/model/Event.java | 4 ++++ .../com/apptentive/android/sdk/model/EventManager.java | 7 ++++--- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java index 45eb2f587..508ae63c4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java @@ -37,6 +37,12 @@ public static void dispatchDebugEvent(String name) { } } + public static void dispatchDebugEvent(String name, Object arg) { + if (isListeningForDebugEvents()) { + notifyEvent(name, arg); + } + } + public static void dispatchDebugEvent(String name, boolean arg) { if (isListeningForDebugEvents()) { notifyEvent(name, arg); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java index 2ea29eca6..4868af0d9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java @@ -4,4 +4,5 @@ public class TesterEvent { public static final String EVT_FETCH_CONVERSATION_TOKEN = "fetch_conversation_token"; public static final String EVT_INSTANCE_CREATED = "instance_created"; // { successful:boolean } public static final String EVT_EXCEPTION = "exception"; // { class:String, message:String, stackTrace:String } + public static final String EVT_APPTENTIVE_EVENT = "apptentive_event"; // { eventLabel:String } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Event.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Event.java index 3f8c63c3e..3f116b3d3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Event.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Event.java @@ -85,6 +85,10 @@ public Event(String label, String interactionId, String data, Map customData) { JSONObject ret = new JSONObject(); for (String key : customData.keySet()) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java index de0bc0d5b..8fd7150f8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java @@ -9,9 +9,9 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.storage.EventStore; -/** - * @author Sky Kelsey - */ +import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; +import static com.apptentive.android.sdk.debug.TesterEvent.EVT_APPTENTIVE_EVENT; + public class EventManager { private static EventStore getEventStore() { @@ -19,6 +19,7 @@ private static EventStore getEventStore() { } public static void sendEvent(Event event) { + dispatchDebugEvent(EVT_APPTENTIVE_EVENT, event.getEventLabel()); getEventStore().addPayload(event); } } From c649646b523b6ba1284a8f16417c725b2603e19e Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 16 Feb 2017 20:20:42 -0800 Subject: [PATCH 098/465] Emit event when Conversation becomes Active Also add more logging. --- .../android/sdk/ApptentiveInternal.java | 6 +++++ .../android/sdk/ApptentiveLogTag.java | 3 ++- .../android/sdk/debug/TesterEvent.java | 1 + .../android/sdk/network/HttpRequest.java | 24 ++++++++++++++++++- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index bb8fd3083..f9bafc327 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -549,6 +549,9 @@ public boolean init() { messageManager.init(); } sessionData.setInteractionManager(new InteractionManager(sessionData)); + + dispatchDebugEvent(EVT_CONVERSATION_BECAME_ACTIVE); + // TODO: Make a callback like conversationBecameCurrent(), and call this there scheduleInteractionFetch(); } else { @@ -719,6 +722,7 @@ private void fetchConversationToken() { @Override public void onFinish(HttpJsonRequest request) { try { + ApptentiveLog.v(CONVERSATION, request.toString()); JSONObject root = request.getResponseObject(); String conversationToken = root.getString("token"); ApptentiveLog.d(CONVERSATION, "ConversationToken: " + conversationToken); @@ -739,6 +743,8 @@ public void onFinish(HttpJsonRequest request) { ApptentiveLog.d(CONVERSATION, "PersonId: " + personId); sessionData.setPersonId(personId); + dispatchDebugEvent(EVT_CONVERSATION_BECAME_ACTIVE); + // TODO: Make a callback like sessionBecameCurrent(), and call this there scheduleInteractionFetch(); } catch (Exception e) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java index d9d5b0339..ad188f8c9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java @@ -2,7 +2,8 @@ public enum ApptentiveLogTag { NETWORK, - CONVERSATION; + CONVERSATION, + TESTER_COMMANDS; public boolean enabled = true; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java index 4868af0d9..458108c48 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java @@ -5,4 +5,5 @@ public class TesterEvent { public static final String EVT_INSTANCE_CREATED = "instance_created"; // { successful:boolean } public static final String EVT_EXCEPTION = "exception"; // { class:String, message:String, stackTrace:String } public static final String EVT_APPTENTIVE_EVENT = "apptentive_event"; // { eventLabel:String } + public static final String EVT_CONVERSATION_BECAME_ACTIVE = "conversation_became_active"; // { } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 8f08636a6..cc783d96d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -349,7 +349,29 @@ public void setRequestProperty(String key, Object value) { //region String representation public String toString() { - return urlString; + try { + return String.format( + "\n" + + "Request:\n" + + "\t%s %s\n" + + "\t%s\n" + + "\t%s\n" + + "Response:\n" + + "\t%d\n" + + "\t%s\n" + + "\t%s", + /* Request */ + method.name(), urlString, + requestProperties, + new String(createRequestData()), + /* Response */ + responseCode, + responseData, + responseHeaders); + } catch (IOException e) { + ApptentiveLog.e("", e); + } + return null; } //endregion From 82d70cc7cbe7b8fda57b9b354d48fcde40a13e8e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 16 Feb 2017 20:31:43 -0800 Subject: [PATCH 099/465] Fixed updating server URL --- .../com/apptentive/android/sdk/ApptentiveInternal.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index f9bafc327..47089269e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -159,15 +159,16 @@ public static PushAction parse(String name) { @SuppressLint("StaticFieldLeak") private static volatile ApptentiveInternal sApptentiveInternal; - private ApptentiveInternal(Context context, String apiKey) { + private ApptentiveInternal(Context context, String apiKey, String serverUrl) { this.apiKey = apiKey; + this.serverUrl = serverUrl; appContext = context.getApplicationContext(); globalSharedPrefs = context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); backgroundQueue = DispatchQueue.createBackgroundQueue("Apptentive Serial Queue", DispatchQueueType.Serial); componentRegistry = new ApptentiveComponentRegistry(); - apptentiveHttpClient = new ApptentiveHttpClient(apiKey, getEndpointBase(globalSharedPrefs)); + apptentiveHttpClient = new ApptentiveHttpClient(apiKey, serverUrl); appRelease = AppReleaseManager.generateCurrentAppRelease(context, this); messageManager = new MessageManager(); @@ -206,9 +207,8 @@ public static ApptentiveInternal createInstance(Context context, String apptenti apptentiveApiKey = resolveManifestApiKey(context); } - sApptentiveInternal = new ApptentiveInternal(context, apptentiveApiKey); + sApptentiveInternal = new ApptentiveInternal(context, apptentiveApiKey, serverUrl); isApptentiveInitialized.set(false); - sApptentiveInternal.serverUrl = serverUrl; } } } From 98c5b3022887f5f33e9ca65d8da8c83a22a07434 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 20 Feb 2017 21:29:52 -0800 Subject: [PATCH 100/465] Refactoring Moved SessionData to package com.apptentive.android.sdk.conversation --- .../java/com/apptentive/android/sdk/Apptentive.java | 2 +- .../com/apptentive/android/sdk/ApptentiveInternal.java | 2 +- .../sdk/{storage => conversation}/SessionData.java | 10 +++++++++- .../sdk/module/engagement/EngagementModule.java | 2 +- .../engagement/interaction/InteractionManager.java | 5 +---- .../interaction/fragment/MessageCenterFragment.java | 2 +- .../engagement/interaction/fragment/NoteFragment.java | 2 +- .../sdk/module/engagement/logic/FieldManager.java | 3 +-- .../sdk/module/messagecenter/MessageManager.java | 3 +-- .../android/sdk/storage/SessionDataTest.java | 1 + 10 files changed, 18 insertions(+), 14 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/{storage => conversation}/SessionData.java (94%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index a24038135..f7e210941 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -30,7 +30,7 @@ import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; import com.apptentive.android.sdk.storage.IntegrationConfig; import com.apptentive.android.sdk.storage.IntegrationConfigItem; -import com.apptentive.android.sdk.storage.SessionData; +import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.util.Util; import org.json.JSONException; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 643723eb1..a48c6e8d4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -52,7 +52,7 @@ import com.apptentive.android.sdk.storage.PayloadSendWorker; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; -import com.apptentive.android.sdk.storage.SessionData; +import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.storage.VersionHistoryItem; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/SessionData.java similarity index 94% rename from apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java rename to apptentive/src/main/java/com/apptentive/android/sdk/conversation/SessionData.java index 34e08968e..d9057a780 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/SessionData.java @@ -4,11 +4,19 @@ * under which redistribution and use of this file is permitted. */ -package com.apptentive.android.sdk.storage; +package com.apptentive.android.sdk.conversation; import android.text.TextUtils; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; +import com.apptentive.android.sdk.storage.AppRelease; +import com.apptentive.android.sdk.storage.DataChangedListener; +import com.apptentive.android.sdk.storage.Device; +import com.apptentive.android.sdk.storage.EventData; +import com.apptentive.android.sdk.storage.Person; +import com.apptentive.android.sdk.storage.Saveable; +import com.apptentive.android.sdk.storage.Sdk; +import com.apptentive.android.sdk.storage.VersionHistory; public class SessionData implements Saveable, DataChangedListener { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java index 88f2040fa..6b44be404 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java @@ -19,7 +19,7 @@ import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; import com.apptentive.android.sdk.module.metric.MetricModule; -import com.apptentive.android.sdk.storage.SessionData; +import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java index fa66808e7..f92ea1843 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java @@ -7,8 +7,6 @@ package com.apptentive.android.sdk.module.engagement.interaction; import android.content.SharedPreferences; -import android.os.AsyncTask; -import android.os.Build; import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; @@ -18,8 +16,7 @@ import com.apptentive.android.sdk.module.engagement.interaction.model.Interactions; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.Targets; -import com.apptentive.android.sdk.module.metric.MetricModule; -import com.apptentive.android.sdk.storage.SessionData; +import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index 51080998e..37121d159 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -57,7 +57,7 @@ import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerView; import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerViewAdapter; import com.apptentive.android.sdk.module.messagecenter.view.holder.MessageComposerHolder; -import com.apptentive.android.sdk.storage.SessionData; +import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.util.AnimationUtil; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java index 7a07366ee..9c5a3abe0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java @@ -26,7 +26,7 @@ import com.apptentive.android.sdk.module.engagement.interaction.model.common.Action; import com.apptentive.android.sdk.module.engagement.interaction.model.common.Actions; import com.apptentive.android.sdk.module.engagement.interaction.model.common.LaunchInteractionAction; -import com.apptentive.android.sdk.storage.SessionData; +import com.apptentive.android.sdk.conversation.SessionData; import org.json.JSONException; import org.json.JSONObject; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java index a5bf7ccf7..1fa6f54e4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java @@ -13,8 +13,7 @@ import com.apptentive.android.sdk.storage.CustomData; import com.apptentive.android.sdk.storage.Device; import com.apptentive.android.sdk.storage.Person; -import com.apptentive.android.sdk.storage.SessionData; -import com.apptentive.android.sdk.storage.legacy.VersionHistoryStore; +import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 31dfbc843..a7c9c7f01 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -10,7 +10,6 @@ import android.app.Notification; import android.app.PendingIntent; import android.content.Context; -import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Build; import android.os.Handler; @@ -29,7 +28,7 @@ import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; import com.apptentive.android.sdk.module.metric.MetricModule; import com.apptentive.android.sdk.storage.MessageStore; -import com.apptentive.android.sdk.storage.SessionData; +import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.util.Util; import org.json.JSONArray; diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java index d3136b93f..b1b6fc332 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java @@ -8,6 +8,7 @@ import android.text.TextUtils; +import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.util.Util; import org.junit.Before; From a0667cbfa1dd8958ddf920aa5de7a624d314e238 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 20 Feb 2017 21:56:54 -0800 Subject: [PATCH 101/465] Replaced Conversation with SessionData --- .../apptentive/android/sdk/conversation/Conversation.java | 4 ---- .../android/sdk/conversation/ConversationManager.java | 6 +++--- 2 files changed, 3 insertions(+), 7 deletions(-) delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java deleted file mode 100644 index 9fe5561e2..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.apptentive.android.sdk.conversation; - -public class Conversation { -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index db35ffeb5..ed2bfd862 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -60,7 +60,7 @@ private void loadConversation(final Filter filter, final Callback callback) { operationQueue.dispatchAsync(new DispatchTask() { @Override protected void execute() { - Conversation conversation = null; + SessionData conversation = null; String errorMessage = null; try { conversation = loadConversationSync(filter); @@ -78,7 +78,7 @@ protected void execute() { /** * Loads selected conversation on a background queue */ - private Conversation loadConversationSync(Filter filter) throws IOException { + private SessionData loadConversationSync(Filter filter) throws IOException { if (conversationMetadata == null) { conversationMetadata = ObjectSerialization.deserialize(new File(""), ConversationMetadata.class); } @@ -107,7 +107,7 @@ public interface Callback { * @param conversation - null if loading failed * @param errorMessage - error description in case if loading failed (null is succeed) */ - void onFinishLoading(Conversation conversation, String errorMessage); + void onFinishLoading(SessionData conversation, String errorMessage); } /** From a3dd17697a118600ec48d17d2bf85b421e4da17b Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 20 Feb 2017 22:04:33 -0800 Subject: [PATCH 102/465] Refactoring Renamed SessionData to Conversation --- .../apptentive/android/sdk/Apptentive.java | 104 ++++++++-------- .../android/sdk/ApptentiveInternal.java | 86 +++++++------- .../android/sdk/comm/ApptentiveClient.java | 22 ++-- .../{SessionData.java => Conversation.java} | 4 +- .../sdk/conversation/ConversationManager.java | 6 +- .../module/engagement/EngagementModule.java | 18 +-- .../interaction/InteractionManager.java | 38 +++--- .../fragment/MessageCenterFragment.java | 54 ++++----- .../interaction/fragment/NoteFragment.java | 8 +- .../module/engagement/logic/FieldManager.java | 36 +++--- .../module/messagecenter/MessageManager.java | 10 +- .../sdk/storage/PayloadSendWorker.java | 6 +- .../image/ApptentiveAttachmentLoader.java | 2 +- ...ionDataTest.java => ConversationTest.java} | 112 +++++++++--------- 14 files changed, 253 insertions(+), 253 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/conversation/{SessionData.java => Conversation.java} (98%) rename apptentive/src/test/java/com/apptentive/android/sdk/storage/{SessionDataTest.java => ConversationTest.java} (70%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index f7e210941..7ab795e91 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -16,6 +16,7 @@ import android.text.TextUtils; import android.webkit.MimeTypeMap; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.model.CommerceExtendedData; import com.apptentive.android.sdk.model.ExtendedData; import com.apptentive.android.sdk.model.LocationExtendedData; @@ -30,7 +31,6 @@ import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; import com.apptentive.android.sdk.storage.IntegrationConfig; import com.apptentive.android.sdk.storage.IntegrationConfigItem; -import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.util.Util; import org.json.JSONException; @@ -77,10 +77,10 @@ public static void register(Application application, String apptentiveApiKey) { */ public static void setPersonEmail(String email) { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { // FIXME: Make sure Person object diff is sent. - sessionData.setPersonEmail(email); + conversation.setPersonEmail(email); } } } @@ -93,9 +93,9 @@ public static void setPersonEmail(String email) { */ public static String getPersonEmail() { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - return sessionData.getPersonEmail(); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + return conversation.getPersonEmail(); } } return null; @@ -112,10 +112,10 @@ public static String getPersonEmail() { */ public static void setPersonName(String name) { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { // FIXME: Make sure Person object diff is sent. - sessionData.setPersonName(name); + conversation.setPersonName(name); } } } @@ -128,9 +128,9 @@ public static void setPersonName(String name) { */ public static String getPersonName() { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - return sessionData.getPersonName(); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + return conversation.getPersonName(); } } return null; @@ -149,9 +149,9 @@ public static void addCustomDeviceData(String key, String value) { if (value != null) { value = value.trim(); } - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getDevice().getCustomData().put(key, value); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getDevice().getCustomData().put(key, value); } } } @@ -166,9 +166,9 @@ public static void addCustomDeviceData(String key, String value) { */ public static void addCustomDeviceData(String key, Number value) { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getDevice().getCustomData().put(key, value); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getDevice().getCustomData().put(key, value); } } } @@ -183,27 +183,27 @@ public static void addCustomDeviceData(String key, Number value) { */ public static void addCustomDeviceData(String key, Boolean value) { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getDevice().getCustomData().put(key, value); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getDevice().getCustomData().put(key, value); } } } private static void addCustomDeviceData(String key, Version version) { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getDevice().getCustomData().put(key, version); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getDevice().getCustomData().put(key, version); } } } private static void addCustomDeviceData(String key, DateTime dateTime) { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getDevice().getCustomData().put(key, dateTime); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getDevice().getCustomData().put(key, dateTime); } } } @@ -215,9 +215,9 @@ private static void addCustomDeviceData(String key, DateTime dateTime) { */ public static void removeCustomDeviceData(String key) { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getDevice().getCustomData().remove(key); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getDevice().getCustomData().remove(key); } } } @@ -235,9 +235,9 @@ public static void addCustomPersonData(String key, String value) { if (value != null) { value = value.trim(); } - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getPerson().getCustomData().put(key, value); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().getCustomData().put(key, value); } } } @@ -252,9 +252,9 @@ public static void addCustomPersonData(String key, String value) { */ public static void addCustomPersonData(String key, Number value) { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getPerson().getCustomData().put(key, value); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().getCustomData().put(key, value); } } } @@ -269,27 +269,27 @@ public static void addCustomPersonData(String key, Number value) { */ public static void addCustomPersonData(String key, Boolean value) { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getPerson().getCustomData().put(key, value); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().getCustomData().put(key, value); } } } private static void addCustomPersonData(String key, Version version) { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getPerson().getCustomData().put(key, version); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().getCustomData().put(key, version); } } } private static void addCustomPersonData(String key, DateTime dateTime) { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getPerson().getCustomData().remove(key); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().getCustomData().remove(key); } } } @@ -301,9 +301,9 @@ private static void addCustomPersonData(String key, DateTime dateTime) { */ public static void removeCustomPersonData(String key) { if (ApptentiveInternal.isApptentiveRegistered()) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getPerson().getCustomData().remove(key); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().getCustomData().remove(key); } } } @@ -375,9 +375,9 @@ public static void setPushNotificationIntegration(int pushProvider, String token if (!ApptentiveInternal.isApptentiveRegistered()) { return; } - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - IntegrationConfig integrationConfig = ApptentiveInternal.getInstance().getSessionData().getDevice().getIntegrationConfig(); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + IntegrationConfig integrationConfig = ApptentiveInternal.getInstance().getConversation().getDevice().getIntegrationConfig(); IntegrationConfigItem item = new IntegrationConfigItem(); item.put(INTEGRATION_PUSH_TOKEN, token); switch (pushProvider) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index a48c6e8d4..717044925 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -27,6 +27,7 @@ import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.lifecycle.ApptentiveActivityLifecycleCallbacks; import com.apptentive.android.sdk.listeners.OnUserLogOutListener; import com.apptentive.android.sdk.model.Configuration; @@ -52,7 +53,6 @@ import com.apptentive.android.sdk.storage.PayloadSendWorker; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; -import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.storage.VersionHistoryItem; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -107,7 +107,7 @@ public class ApptentiveInternal implements DataChangedListener { String personId; String androidId; String appPackageName; - SessionData sessionData; + Conversation conversation; private FileSerializer fileSerializer; // private background serial dispatch queue for internal SDK tasks @@ -411,8 +411,8 @@ public int getDefaultStatusBarColor() { return statusBarColorDefault; } - public SessionData getSessionData() { - return sessionData; + public Conversation getConversation() { + return conversation; } public String getApptentiveApiKey() { @@ -545,20 +545,20 @@ public boolean init() { */ File internalStorage = appContext.getFilesDir(); - File sessionDataFile = new File(internalStorage, "apptentive/SessionData.ser"); + File sessionDataFile = new File(internalStorage, "apptentive/Conversation.ser"); sessionDataFile.getParentFile().mkdirs(); fileSerializer = new FileSerializer(sessionDataFile); - sessionData = (SessionData) fileSerializer.deserialize(); - if (sessionData != null) { - sessionData.setDataChangedListener(this); - ApptentiveLog.d("Restored existing SessionData"); - ApptentiveLog.v("Restored EventData: %s", sessionData.getEventData()); + conversation = (Conversation) fileSerializer.deserialize(); + if (conversation != null) { + conversation.setDataChangedListener(this); + ApptentiveLog.d("Restored existing Conversation"); + ApptentiveLog.v("Restored EventData: %s", conversation.getEventData()); // FIXME: Move this to wherever the sessions first comes online? - boolean featureEverUsed = sessionData != null && sessionData.isMessageCenterFeatureUsed(); + boolean featureEverUsed = conversation != null && conversation.isMessageCenterFeatureUsed(); if (featureEverUsed) { messageManager.init(); } - sessionData.setInteractionManager(new InteractionManager(sessionData)); + conversation.setInteractionManager(new InteractionManager(conversation)); // TODO: Make a callback like conversationBecameCurrent(), and call this there scheduleInteractionFetch(); } else { @@ -640,7 +640,7 @@ public boolean init() { } private void checkSendVersionChanges() { - if (sessionData == null) { + if (conversation == null) { ApptentiveLog.e("Can't check session data changes: session data is not initialized"); return; } @@ -648,7 +648,7 @@ private void checkSendVersionChanges() { boolean appReleaseChanged = false; boolean sdkChanged = false; - final VersionHistoryItem lastVersionItemSeen = sessionData.getVersionHistory().getLastVersionSeen(); + final VersionHistoryItem lastVersionItemSeen = conversation.getVersionHistory().getLastVersionSeen(); final int currentVersionCode = appRelease.getVersionCode(); final String currentVersionName = appRelease.getVersionName(); @@ -669,7 +669,7 @@ private void checkSendVersionChanges() { } // TODO: Move this into a session became active handler. - final String lastSeenSdkVersion = sessionData.getLastSeenSdkVersion(); + final String lastSeenSdkVersion = conversation.getLastSeenSdkVersion(); final String currentSdkVersion = Constants.APPTENTIVE_SDK_VERSION; if (!TextUtils.equals(lastSeenSdkVersion, currentSdkVersion)) { sdkChanged = true; @@ -677,17 +677,17 @@ private void checkSendVersionChanges() { if (appReleaseChanged) { ApptentiveLog.i("Version changed: Name: %s => %s, Code: %d => %d", previousVersionName, currentVersionName, previousVersionCode, currentVersionCode); - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.getVersionHistory().updateVersionHistory(Util.currentTimeSeconds(), currentVersionCode, currentVersionName); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getVersionHistory().updateVersionHistory(Util.currentTimeSeconds(), currentVersionCode, currentVersionName); } } Sdk sdk = SdkManager.generateCurrentSdk(); if (sdkChanged) { ApptentiveLog.i("SDK version changed: %s => %s", lastSeenSdkVersion, currentSdkVersion); - sessionData.setLastSeenSdkVersion(currentSdkVersion); - sessionData.setSdk(sdk); + conversation.setLastSeenSdkVersion(currentSdkVersion); + conversation.setSdk(sdk); } if (appReleaseChanged || sdkChanged) { @@ -700,9 +700,9 @@ private void checkSendVersionChanges() { * We want to make sure the app is using the latest configuration from the server if the app or sdk version changes. */ private void invalidateCaches() { - SessionData sessionData = getSessionData(); - if (sessionData != null) { - sessionData.setInteractionExpiration(0L); + Conversation conversation = getConversation(); + if (conversation != null) { + conversation.setInteractionExpiration(0L); } Configuration config = Configuration.load(); config.setConfigurationCacheExpirationMillis(System.currentTimeMillis()); @@ -735,19 +735,19 @@ public void onFinish(HttpJsonRequest request) { String conversationId = root.getString("id"); ApptentiveLog.d(CONVERSATION, "New Conversation id: %s", conversationId); - sessionData = new SessionData(); - sessionData.setDataChangedListener(ApptentiveInternal.this); + conversation = new Conversation(); + conversation.setDataChangedListener(ApptentiveInternal.this); if (conversationToken != null && !conversationToken.equals("")) { - sessionData.setConversationToken(conversationToken); - sessionData.setConversationId(conversationId); - sessionData.setDevice(device); - sessionData.setSdk(sdk); - sessionData.setAppRelease(appRelease); - sessionData.setInteractionManager(new InteractionManager(sessionData)); + conversation.setConversationToken(conversationToken); + conversation.setConversationId(conversationId); + conversation.setDevice(device); + conversation.setSdk(sdk); + conversation.setAppRelease(appRelease); + conversation.setInteractionManager(new InteractionManager(conversation)); } String personId = root.getString("person_id"); ApptentiveLog.d(CONVERSATION, "PersonId: " + personId); - sessionData.setPersonId(personId); + conversation.setPersonId(personId); // TODO: Make a callback like sessionBecameCurrent(), and call this there scheduleInteractionFetch(); @@ -1065,18 +1065,18 @@ public static boolean checkRegistered() { private synchronized void scheduleSessionDataSave() { boolean scheduled = backgroundQueue.dispatchAsyncOnce(saveSessionTask, 100L); if (scheduled) { - ApptentiveLog.d("Scheduling SessionData save."); + ApptentiveLog.d("Scheduling Conversation save."); } else { - ApptentiveLog.d("SessionData save already scheduled."); + ApptentiveLog.d("Conversation save already scheduled."); } } private synchronized void scheduleInteractionFetch() { - if (sessionData != null) { - InteractionManager interactionManager = sessionData.getInteractionManager(); + if (conversation != null) { + InteractionManager interactionManager = conversation.getInteractionManager(); if (interactionManager != null) { if (interactionManager.isPollForInteractions()) { - boolean cacheExpired = sessionData.getInteractionExpiration() > Util.currentTimeSeconds(); + boolean cacheExpired = conversation.getInteractionExpiration() > Util.currentTimeSeconds(); boolean force = appRelease != null && appRelease.isDebug(); if (cacheExpired || force) { backgroundQueue.dispatchAsyncOnce(fetchInteractionsTask); @@ -1091,10 +1091,10 @@ private synchronized void scheduleInteractionFetch() { private final DispatchTask saveSessionTask = new DispatchTask() { @Override protected void execute() { - ApptentiveLog.d("Saving SessionData"); - ApptentiveLog.v("EventData: %s", sessionData.getEventData().toString()); + ApptentiveLog.d("Saving Conversation"); + ApptentiveLog.v("EventData: %s", conversation.getEventData().toString()); if (fileSerializer != null) { - fileSerializer.serialize(sessionData); + fileSerializer.serialize(conversation); } } }; @@ -1115,9 +1115,9 @@ private String getEndpointBase(SharedPreferences prefs) { private final DispatchTask fetchInteractionsTask = new DispatchTask() { @Override protected void execute() { - SessionData sessionData = getSessionData(); - if (sessionData != null) { - sessionData.getInteractionManager().fetchInteractions(); + Conversation conversation = getConversation(); + if (conversation != null) { + conversation.getInteractionManager().fetchInteractions(); } } }; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 3aeda6d01..635bf853a 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -52,7 +52,7 @@ public class ApptentiveClient { // private static final String ENDPOINT_SURVEYS_FETCH = ENDPOINT_BASE + "/surveys"; public static ApptentiveHttpResponse getAppConfiguration() { - return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_CONFIGURATION, Method.GET, null); + return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_CONFIGURATION, Method.GET, null); } /** @@ -62,7 +62,7 @@ public static ApptentiveHttpResponse getAppConfiguration() { */ public static ApptentiveHttpResponse getMessages(Integer count, String afterId, String beforeId) { String uri = String.format(ENDPOINT_CONVERSATION_FETCH, count == null ? "" : count.toString(), afterId == null ? "" : afterId, beforeId == null ? "" : beforeId); - return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), uri, Method.GET, null); + return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), uri, Method.GET, null); } public static ApptentiveHttpResponse postMessage(ApptentiveMessage apptentiveMessage) { @@ -70,7 +70,7 @@ public static ApptentiveHttpResponse postMessage(ApptentiveMessage apptentiveMes case CompoundMessage: { CompoundMessage compoundMessage = (CompoundMessage) apptentiveMessage; List associatedFiles = compoundMessage.getAssociatedFiles(); - return performMultipartFilePost(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_MESSAGES, apptentiveMessage.marshallForSending(), associatedFiles); + return performMultipartFilePost(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_MESSAGES, apptentiveMessage.marshallForSending(), associatedFiles); } case unknown: break; @@ -79,36 +79,36 @@ public static ApptentiveHttpResponse postMessage(ApptentiveMessage apptentiveMes } public static ApptentiveHttpResponse postEvent(Event event) { - return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_EVENTS, Method.POST, event.marshallForSending()); + return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_EVENTS, Method.POST, event.marshallForSending()); } public static ApptentiveHttpResponse putDevice(Device device) { - return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_DEVICES, Method.PUT, device.marshallForSending()); + return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_DEVICES, Method.PUT, device.marshallForSending()); } public static ApptentiveHttpResponse putSdk(Sdk sdk) { - return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, sdk.marshallForSending()); + return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, sdk.marshallForSending()); } public static ApptentiveHttpResponse putAppRelease(AppRelease appRelease) { - return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, appRelease.marshallForSending()); + return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, appRelease.marshallForSending()); } public static ApptentiveHttpResponse putSdkAndAppRelease(SdkAndAppReleasePayload payload) { - return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, payload.marshallForSending()); + return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, payload.marshallForSending()); } public static ApptentiveHttpResponse putPerson(Person person) { - return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_PEOPLE, Method.PUT, person.marshallForSending()); + return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_PEOPLE, Method.PUT, person.marshallForSending()); } public static ApptentiveHttpResponse postSurvey(SurveyResponse survey) { String endpoint = String.format(ENDPOINT_SURVEYS_POST, survey.getId()); - return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), endpoint, Method.POST, survey.marshallForSending()); + return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), endpoint, Method.POST, survey.marshallForSending()); } public static ApptentiveHttpResponse getInteractions() { - return performHttpRequest(ApptentiveInternal.getInstance().getSessionData().getConversationToken(), ENDPOINT_INTERACTIONS, Method.GET, null); + return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_INTERACTIONS, Method.GET, null); } /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/SessionData.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java similarity index 98% rename from apptentive/src/main/java/com/apptentive/android/sdk/conversation/SessionData.java rename to apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index d9057a780..f8b8f586e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/SessionData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -18,7 +18,7 @@ import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.VersionHistory; -public class SessionData implements Saveable, DataChangedListener { +public class Conversation implements Saveable, DataChangedListener { private static final long serialVersionUID = 1L; @@ -47,7 +47,7 @@ public class SessionData implements Saveable, DataChangedListener { // TODO: Maybe move this up to a wrapping Conversation class? private transient InteractionManager interactionManager; - public SessionData() { + public Conversation() { this.device = new Device(); this.person = new Person(); this.sdk = new Sdk(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index ed2bfd862..db35ffeb5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -60,7 +60,7 @@ private void loadConversation(final Filter filter, final Callback callback) { operationQueue.dispatchAsync(new DispatchTask() { @Override protected void execute() { - SessionData conversation = null; + Conversation conversation = null; String errorMessage = null; try { conversation = loadConversationSync(filter); @@ -78,7 +78,7 @@ protected void execute() { /** * Loads selected conversation on a background queue */ - private SessionData loadConversationSync(Filter filter) throws IOException { + private Conversation loadConversationSync(Filter filter) throws IOException { if (conversationMetadata == null) { conversationMetadata = ObjectSerialization.deserialize(new File(""), ConversationMetadata.class); } @@ -107,7 +107,7 @@ public interface Callback { * @param conversation - null if loading failed * @param errorMessage - error description in case if loading failed (null is succeed) */ - void onFinishLoading(SessionData conversation, String errorMessage); + void onFinishLoading(Conversation conversation, String errorMessage); } /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java index 6b44be404..7577ec306 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java @@ -19,7 +19,7 @@ import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; import com.apptentive.android.sdk.module.metric.MetricModule; -import com.apptentive.android.sdk.conversation.SessionData; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -54,11 +54,11 @@ public static synchronized boolean engage(Context context, String vendor, String String eventLabel = generateEventLabel(vendor, interaction, eventName); ApptentiveLog.d("engage(%s)", eventLabel); - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); - sessionData.getEventData().storeEventForCurrentAppVersion(Util.currentTimeSeconds(), versionCode, versionName, eventLabel); + conversation.getEventData().storeEventForCurrentAppVersion(Util.currentTimeSeconds(), versionCode, versionName, eventLabel); EventManager.sendEvent(new Event(eventLabel, interactionId, data, customData, extendedData)); return doEngage(context, eventLabel); } @@ -69,13 +69,13 @@ public static synchronized boolean engage(Context context, String vendor, String } public static boolean doEngage(Context context, String eventLabel) { - Interaction interaction = ApptentiveInternal.getInstance().getSessionData().getInteractionManager().getApplicableInteraction(eventLabel); + Interaction interaction = ApptentiveInternal.getInstance().getConversation().getInteractionManager().getApplicableInteraction(eventLabel); if (interaction != null) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); - sessionData.getEventData().storeInteractionForCurrentAppVersion(Util.currentTimeSeconds(), versionCode, versionName, interaction.getId()); + conversation.getEventData().storeInteractionForCurrentAppVersion(Util.currentTimeSeconds(), versionCode, versionName, interaction.getId()); } launchInteraction(context, interaction); return true; @@ -126,7 +126,7 @@ public static boolean canShowInteraction(String vendor, String interaction, Stri } private static boolean canShowInteraction(String eventLabel) { - Interaction interaction = ApptentiveInternal.getInstance().getSessionData().getInteractionManager().getApplicableInteraction(eventLabel); + Interaction interaction = ApptentiveInternal.getInstance().getConversation().getInteractionManager().getApplicableInteraction(eventLabel); return interaction != null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java index f92ea1843..9b0bf0f2a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java @@ -12,11 +12,11 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.module.engagement.interaction.model.InteractionManifest; import com.apptentive.android.sdk.module.engagement.interaction.model.Interactions; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.Targets; -import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -30,10 +30,10 @@ public class InteractionManager { // boolean to prevent multiple fetching threads private AtomicBoolean isFetchPending = new AtomicBoolean(false); - private SessionData sessionData; + private Conversation conversation; - public InteractionManager(SessionData sessionData) { - this.sessionData = sessionData; + public InteractionManager(Conversation conversation) { + this.conversation = conversation; } public interface InteractionUpdateListener { @@ -42,17 +42,17 @@ public interface InteractionUpdateListener { public Interaction getApplicableInteraction(String eventLabel) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData == null) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation == null) { return null; } - String targetsString = sessionData.getTargets(); + String targetsString = conversation.getTargets(); if (targetsString != null) { try { - Targets targets = new Targets(sessionData.getTargets()); + Targets targets = new Targets(conversation.getTargets()); String interactionId = targets.getApplicableInteraction(eventLabel); if (interactionId != null) { - String interactionsString = sessionData.getInteractions(); + String interactionsString = conversation.getInteractions(); if (interactionsString != null) { Interactions interactions = new Interactions(interactionsString); return interactions.getInteraction(interactionId); @@ -68,8 +68,8 @@ public Interaction getApplicableInteraction(String eventLabel) { // TODO: Refactor this class to dispatch to its own queue. public void fetchInteractions() { ApptentiveLog.v("Fetching Interactions"); - if (sessionData != null) { - InteractionManager interactionManager = sessionData.getInteractionManager(); + if (conversation != null) { + InteractionManager interactionManager = conversation.getInteractionManager(); if (interactionManager != null) { ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); @@ -97,14 +97,14 @@ else if (!response.isSuccessful()) { if (cacheSeconds == null) { cacheSeconds = Constants.CONFIG_DEFAULT_INTERACTION_CACHE_EXPIRATION_DURATION_SECONDS; } - sessionData.setInteractionExpiration(Util.currentTimeSeconds() + cacheSeconds); + conversation.setInteractionExpiration(Util.currentTimeSeconds() + cacheSeconds); try { InteractionManifest payload = new InteractionManifest(interactionsPayloadString); Interactions interactions = payload.getInteractions(); Targets targets = payload.getTargets(); if (interactions != null && targets != null) { - sessionData.setTargets(targets.toString()); - sessionData.setInteractions(interactions.toString()); + conversation.setTargets(targets.toString()); + conversation.setInteractions(interactions.toString()); } else { ApptentiveLog.e("Unable to save interactionManifest."); } @@ -116,7 +116,7 @@ else if (!response.isSuccessful()) { ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); } } else { - ApptentiveLog.v("Cancelled Interaction fetch due to null SessionData."); + ApptentiveLog.v("Cancelled Interaction fetch due to null Conversation."); } } @@ -124,8 +124,8 @@ else if (!response.isSuccessful()) { * Made public for testing. There is no other reason to use this method directly. */ public void storeInteractionManifest(String interactionManifest) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData == null) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation == null) { return; } try { @@ -133,8 +133,8 @@ public void storeInteractionManifest(String interactionManifest) { Interactions interactions = payload.getInteractions(); Targets targets = payload.getTargets(); if (interactions != null && targets != null) { - sessionData.setTargets(targets.toString()); - sessionData.setInteractions(interactions.toString()); + conversation.setTargets(targets.toString()); + conversation.setInteractions(interactions.toString()); } else { ApptentiveLog.e("Unable to save interactionManifest."); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index 37121d159..a924dfe95 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -42,6 +42,7 @@ import com.apptentive.android.sdk.ApptentiveViewActivity; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; import com.apptentive.android.sdk.module.messagecenter.MessageManager; @@ -57,7 +58,6 @@ import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerView; import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerViewAdapter; import com.apptentive.android.sdk.module.messagecenter.view.holder.MessageComposerHolder; -import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.util.AnimationUtil; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -434,9 +434,9 @@ else if (pendingWhoCardName != null || pendingWhoCardEmail != null || pendingWho // Retrieve any saved attachments final SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null && sessionData.getMessageCenterPendingAttachments() != null) { - String pendingAttachmentsString = sessionData.getMessageCenterPendingAttachments(); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null && conversation.getMessageCenterPendingAttachments() != null) { + String pendingAttachmentsString = conversation.getMessageCenterPendingAttachments(); JSONArray savedAttachmentsJsonArray = null; try { savedAttachmentsJsonArray = new JSONArray(pendingAttachmentsString); @@ -461,7 +461,7 @@ else if (pendingWhoCardName != null || pendingWhoCardEmail != null || pendingWho } } // Stored pending attachments have been restored, remove it from the persistent storage - sessionData.setMessageCenterPendingAttachments(null); + conversation.setMessageCenterPendingAttachments(null); } updateMessageSentStates(); } @@ -743,7 +743,7 @@ public void onComposingViewCreated(MessageComposerHolder composer, final EditTex this.composer = composer; this.composerEditText = composerEditText; - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); // Restore composing text editing state, such as cursor position, after rotation if (composingViewSavedState != null) { if (this.composerEditText != null) { @@ -751,18 +751,18 @@ public void onComposingViewCreated(MessageComposerHolder composer, final EditTex } composingViewSavedState = null; // Stored pending composing text has been restored from the saved state, so it's not needed here anymore - if (sessionData != null) { - sessionData.setMessageCenterPendingMessage(null); + if (conversation != null) { + conversation.setMessageCenterPendingMessage(null); } } // Restore composing text - else if (sessionData != null && !TextUtils.isEmpty(sessionData.getMessageCenterPendingMessage())) { - String messageText = sessionData.getMessageCenterPendingMessage(); + else if (conversation != null && !TextUtils.isEmpty(conversation.getMessageCenterPendingMessage())) { + String messageText = conversation.getMessageCenterPendingMessage(); if (messageText != null && this.composerEditText != null) { this.composerEditText.setText(messageText); } // Stored pending composing text has been restored, remove it from the persistent storage - sessionData.setMessageCenterPendingMessage(null); + conversation.setMessageCenterPendingMessage(null); } setAttachmentsInComposer(pendingAttachments); @@ -991,19 +991,19 @@ public void onAttachImage() { } private void setWhoCardAsPreviouslyDisplayed() { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData == null) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation == null) { return; } - sessionData.setMessageCenterWhoCardPreviouslyDisplayed(true); + conversation.setMessageCenterWhoCardPreviouslyDisplayed(true); } private boolean wasWhoCardAsPreviouslyDisplayed() { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData == null) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation == null) { return false; } - return sessionData.isMessageCenterWhoCardPreviouslyDisplayed(); + return conversation.isMessageCenterWhoCardPreviouslyDisplayed(); } // Retrieve the content from the composing area @@ -1016,14 +1016,14 @@ public void savePendingComposingMessage() { Editable content = getPendingComposingContent(); SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); SharedPreferences.Editor editor = prefs.edit(); - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData == null) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation == null) { return; } if (content != null) { - sessionData.setMessageCenterPendingMessage(content.toString().trim()); + conversation.setMessageCenterPendingMessage(content.toString().trim()); } else { - sessionData.setMessageCenterPendingMessage(null); + conversation.setMessageCenterPendingMessage(null); } JSONArray pendingAttachmentsJsonArray = new JSONArray(); // Save pending attachment @@ -1032,9 +1032,9 @@ public void savePendingComposingMessage() { } if (pendingAttachmentsJsonArray.length() > 0) { - sessionData.setMessageCenterPendingAttachments(pendingAttachmentsJsonArray.toString()); + conversation.setMessageCenterPendingAttachments(pendingAttachmentsJsonArray.toString()); } else { - sessionData.setMessageCenterPendingAttachments(null); + conversation.setMessageCenterPendingAttachments(null); } editor.apply(); } @@ -1043,10 +1043,10 @@ public void savePendingComposingMessage() { * will clear the pending composing message previously saved in shared preference */ public void clearPendingComposingMessage() { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.setMessageCenterPendingMessage(null); - sessionData.setMessageCenterPendingAttachments(null); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.setMessageCenterPendingMessage(null); + conversation.setMessageCenterPendingAttachments(null); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java index 9c5a3abe0..647863275 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java @@ -26,7 +26,7 @@ import com.apptentive.android.sdk.module.engagement.interaction.model.common.Action; import com.apptentive.android.sdk.module.engagement.interaction.model.common.Actions; import com.apptentive.android.sdk.module.engagement.interaction.model.common.LaunchInteractionAction; -import com.apptentive.android.sdk.conversation.SessionData; +import com.apptentive.android.sdk.conversation.Conversation; import org.json.JSONException; import org.json.JSONObject; @@ -132,9 +132,9 @@ public void onClick(View view) { Interaction invokedInteraction = null; if (interactionIdToLaunch != null) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - String interactionsString = sessionData.getInteractions(); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + String interactionsString = conversation.getInteractions(); if (interactionsString != null) { try { Interactions interactions = new Interactions(interactionsString); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java index 1fa6f54e4..88c9fb5e9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java @@ -10,10 +10,10 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.BuildConfig; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.storage.CustomData; import com.apptentive.android.sdk.storage.Device; import com.apptentive.android.sdk.storage.Person; -import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -31,8 +31,8 @@ public static Comparable getValue(String query) { } public static Object doGetValue(String query) { - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData == null) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation == null) { return null; } query = query.trim(); @@ -80,9 +80,9 @@ public static Object doGetValue(String query) { QueryPart subQuery = QueryPart.parse(tokens[1]); switch (subQuery) { case version_code: - return sessionData.getVersionHistory().isUpdateForVersionCode(); + return conversation.getVersionHistory().isUpdateForVersionCode(); case version_name: - return sessionData.getVersionHistory().isUpdateForVersionName(); + return conversation.getVersionHistory().isUpdateForVersionName(); default: break; } @@ -92,11 +92,11 @@ public static Object doGetValue(String query) { QueryPart subQuery = QueryPart.parse(tokens[1]); switch (subQuery) { case total: - return sessionData.getVersionHistory().getTimeAtInstallTotal(); + return conversation.getVersionHistory().getTimeAtInstallTotal(); case version_code: - return sessionData.getVersionHistory().getTimeAtInstallForCurrentVersionCode(); + return conversation.getVersionHistory().getTimeAtInstallForCurrentVersionCode(); case version_name: - return sessionData.getVersionHistory().getTimeAtInstallForCurrentVersionName(); + return conversation.getVersionHistory().getTimeAtInstallForCurrentVersionName(); } return new Apptentive.DateTime(Util.currentTimeSeconds()); } @@ -108,13 +108,13 @@ public static Object doGetValue(String query) { QueryPart queryPart2 = QueryPart.parse(tokens[3]); switch (queryPart2) { case total: // Get total for all versions of the app. - return new BigDecimal(sessionData.getEventData().getInteractionCountTotal(interactionId)); + return new BigDecimal(conversation.getEventData().getInteractionCountTotal(interactionId)); case version_code: Integer appVersionCode = Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext()); - return new BigDecimal(sessionData.getEventData().getInteractionCountForVersionCode(interactionId, appVersionCode)); + return new BigDecimal(conversation.getEventData().getInteractionCountForVersionCode(interactionId, appVersionCode)); case version_name: String appVersionName = Util.getAppVersionName(ApptentiveInternal.getInstance().getApplicationContext()); - return new BigDecimal(sessionData.getEventData().getInteractionCountForVersionName(interactionId, appVersionName)); + return new BigDecimal(conversation.getEventData().getInteractionCountForVersionName(interactionId, appVersionName)); default: break; } @@ -123,7 +123,7 @@ public static Object doGetValue(String query) { QueryPart queryPart3 = QueryPart.parse(tokens[3]); switch (queryPart3) { case total: - Double lastInvoke = sessionData.getEventData().getTimeOfLastInteractionInvocation(interactionId); + Double lastInvoke = conversation.getEventData().getTimeOfLastInteractionInvocation(interactionId); if (lastInvoke != null) { return new Apptentive.DateTime(lastInvoke); } @@ -143,13 +143,13 @@ public static Object doGetValue(String query) { QueryPart queryPart2 = QueryPart.parse(tokens[3]); switch (queryPart2) { case total: // Get total for all versions of the app. - return new BigDecimal(sessionData.getEventData().getEventCountTotal(eventLabel)); + return new BigDecimal(conversation.getEventData().getEventCountTotal(eventLabel)); case version_code: Integer appVersionCode = Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext()); - return new BigDecimal(sessionData.getEventData().getEventCountForVersionCode(eventLabel, appVersionCode)); + return new BigDecimal(conversation.getEventData().getEventCountForVersionCode(eventLabel, appVersionCode)); case version_name: String appVersionName = Util.getAppVersionName(ApptentiveInternal.getInstance().getApplicationContext()); - return new BigDecimal(sessionData.getEventData().getEventCountForVersionName(eventLabel, appVersionName)); + return new BigDecimal(conversation.getEventData().getEventCountForVersionName(eventLabel, appVersionName)); default: break; } @@ -158,7 +158,7 @@ public static Object doGetValue(String query) { QueryPart queryPart3 = QueryPart.parse(tokens[3]); switch (queryPart3) { case total: - Double lastInvoke = sessionData.getEventData().getTimeOfLastEventInvocation(eventLabel); + Double lastInvoke = conversation.getEventData().getTimeOfLastEventInvocation(eventLabel); if (lastInvoke != null) { return new Apptentive.DateTime(lastInvoke); } @@ -172,7 +172,7 @@ public static Object doGetValue(String query) { } case person: { QueryPart subQuery = QueryPart.parse(tokens[1]); - Person person = sessionData.getPerson(); + Person person = conversation.getPerson(); if (person == null) { return null; } @@ -194,7 +194,7 @@ public static Object doGetValue(String query) { } case device: { QueryPart subQuery = QueryPart.parse(tokens[1]); - Device device = sessionData.getDevice(); + Device device = conversation.getDevice(); if (device == null) { return null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index a7c9c7f01..a27b1454e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -21,6 +21,7 @@ import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveToastNotification; import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; @@ -28,7 +29,6 @@ import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; import com.apptentive.android.sdk.module.metric.MetricModule; import com.apptentive.android.sdk.storage.MessageStore; -import com.apptentive.android.sdk.conversation.SessionData; import com.apptentive.android.sdk.util.Util; import org.json.JSONArray; @@ -108,9 +108,9 @@ public void handleMessage(android.os.Message msg) { /* Set SharePreference to indicate Message Center feature is desired. It will always be checked * during Apptentive initialization. */ - SessionData sessionData = ApptentiveInternal.getInstance().getSessionData(); - if (sessionData != null) { - sessionData.setMessageCenterFeatureUsed(true); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.setMessageCenterFeatureUsed(true); } } } @@ -159,7 +159,7 @@ protected void onPostExecute(Void v) { * @return true if messages were returned, else false. */ public synchronized boolean fetchAndStoreMessages(boolean isMessageCenterForeground, boolean showToast) { - if (ApptentiveInternal.getInstance().getSessionData().getConversationToken() == null) { + if (ApptentiveInternal.getInstance().getConversation().getConversationToken() == null) { ApptentiveLog.d("Can't fetch messages because the conversation has not yet been initialized."); return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java index 8e41b5480..c34179ecb 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java @@ -102,8 +102,8 @@ public void run() { while (appInForeground.get()) { MessageManager mgr = ApptentiveInternal.getInstance().getMessageManager(); - if (ApptentiveInternal.getInstance().getSessionData() == null){ - ApptentiveLog.i("SessionData is null."); + if (ApptentiveInternal.getInstance().getConversation() == null){ + ApptentiveLog.i("Conversation is null."); if (mgr != null) { mgr.pauseSending(MessageManager.SEND_PAUSE_REASON_SERVER); } @@ -111,7 +111,7 @@ public void run() { break; } - if (TextUtils.isEmpty(ApptentiveInternal.getInstance().getSessionData().getConversationToken())){ + if (TextUtils.isEmpty(ApptentiveInternal.getInstance().getConversation().getConversationToken())){ ApptentiveLog.i("No conversation token yet."); if (mgr != null) { mgr.pauseSending(MessageManager.SEND_PAUSE_REASON_SERVER); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java index 137c22531..6f107ec20 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java @@ -217,7 +217,7 @@ public void doDownload() { try { ApptentiveLog.v("ApptentiveAttachmentLoader doDownload: " + uri); // Conversation token is needed if the download url is a redirect link from an Apptentive endpoint - String conversationToken = ApptentiveInternal.getInstance().getSessionData().getConversationToken(); + String conversationToken = ApptentiveInternal.getInstance().getConversation().getConversationToken(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { mDrawableDownloaderTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uri, diskCacheFilePath, conversationToken); } else { diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationTest.java similarity index 70% rename from apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java rename to apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationTest.java index b1b6fc332..df8947d51 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/SessionDataTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationTest.java @@ -8,7 +8,7 @@ import android.text.TextUtils; -import com.apptentive.android.sdk.conversation.SessionData; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.util.Util; import org.junit.Before; @@ -33,7 +33,7 @@ @RunWith(PowerMockRunner.class) @PrepareForTest(TextUtils.class) -public class SessionDataTest { +public class ConversationTest { @Before public void setup() { @@ -49,7 +49,7 @@ public Boolean answer(InvocationOnMock invocation) throws Throwable { @Test public void testSerialization() { - SessionData expected = new SessionData(); + Conversation expected = new Conversation(); expected.setConversationId("jvnuveanesndndnadldbj"); expected.setConversationToken("watgsiovncsagjmcneiusdolnfcs"); expected.setPersonId("sijngmkmvewsnblkfmsd"); @@ -83,7 +83,7 @@ public void testSerialization() { bais = new ByteArrayInputStream(baos.toByteArray()); ois = new ObjectInputStream(bais); - SessionData result = (SessionData) ois.readObject(); + Conversation result = (Conversation) ois.readObject(); assertEquals(expected.getConversationId(), result.getConversationId()); assertEquals(expected.getConversationToken(), result.getConversationToken()); assertEquals(expected.getPersonId(), result.getPersonId()); @@ -120,212 +120,212 @@ public void onDataChanged() { } }; - SessionData sessionData = new SessionData(); - sessionData.setDataChangedListener(listener); + Conversation conversation = new Conversation(); + conversation.setDataChangedListener(listener); assertFalse(listenerFired); - sessionData.setConversationToken("foo"); + conversation.setConversationToken("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setConversationId("foo"); + conversation.setConversationId("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setPersonId("foo"); + conversation.setPersonId("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setPersonEmail("foo"); + conversation.setPersonEmail("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setPersonName("foo"); + conversation.setPersonName("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setLastSeenSdkVersion("foo"); + conversation.setLastSeenSdkVersion("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setMessageCenterFeatureUsed(true); + conversation.setMessageCenterFeatureUsed(true); assertTrue(listenerFired); listenerFired = false; - sessionData.setMessageCenterWhoCardPreviouslyDisplayed(true); + conversation.setMessageCenterWhoCardPreviouslyDisplayed(true); assertTrue(listenerFired); listenerFired = false; - sessionData.setMessageCenterPendingMessage("foo"); + conversation.setMessageCenterPendingMessage("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setMessageCenterPendingAttachments("foo"); + conversation.setMessageCenterPendingAttachments("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setInteractions("foo"); + conversation.setInteractions("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setTargets("foo"); + conversation.setTargets("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setInteractionExpiration(1000L); + conversation.setInteractionExpiration(1000L); assertTrue(listenerFired); listenerFired = false; - sessionData.getDevice().getCustomData().put("foo", "bar"); + conversation.getDevice().getCustomData().put("foo", "bar"); assertTrue(listenerFired); listenerFired = false; - sessionData.getDevice().getCustomData().remove("foo"); + conversation.getDevice().getCustomData().remove("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setDevice(new Device()); + conversation.setDevice(new Device()); assertTrue(listenerFired); listenerFired = false; - sessionData.getDevice().getCustomData().put("foo", "bar"); + conversation.getDevice().getCustomData().put("foo", "bar"); assertTrue(listenerFired); listenerFired = false; - sessionData.getDevice().getIntegrationConfig().setAmazonAwsSns(new IntegrationConfigItem()); + conversation.getDevice().getIntegrationConfig().setAmazonAwsSns(new IntegrationConfigItem()); assertTrue(listenerFired); listenerFired = false; - sessionData.getDevice().setIntegrationConfig(new IntegrationConfig()); + conversation.getDevice().setIntegrationConfig(new IntegrationConfig()); assertTrue(listenerFired); listenerFired = false; - sessionData.getDevice().getIntegrationConfig().setAmazonAwsSns(new IntegrationConfigItem()); + conversation.getDevice().getIntegrationConfig().setAmazonAwsSns(new IntegrationConfigItem()); assertTrue(listenerFired); listenerFired = false; - sessionData.getDevice().setOsApiLevel(5); + conversation.getDevice().setOsApiLevel(5); assertTrue(listenerFired); listenerFired = false; - sessionData.getDevice().setUuid("foo"); + conversation.getDevice().setUuid("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setLastSentDevice(new Device()); + conversation.setLastSentDevice(new Device()); assertTrue(listenerFired); listenerFired = false; - sessionData.getLastSentDevice().setUuid("foo"); + conversation.getLastSentDevice().setUuid("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setPerson(new Person()); + conversation.setPerson(new Person()); assertTrue(listenerFired); listenerFired = false; - sessionData.getPerson().setId("foo"); + conversation.getPerson().setId("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.getPerson().setEmail("foo"); + conversation.getPerson().setEmail("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.getPerson().setName("foo"); + conversation.getPerson().setName("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.getPerson().setFacebookId("foo"); + conversation.getPerson().setFacebookId("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.getPerson().setPhoneNumber("foo"); + conversation.getPerson().setPhoneNumber("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.getPerson().setStreet("foo"); + conversation.getPerson().setStreet("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.getPerson().setCity("foo"); + conversation.getPerson().setCity("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.getPerson().setZip("foo"); + conversation.getPerson().setZip("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.getPerson().setCountry("foo"); + conversation.getPerson().setCountry("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.getPerson().setBirthday("foo"); + conversation.getPerson().setBirthday("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.getPerson().setCustomData(new CustomData()); + conversation.getPerson().setCustomData(new CustomData()); assertTrue(listenerFired); listenerFired = false; - sessionData.getPerson().getCustomData().put("foo", "bar"); + conversation.getPerson().getCustomData().put("foo", "bar"); assertTrue(listenerFired); listenerFired = false; - sessionData.getPerson().getCustomData().remove("foo"); + conversation.getPerson().getCustomData().remove("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setLastSentPerson(new Person()); + conversation.setLastSentPerson(new Person()); assertTrue(listenerFired); listenerFired = false; - sessionData.getLastSentPerson().setId("foo"); + conversation.getLastSentPerson().setId("foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setSdk(new Sdk()); + conversation.setSdk(new Sdk()); assertTrue(listenerFired); listenerFired = false; - sessionData.setAppRelease(new AppRelease()); + conversation.setAppRelease(new AppRelease()); assertTrue(listenerFired); listenerFired = false; - sessionData.getVersionHistory().updateVersionHistory(100D, 1, "1"); + conversation.getVersionHistory().updateVersionHistory(100D, 1, "1"); assertTrue(listenerFired); listenerFired = false; - sessionData.setVersionHistory(new VersionHistory()); + conversation.setVersionHistory(new VersionHistory()); assertTrue(listenerFired); listenerFired = false; - sessionData.getVersionHistory().updateVersionHistory(100D, 1, "1"); + conversation.getVersionHistory().updateVersionHistory(100D, 1, "1"); assertTrue(listenerFired); listenerFired = false; - sessionData.getEventData().storeEventForCurrentAppVersion(100D, 10, "1.0", "foo"); + conversation.getEventData().storeEventForCurrentAppVersion(100D, 10, "1.0", "foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.getEventData().storeInteractionForCurrentAppVersion(100D, 10, "1.0", "foo"); + conversation.getEventData().storeInteractionForCurrentAppVersion(100D, 10, "1.0", "foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.setEventData(new EventData()); + conversation.setEventData(new EventData()); assertTrue(listenerFired); listenerFired = false; - sessionData.getEventData().storeEventForCurrentAppVersion(100D, 10, "1.0", "foo"); + conversation.getEventData().storeEventForCurrentAppVersion(100D, 10, "1.0", "foo"); assertTrue(listenerFired); listenerFired = false; - sessionData.getEventData().storeInteractionForCurrentAppVersion(100D, 10, "1.0", "foo"); + conversation.getEventData().storeInteractionForCurrentAppVersion(100D, 10, "1.0", "foo"); assertTrue(listenerFired); listenerFired = false; } From 66d5ecd9dc1e75f2a27dcb7eaff729140d8a37c8 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Feb 2017 00:20:08 -0800 Subject: [PATCH 103/465] Conversation manager progress --- .../sdk/conversation/ConversationManager.java | 170 ++++++++++++------ .../conversation/ConversationMetadata.java | 21 +++ 2 files changed, 134 insertions(+), 57 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index db35ffeb5..a313efd90 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -1,12 +1,17 @@ package com.apptentive.android.sdk.conversation; +import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.serialization.ObjectSerialization; +import com.apptentive.android.sdk.storage.DataChangedListener; +import com.apptentive.android.sdk.storage.FileSerializer; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; import java.io.File; import java.io.IOException; +import static com.apptentive.android.sdk.ApptentiveLogTag.CONVERSATION; + /** * Class responsible for managing conversations. *
@@ -16,7 +21,10 @@
  *   - Migrating legacy conversation data.
  * 
*/ -public class ConversationManager { +public class ConversationManager implements DataChangedListener { + + protected static final String CONVERSATION_METADATA_PATH = "conversation-v1.meta"; + /** * Private serial dispatch queue for background operations */ @@ -32,6 +40,8 @@ public class ConversationManager { */ private ConversationMetadata conversationMetadata; + private Conversation activeConversation; + public ConversationManager(DispatchQueue operationQueue, File storageDir) { if (operationQueue == null) { throw new IllegalArgumentException("Operation queue is null"); @@ -42,78 +52,124 @@ public ConversationManager(DispatchQueue operationQueue, File storageDir) { } /** - * Attempts to load an active conversation asynchronously. + * Attempts to load an active conversation. Returns false if active conversation is + * missing or cannnot be loaded */ - public void loadActiveConversation(Callback callback) { - loadConversation(new Filter() { - @Override - public boolean accept(ConversationMetadataItem metadata) { - return metadata.isActive(); + public boolean loadActiveConversation() { + try { + // resolving metadata + conversationMetadata = resolveMetadata(); + + // attempt to load existing conversation + activeConversation = loadActiveConversationGuarded(); + if (activeConversation != null) { + activeConversation.setDataChangedListener(this); + return true; } - }, callback); + + // no conversation - fetch one + fetchConversation(); + + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while loading active conversation"); + } + + return false; } - /** - * Helper method for async conversation loading with specified filter. - */ - private void loadConversation(final Filter filter, final Callback callback) { - operationQueue.dispatchAsync(new DispatchTask() { + private void fetchConversation() { + + } + + private Conversation loadActiveConversationGuarded() throws IOException { + // if the user was logged in previously - we should have an active conversation + ApptentiveLog.v(CONVERSATION, "Loading active conversation..."); + final ConversationMetadataItem activeItem = conversationMetadata.findItem(new ConversationMetadata.Filter() { @Override - protected void execute() { - Conversation conversation = null; - String errorMessage = null; - try { - conversation = loadConversationSync(filter); - } catch (Exception e) { - errorMessage = e.getMessage(); - } - - if (callback != null) { - callback.onFinishLoading(conversation, errorMessage); - } + public boolean accept(ConversationMetadataItem item) { + return item.isActive(); } }); - } - - /** - * Loads selected conversation on a background queue - */ - private Conversation loadConversationSync(Filter filter) throws IOException { - if (conversationMetadata == null) { - conversationMetadata = ObjectSerialization.deserialize(new File(""), ConversationMetadata.class); + if (activeItem != null) { + return loadConversation(activeItem); } - ConversationMetadataItem matchingItem = null; - for (ConversationMetadataItem item : conversationMetadata.getItems()) { - if (filter.accept(item)) { - matchingItem = item; + // if no user was logged in previously - we might have a default conversation + ApptentiveLog.v(CONVERSATION, "Loading default conversation..."); + final ConversationMetadataItem defaultItem = conversationMetadata.findItem(new ConversationMetadata.Filter() { + @Override + public boolean accept(ConversationMetadataItem item) { + return item.isDefault(); } + }); + if (defaultItem != null) { + return loadConversation(defaultItem); } - if (matchingItem == null) { - return null; - } - + // TODO: check for legacy conversations + ApptentiveLog.v(CONVERSATION, "Can't load conversation"); return null; } - /** - * Callback listener interface - */ - public interface Callback { - /** - * Called when conversation loading is finished. - * - * @param conversation - null if loading failed - * @param errorMessage - error description in case if loading failed (null is succeed) - */ - void onFinishLoading(Conversation conversation, String errorMessage); + private Conversation loadConversation(ConversationMetadataItem item) { + // TODO: use same serialization logic across the project + File file = new File(item.filename); + FileSerializer serializer = new FileSerializer(file); + return (Conversation) serializer.deserialize(); } - /** - * Interface which encapsulates conversation metadata filtering (visitor pattern) - */ - interface Filter { - boolean accept(ConversationMetadataItem item); + //region Metadata + + private ConversationMetadata resolveMetadata() { + try { + File metaFile = new File(storageDir, CONVERSATION_METADATA_PATH); + if (metaFile.exists()) { + ApptentiveLog.v(CONVERSATION, "Loading meta file: " + metaFile); + return ObjectSerialization.deserialize(metaFile, ConversationMetadata.class); + } else { + ApptentiveLog.v(CONVERSATION, "Meta file does not exist: " + metaFile); + } + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while loading conversation metadata"); + } + + return new ConversationMetadata(); } + + //endregion + + //region DataChangedListener + + @Override + public void onDataChanged() { + boolean scheduled = operationQueue.dispatchAsyncOnce(saveSessionTask, 100L); + if (scheduled) { + ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); + } else { + ApptentiveLog.d(CONVERSATION, "Conversation save already scheduled."); + } + } + + //endregion + + //region Dispatch Tasks + + private final DispatchTask saveSessionTask = new DispatchTask() { + @Override + protected void execute() { +// ApptentiveLog.d("Saving Conversation"); +// ApptentiveLog.v("EventData: %s", conversation.getEventData().toString()); +// if (fileSerializer != null) { +// fileSerializer.serialize(conversation); +// } + + throw new RuntimeException("Implement me"); + } + }; + + //endregion + + //region Listener + //endregion + } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java index c89e11128..d2e44b0da 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java @@ -46,6 +46,19 @@ public void writeExternal(DataOutput out) throws IOException { //endregion + //region Filtering + + public ConversationMetadataItem findItem(Filter filter) { + for (ConversationMetadataItem item : items) { + if (filter.accept(item)) { + return item; + } + } + return null; + } + + //endregion + //region Getters/Setters public List getItems() { @@ -53,4 +66,12 @@ public List getItems() { } //endregion + + //region Filter + + public interface Filter { + boolean accept(ConversationMetadataItem item); + } + + //endregion } From 329d6e92538ad8bb3a67375ab32aa607e125709f Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 21 Feb 2017 10:31:22 -0800 Subject: [PATCH 104/465] Start moving methods out of `InteractionManager` into `Conversation` --- .../sdk/conversation/Conversation.java | 35 +++++++++++++++++++ .../module/engagement/EngagementModule.java | 4 +-- .../interaction/InteractionManager.java | 25 +------------ 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index f8b8f586e..ad5344367 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -8,7 +8,12 @@ import android.text.TextUtils; +import com.apptentive.android.sdk.ApptentiveInternal; +import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; +import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; +import com.apptentive.android.sdk.module.engagement.interaction.model.Interactions; +import com.apptentive.android.sdk.module.engagement.interaction.model.Targets; import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.DataChangedListener; import com.apptentive.android.sdk.storage.Device; @@ -18,6 +23,8 @@ import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.VersionHistory; +import org.json.JSONException; + public class Conversation implements Saveable, DataChangedListener { private static final long serialVersionUID = 1L; @@ -56,6 +63,34 @@ public Conversation() { this.versionHistory = new VersionHistory(); } + //region Interactions + + /** + * Returns an Interaction for eventLabel if there is one that can be displayed. + */ + public Interaction getApplicableInteraction(String eventLabel) { + String targetsString = getTargets(); + if (targetsString != null) { + try { + Targets targets = new Targets(getTargets()); + String interactionId = targets.getApplicableInteraction(eventLabel); + if (interactionId != null) { + String interactionsString = getInteractions(); + if (interactionsString != null) { + Interactions interactions = new Interactions(interactionsString); + return interactions.getInteraction(interactionId); + } + } + } catch (JSONException e) { + ApptentiveLog.e(e, "Exception while getting applicable interaction: %s", eventLabel); + } + } + return null; + } + + //endregion + + //region Listeners private transient DataChangedListener listener; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java index 7577ec306..bf63bd852 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java @@ -69,7 +69,7 @@ public static synchronized boolean engage(Context context, String vendor, String } public static boolean doEngage(Context context, String eventLabel) { - Interaction interaction = ApptentiveInternal.getInstance().getConversation().getInteractionManager().getApplicableInteraction(eventLabel); + Interaction interaction = ApptentiveInternal.getInstance().getConversation().getApplicableInteraction(eventLabel); if (interaction != null) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { @@ -126,7 +126,7 @@ public static boolean canShowInteraction(String vendor, String interaction, Stri } private static boolean canShowInteraction(String eventLabel) { - Interaction interaction = ApptentiveInternal.getInstance().getConversation().getInteractionManager().getApplicableInteraction(eventLabel); + Interaction interaction = ApptentiveInternal.getInstance().getConversation().getApplicableInteraction(eventLabel); return interaction != null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java index 9b0bf0f2a..2e0d10aaa 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java @@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean; +/** Determining wheather interaction should be fetched and performing the fetch */ public class InteractionManager { private Boolean pollForInteractions; @@ -40,30 +41,6 @@ public interface InteractionUpdateListener { void onInteractionUpdated(boolean successful); } - public Interaction getApplicableInteraction(String eventLabel) { - - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation == null) { - return null; - } - String targetsString = conversation.getTargets(); - if (targetsString != null) { - try { - Targets targets = new Targets(conversation.getTargets()); - String interactionId = targets.getApplicableInteraction(eventLabel); - if (interactionId != null) { - String interactionsString = conversation.getInteractions(); - if (interactionsString != null) { - Interactions interactions = new Interactions(interactionsString); - return interactions.getInteraction(interactionId); - } - } - } catch (JSONException e) { - ApptentiveLog.e(""); - } - } - return null; - } // TODO: Refactor this class to dispatch to its own queue. public void fetchInteractions() { From 0f2f6a38e2497394112b3067f540dc31aa2d74b4 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 21 Feb 2017 13:20:07 -0800 Subject: [PATCH 105/465] Conversation manager progress --- .../android/sdk/ApptentiveInternal.java | 218 ++++-------------- .../apptentive/android/sdk/ApptentiveLog.java | 5 + .../sdk/conversation/Conversation.java | 72 ++++++ .../sdk/conversation/ConversationManager.java | 198 +++++++++++++--- .../interaction/InteractionManager.java | 60 ----- .../com/apptentive/android/sdk/util/Util.java | 14 ++ 6 files changed, 300 insertions(+), 267 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 717044925..5dbf94143 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -28,10 +28,10 @@ import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.conversation.Conversation; +import com.apptentive.android.sdk.conversation.ConversationManager; import com.apptentive.android.sdk.lifecycle.ApptentiveActivityLifecycleCallbacks; import com.apptentive.android.sdk.listeners.OnUserLogOutListener; import com.apptentive.android.sdk.model.Configuration; -import com.apptentive.android.sdk.model.ConversationTokenRequest; import com.apptentive.android.sdk.model.Event; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; @@ -41,15 +41,9 @@ import com.apptentive.android.sdk.module.rating.IRatingProvider; import com.apptentive.android.sdk.module.rating.impl.GooglePlayRatingProvider; import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; -import com.apptentive.android.sdk.network.HttpJsonRequest; -import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.AppReleaseManager; import com.apptentive.android.sdk.storage.ApptentiveTaskManager; -import com.apptentive.android.sdk.storage.DataChangedListener; -import com.apptentive.android.sdk.storage.Device; -import com.apptentive.android.sdk.storage.DeviceManager; -import com.apptentive.android.sdk.storage.FileSerializer; import com.apptentive.android.sdk.storage.PayloadSendWorker; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; @@ -59,12 +53,10 @@ import com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchQueueType; -import com.apptentive.android.sdk.util.threading.DispatchTask; import org.json.JSONException; import org.json.JSONObject; -import java.io.File; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Iterator; @@ -83,10 +75,9 @@ /** * This class contains only internal methods. These methods should not be access directly by the host app. */ -public class ApptentiveInternal implements DataChangedListener { +public class ApptentiveInternal { static AtomicBoolean isApptentiveInitialized = new AtomicBoolean(false); - private InteractionManager interactionManager; private final MessageManager messageManager; private final PayloadSendWorker payloadWorker; private final ApptentiveTaskManager taskManager; @@ -94,6 +85,7 @@ public class ApptentiveInternal implements DataChangedListener { ApptentiveActivityLifecycleCallbacks lifecycleCallbacks; private final ApptentiveComponentRegistry componentRegistry; private final ApptentiveHttpClient apptentiveHttpClient; + private final ConversationManager conversationManager; // These variables are initialized in Apptentive.register(), and so they are freely thereafter. If they are unexpectedly null, then if means the host app did not register Apptentive. private final Context appContext; @@ -107,8 +99,6 @@ public class ApptentiveInternal implements DataChangedListener { String personId; String androidId; String appPackageName; - Conversation conversation; - private FileSerializer fileSerializer; // private background serial dispatch queue for internal SDK tasks private final DispatchQueue backgroundQueue; @@ -122,8 +112,8 @@ public class ApptentiveInternal implements DataChangedListener { int statusBarColorDefault; String defaultAppDisplayName = "this app"; // booleans to prevent starting multiple fetching asyncTasks simultaneously - AtomicBoolean isConversationTokenFetchPending = new AtomicBoolean(false); - AtomicBoolean isConfigurationFetchPending = new AtomicBoolean(false); + + AtomicBoolean isConfigurationFetchPending = new AtomicBoolean(false); // TODO: remove me! IRatingProvider ratingProvider; Map ratingProviderArgs; @@ -167,6 +157,7 @@ private ApptentiveInternal(Context context, String apiKey) { backgroundQueue = DispatchQueue.createBackgroundQueue("Apptentive Serial Queue", DispatchQueueType.Serial); componentRegistry = new ApptentiveComponentRegistry(); apptentiveHttpClient = new ApptentiveHttpClient(apiKey, getEndpointBase(globalSharedPrefs)); + conversationManager = new ConversationManager(appContext, backgroundQueue, Util.getInternalDir(appContext, "conversations", true)); appRelease = AppReleaseManager.generateCurrentAppRelease(context, this); messageManager = new MessageManager(); @@ -207,25 +198,6 @@ public static ApptentiveInternal createInstance(Context context, String apptenti sApptentiveInternal = new ApptentiveInternal(context, apptentiveApiKey); isApptentiveInitialized.set(false); -/* -<<<<<<< HEAD -======= - sApptentiveInternal.appContext = context.getApplicationContext(); - sApptentiveInternal.globalSharedPrefs = sApptentiveInternal.appContext.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); - - MessageManager msgManager = new MessageManager(); - PayloadSendWorker payloadWorker = new PayloadSendWorker(); - ApptentiveTaskManager worker = new ApptentiveTaskManager(sApptentiveInternal.appContext); - ApptentiveComponentRegistry componentRegistry = new ApptentiveComponentRegistry(); - - sApptentiveInternal.messageManager = msgManager; - sApptentiveInternal.payloadWorker = payloadWorker; - sApptentiveInternal.taskManager = worker; - sApptentiveInternal.cachedExecutor = Executors.newCachedThreadPool(); - sApptentiveInternal.componentRegistry = componentRegistry; - sApptentiveInternal.apiKey = Util.trim(apptentiveApiKey); ->>>>>>> feature/fetch_interactions -*/ } } } @@ -412,7 +384,7 @@ public int getDefaultStatusBarColor() { } public Conversation getConversation() { - return conversation; + return conversationManager.getActiveConversation(); } public String getApptentiveApiKey() { @@ -435,6 +407,15 @@ public SharedPreferences getGlobalSharedPrefs() { return globalSharedPrefs; } + // FIXME: remove app release from this class + public AppRelease getAppRelease() { + return appRelease; + } + + public ApptentiveHttpClient getApptentiveHttpClient() { + return apptentiveHttpClient; + } + public void runOnWorkerThread(Runnable r) { cachedExecutor.execute(r); } @@ -450,7 +431,7 @@ public void onAppExit(final Context appContext) { public void onActivityStarted(Activity activity) { if (activity != null) { // Set current foreground activity reference whenever a new activity is started - currentTaskStackTopActivity = new WeakReference(activity); + currentTaskStackTopActivity = new WeakReference<>(activity); messageManager.setCurrentForegroundActivity(activity); } } @@ -458,7 +439,7 @@ public void onActivityStarted(Activity activity) { public void onActivityResumed(Activity activity) { if (activity != null) { // Set current foreground activity reference whenever a new activity is started - currentTaskStackTopActivity = new WeakReference(activity); + currentTaskStackTopActivity = new WeakReference<>(activity); messageManager.setCurrentForegroundActivity(activity); } @@ -544,26 +525,9 @@ public boolean init() { * 3. An unreadMessageCountListener() is set up */ - File internalStorage = appContext.getFilesDir(); - File sessionDataFile = new File(internalStorage, "apptentive/Conversation.ser"); - sessionDataFile.getParentFile().mkdirs(); - fileSerializer = new FileSerializer(sessionDataFile); - conversation = (Conversation) fileSerializer.deserialize(); - if (conversation != null) { - conversation.setDataChangedListener(this); - ApptentiveLog.d("Restored existing Conversation"); - ApptentiveLog.v("Restored EventData: %s", conversation.getEventData()); - // FIXME: Move this to wherever the sessions first comes online? - boolean featureEverUsed = conversation != null && conversation.isMessageCenterFeatureUsed(); - if (featureEverUsed) { - messageManager.init(); - } - conversation.setInteractionManager(new InteractionManager(conversation)); - // TODO: Make a callback like conversationBecameCurrent(), and call this there - scheduleInteractionFetch(); - } else { - fetchConversationToken(); - } + long start = System.currentTimeMillis(); + boolean conversationLoaded = conversationManager.loadActiveConversation(); + ApptentiveLog.i(CONVERSATION, "Active conversation is%s loaded. Took %d ms", conversationLoaded ? " not" : "", System.currentTimeMillis() - start); apptentiveToolbarTheme = appContext.getResources().newTheme(); @@ -640,6 +604,7 @@ public boolean init() { } private void checkSendVersionChanges() { + final Conversation conversation = getConversation(); if (conversation == null) { ApptentiveLog.e("Can't check session data changes: session data is not initialized"); return; @@ -677,10 +642,7 @@ private void checkSendVersionChanges() { if (appReleaseChanged) { ApptentiveLog.i("Version changed: Name: %s => %s, Code: %d => %d", previousVersionName, currentVersionName, previousVersionCode, currentVersionCode); - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getVersionHistory().updateVersionHistory(Util.currentTimeSeconds(), currentVersionCode, currentVersionName); - } + conversation.getVersionHistory().updateVersionHistory(Util.currentTimeSeconds(), currentVersionCode, currentVersionName); } Sdk sdk = SdkManager.generateCurrentSdk(); @@ -709,69 +671,6 @@ private void invalidateCaches() { config.save(); } - private void fetchConversationToken() { - if (isConversationTokenFetchPending.compareAndSet(false, true)) { - ApptentiveLog.i(CONVERSATION, "Fetching Configuration token task started."); - dispatchDebugEvent(EVT_FETCH_CONVERSATION_TOKEN); - - // Try to fetch a new one from the server. - ConversationTokenRequest request = new ConversationTokenRequest(); - - // Send the Device and Sdk now, so they are available on the server from the start. - final Device device = DeviceManager.generateNewDevice(appContext); - final Sdk sdk = SdkManager.generateCurrentSdk(); - - request.setDevice(DeviceManager.getDiffPayload(null, device)); - request.setSdk(SdkManager.getPayload(sdk)); - request.setAppRelease(AppReleaseManager.getPayload(appRelease)); - - apptentiveHttpClient.getConversationToken(request, new HttpRequest.Listener() { - @Override - public void onFinish(HttpJsonRequest request) { - try { - JSONObject root = request.getResponseObject(); - String conversationToken = root.getString("token"); - ApptentiveLog.d(CONVERSATION, "ConversationToken: " + conversationToken); - String conversationId = root.getString("id"); - ApptentiveLog.d(CONVERSATION, "New Conversation id: %s", conversationId); - - conversation = new Conversation(); - conversation.setDataChangedListener(ApptentiveInternal.this); - if (conversationToken != null && !conversationToken.equals("")) { - conversation.setConversationToken(conversationToken); - conversation.setConversationId(conversationId); - conversation.setDevice(device); - conversation.setSdk(sdk); - conversation.setAppRelease(appRelease); - conversation.setInteractionManager(new InteractionManager(conversation)); - } - String personId = root.getString("person_id"); - ApptentiveLog.d(CONVERSATION, "PersonId: " + personId); - conversation.setPersonId(personId); - - // TODO: Make a callback like sessionBecameCurrent(), and call this there - scheduleInteractionFetch(); - } catch (Exception e) { - ApptentiveLog.e(e, "Exception while handling conversation token"); - } finally { - isConversationTokenFetchPending.set(false); - } - } - - @Override - public void onCancel(HttpJsonRequest request) { - isConversationTokenFetchPending.set(false); - } - - @Override - public void onFail(HttpJsonRequest request, String reason) { - ApptentiveLog.w("Failed to fetch conversation token: %s", reason); - isConversationTokenFetchPending.set(false); - } - }); - } - } - /** * Fetches the global app configuration from the server and stores the keys into our SharedPreferences. */ @@ -813,14 +712,14 @@ public Map getRatingProviderArgs() { public void putRatingProviderArg(String key, String value) { if (ratingProviderArgs == null) { - ratingProviderArgs = new HashMap(); + ratingProviderArgs = new HashMap<>(); } ratingProviderArgs.put(key, value); } public void setOnSurveyFinishedListener(OnSurveyFinishedListener onSurveyFinishedListener) { if (onSurveyFinishedListener != null) { - this.onSurveyFinishedListener = new WeakReference(onSurveyFinishedListener); + this.onSurveyFinishedListener = new WeakReference<>(onSurveyFinishedListener); } else { this.onSurveyFinishedListener = null; } @@ -1062,43 +961,24 @@ public static boolean checkRegistered() { // Multi-tenancy work - private synchronized void scheduleSessionDataSave() { - boolean scheduled = backgroundQueue.dispatchAsyncOnce(saveSessionTask, 100L); - if (scheduled) { - ApptentiveLog.d("Scheduling Conversation save."); - } else { - ApptentiveLog.d("Conversation save already scheduled."); - } - } - private synchronized void scheduleInteractionFetch() { - if (conversation != null) { - InteractionManager interactionManager = conversation.getInteractionManager(); - if (interactionManager != null) { - if (interactionManager.isPollForInteractions()) { - boolean cacheExpired = conversation.getInteractionExpiration() > Util.currentTimeSeconds(); - boolean force = appRelease != null && appRelease.isDebug(); - if (cacheExpired || force) { - backgroundQueue.dispatchAsyncOnce(fetchInteractionsTask); - } - } else { - ApptentiveLog.v("Interaction polling is disabled."); - } - } - } +// if (conversation != null) { +// InteractionManager interactionManager = conversation.getInteractionManager(); +// if (interactionManager != null) { +// if (interactionManager.isPollForInteractions()) { +// boolean cacheExpired = conversation.getInteractionExpiration() > Util.currentTimeSeconds(); +// boolean force = appRelease != null && appRelease.isDebug(); +// if (cacheExpired || force) { +// backgroundQueue.dispatchAsyncOnce(fetchInteractionsTask); +// } +// } else { +// ApptentiveLog.v("Interaction polling is disabled."); +// } +// } +// } + throw new RuntimeException("Remove me"); } - private final DispatchTask saveSessionTask = new DispatchTask() { - @Override - protected void execute() { - ApptentiveLog.d("Saving Conversation"); - ApptentiveLog.v("EventData: %s", conversation.getEventData().toString()); - if (fileSerializer != null) { - fileSerializer.serialize(conversation); - } - } - }; - //region Helpers private String getEndpointBase(SharedPreferences prefs) { @@ -1112,24 +992,6 @@ private String getEndpointBase(SharedPreferences prefs) { //endregion - private final DispatchTask fetchInteractionsTask = new DispatchTask() { - @Override - protected void execute() { - Conversation conversation = getConversation(); - if (conversation != null) { - conversation.getInteractionManager().fetchInteractions(); - } - } - }; - - //region Listeners - - @Override - public void onDataChanged() { - scheduleSessionDataSave(); - } - //endregion - /** * Ends current user session */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java index ab3d5c349..fa1450467 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java @@ -101,6 +101,11 @@ public static void w(String message, Throwable throwable, Object... args){ doLog(Level.WARN, throwable, message, args); } + public static void e(ApptentiveLogTag tag, String message, Object... args){ + if (tag.enabled) { + doLog(Level.ERROR, null, message, args); + } + } public static void e(String message, Object... args){ doLog(Level.ERROR, null, message, args); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index ad5344367..dddfce1ab 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -6,12 +6,16 @@ package com.apptentive.android.sdk.conversation; +import android.content.SharedPreferences; import android.text.TextUtils; import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.comm.ApptentiveClient; +import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; +import com.apptentive.android.sdk.module.engagement.interaction.model.InteractionManifest; import com.apptentive.android.sdk.module.engagement.interaction.model.Interactions; import com.apptentive.android.sdk.module.engagement.interaction.model.Targets; import com.apptentive.android.sdk.storage.AppRelease; @@ -22,9 +26,14 @@ import com.apptentive.android.sdk.storage.Saveable; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.VersionHistory; +import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.util.Util; +import com.apptentive.android.sdk.util.threading.DispatchTask; import org.json.JSONException; +import static com.apptentive.android.sdk.ApptentiveLogTag.*; + public class Conversation implements Saveable, DataChangedListener { private static final long serialVersionUID = 1L; @@ -88,6 +97,62 @@ public Interaction getApplicableInteraction(String eventLabel) { return null; } + void fetchInteractions() { + ApptentiveLog.v(CONVERSATION, "Fetching Interactions"); + ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); + + // TODO: Move this to global config + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); + boolean updateSuccessful = true; + + // We weren't able to connect to the internet. + if (response.isException()) { + prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, false).apply(); + updateSuccessful = false; + } + // We got a server error. + else if (!response.isSuccessful()) { + prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, true).apply(); + updateSuccessful = false; + } + + if (updateSuccessful) { + String interactionsPayloadString = response.getContent(); + + // Store new integration cache expiration. + String cacheControl = response.getHeaders().get("Cache-Control"); + Integer cacheSeconds = Util.parseCacheControlHeader(cacheControl); + if (cacheSeconds == null) { + cacheSeconds = Constants.CONFIG_DEFAULT_INTERACTION_CACHE_EXPIRATION_DURATION_SECONDS; + } + setInteractionExpiration(Util.currentTimeSeconds() + cacheSeconds); + try { + InteractionManifest payload = new InteractionManifest(interactionsPayloadString); + Interactions interactions = payload.getInteractions(); + Targets targets = payload.getTargets(); + if (interactions != null && targets != null) { + setTargets(targets.toString()); + setInteractions(interactions.toString()); + } else { + ApptentiveLog.e(CONVERSATION, "Unable to save interactionManifest."); + } + } catch (JSONException e) { + ApptentiveLog.e(e, "Invalid InteractionManifest received."); + } + } + ApptentiveLog.v(CONVERSATION, "Fetching new Interactions asyncTask finished. Successful? %b", updateSuccessful); + + // Update pending state on UI thread after finishing the task + ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); + } + + private final DispatchTask fetchInteractionsTask = new DispatchTask() { + @Override + protected void execute() { + fetchInteractions(); + } + }; + //endregion @@ -345,5 +410,12 @@ public void setInteractionManager(InteractionManager interactionManager) { this.interactionManager = interactionManager; } + /** + * Returns a filename unique to the convesation for persistant storage + */ + String getFilename() { + return String.format("conversation-%s.bin", conversationId); + } + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index a313efd90..dd6752403 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -1,16 +1,34 @@ package com.apptentive.android.sdk.conversation; +import android.content.Context; + +import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.model.ConversationTokenRequest; +import com.apptentive.android.sdk.network.HttpJsonRequest; +import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.serialization.ObjectSerialization; +import com.apptentive.android.sdk.storage.AppRelease; +import com.apptentive.android.sdk.storage.AppReleaseManager; import com.apptentive.android.sdk.storage.DataChangedListener; +import com.apptentive.android.sdk.storage.Device; +import com.apptentive.android.sdk.storage.DeviceManager; import com.apptentive.android.sdk.storage.FileSerializer; +import com.apptentive.android.sdk.storage.Sdk; +import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; +import org.json.JSONObject; + import java.io.File; import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicBoolean; -import static com.apptentive.android.sdk.ApptentiveLogTag.CONVERSATION; +import static com.apptentive.android.sdk.ApptentiveLogTag.*; +import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; +import static com.apptentive.android.sdk.debug.TesterEvent.*; /** * Class responsible for managing conversations. @@ -25,6 +43,8 @@ public class ConversationManager implements DataChangedListener { protected static final String CONVERSATION_METADATA_PATH = "conversation-v1.meta"; + private final WeakReference contextRef; + /** * Private serial dispatch queue for background operations */ @@ -42,15 +62,24 @@ public class ConversationManager implements DataChangedListener { private Conversation activeConversation; - public ConversationManager(DispatchQueue operationQueue, File storageDir) { + private final AtomicBoolean isConversationTokenFetchPending = new AtomicBoolean(false); + + public ConversationManager(Context context, DispatchQueue operationQueue, File storageDir) { + if (context == null) { + throw new IllegalArgumentException("Context is null"); + } + if (operationQueue == null) { throw new IllegalArgumentException("Operation queue is null"); } + this.contextRef = new WeakReference<>(context.getApplicationContext()); this.operationQueue = operationQueue; this.storageDir = storageDir; } + //region Conversations + /** * Attempts to load an active conversation. Returns false if active conversation is * missing or cannnot be loaded @@ -64,11 +93,17 @@ public boolean loadActiveConversation() { activeConversation = loadActiveConversationGuarded(); if (activeConversation != null) { activeConversation.setDataChangedListener(this); + + boolean featureEverUsed = activeConversation.isMessageCenterFeatureUsed(); + if (featureEverUsed) { + // messageManager.init(); // // FIXME: 2/21/17 init message messenger + } + return true; } // no conversation - fetch one - fetchConversation(); + fetchConversationToken(); } catch (Exception e) { ApptentiveLog.e(e, "Exception while loading active conversation"); @@ -77,10 +112,6 @@ public boolean loadActiveConversation() { return false; } - private void fetchConversation() { - - } - private Conversation loadActiveConversationGuarded() throws IOException { // if the user was logged in previously - we should have an active conversation ApptentiveLog.v(CONVERSATION, "Loading active conversation..."); @@ -118,6 +149,129 @@ private Conversation loadConversation(ConversationMetadataItem item) { return (Conversation) serializer.deserialize(); } + //endregion + + //region Conversation fetching + + private void fetchConversationToken() { + if (isConversationTokenFetchPending.compareAndSet(false, true)) { + ApptentiveLog.i(CONVERSATION, "Fetching Configuration token task started."); + dispatchDebugEvent(EVT_FETCH_CONVERSATION_TOKEN); + + final Context context = getContext(); + if (context == null) { + ApptentiveLog.w(CONVERSATION, "Unable to fetch convesation token: context reference is lost"); + isConversationTokenFetchPending.set(false); + return; + } + + // Try to fetch a new one from the server. + ConversationTokenRequest request = new ConversationTokenRequest(); + + // Send the Device and Sdk now, so they are available on the server from the start. + final Device device = DeviceManager.generateNewDevice(context); + final Sdk sdk = SdkManager.generateCurrentSdk(); + final AppRelease appRelease = ApptentiveInternal.getInstance().getAppRelease(); + + request.setDevice(DeviceManager.getDiffPayload(null, device)); + request.setSdk(SdkManager.getPayload(sdk)); + request.setAppRelease(AppReleaseManager.getPayload(appRelease)); + + ApptentiveInternal.getInstance().getApptentiveHttpClient() + .getConversationToken(request, new HttpRequest.Listener() { + @Override + public void onFinish(HttpJsonRequest request) { + try { + JSONObject root = request.getResponseObject(); + String conversationToken = root.getString("token"); + ApptentiveLog.d(CONVERSATION, "ConversationToken: " + conversationToken); + String conversationId = root.getString("id"); + ApptentiveLog.d(CONVERSATION, "New Conversation id: %s", conversationId); + + // create new conversation + Conversation conversation = new Conversation(); + if (conversationToken != null && !conversationToken.equals("")) { // FIXME: handle "unhappy" path + conversation.setConversationToken(conversationToken); + conversation.setConversationId(conversationId); + conversation.setDevice(device); + conversation.setSdk(sdk); + conversation.setAppRelease(appRelease); + } + String personId = root.getString("person_id"); + ApptentiveLog.d(CONVERSATION, "PersonId: " + personId); + conversation.setPersonId(personId); + conversation.setDataChangedListener(ConversationManager.this); + + // write conversation to the dist + saveConversation(conversation); + + // update active conversation + setActiveConversation(conversation); + + // fetch interactions + conversation.fetchInteractions(); + + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while handling conversation token"); + } finally { + isConversationTokenFetchPending.set(false); + } + } + + @Override + public void onCancel(HttpJsonRequest request) { + isConversationTokenFetchPending.set(false); + } + + @Override + public void onFail(HttpJsonRequest request, String reason) { + ApptentiveLog.w("Failed to fetch conversation token: %s", reason); + isConversationTokenFetchPending.set(false); + } + }); + } + } + + private synchronized void setActiveConversation(Conversation conversation) { + activeConversation = conversation; + throw new RuntimeException("Implement me: update metadata"); + } + + //endregion + + //region Serialization + + private void scheduleConversationSave() { + boolean scheduled = operationQueue.dispatchAsyncOnce(saveSessionTask, 100L); + if (scheduled) { + ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); + } else { + ApptentiveLog.d(CONVERSATION, "Conversation save already scheduled."); + } + } + + private final DispatchTask saveSessionTask = new DispatchTask() { + @Override + protected void execute() { + if (activeConversation != null) { + saveConversation(activeConversation); + } else { + ApptentiveLog.w(CONVERSATION, "Can't save conversation: active conversation is missing"); + } + } + }; + + private void saveConversation(Conversation conversation) { + ApptentiveLog.d(CONVERSATION, "Saving Conversation"); + ApptentiveLog.v(CONVERSATION, "EventData: %s", conversation.getEventData().toString()); // TODO: remove + + File conversationFile = new File(storageDir, conversation.getFilename()); + FileSerializer serializer = new FileSerializer(conversationFile); + serializer.serialize(conversation); + } + + //endregion + //region Metadata private ConversationMetadata resolveMetadata() { @@ -142,34 +296,20 @@ private ConversationMetadata resolveMetadata() { @Override public void onDataChanged() { - boolean scheduled = operationQueue.dispatchAsyncOnce(saveSessionTask, 100L); - if (scheduled) { - ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); - } else { - ApptentiveLog.d(CONVERSATION, "Conversation save already scheduled."); - } + scheduleConversationSave(); } //endregion - //region Dispatch Tasks + //region Getters/Setters - private final DispatchTask saveSessionTask = new DispatchTask() { - @Override - protected void execute() { -// ApptentiveLog.d("Saving Conversation"); -// ApptentiveLog.v("EventData: %s", conversation.getEventData().toString()); -// if (fileSerializer != null) { -// fileSerializer.serialize(conversation); -// } - - throw new RuntimeException("Implement me"); - } - }; + public Conversation getActiveConversation() { + return activeConversation; + } - //endregion + private Context getContext() { + return contextRef.get(); + } - //region Listener //endregion - } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java index 2e0d10aaa..d050f1128 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java @@ -10,15 +10,11 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.comm.ApptentiveClient; -import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.module.engagement.interaction.model.InteractionManifest; import com.apptentive.android.sdk.module.engagement.interaction.model.Interactions; -import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.Targets; import com.apptentive.android.sdk.util.Constants; -import com.apptentive.android.sdk.util.Util; import org.json.JSONException; @@ -41,62 +37,6 @@ public interface InteractionUpdateListener { void onInteractionUpdated(boolean successful); } - - // TODO: Refactor this class to dispatch to its own queue. - public void fetchInteractions() { - ApptentiveLog.v("Fetching Interactions"); - if (conversation != null) { - InteractionManager interactionManager = conversation.getInteractionManager(); - if (interactionManager != null) { - ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); - - // TODO: Move this to global config - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - boolean updateSuccessful = true; - - // We weren't able to connect to the internet. - if (response.isException()) { - prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, false).apply(); - updateSuccessful = false; - } - // We got a server error. - else if (!response.isSuccessful()) { - prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, true).apply(); - updateSuccessful = false; - } - - if (updateSuccessful) { - String interactionsPayloadString = response.getContent(); - - // Store new integration cache expiration. - String cacheControl = response.getHeaders().get("Cache-Control"); - Integer cacheSeconds = Util.parseCacheControlHeader(cacheControl); - if (cacheSeconds == null) { - cacheSeconds = Constants.CONFIG_DEFAULT_INTERACTION_CACHE_EXPIRATION_DURATION_SECONDS; - } - conversation.setInteractionExpiration(Util.currentTimeSeconds() + cacheSeconds); - try { - InteractionManifest payload = new InteractionManifest(interactionsPayloadString); - Interactions interactions = payload.getInteractions(); - Targets targets = payload.getTargets(); - if (interactions != null && targets != null) { - conversation.setTargets(targets.toString()); - conversation.setInteractions(interactions.toString()); - } else { - ApptentiveLog.e("Unable to save interactionManifest."); - } - } catch (JSONException e) { - ApptentiveLog.w("Invalid InteractionManifest received."); - } } - ApptentiveLog.d("Fetching new Interactions asyncTask finished. Successful? %b", updateSuccessful); - // Update pending state on UI thread after finishing the task - ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); - } - } else { - ApptentiveLog.v("Cancelled Interaction fetch due to null Conversation."); - } - } - /** * Made public for testing. There is no other reason to use this method directly. */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java index 82eea2ca6..147009dba 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java @@ -846,4 +846,18 @@ public static String getAndroidId(Context context) { } return Settings.Secure.getString(context.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID); } + /** + * Returns and internal storage directory + */ + public static File getInternalDir(Context context, String path, boolean createIfNecessary) { + File filesDir = context.getFilesDir(); + File internalDir = new File(filesDir, path); + if (!internalDir.exists() && createIfNecessary) { + boolean succeed = internalDir.mkdirs(); + if (!succeed) { + ApptentiveLog.w("Unable to create internal directory: %s", internalDir); + } + } + return internalDir; + } } From 9934aa1b916f15d0abcf0c7fe482d582ab5c843c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Feb 2017 14:24:26 -0800 Subject: [PATCH 106/465] Fixed saving conversation metadata + some refactorings --- .../android/sdk/ApptentiveInternal.java | 8 ++++ .../sdk/conversation/ConversationManager.java | 42 ++++++++++++------- .../conversation/ConversationMetadata.java | 27 ++++++++++++ .../ConversationMetadataItem.java | 24 ++++++++--- .../android/sdk/util/StringUtils.java | 8 ++++ 5 files changed, 89 insertions(+), 20 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 5dbf94143..29f77452b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -527,6 +527,14 @@ public boolean init() { long start = System.currentTimeMillis(); boolean conversationLoaded = conversationManager.loadActiveConversation(); + if (conversationLoaded) { + Conversation activeConversation = conversationManager.getActiveConversation(); + boolean featureEverUsed = activeConversation.isMessageCenterFeatureUsed(); + if (featureEverUsed) { + messageManager.init(); + } + } + ApptentiveLog.i(CONVERSATION, "Active conversation is%s loaded. Took %d ms", conversationLoaded ? " not" : "", System.currentTimeMillis() - start); apptentiveToolbarTheme = appContext.getResources().newTheme(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index dd6752403..53966d36e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -16,6 +16,7 @@ import com.apptentive.android.sdk.storage.FileSerializer; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; +import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -93,12 +94,6 @@ public boolean loadActiveConversation() { activeConversation = loadActiveConversationGuarded(); if (activeConversation != null) { activeConversation.setDataChangedListener(this); - - boolean featureEverUsed = activeConversation.isMessageCenterFeatureUsed(); - if (featureEverUsed) { - // messageManager.init(); // // FIXME: 2/21/17 init message messenger - } - return true; } @@ -188,15 +183,24 @@ public void onFinish(HttpJsonRequest request) { String conversationId = root.getString("id"); ApptentiveLog.d(CONVERSATION, "New Conversation id: %s", conversationId); + if (StringUtils.isNullOrEmpty(conversationToken)) { + ApptentiveLog.e(CONVERSATION, "Can't fetch convesation: missing 'token'"); + return; + } + + if (StringUtils.isNullOrEmpty(conversationId)) { + ApptentiveLog.e(CONVERSATION, "Can't fetch convesation: missing 'id'"); + return; + } + // create new conversation Conversation conversation = new Conversation(); - if (conversationToken != null && !conversationToken.equals("")) { // FIXME: handle "unhappy" path - conversation.setConversationToken(conversationToken); - conversation.setConversationId(conversationId); - conversation.setDevice(device); - conversation.setSdk(sdk); - conversation.setAppRelease(appRelease); - } + conversation.setConversationToken(conversationToken); + conversation.setConversationId(conversationId); + conversation.setDevice(device); + conversation.setSdk(sdk); + conversation.setAppRelease(appRelease); + String personId = root.getString("person_id"); ApptentiveLog.d(CONVERSATION, "PersonId: " + personId); conversation.setPersonId(personId); @@ -234,7 +238,8 @@ public void onFail(HttpJsonRequest request, String reason) { private synchronized void setActiveConversation(Conversation conversation) { activeConversation = conversation; - throw new RuntimeException("Implement me: update metadata"); + conversationMetadata.setActiveConversation(conversation); + saveMetadata(); } //endregion @@ -290,6 +295,15 @@ private ConversationMetadata resolveMetadata() { return new ConversationMetadata(); } + private void saveMetadata() { + try { + File metaFile = new File(storageDir, CONVERSATION_METADATA_PATH); + ObjectSerialization.serialize(metaFile, conversationMetadata); + } catch (Exception e) { + ApptentiveLog.e(CONVERSATION, "Exception while saving metadata"); + } + } + //endregion //region DataChangedListener diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java index d2e44b0da..efba162a1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java @@ -1,6 +1,7 @@ package com.apptentive.android.sdk.conversation; import com.apptentive.android.sdk.serialization.SerializableObject; +import com.apptentive.android.sdk.util.StringUtils; import java.io.DataInput; import java.io.DataOutput; @@ -8,6 +9,8 @@ import java.util.ArrayList; import java.util.List; +import static com.apptentive.android.sdk.conversation.ConversationMetadataItem.*; + /** * Class which represents all conversation entries stored on the disk */ @@ -46,6 +49,30 @@ public void writeExternal(DataOutput out) throws IOException { //endregion + //region Conversatiosn + + // TODO: replace it with notifications so that the active conversation can send out events and clean itself up. + public void setActiveConversation(final Conversation conversation) + { + // clear 'active' state + boolean found = false; + for (ConversationMetadataItem item : items) { + if (StringUtils.equal(conversation.getConversationId(), item.conversationId)) { + found = true; + item.state = CONVERSATION_STATE_ACTIVE; + } else if (item.state == CONVERSATION_STATE_ACTIVE) { + item.state = CONVERSATION_STATE_INACTIVE; + } + } + + // add a new item if it was not found + if (!found) { + items.add(new ConversationMetadataItem(conversation.getConversationId(), conversation.getFilename())); + } + } + + //endregion + //region Filtering public ConversationMetadataItem findItem(Filter filter) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index 09796b2a0..2e8b38532 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -1,6 +1,7 @@ package com.apptentive.android.sdk.conversation; import com.apptentive.android.sdk.serialization.SerializableObject; +import com.apptentive.android.sdk.util.StringUtils; import java.io.DataInput; import java.io.DataOutput; @@ -31,22 +32,33 @@ class ConversationMetadataItem implements SerializableObject { public static byte CONVERSATION_STATE_INACTIVE = 3; byte state = CONVERSATION_STATE_UNDEFINED; - String userId; + String conversationId; String filename; - String keyId; + + public ConversationMetadataItem(String conversationId, String filename) + { + if (StringUtils.isNullOrEmpty(conversationId)) { + throw new IllegalArgumentException("Conversation id is null or empty"); + } + + if (StringUtils.isNullOrEmpty(filename)) { + throw new IllegalArgumentException("Filename is null or empty"); + } + + this.conversationId = conversationId; + this.filename = filename; + } public ConversationMetadataItem(DataInput in) throws IOException { - userId = in.readUTF(); + conversationId = in.readUTF(); filename = in.readUTF(); - keyId = in.readUTF(); state = in.readByte(); } @Override public void writeExternal(DataOutput out) throws IOException { - out.writeUTF(userId); + out.writeUTF(conversationId); out.writeUTF(filename); - out.writeUTF(keyId); out.writeByte(state); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java index b0aba87f9..73f6c1ede 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java @@ -127,4 +127,12 @@ public static String createQueryString(Map params) { // FIXME: u } return result.toString(); } + + public static boolean isNullOrEmpty(String str) { + return str == null || str.length() == 0; + } + + public static boolean equal(String str1, String str2) { + return str1 != null && str2 != null && str1.equals(str2); + } } From 099948a288ceca9bafbc5cee036c20e8d19b7909 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Feb 2017 14:42:42 -0800 Subject: [PATCH 107/465] Fixed conversation serialization --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 4 ++-- .../com/apptentive/android/sdk/conversation/Conversation.java | 2 +- .../android/sdk/conversation/ConversationManager.java | 4 ++-- .../android/sdk/serialization/ObjectSerialization.java | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 29f77452b..b6019b9d0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -527,6 +527,8 @@ public boolean init() { long start = System.currentTimeMillis(); boolean conversationLoaded = conversationManager.loadActiveConversation(); + ApptentiveLog.i(CONVERSATION, "Active conversation is%s loaded. Took %d ms", conversationLoaded ? " not" : "", System.currentTimeMillis() - start); + if (conversationLoaded) { Conversation activeConversation = conversationManager.getActiveConversation(); boolean featureEverUsed = activeConversation.isMessageCenterFeatureUsed(); @@ -535,8 +537,6 @@ public boolean init() { } } - ApptentiveLog.i(CONVERSATION, "Active conversation is%s loaded. Took %d ms", conversationLoaded ? " not" : "", System.currentTimeMillis() - start); - apptentiveToolbarTheme = appContext.getResources().newTheme(); boolean apptentiveDebug = false; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index dddfce1ab..fffb794f0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -146,7 +146,7 @@ else if (!response.isSuccessful()) { ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); } - private final DispatchTask fetchInteractionsTask = new DispatchTask() { + private final transient DispatchTask fetchInteractionsTask = new DispatchTask() { @Override protected void execute() { fetchInteractions(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 53966d36e..30c1c2062 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -184,12 +184,12 @@ public void onFinish(HttpJsonRequest request) { ApptentiveLog.d(CONVERSATION, "New Conversation id: %s", conversationId); if (StringUtils.isNullOrEmpty(conversationToken)) { - ApptentiveLog.e(CONVERSATION, "Can't fetch convesation: missing 'token'"); + ApptentiveLog.e(CONVERSATION, "Can't fetch conversation: missing 'token'"); return; } if (StringUtils.isNullOrEmpty(conversationId)) { - ApptentiveLog.e(CONVERSATION, "Can't fetch convesation: missing 'id'"); + ApptentiveLog.e(CONVERSATION, "Can't fetch conversation: missing 'id'"); return; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/serialization/ObjectSerialization.java b/apptentive/src/main/java/com/apptentive/android/sdk/serialization/ObjectSerialization.java index d5aa68b67..4da135884 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/serialization/ObjectSerialization.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/serialization/ObjectSerialization.java @@ -40,6 +40,7 @@ public static T deserialize(File file, Class c try { Constructor constructor = cls.getDeclaredConstructor(DataInput.class); + constructor.setAccessible(true); return constructor.newInstance(in); } catch (Exception e) { throw new IOException("Unable to instantiate class: " + cls, e); From 68b4baaa9a111c70ae7bffd60ba12e0dc14879cc Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Feb 2017 14:54:58 -0800 Subject: [PATCH 108/465] Fixed TestClient compilation --- .../main/java/com/apptentive/android/sdk/ApptentiveLogTag.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java index d9d5b0339..ad188f8c9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java @@ -2,7 +2,8 @@ public enum ApptentiveLogTag { NETWORK, - CONVERSATION; + CONVERSATION, + TESTER_COMMANDS; public boolean enabled = true; } From a2f4eb567687c4e270831fcae89acd57380dd226 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Feb 2017 16:43:27 -0800 Subject: [PATCH 109/465] Fixed saving conversations + added tester events --- .../android/sdk/conversation/Conversation.java | 7 ++++++- .../sdk/conversation/ConversationManager.java | 17 +++++++++++++---- .../sdk/conversation/ConversationMetadata.java | 4 +++- .../android/sdk/debug/TesterEvent.java | 6 +++++- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index fffb794f0..f356537e0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -97,7 +97,10 @@ public Interaction getApplicableInteraction(String eventLabel) { return null; } - void fetchInteractions() { + /** + * Fetches interaction synchronously. Returns true if succeed. + */ + boolean fetchInteractions() { ApptentiveLog.v(CONVERSATION, "Fetching Interactions"); ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); @@ -144,6 +147,8 @@ else if (!response.isSuccessful()) { // Update pending state on UI thread after finishing the task ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); + + return updateSuccessful; } private final transient DispatchTask fetchInteractionsTask = new DispatchTask() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 30c1c2062..03e9f044a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -92,6 +92,8 @@ public boolean loadActiveConversation() { // attempt to load existing conversation activeConversation = loadActiveConversationGuarded(); + dispatchDebugEvent(EVT_CONVERSATION_LOAD_ACTIVE, activeConversation != null); + if (activeConversation != null) { activeConversation.setDataChangedListener(this); return true; @@ -139,7 +141,7 @@ public boolean accept(ConversationMetadataItem item) { private Conversation loadConversation(ConversationMetadataItem item) { // TODO: use same serialization logic across the project - File file = new File(item.filename); + File file = new File(storageDir, item.filename); FileSerializer serializer = new FileSerializer(file); return (Conversation) serializer.deserialize(); } @@ -151,7 +153,6 @@ private Conversation loadConversation(ConversationMetadataItem item) { private void fetchConversationToken() { if (isConversationTokenFetchPending.compareAndSet(false, true)) { ApptentiveLog.i(CONVERSATION, "Fetching Configuration token task started."); - dispatchDebugEvent(EVT_FETCH_CONVERSATION_TOKEN); final Context context = getContext(); if (context == null) { @@ -213,24 +214,29 @@ public void onFinish(HttpJsonRequest request) { setActiveConversation(conversation); // fetch interactions - conversation.fetchInteractions(); + boolean fetchSucceed = conversation.fetchInteractions(); + dispatchDebugEvent(EVT_INTERACTION_FETCH, fetchSucceed); + // TODO: create listener and notify other parts of SDK about new conversation } catch (Exception e) { ApptentiveLog.e(e, "Exception while handling conversation token"); } finally { isConversationTokenFetchPending.set(false); + dispatchDebugEvent(EVT_CONVERSATION_CREATE, activeConversation != null); } } @Override public void onCancel(HttpJsonRequest request) { isConversationTokenFetchPending.set(false); + dispatchDebugEvent(EVT_CONVERSATION_CREATE, false); } @Override public void onFail(HttpJsonRequest request, String reason) { ApptentiveLog.w("Failed to fetch conversation token: %s", reason); isConversationTokenFetchPending.set(false); + dispatchDebugEvent(EVT_CONVERSATION_CREATE, false); } }); } @@ -284,7 +290,9 @@ private ConversationMetadata resolveMetadata() { File metaFile = new File(storageDir, CONVERSATION_METADATA_PATH); if (metaFile.exists()) { ApptentiveLog.v(CONVERSATION, "Loading meta file: " + metaFile); - return ObjectSerialization.deserialize(metaFile, ConversationMetadata.class); + final ConversationMetadata metadata = ObjectSerialization.deserialize(metaFile, ConversationMetadata.class); + dispatchDebugEvent(EVT_CONVERSATION_METADATA_LOAD, true); + return metadata; } else { ApptentiveLog.v(CONVERSATION, "Meta file does not exist: " + metaFile); } @@ -292,6 +300,7 @@ private ConversationMetadata resolveMetadata() { ApptentiveLog.e(e, "Exception while loading conversation metadata"); } + dispatchDebugEvent(EVT_CONVERSATION_METADATA_LOAD, false); return new ConversationMetadata(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java index efba162a1..440b237c5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java @@ -67,7 +67,9 @@ public void setActiveConversation(final Conversation conversation) // add a new item if it was not found if (!found) { - items.add(new ConversationMetadataItem(conversation.getConversationId(), conversation.getFilename())); + final ConversationMetadataItem item = new ConversationMetadataItem(conversation.getConversationId(), conversation.getFilename()); + item.state = CONVERSATION_STATE_ACTIVE; + items.add(item); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java index 2ea29eca6..04741c307 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java @@ -1,7 +1,11 @@ package com.apptentive.android.sdk.debug; public class TesterEvent { - public static final String EVT_FETCH_CONVERSATION_TOKEN = "fetch_conversation_token"; + + public static final String EVT_CONVERSATION_LOAD_ACTIVE = "conversation_load_active"; // { successful:boolean } + public static final String EVT_CONVERSATION_CREATE = "conversation_create"; // { successful:boolean } + public static final String EVT_CONVERSATION_METADATA_LOAD = "conversation_metadata_load"; // { successful:boolean } + public static final String EVT_INTERACTION_FETCH = "interaction_fetch"; // { successful:boolean } public static final String EVT_INSTANCE_CREATED = "instance_created"; // { successful:boolean } public static final String EVT_EXCEPTION = "exception"; // { class:String, message:String, stackTrace:String } } From d325f69b2f455b13c6b3742bfa281b62400a1100 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Feb 2017 17:04:11 -0800 Subject: [PATCH 110/465] Fixed debug event order --- .../android/sdk/conversation/ConversationManager.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 03e9f044a..3933c6e01 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -186,11 +186,13 @@ public void onFinish(HttpJsonRequest request) { if (StringUtils.isNullOrEmpty(conversationToken)) { ApptentiveLog.e(CONVERSATION, "Can't fetch conversation: missing 'token'"); + dispatchDebugEvent(EVT_CONVERSATION_CREATE, false); return; } if (StringUtils.isNullOrEmpty(conversationId)) { ApptentiveLog.e(CONVERSATION, "Can't fetch conversation: missing 'id'"); + dispatchDebugEvent(EVT_CONVERSATION_CREATE, false); return; } @@ -212,6 +214,7 @@ public void onFinish(HttpJsonRequest request) { // update active conversation setActiveConversation(conversation); + dispatchDebugEvent(EVT_CONVERSATION_CREATE, true); // fetch interactions boolean fetchSucceed = conversation.fetchInteractions(); @@ -220,9 +223,9 @@ public void onFinish(HttpJsonRequest request) { // TODO: create listener and notify other parts of SDK about new conversation } catch (Exception e) { ApptentiveLog.e(e, "Exception while handling conversation token"); + dispatchDebugEvent(EVT_CONVERSATION_CREATE, false); } finally { isConversationTokenFetchPending.set(false); - dispatchDebugEvent(EVT_CONVERSATION_CREATE, activeConversation != null); } } From f5058c579c630a7f6f2facbe4cea4f8ffc563a5c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 22 Feb 2017 11:29:27 -0800 Subject: [PATCH 111/465] WIP --- .../apptentive/android/sdk/conversation/Conversation.java | 8 -------- .../android/sdk/conversation/ConversationManager.java | 7 +++++++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index f356537e0..480253f74 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -151,16 +151,8 @@ else if (!response.isSuccessful()) { return updateSuccessful; } - private final transient DispatchTask fetchInteractionsTask = new DispatchTask() { - @Override - protected void execute() { - fetchInteractions(); - } - }; - //endregion - //region Listeners private transient DataChangedListener listener; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 3933c6e01..330837d93 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -96,6 +96,7 @@ public boolean loadActiveConversation() { if (activeConversation != null) { activeConversation.setDataChangedListener(this); + notifyConversationBecameActive(); return true; } @@ -216,6 +217,8 @@ public void onFinish(HttpJsonRequest request) { setActiveConversation(conversation); dispatchDebugEvent(EVT_CONVERSATION_CREATE, true); + notifyConversationBecameActive(); + // fetch interactions boolean fetchSucceed = conversation.fetchInteractions(); dispatchDebugEvent(EVT_INTERACTION_FETCH, fetchSucceed); @@ -245,6 +248,10 @@ public void onFail(HttpJsonRequest request, String reason) { } } + private void notifyConversationBecameActive() { + + } + private synchronized void setActiveConversation(Conversation conversation) { activeConversation = conversation; conversationMetadata.setActiveConversation(conversation); From e8723ba022168033b964c24fedb3c83b6119d533 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 27 Feb 2017 13:44:17 -0800 Subject: [PATCH 112/465] Pulled changes from branch 'hotfix/lowes-dismiss-ui-request' into develop --- .../android/sdk/ApptentiveBaseActivity.java | 52 +++++ .../sdk/ApptentiveComponentActivity.java | 57 ----- .../android/sdk/ApptentiveInternal.java | 28 +-- .../android/sdk/ApptentiveViewActivity.java | 26 +-- .../android/sdk/ApptentiveViewExitType.java | 39 ++++ .../interaction/fragment/AboutFragment.java | 8 +- .../fragment/AppStoreRatingFragment.java | 3 +- .../fragment/ApptentiveBaseFragment.java | 9 +- .../fragment/EnjoymentDialogFragment.java | 5 +- .../fragment/MessageCenterErrorFragment.java | 5 +- .../fragment/MessageCenterFragment.java | 7 +- .../fragment/NavigateToLinkFragment.java | 3 +- .../interaction/fragment/NoteFragment.java | 5 +- .../fragment/RatingDialogFragment.java | 5 +- .../interaction/fragment/SurveyFragment.java | 7 +- .../fragment/UpgradeMessageFragment.java | 5 +- .../notifications/ApptentiveNotification.java | 38 ++++ .../ApptentiveNotificationCenter.java | 196 ++++++++++++++++++ .../ApptentiveNotificationObserver.java | 11 + .../ApptentiveNotificationObserverList.java | 160 ++++++++++++++ .../android/sdk/util/ObjectUtils.java | 21 +- .../android/sdk/util/StringUtils.java | 44 ++++ .../util/registry/ApptentiveComponent.java | 8 - .../ApptentiveComponentReference.java | 21 -- .../registry/ApptentiveComponentRegistry.java | 186 ----------------- .../ApptentiveNotificationCenterTest.java | 110 ++++++++++ ...pptentiveNotificationObserverListTest.java | 171 +++++++++++++++ .../ApptentiveComponentRegistryTest.java | 189 ----------------- 28 files changed, 903 insertions(+), 516 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewExitType.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserver.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverList.java delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponent.java delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentReference.java delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java create mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java create mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverListTest.java delete mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java new file mode 100644 index 000000000..a5029c7b7 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java @@ -0,0 +1,52 @@ +package com.apptentive.android.sdk; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; + +import com.apptentive.android.sdk.notifications.ApptentiveNotification; +import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; +import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; + +import static com.apptentive.android.sdk.ApptentiveInternal.NOTIFICATION_INTERACTIONS_SHOULD_DISMISS; + +/** A base class for any SDK activity */ +public class ApptentiveBaseActivity extends AppCompatActivity implements ApptentiveNotificationObserver { + + //region Activity life cycle + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + registerNotifications(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + unregisterNotification(); + } + + //endregion + + //region Notifications + + protected void registerNotifications() { + ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_INTERACTIONS_SHOULD_DISMISS, this); + } + + protected void unregisterNotification() { + ApptentiveNotificationCenter.defaultCenter().removeObserver(this); + } + + //endregion + + //region ApptentiveNotificationObserver + + @Override + public void onReceiveNotification(ApptentiveNotification notification) { + // handle notification in subclasses + } + + //endregion +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java deleted file mode 100644 index bb340bac1..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveComponentActivity.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.apptentive.android.sdk; - -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; - -import com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry; -import com.apptentive.android.sdk.util.registry.ApptentiveComponent; - -import static com.apptentive.android.sdk.util.ObjectUtils.*; - -/** A base class for any SDK activity */ -public class ApptentiveComponentActivity extends AppCompatActivity implements ApptentiveComponent { - - //region Activity life cycle - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - registerComponent(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - unregisterComponent(); - } - - //endregion - - //region Object registry - - private void registerComponent() { - ApptentiveComponentRegistry componentRegistry = getComponentRegistry(); - if (componentRegistry != null) { - componentRegistry.register(this); - } - } - - private void unregisterComponent() { - ApptentiveComponentRegistry componentRegistry = getComponentRegistry(); - if (componentRegistry != null) { - componentRegistry.unregister(this); - } - } - - private ApptentiveComponentRegistry getComponentRegistry() { - ApptentiveInternal instance = notNull(ApptentiveInternal.getInstance(), "ApptentiveInternal is not initialized"); - if (instance == null) { - return null; - } - - return notNull(instance.getComponentRegistry(), "ApptentiveComponentRegistry is not initialized"); - } - - //endregion -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 643723eb1..9a29c3d8b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -28,7 +28,6 @@ import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.lifecycle.ApptentiveActivityLifecycleCallbacks; -import com.apptentive.android.sdk.listeners.OnUserLogOutListener; import com.apptentive.android.sdk.model.Configuration; import com.apptentive.android.sdk.model.ConversationTokenRequest; import com.apptentive.android.sdk.model.Event; @@ -43,6 +42,7 @@ import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.storage.AppRelease; +import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; import com.apptentive.android.sdk.storage.AppReleaseManager; import com.apptentive.android.sdk.storage.ApptentiveTaskManager; import com.apptentive.android.sdk.storage.DataChangedListener; @@ -56,7 +56,6 @@ import com.apptentive.android.sdk.storage.VersionHistoryItem; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; -import com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchQueueType; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -78,13 +77,17 @@ import static com.apptentive.android.sdk.ApptentiveLogTag.*; import static com.apptentive.android.sdk.debug.Tester.*; import static com.apptentive.android.sdk.debug.TesterEvent.*; -import static com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry.ComponentNotifier; /** * This class contains only internal methods. These methods should not be access directly by the host app. */ public class ApptentiveInternal implements DataChangedListener { + /** + * Sent if user requested to close all interactions. + */ + public static final String NOTIFICATION_INTERACTIONS_SHOULD_DISMISS = "NOTIFICATION_INTERACTIONS_SHOULD_DISMISS"; + static AtomicBoolean isApptentiveInitialized = new AtomicBoolean(false); private InteractionManager interactionManager; private final MessageManager messageManager; @@ -92,7 +95,6 @@ public class ApptentiveInternal implements DataChangedListener { private final ApptentiveTaskManager taskManager; ApptentiveActivityLifecycleCallbacks lifecycleCallbacks; - private final ApptentiveComponentRegistry componentRegistry; private final ApptentiveHttpClient apptentiveHttpClient; // These variables are initialized in Apptentive.register(), and so they are freely thereafter. If they are unexpectedly null, then if means the host app did not register Apptentive. @@ -165,7 +167,6 @@ private ApptentiveInternal(Context context, String apiKey) { globalSharedPrefs = context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); backgroundQueue = DispatchQueue.createBackgroundQueue("Apptentive Serial Queue", DispatchQueueType.Serial); - componentRegistry = new ApptentiveComponentRegistry(); apptentiveHttpClient = new ApptentiveHttpClient(apiKey, getEndpointBase(globalSharedPrefs)); appRelease = AppReleaseManager.generateCurrentAppRelease(context, this); @@ -374,10 +375,6 @@ public ApptentiveActivityLifecycleCallbacks getRegisteredLifecycleCallbacks() { return lifecycleCallbacks; } - public ApptentiveComponentRegistry getComponentRegistry() { - return componentRegistry; - } - /* Get the foreground activity from the current application, i.e. at the top of the task * It is tracked through {@link #onActivityStarted(Activity)} and {@link #onActivityStopped(Activity)} * @@ -1128,18 +1125,13 @@ protected void execute() { public void onDataChanged() { scheduleSessionDataSave(); } + //endregion /** - * Ends current user session + * Dismisses any currently-visible interactions. This method is for internal use and is subject to change. */ - public static void logout() { - getInstance().getComponentRegistry() - .notifyComponents(new ComponentNotifier(OnUserLogOutListener.class) { - @Override - public void onComponentNotify(OnUserLogOutListener component) { - component.onUserLogOut(); - } - }); + public static void dismissAllInteractions() { + ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_INTERACTIONS_SHOULD_DISMISS); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index cb6fa34a4..4600b5acc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -31,18 +31,16 @@ import android.view.WindowManager; import com.apptentive.android.sdk.adapter.ApptentiveViewPagerAdapter; -import com.apptentive.android.sdk.listeners.OnUserLogOutListener; import com.apptentive.android.sdk.model.FragmentFactory; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.fragment.ApptentiveBaseFragment; import com.apptentive.android.sdk.module.metric.MetricModule; +import com.apptentive.android.sdk.notifications.ApptentiveNotification; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; -public class ApptentiveViewActivity extends ApptentiveComponentActivity - implements ApptentiveBaseFragment.OnFragmentTransitionListener, OnUserLogOutListener { - +public class ApptentiveViewActivity extends ApptentiveBaseActivity implements ApptentiveBaseFragment.OnFragmentTransitionListener { private static final String FRAGMENT_TAG = "fragmentTag"; private int fragmentType; @@ -201,7 +199,7 @@ public void onPageScrollStateChanged(int pos) { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: - exitActivity(false); + exitActivity(ApptentiveViewExitType.MENU_ITEM); return true; default: @@ -212,10 +210,10 @@ public boolean onOptionsItemSelected(MenuItem item) { /** * Helper to clean up the Activity, whether it is exited through the toolbar back button, or the hardware back button. */ - private void exitActivity(boolean hardwareBackButtonWasPressed) { + private void exitActivity(ApptentiveViewExitType exitType) { ApptentiveBaseFragment currentFragment = (ApptentiveBaseFragment) viewPager_Adapter.getItem(viewPager.getCurrentItem()); if (currentFragment != null && currentFragment.isVisible()) { - if (currentFragment.onBackPressed(hardwareBackButtonWasPressed)) { + if (currentFragment.onFragmentExit(exitType)) { return; } @@ -230,7 +228,7 @@ private void exitActivity(boolean hardwareBackButtonWasPressed) { } public void onBackPressed() { - exitActivity(true); + exitActivity(ApptentiveViewExitType.BACK_BUTTON); } @Override @@ -405,12 +403,16 @@ private void setStatusBarColor() { } } - //region User log out listener + //region ApptentiveNotificationObserver + @Override - public void onUserLogOut() { - if (!isFinishing()) { - finish(); + public void onReceiveNotification(ApptentiveNotification notification) { + if (notification.getName().equals(ApptentiveInternal.NOTIFICATION_INTERACTIONS_SHOULD_DISMISS)) { + if (!isFinishing()) { + exitActivity(ApptentiveViewExitType.NOTIFICATION); + } } } + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewExitType.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewExitType.java new file mode 100644 index 000000000..c1bbce3f4 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewExitType.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk; + +/** + * Enum class describing the action which cased the activity to finish. + */ +public enum ApptentiveViewExitType { + MENU_ITEM("menu_item", false), // user selected options menu item + BACK_BUTTON("back_button", false), // user pressed back button + NOTIFICATION("notification", true); // dismiss-ui notification was received + + /** + * Exit mode name as sent with 'exit' event + */ + private final String name; + + /** + * If true the exit mode will be sent with engage event. + */ + private final boolean shouldAddToEngage; + + ApptentiveViewExitType(String name, boolean shouldAddToEngage) { + this.name = name; + this.shouldAddToEngage = shouldAddToEngage; + } + + public String getName() { + return name; + } + + public boolean isShouldAddToEngage() { + return shouldAddToEngage; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/AboutFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/AboutFragment.java index 2ed453b53..7a2304a6a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/AboutFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/AboutFragment.java @@ -14,6 +14,7 @@ import android.view.ViewGroup; import android.widget.Button; +import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.model.ExtendedData; import com.apptentive.android.sdk.module.engagement.EngagementModule; @@ -93,16 +94,15 @@ public void onClick(View view) { return root; } - public boolean onBackPressed(boolean hardwareButton) { - if (hardwareButton) { + public boolean onFragmentExit(ApptentiveViewExitType exitType) { + if (exitType.equals(ApptentiveViewExitType.BACK_BUTTON)) { EngagementModule.engage(getActivity(), "com.apptentive", INTERACTION_NAME, null, EVENT_NAME_CANCEL, null, null, (ExtendedData[]) null); } else { - EngagementModule.engage(getActivity(), "com.apptentive", INTERACTION_NAME, null, EVENT_NAME_CLOSE, null, null, (ExtendedData[]) null); + EngagementModule.engage(getActivity(), "com.apptentive", INTERACTION_NAME, null, EVENT_NAME_CLOSE, exitTypeToDataJson(exitType), null, (ExtendedData[]) null); } return false; } - @Override protected void sendLaunchEvent(Activity activity) { EngagementModule.engage(getActivity(), "com.apptentive", INTERACTION_NAME, null, EVENT_NAME_LAUNCH, null, null, (ExtendedData[]) null); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/AppStoreRatingFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/AppStoreRatingFragment.java index e17940c7e..9dbcbb339 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/AppStoreRatingFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/AppStoreRatingFragment.java @@ -10,6 +10,7 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.model.Configuration; @@ -88,7 +89,7 @@ public void onPause() { } @Override - public boolean onBackPressed(boolean hardwareButton) { + public boolean onFragmentExit(ApptentiveViewExitType exitType) { return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java index f67ffcbc4..0b15d3000 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java @@ -34,11 +34,13 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; import java.lang.reflect.Field; @@ -409,10 +411,9 @@ public void showToolbarElevation(boolean visible) { /** * Delegates the hardware or software back button press to the Interaction Fragment. - * @param hardwareButton True if the Hardware back button was pressed. False if the software back button was pressed. * @return */ - public boolean onBackPressed(boolean hardwareButton) { + public boolean onFragmentExit(ApptentiveViewExitType exitType) { List fragments = getRetainedChildFragmentManager().getFragments(); if (fragments != null) { @@ -435,6 +436,10 @@ public boolean onBackPressed(boolean hardwareButton) { return false; } + protected String exitTypeToDataJson(ApptentiveViewExitType exitType) { + return exitType.isShouldAddToEngage() ? StringUtils.asJson("cause", exitType.getName()) : null; + } + @TargetApi(21) private void showToolbarElevationLollipop(boolean visible) { if (!isAdded()) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/EnjoymentDialogFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/EnjoymentDialogFragment.java index 5f22ba6f5..32c76c373 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/EnjoymentDialogFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/EnjoymentDialogFragment.java @@ -14,6 +14,7 @@ import android.widget.ImageButton; import android.widget.TextView; +import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.EnjoymentDialogInteraction; @@ -71,8 +72,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa } @Override - public boolean onBackPressed(boolean hardwareButton) { - EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_CANCEL); + public boolean onFragmentExit(ApptentiveViewExitType exitType) { + EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_CANCEL, exitTypeToDataJson(exitType)); return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterErrorFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterErrorFragment.java index 7e04ad23a..88e8b502a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterErrorFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterErrorFragment.java @@ -18,6 +18,7 @@ import com.apptentive.android.sdk.Apptentive; import com.apptentive.android.sdk.ApptentiveInternal; +import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.model.ExtendedData; import com.apptentive.android.sdk.module.engagement.EngagementModule; @@ -82,8 +83,8 @@ private boolean wasLastAttemptServerError(Context context) { } - public boolean onBackPressed(boolean hardwareBackButtonWasPressed) { - EngagementModule.engage(getActivity(), "com.apptentive", "MessageCenter", null, EVENT_NAME_NO_INTERACTION_CLOSE, null, null, (ExtendedData[]) null); + public boolean onFragmentExit(ApptentiveViewExitType exitType) { + EngagementModule.engage(getActivity(), "com.apptentive", "MessageCenter", null, EVENT_NAME_NO_INTERACTION_CLOSE, exitTypeToDataJson(exitType), null, (ExtendedData[]) null); return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index 51080998e..7712627cc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -40,6 +40,7 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.ApptentiveViewActivity; +import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.module.engagement.EngagementModule; @@ -505,7 +506,7 @@ public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } - public boolean onBackPressed(boolean hardwareButton) { + public boolean onFragmentExit(ApptentiveViewExitType exitType) { savePendingComposingMessage(); ApptentiveViewActivity hostingActivity = (ApptentiveViewActivity) hostingActivityRef.get(); if (hostingActivity != null) { @@ -514,10 +515,10 @@ public boolean onBackPressed(boolean hardwareButton) { myFrag.dismiss(); } cleanup(); - if (hardwareButton) { + if (exitType.equals(ApptentiveViewExitType.BACK_BUTTON)) { EngagementModule.engageInternal(hostingActivity, interaction, MessageCenterInteraction.EVENT_NAME_CANCEL); } else { - EngagementModule.engageInternal(hostingActivity, interaction, MessageCenterInteraction.EVENT_NAME_CLOSE); + EngagementModule.engageInternal(hostingActivity, interaction, MessageCenterInteraction.EVENT_NAME_CLOSE, exitTypeToDataJson(exitType)); } } return false; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NavigateToLinkFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NavigateToLinkFragment.java index a2a48ea38..3c61903e9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NavigateToLinkFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NavigateToLinkFragment.java @@ -19,6 +19,7 @@ import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.NavigateToLinkInteraction; import com.apptentive.android.sdk.util.Util; @@ -88,7 +89,7 @@ public void onPause() { @Override - public boolean onBackPressed(boolean hardwareButton) { + public boolean onFragmentExit(ApptentiveViewExitType exitType) { return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java index 7a07366ee..66ef4438f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java @@ -17,6 +17,7 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; @@ -175,8 +176,8 @@ public void onClick(View view) { } @Override - public boolean onBackPressed(boolean hardwareButton) { - EngagementModule.engageInternal(getActivity(), interaction, TextModalInteraction.EVENT_NAME_CANCEL); + public boolean onFragmentExit(ApptentiveViewExitType exitType) { + EngagementModule.engageInternal(getActivity(), interaction, TextModalInteraction.EVENT_NAME_CANCEL, exitTypeToDataJson(exitType)); return false; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/RatingDialogFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/RatingDialogFragment.java index 01ba84ff6..0db7eec52 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/RatingDialogFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/RatingDialogFragment.java @@ -13,6 +13,7 @@ import android.widget.Button; import android.widget.TextView; +import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.RatingDialogInteraction; @@ -89,8 +90,8 @@ public void onClick(View view) { } @Override - public boolean onBackPressed(boolean hardwareButton) { - EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_CANCEL); + public boolean onFragmentExit(ApptentiveViewExitType exitType) { + EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_CANCEL, exitTypeToDataJson(exitType)); return false; } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java index f803e5e75..5df9db909 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java @@ -28,6 +28,7 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.model.SurveyResponse; import com.apptentive.android.sdk.module.engagement.EngagementModule; @@ -267,11 +268,11 @@ private void callListener(boolean completed) { } @Override - public boolean onBackPressed(boolean hardwareButton) { - if (hardwareButton) { + public boolean onFragmentExit(ApptentiveViewExitType exitType) { + if (exitType.equals(ApptentiveViewExitType.BACK_BUTTON)) { EngagementModule.engageInternal(getActivity(), interaction, EVENT_CANCEL); } else { - EngagementModule.engageInternal(getActivity(), interaction, EVENT_CLOSE); + EngagementModule.engageInternal(getActivity(), interaction, EVENT_CLOSE, exitTypeToDataJson(exitType)); } return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/UpgradeMessageFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/UpgradeMessageFragment.java index 1cb0e8237..07a9ebf64 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/UpgradeMessageFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/UpgradeMessageFragment.java @@ -20,6 +20,7 @@ import android.widget.ImageView; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.model.Configuration; import com.apptentive.android.sdk.module.engagement.EngagementModule; @@ -64,8 +65,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, } @Override - public boolean onBackPressed(boolean hardwareButton) { - EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_DISMISS); + public boolean onFragmentExit(ApptentiveViewExitType exitType) { + EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_DISMISS, exitTypeToDataJson(exitType)); return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java new file mode 100644 index 000000000..f1441251f --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java @@ -0,0 +1,38 @@ +package com.apptentive.android.sdk.notifications; + +import com.apptentive.android.sdk.util.StringUtils; + +import java.util.Map; + +/** + * {@link ApptentiveNotification} objects encapsulate information so that it can be broadcast to + * other objects by {@link ApptentiveNotificationCenter}. An {@link ApptentiveNotification} object + * contains a name and an optional dictionary. The name is a tag identifying the notification. + * The dictionary stores other related objects, if any. + * {@link ApptentiveNotification} objects are immutable objects. + */ +public class ApptentiveNotification { + private final String name; + private final Map userInfo; + + public ApptentiveNotification(String name, Map userInfo) { + if (StringUtils.isNullOrEmpty(name)) { + throw new IllegalArgumentException("Name is null or empty"); + } + this.name = name; + this.userInfo = userInfo; + } + + public String getName() { + return name; + } + + public Map getUserInfo() { + return userInfo; + } + + @Override + public String toString() { + return String.format("[%s] name=%s userInfo=%s", name, StringUtils.toString(userInfo)); + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java new file mode 100644 index 000000000..a288ea9a6 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java @@ -0,0 +1,196 @@ +package com.apptentive.android.sdk.notifications; + +import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchTask; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * An {@link ApptentiveNotificationCenter} object (or simply, notification center) provides a + * mechanism for broadcasting information within a program. An {@link ApptentiveNotificationCenter} + * object is essentially a notification dispatch table. + */ +public class ApptentiveNotificationCenter { + + /** + * Shared empty user data for notification. + */ + private static final Map EMPTY_USER_INFO = Collections.emptyMap(); + + /** + * Lookup table for notification-to-observers search. + */ + private final Map observerListLookup; + + /** + * Dispatch queue for posting notifications. + */ + private final DispatchQueue notificationQueue; + + /** + * Dispatch queue for the concurrent access to the internal data structures + * (adding/removing observers, etc). + */ + private final DispatchQueue operationQueue; + + ApptentiveNotificationCenter(DispatchQueue notificationQueue, DispatchQueue operationQueue) { + if (notificationQueue == null) { + throw new IllegalArgumentException("Notification queue is not defined"); + } + + if (operationQueue == null) { + throw new IllegalArgumentException("Operation queue is not defined"); + } + + this.observerListLookup = new HashMap<>(); + this.notificationQueue = notificationQueue; + this.operationQueue = operationQueue; + } + + //region Observers + + /** + * Adds an entry to the receiver’s dispatch table with an observer using strong reference. + */ + public void addObserver(final String notification, final ApptentiveNotificationObserver observer) { + addObserver(notification, observer, false); + } + + /** + * Adds an entry to the receiver’s dispatch table with an observer. + * + * @param useWeakReference - weak reference is used if true + */ + public void addObserver(final String notification, final ApptentiveNotificationObserver observer, final boolean useWeakReference) { + operationQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + final ApptentiveNotificationObserverList list = resolveObserverList(notification); + list.addObserver(observer, useWeakReference); + } + }); + } + + /** + * Removes matching entries from the receiver’s dispatch table. + */ + public void removeObserver(final String notification, final ApptentiveNotificationObserver observer) { + operationQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + final ApptentiveNotificationObserverList list = findObserverList(notification); + if (list != null) { + list.removeObserver(observer); + } + } + }); + } + + /** + * Removes all the entries specifying a given observer from the receiver’s dispatch table. + */ + public void removeObserver(final ApptentiveNotificationObserver observer) { + operationQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + for (ApptentiveNotificationObserverList observers : observerListLookup.values()) { + observers.removeObserver(observer); + } + } + }); + } + + //endregion + + //region Notifications + + /** + * Posts a given notification to the receiver. + */ + public void postNotification(String name) { + postNotification(name, EMPTY_USER_INFO); + } + + /** + * Creates a notification with a given name and information and posts it to the receiver. + */ + public void postNotification(String name, Map userInfo) { + postNotification(new ApptentiveNotification(name, userInfo)); + } + + /** + * Posts a given notification to the receiver. + */ + public void postNotification(final ApptentiveNotification notification) { + operationQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + if (notificationQueue == operationQueue) { // is it the same queue? + postNotificationSync(notification); + } else { + notificationQueue.dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + postNotificationSync(notification); + } + }); + } + } + }); + } + + // this method is not thread-safe + private void postNotificationSync(ApptentiveNotification notification) { + final ApptentiveNotificationObserverList list = findObserverList(notification.getName()); + if (list != null) { + list.notifyObservers(notification); + } + } + + //endregion + + //region Helpers + + /** + * Find an observer list for the specified name. + * + * @return null is not found + */ + private ApptentiveNotificationObserverList findObserverList(String name) { + return observerListLookup.get(name); + } + + /** + * Find an observer list for the specified name or creates a new one if not found. + */ + private ApptentiveNotificationObserverList resolveObserverList(String name) { + ApptentiveNotificationObserverList list = observerListLookup.get(name); + if (list == null) { + list = new ApptentiveNotificationObserverList(); + observerListLookup.put(name, list); + } + return list; + } + + //endregion + + //region Singleton + + /** + * Returns the process’s default notification center. + */ + public static ApptentiveNotificationCenter defaultCenter() { + return Holder.INSTANCE; + } + + /** + * Thread-safe initialization trick + */ + private static class Holder { + static final ApptentiveNotificationCenter INSTANCE = new ApptentiveNotificationCenter(DispatchQueue.mainQueue(), DispatchQueue.mainQueue()); + } + + //endregion +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserver.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserver.java new file mode 100644 index 000000000..ec41f3438 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserver.java @@ -0,0 +1,11 @@ +package com.apptentive.android.sdk.notifications; + +/** + * Interface definition for a callback to be invoked when a notification is received. + */ +public interface ApptentiveNotificationObserver { + /** + * Called when a view has been received. + */ + void onReceiveNotification(ApptentiveNotification notification); +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverList.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverList.java new file mode 100644 index 000000000..71980f979 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverList.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.notifications; + +import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.util.ObjectUtils; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class for storing weak/strong references to {@link ApptentiveNotificationObserverList} + * and posting notification. Lost reference cleanup is done automatically. + */ +class ApptentiveNotificationObserverList { + + /** + * List of observers. + */ + private final List observers; + + ApptentiveNotificationObserverList() { + observers = new ArrayList<>(); + } + + /** + * Posts notification to all observers. + */ + void notifyObservers(ApptentiveNotification notification) { + boolean hasLostReferences = false; + + // create a temporary list of observers to avoid concurrent modification errors + List temp = new ArrayList<>(observers.size()); + for (int i = 0; i < observers.size(); ++i) { + ApptentiveNotificationObserver observer = observers.get(i); + ObserverWeakReference observerRef = ObjectUtils.as(observer, ObserverWeakReference.class); + if (observerRef == null || !observerRef.isReferenceLost()) { + temp.add(observer); + } else { + hasLostReferences = true; + } + } + + // notify observers + for (int i = 0; i < temp.size(); ++i) { + try { + temp.get(i).onReceiveNotification(notification); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while posting notification: %s", notification); + } + } + + // clean lost references + if (hasLostReferences) { + for (int i = observers.size() - 1; i >= 0; --i) { + final ObserverWeakReference observerRef = ObjectUtils.as(observers.get(i), ObserverWeakReference.class); + if (observerRef != null && observerRef.isReferenceLost()) { + observers.remove(i); + } + } + } + } + + /** + * Adds an observer to the list without duplicates. + * + * @param useWeakReference - use weak reference if true + * @return true - if observer was added + */ + boolean addObserver(ApptentiveNotificationObserver observer, boolean useWeakReference) { + if (observer == null) { + throw new IllegalArgumentException("Observer is null"); + } + + if (!contains(observer)) { + observers.add(useWeakReference ? new ObserverWeakReference(observer) : observer); + return true; + } + + return false; + } + + /** + * Removes observer os its weak reference from the list + * + * @return true if observer was returned + */ + boolean removeObserver(ApptentiveNotificationObserver observer) { + int index = indexOf(observer); + if (index != -1) { + observers.remove(index); + return true; + } + return false; + } + + /** + * Size of the list + */ + public int size() { + return observers.size(); + } + + /** + * Returns an index of the observer or its weak reference. + * + * @return -1 if not found + */ + private int indexOf(ApptentiveNotificationObserver observer) { + for (int i = 0; i < observers.size(); ++i) { + final ApptentiveNotificationObserver other = observers.get(i); + if (other == observer) { + return i; + } + + final ObserverWeakReference otherReference = ObjectUtils.as(other, ObserverWeakReference.class); + if (otherReference != null && otherReference.get() == observer) { + return i; + } + } + return -1; + } + + /** + * Checks if observer or its weak references are in the list. + */ + private boolean contains(ApptentiveNotificationObserver observer) { + return indexOf(observer) != -1; + } + + /** + * Helper class for stored {@link ApptentiveNotificationObserver} weak reference + */ + private static class ObserverWeakReference extends WeakReference implements ApptentiveNotificationObserver { + + ObserverWeakReference(ApptentiveNotificationObserver referent) { + super(referent); + } + + @Override + public void onReceiveNotification(ApptentiveNotification notification) { + ApptentiveNotificationObserver observer = get(); + if (observer != null) { + observer.onReceiveNotification(notification); + } + } + + /** + * Returns true if observer's memory was freed. + */ + boolean isReferenceLost() { + return get() == null; + } + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java index 21f21d4da..97b36cd66 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java @@ -23,11 +23,17 @@ import static com.apptentive.android.sdk.debug.Assert.*; +import java.util.HashMap; +import java.util.Map; + /** * A collection of useful object-related functions */ public final class ObjectUtils { - + /** + * Attempts to cast object to class cls. + * Returns null if cast is impossible. + */ @SuppressWarnings("unchecked") public static T as(Object object, Class cls) { return cls.isInstance(object) ? (T) object : null; @@ -37,4 +43,17 @@ public static T notNull(T object, String message) { assertNotNull(object, message); return object; } + + public static Map toMap(Object... args) { + if (args.length % 2 != 0) { + throw new IllegalArgumentException("Invalid args"); + } + + Map map = new HashMap<>(); + for (int i = 0; i < args.length; i += 2) { + map.put((String) args[i], args[i + 1]); + } + + return map; + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java index b0aba87f9..674975f3b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java @@ -21,6 +21,10 @@ package com.apptentive.android.sdk.util; +import com.apptentive.android.sdk.ApptentiveLog; + +import org.json.JSONObject; + import java.net.URLEncoder; import java.util.List; import java.util.Map; @@ -52,6 +56,25 @@ public static String toString(Object value) { return value != null ? value.toString() : "null"; } + /** + * Transforms dictionary to string + */ + public static String toString(Map map) { + if (map == null) return null; + + StringBuilder result = new StringBuilder(); + for (Map.Entry entry : map.entrySet()) { + if (result.length() > 0) result.append(", "); + result.append("'"); + result.append(entry.getKey()); + result.append("':'"); + result.append(entry.getValue()); + result.append("'"); + } + + return result.toString(); + } + /** * Constructs and returns a string object that is the result of interposing a separator between the elements of the array */ @@ -127,4 +150,25 @@ public static String createQueryString(Map params) { // FIXME: u } return result.toString(); } + + /** + * Checks is string is null or empty + */ + public static boolean isNullOrEmpty(String str) { + return str == null || str.length() == 0; + } + + /** + * Creates a simple json string from key and value + */ + public static String asJson(String key, Object value) { + try { + JSONObject json = new JSONObject(); + json.put(key, value); + return json.toString(); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while creating json-string { %s:%s }", key, value); + return null; + } + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponent.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponent.java deleted file mode 100644 index 11875ee84..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponent.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.apptentive.android.sdk.util.registry; - -/** - * Interface marker for any class which represents an SDK component - * in {@link ApptentiveComponentRegistry} - */ -public interface ApptentiveComponent { -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentReference.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentReference.java deleted file mode 100644 index af25a997b..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentReference.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.apptentive.android.sdk.util.registry; - -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; - -/** - * A simple subclass of WeakReference to handle {@link ApptentiveComponent} objects - */ -class ApptentiveComponentReference extends WeakReference { - public ApptentiveComponentReference(ApptentiveComponent referent) { - super(referent); - } - - public ApptentiveComponentReference(ApptentiveComponent referent, ReferenceQueue q) { - super(referent, q); - } - - public boolean isReferenceLost() { - return get() == null; - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java deleted file mode 100644 index 5fb004442..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistry.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.apptentive.android.sdk.util.registry; - -import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.util.threading.DispatchQueue; -import com.apptentive.android.sdk.util.threading.DispatchTask; - -import java.util.ArrayList; -import java.util.List; - -import static com.apptentive.android.sdk.debug.Assert.*; -import static com.apptentive.android.sdk.util.ObjectUtils.*; - -/** - * A registry that holds a list of weak references to {@link ApptentiveComponent} and - * provides an easy mechanism for broadcasting information within a program. - * Each registered component would receive notifications based on types of the interfaces it - * implements. - *

- * Component example: - * - * public class MyActivity extends Activity implements ApptentiveComponent, OnUserLogOutListener { - * ... - * public void onUserLogOut() { - * finish(); - * } - * ... - * } - * - *

- * Notification example: - * - * public void logout() { - * getComponentRegistry() - * .notifyComponents(new ComponentNotifier(OnUserLogOutListener.class) { - * public void onComponentNotify(OnUserLogOutListener component) { - * component.onUserLogOut(); - * } - * }); - * } - * - */ -public class ApptentiveComponentRegistry { - /** - * List of references for currently registered components - */ - private final List componentReferences; - - public ApptentiveComponentRegistry() { - componentReferences = new ArrayList<>(); - } - - //region Object registration - - /** - * Registers component - */ - public synchronized void register(ApptentiveComponent component) { - assertNotNull(component, "Attempted to register a null component"); - if (component != null) { - boolean alreadyRegistered = isRegistered(component); - assertFalse(alreadyRegistered, "Attempted to register component twice: %s", component); - if (!alreadyRegistered) { - componentReferences.add(new ApptentiveComponentReference(component)); - } - } - } - - /** - * Unregisters component - */ - public synchronized void unregister(ApptentiveComponent component) { - assertNotNull(component, "Attempted to unregister a null component"); - if (component != null) { - int index = indexOf(component); - assertNotEquals(index, -1, "Attempted to unregister component twice: %s", component); - if (index != -1) { - componentReferences.remove(index); - } - } - } - - /** - * Returns true if component is already registered - */ - public synchronized boolean isRegistered(ApptentiveComponent component) { - assertNotNull(component); - return component != null && contains(component); - } - - /** - * Return true if component is registered - */ - private synchronized boolean contains(ApptentiveComponent component) { - return indexOf(component) != -1; - } - - /** - * Return the index of component or -1 if component is not registered - */ - private synchronized int indexOf(ApptentiveComponent component) { - int index = 0; - for (ApptentiveComponentReference componentReference : componentReferences) { - if (componentReference.get() == component) { - return index; - } - ++index; - } - - return -1; - } - - //endregion - - //region Notifications - - /** - * Notify all the listeners of type T about event - */ - public synchronized void notifyComponents(final ComponentNotifier notifier) { - // in order to avoid UI-related issues - we dispatch all the notification on main thread - DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - notifyComponentsSafe(notifier); - } - }); - } - - private synchronized void notifyComponentsSafe(final ComponentNotifier notifier) { - List components = new ArrayList<>(componentReferences.size()); - boolean hasLostReferences = false; - - // we put all the qualified components into a separate list to avoid ConcurrentModificationException - Class targetType = notifier.getType(); - for (ApptentiveComponentReference reference : componentReferences) { - ApptentiveComponent component = reference.get(); - if (component == null) { - hasLostReferences = true; - continue; - } - - T target = as(component, targetType); - if (target != null) { - components.add(target); - } - } - - // notify each object safely - for (T component : components) { - try { - notifier.onComponentNotify(component); - } catch (Exception e) { - ApptentiveLog.e(e, "Exception while notifying object: %s", component); - } - } - - // cleanup up lost references - if (hasLostReferences) { - for (int i = componentReferences.size() - 1; i >= 0; --i) { - if (componentReferences.get(i).isReferenceLost()) { - componentReferences.remove(i); - } - } - } - } - - //endregion - - //region Component notifier helper class - - public static abstract class ComponentNotifier { - private final Class type; - - public ComponentNotifier(Class type) { - this.type = type; - } - - public abstract void onComponentNotify(T object); - - public Class getType() { - return type; - } - } - - //endregion -} \ No newline at end of file diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java new file mode 100644 index 000000000..a55c47f0a --- /dev/null +++ b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.notifications; + +import com.apptentive.android.sdk.TestCaseBase; +import com.apptentive.android.sdk.util.ObjectUtils; +import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.util.threading.MockDispatchQueue; + +import org.junit.Before; +import org.junit.Test; + +public class ApptentiveNotificationCenterTest extends TestCaseBase { + + private ApptentiveNotificationCenter notificationCenter; + + @Before + public void setUp() throws Exception { + notificationCenter = new ApptentiveNotificationCenter(new MockDispatchQueue(true), new MockDispatchQueue(true)); + } + + private static final boolean WEAK_REFERENCE = true; + private static final boolean STRONG_REFERENCE = false; + + @Test + public void testPostNotifications() { + Observer o1 = new Observer("observer1"); + Observer o2 = new Observer("observer2"); + Observer o3 = new Observer("observer3"); + Observer o4 = new Observer("observer4"); + + notificationCenter.addObserver("notification1", o1, WEAK_REFERENCE); + notificationCenter.addObserver("notification1", o2, STRONG_REFERENCE); + + notificationCenter.addObserver("notification2", o3, WEAK_REFERENCE); + notificationCenter.addObserver("notification2", o4, STRONG_REFERENCE); + + notificationCenter.postNotification("notification1", ObjectUtils.toMap("key1", "value1")); + assertResult("observer1: notification1 {'key1':'value1'}", "observer2: notification1 {'key1':'value1'}"); + + notificationCenter.postNotification("notification2", ObjectUtils.toMap("key2", "value2")); + assertResult("observer3: notification2 {'key2':'value2'}", "observer4: notification2 {'key2':'value2'}"); + + notificationCenter.postNotification("notification3"); + assertResult(); + + // remove some observers + notificationCenter.removeObserver(o1); + notificationCenter.removeObserver(o4); + + notificationCenter.postNotification("notification1", ObjectUtils.toMap("key1", "value1")); + assertResult("observer2: notification1 {'key1':'value1'}"); + + notificationCenter.postNotification("notification2", ObjectUtils.toMap("key2", "value2")); + assertResult("observer3: notification2 {'key2':'value2'}"); + + notificationCenter.postNotification("notification3"); + assertResult(); + + // remove the rest + notificationCenter.removeObserver("notification2", o2); // hit & miss + notificationCenter.removeObserver("notification1", o3); // hit & miss + + notificationCenter.removeObserver("notification1", o2); + notificationCenter.removeObserver("notification2", o3); + + notificationCenter.postNotification("notification1", ObjectUtils.toMap("key1", "value1")); + assertResult(); + + notificationCenter.postNotification("notification2", ObjectUtils.toMap("key2", "value2")); + assertResult(); + + notificationCenter.postNotification("notification3"); + assertResult(); + + // add back + notificationCenter.addObserver("notification1", o1, WEAK_REFERENCE); + notificationCenter.addObserver("notification1", o2, STRONG_REFERENCE); + + notificationCenter.addObserver("notification2", o3, WEAK_REFERENCE); + notificationCenter.addObserver("notification2", o4, STRONG_REFERENCE); + + notificationCenter.postNotification("notification1", ObjectUtils.toMap("key1", "value1")); + assertResult("observer1: notification1 {'key1':'value1'}", "observer2: notification1 {'key1':'value1'}"); + + notificationCenter.postNotification("notification2", ObjectUtils.toMap("key2", "value2")); + assertResult("observer3: notification2 {'key2':'value2'}", "observer4: notification2 {'key2':'value2'}"); + + notificationCenter.postNotification("notification3"); + assertResult(); + } + + private class Observer implements ApptentiveNotificationObserver { + + private final String name; + + public Observer(String name) { + this.name = name; + } + + @Override + public void onReceiveNotification(ApptentiveNotification notification) { + addResult(String.format("%s: %s {%s}", name, notification.getName(), StringUtils.toString(notification.getUserInfo()))); + } + } +} \ No newline at end of file diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverListTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverListTest.java new file mode 100644 index 000000000..836a640ff --- /dev/null +++ b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverListTest.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.notifications; + +import com.apptentive.android.sdk.TestCaseBase; + +import org.junit.Test; + +import java.util.HashMap; +import static org.junit.Assert.*; + +public class ApptentiveNotificationObserverListTest extends TestCaseBase { + + private static final boolean WEAK_REFERENCE = true; + private static final boolean STRONG_REFERENCE = false; + + @Test + public void testAddObservers() { + ApptentiveNotificationObserverList list = new ApptentiveNotificationObserverList(); + + Observer o1 = new Observer("observer1"); + Observer o2 = new Observer("observer2"); + + list.addObserver(o1, WEAK_REFERENCE); + list.addObserver(o2, STRONG_REFERENCE); + assertEquals(2, list.size()); + + // trying to add duplicates + list.addObserver(o1, WEAK_REFERENCE); + list.addObserver(o1, STRONG_REFERENCE); + assertEquals(2, list.size()); + + list.addObserver(o2, WEAK_REFERENCE); + list.addObserver(o2, STRONG_REFERENCE); + assertEquals(2, list.size()); + + list.notifyObservers(new ApptentiveNotification("notification", new HashMap())); + assertResult("observer1", "observer2"); + } + + @Test + public void testRemoveObservers() { + ApptentiveNotificationObserverList list = new ApptentiveNotificationObserverList(); + + Observer o1 = new Observer("observer1"); + Observer o2 = new Observer("observer2"); + + list.addObserver(o1, WEAK_REFERENCE); + list.addObserver(o2, STRONG_REFERENCE); + + list.notifyObservers(new ApptentiveNotification("notification", new HashMap())); + assertResult("observer1", "observer2"); + assertEquals(2, list.size()); + + list.removeObserver(o1); + assertEquals(1, list.size()); + + list.notifyObservers(new ApptentiveNotification("notification", new HashMap())); + assertResult("observer2"); + + list.removeObserver(o2); + list.notifyObservers(new ApptentiveNotification("notification", new HashMap())); + assertResult(); + assertEquals(0, list.size()); + } + + @Test + public void testWeakReferences() { + ApptentiveNotificationObserverList list = new ApptentiveNotificationObserverList(); + + // begin of the scope + { + Observer o1 = new Observer("observer1"); + Observer o4 = new Observer("observer4"); + + list.addObserver(o1, WEAK_REFERENCE); // this reference won't be lost until the end of the current scope + list.addObserver(new Observer("observer2"), WEAK_REFERENCE); // this reference would be lost right away + list.addObserver(new Observer("observer3"), STRONG_REFERENCE); // this reference won't be lost + list.addObserver(o4, STRONG_REFERENCE); + + // force GC so the weak reference becomes null + System.gc(); + + list.notifyObservers(new ApptentiveNotification("notification", new HashMap())); + assertResult("observer1", "observer3", "observer4"); + assertEquals(3, list.size()); + + o1 = o4 = null; // this step is necessary for a proper GC + } + // end of the scope + + // force GC so the weak reference becomes null + System.gc(); + + list.notifyObservers(new ApptentiveNotification("notification", new HashMap())); + assertResult("observer3", "observer4"); + assertEquals(2, list.size()); + } + + @Test + public void testConcurrentModification() { + final ApptentiveNotificationObserverList list = new ApptentiveNotificationObserverList(); + + final Observer o1 = new Observer("observer1"); + final Observer o2 = new Observer("observer2"); + + list.addObserver(new ApptentiveNotificationObserver() { + @Override + public void onReceiveNotification(ApptentiveNotification notification) { + list.removeObserver(o1); + addResult("anonymous-observer1"); + } + }, STRONG_REFERENCE); + list.addObserver(o1, WEAK_REFERENCE); + list.addObserver(new ApptentiveNotificationObserver() { + @Override + public void onReceiveNotification(ApptentiveNotification notification) { + list.removeObserver(o2); + addResult("anonymous-observer2"); + } + }, STRONG_REFERENCE); + list.addObserver(o2, STRONG_REFERENCE); + + list.notifyObservers(new ApptentiveNotification("notification", new HashMap())); + assertResult("anonymous-observer1", "observer1", "anonymous-observer2", "observer2"); + assertEquals(2, list.size()); + + list.notifyObservers(new ApptentiveNotification("notification", new HashMap())); + assertResult("anonymous-observer1", "anonymous-observer2"); + assertEquals(2, list.size()); + } + + @Test + public void testThrowingException() { + final ApptentiveNotificationObserverList list = new ApptentiveNotificationObserverList(); + + final Observer o1 = new Observer("observer1"); + final Observer o2 = new Observer("observer2"); + + list.addObserver(o1, STRONG_REFERENCE); + list.addObserver(new ApptentiveNotificationObserver() { + @Override + public void onReceiveNotification(ApptentiveNotification notification) { + addResult("error"); + throw new RuntimeException("Error"); + } + }, STRONG_REFERENCE); + list.addObserver(o2, STRONG_REFERENCE); + + list.notifyObservers(new ApptentiveNotification("notification", new HashMap())); + assertResult("observer1", "error", "observer2"); + } + + private class Observer implements ApptentiveNotificationObserver { + + private final String name; + + public Observer(String name) { + this.name = name; + } + + @Override + public void onReceiveNotification(ApptentiveNotification notification) { + addResult(name); + } + } +} \ No newline at end of file diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java deleted file mode 100644 index 4ff9212dc..000000000 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/registry/ApptentiveComponentRegistryTest.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.util.registry; - -import com.apptentive.android.sdk.TestCaseBase; - -import org.junit.Before; -import org.junit.Test; - -import static com.apptentive.android.sdk.util.registry.ApptentiveComponentRegistry.ComponentNotifier; - -public class ApptentiveComponentRegistryTest extends TestCaseBase { - - //region Setup - - @Before - public void setUp() - { - overrideMainQueue(true); - } - - //endregion - - //region Testing - - @Test - public void testComponentRegistration() { - - ApptentiveComponentRegistry registry = new ApptentiveComponentRegistry(); - registry.notifyComponents(new ComponentNotifier(ListenerA.class) { - @Override - public void onComponentNotify(ListenerA listener) { - listener.OnTestEventA(); - } - }); - assertResult(); - - final ComponentA c1 = new ComponentA("ComponentA-1"); - final ComponentA c2 = new ComponentA("ComponentA-2"); - final ComponentA c3 = new ComponentA("ComponentA-3"); - - registry.register(c1); - registry.register(c2); - registry.register(c3); - - registry.notifyComponents(new ComponentNotifier(ListenerA.class) { - @Override - public void onComponentNotify(ListenerA listener) { - listener.OnTestEventA(); - } - }); - assertResult("ListenerA-ComponentA-1", "ListenerA-ComponentA-2", "ListenerA-ComponentA-3"); - - registry.unregister(c2); - registry.notifyComponents(new ComponentNotifier(ListenerA.class) { - @Override - public void onComponentNotify(ListenerA listener) { - listener.OnTestEventA(); - } - }); - assertResult("ListenerA-ComponentA-1", "ListenerA-ComponentA-3"); - - registry.unregister(c1); - registry.notifyComponents(new ComponentNotifier(ListenerA.class) { - @Override - public void onComponentNotify(ListenerA listener) { - listener.OnTestEventA(); - } - }); - assertResult("ListenerA-ComponentA-3"); - - registry.unregister(c3); - registry.notifyComponents(new ComponentNotifier(ListenerA.class) { - @Override - public void onComponentNotify(ListenerA listener) { - listener.OnTestEventA(); - } - }); - assertResult(); - } - - @Test - public void testComponentNotification() { - - ApptentiveComponentRegistry registry = new ApptentiveComponentRegistry(); - registry.register(new ComponentA("ComponentA-1")); - registry.register(new ComponentA("ComponentA-2")); - registry.register(new ComponentAB("ComponentAB-1")); - registry.register(new ComponentAB("ComponentAB-2")); - registry.register(new ComponentC("ComponentC-1")); - registry.register(new ComponentC("ComponentC-2")); - - registry.notifyComponents(new ComponentNotifier(ListenerA.class) { - @Override - public void onComponentNotify(ListenerA listener) { - listener.OnTestEventA(); - } - }); - assertResult("ListenerA-ComponentA-1", "ListenerA-ComponentA-2", "ListenerA-ComponentAB-1", "ListenerA-ComponentAB-2"); - - registry.notifyComponents(new ComponentNotifier(ListenerB.class) { - @Override - public void onComponentNotify(ListenerB listener) { - listener.OnTestEventB(); - } - }); - assertResult("ListenerB-ComponentAB-1", "ListenerB-ComponentAB-2"); - - registry.notifyComponents(new ComponentNotifier(ListenerC.class) { - @Override - public void onComponentNotify(ListenerC listener) { - listener.OnTestEventC(); - } - }); - assertResult("ListenerC-ComponentC-1", "ListenerC-ComponentC-2"); - } - - //endregion - - //region Helpers - - interface ListenerA { - void OnTestEventA(); - } - - interface ListenerB { - void OnTestEventB(); - } - - interface ListenerC { - void OnTestEventC(); - } - - class BaseComponent implements ApptentiveComponent{ - - protected final String name; - - BaseComponent(String name) { - this.name = name; - } - } - - class ComponentA extends BaseComponent implements ListenerA { - - ComponentA(String name) { - super(name); - } - - @Override - public void OnTestEventA() { - addResult("ListenerA-" + name); - } - } - - class ComponentAB extends BaseComponent implements ListenerA, ListenerB { - - ComponentAB(String name) { - super(name); - } - - @Override - public void OnTestEventA() { - addResult("ListenerA-" + name); - } - - @Override - public void OnTestEventB() { - addResult("ListenerB-" + name); - } - } - - class ComponentC extends BaseComponent implements ListenerC { - - ComponentC(String name) { - super(name); - } - - @Override - public void OnTestEventC() { - addResult("ListenerC-" + name); - } - } - - //endregion -} \ No newline at end of file From 2f124302fe9fad269c8c14f1c7a20669863fbb25 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 27 Feb 2017 13:51:44 -0800 Subject: [PATCH 113/465] Add javadoc to our app lifecycle listener. --- .../ApptentiveActivityLifecycleCallbacks.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/lifecycle/ApptentiveActivityLifecycleCallbacks.java b/apptentive/src/main/java/com/apptentive/android/sdk/lifecycle/ApptentiveActivityLifecycleCallbacks.java index f458e0b34..af647a443 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/lifecycle/ApptentiveActivityLifecycleCallbacks.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/lifecycle/ApptentiveActivityLifecycleCallbacks.java @@ -17,11 +17,22 @@ import java.util.concurrent.atomic.AtomicInteger; +/** + * 1. Keeps track of whether the app is in the foreground. It does this by counting the number of active Activities. + * 2 Tells the SDK when the app goes to the background (exits), or comes to the foreground (launches). + * 3. Tells the SDK when an Activity starts or resumes, so the SDK can hold a weak reference to the top Activity. + */ public class ApptentiveActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks { private AtomicInteger foregroundActivities = new AtomicInteger(0); + /** + * A Runnable that is postDelayed() in onActivityStopped() if the foregroundActivities is 0. When it runs, if the count is still 0, it fires appEnteredBackground() + */ private Runnable checkFgBgRoutine; + /** + * Set to false when the app goes to the background. + */ private boolean isAppForeground; private Handler delayedChecker = new Handler(); @@ -66,6 +77,11 @@ public void onActivityCreated(Activity activity, Bundle savedInstanceState) { } + /** + * Decrements the count of running Activities. If it is now 0, start a task that will check again + * after a small delay. If that task still finds 0 running Activities, it will trigger an appEnteredBackground() + * @param activity + */ @Override public void onActivityStopped(Activity activity) { if (foregroundActivities.decrementAndGet() < 0) { From 54ce8e918b3247e643b3d3dcc6ac126ca0086abc Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 27 Feb 2017 14:00:07 -0800 Subject: [PATCH 114/465] Made ApptentiveBaseActivity abstract --- .../java/com/apptentive/android/sdk/ApptentiveBaseActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java index a5029c7b7..a753078bb 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java @@ -11,7 +11,7 @@ import static com.apptentive.android.sdk.ApptentiveInternal.NOTIFICATION_INTERACTIONS_SHOULD_DISMISS; /** A base class for any SDK activity */ -public class ApptentiveBaseActivity extends AppCompatActivity implements ApptentiveNotificationObserver { +public abstract class ApptentiveBaseActivity extends AppCompatActivity implements ApptentiveNotificationObserver { //region Activity life cycle From c643d924ba17b2384d27ba227ef16333896c7e6a Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 27 Feb 2017 16:57:33 -0800 Subject: [PATCH 115/465] Fixed fetching interactions when conversation becomes active MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Added support for activity ‘start’ notification • Check if interactions are expired periodically • Fixed calling network on UI-thread --- .../android/sdk/ApptentiveBaseActivity.java | 2 +- .../android/sdk/ApptentiveInternal.java | 15 ++++--- .../android/sdk/ApptentiveNotifications.java | 21 +++++++++ .../android/sdk/ApptentiveViewActivity.java | 4 +- .../sdk/conversation/Conversation.java | 16 ++++++- .../sdk/conversation/ConversationManager.java | 41 +++++++++++++++-- .../notifications/ApptentiveNotification.java | 6 +++ .../ApptentiveNotificationCenter.java | 6 +++ .../ApptentiveNotificationObserver.java | 6 +++ .../android/sdk/util/RuntimeUtils.java | 45 +++++++++++++++++++ .../threading/ConcurrentDispatchQueue.java | 2 +- .../sdk/util/threading/DispatchQueue.java | 16 ++++++- .../sdk/util/threading/DispatchTask.java | 3 +- 13 files changed, 166 insertions(+), 17 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/RuntimeUtils.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java index a753078bb..809095fe7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java @@ -8,7 +8,7 @@ import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; -import static com.apptentive.android.sdk.ApptentiveInternal.NOTIFICATION_INTERACTIONS_SHOULD_DISMISS; +import static com.apptentive.android.sdk.ApptentiveNotifications.*; /** A base class for any SDK activity */ public abstract class ApptentiveBaseActivity extends AppCompatActivity implements ApptentiveNotificationObserver { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 84ecad06a..053594dfe 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -49,6 +49,7 @@ import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.storage.VersionHistoryItem; import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchQueueType; @@ -67,6 +68,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import static com.apptentive.android.sdk.ApptentiveLogTag.*; +import static com.apptentive.android.sdk.ApptentiveNotifications.*; import static com.apptentive.android.sdk.debug.Tester.*; import static com.apptentive.android.sdk.debug.TesterEvent.*; @@ -75,11 +77,6 @@ */ public class ApptentiveInternal { - /** - * Sent if user requested to close all interactions. - */ - public static final String NOTIFICATION_INTERACTIONS_SHOULD_DISMISS = "NOTIFICATION_INTERACTIONS_SHOULD_DISMISS"; - static AtomicBoolean isApptentiveInitialized = new AtomicBoolean(false); private final MessageManager messageManager; private final PayloadSendWorker payloadWorker; @@ -104,7 +101,7 @@ public class ApptentiveInternal { String appPackageName; // private background serial dispatch queue for internal SDK tasks - private final DispatchQueue backgroundQueue; + private final DispatchQueue backgroundQueue; // TODO: replace with a global concurrent queue? // toolbar theme specified in R.attr.apptentiveToolbarTheme Resources.Theme apptentiveToolbarTheme; @@ -160,7 +157,7 @@ private ApptentiveInternal(Context context, String apiKey, String serverUrl) { globalSharedPrefs = context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); backgroundQueue = DispatchQueue.createBackgroundQueue("Apptentive Serial Queue", DispatchQueueType.Serial); apptentiveHttpClient = new ApptentiveHttpClient(apiKey, getEndpointBase(globalSharedPrefs)); - conversationManager = new ConversationManager(appContext, backgroundQueue, Util.getInternalDir(appContext, "conversations", true)); + conversationManager = new ConversationManager(appContext, DispatchQueue.backgroundQueue(), Util.getInternalDir(appContext, "conversations", true)); appRelease = AppReleaseManager.generateCurrentAppRelease(context, this); messageManager = new MessageManager(); @@ -439,6 +436,10 @@ public void onActivityStarted(Activity activity) { // Set current foreground activity reference whenever a new activity is started currentTaskStackTopActivity = new WeakReference<>(activity); messageManager.setCurrentForegroundActivity(activity); + + // Fire a notification + ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_ACTIVITY_STARTED, + ObjectUtils.toMap(NOTIFICATION_ACTIVITY_STARTED_KEY_ACTIVITY_CLASS, activity.getClass())); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java new file mode 100644 index 000000000..e7d731075 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk; + +public class ApptentiveNotifications { + + /** + * Sent if a new activity is started. + */ + public static final String NOTIFICATION_ACTIVITY_STARTED = "NOTIFICATION_ACTIVITY_STARTED"; // { activityClass : Class } + public static final String NOTIFICATION_ACTIVITY_STARTED_KEY_ACTIVITY_CLASS = "activityClass"; + + /** + * Sent if user requested to close all interactions. + */ + public static final String NOTIFICATION_INTERACTIONS_SHOULD_DISMISS = "NOTIFICATION_INTERACTIONS_SHOULD_DISMISS"; +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index 4600b5acc..e1e0f443b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -39,6 +39,8 @@ import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; +import static com.apptentive.android.sdk.ApptentiveNotifications.*; + public class ApptentiveViewActivity extends ApptentiveBaseActivity implements ApptentiveBaseFragment.OnFragmentTransitionListener { private static final String FRAGMENT_TAG = "fragmentTag"; @@ -407,7 +409,7 @@ private void setStatusBarColor() { @Override public void onReceiveNotification(ApptentiveNotification notification) { - if (notification.getName().equals(ApptentiveInternal.NOTIFICATION_INTERACTIONS_SHOULD_DISMISS)) { + if (notification.getName().equals(NOTIFICATION_INTERACTIONS_SHOULD_DISMISS)) { if (!isFinishing()) { exitActivity(ApptentiveViewExitType.NOTIFICATION); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 480253f74..17aa79a36 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -6,6 +6,7 @@ package com.apptentive.android.sdk.conversation; +import android.content.Context; import android.content.SharedPreferences; import android.text.TextUtils; @@ -27,8 +28,8 @@ import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.VersionHistory; import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.util.RuntimeUtils; import com.apptentive.android.sdk.util.Util; -import com.apptentive.android.sdk.util.threading.DispatchTask; import org.json.JSONException; @@ -97,6 +98,19 @@ public Interaction getApplicableInteraction(String eventLabel) { return null; } + /** + * Attempts to fetch interactions synchronously. Returns true is successful. + */ + boolean checkFetchInteractions(Context context) { + boolean cacheExpired = getInteractionExpiration() > Util.currentTimeSeconds(); + if (cacheExpired || RuntimeUtils.isAppDebuggable(context)) { + return fetchInteractions(); + } + + ApptentiveLog.v(CONVERSATION, "Interaction cache is still valid"); + return false; + } + /** * Fetches interaction synchronously. Returns true if succeed. */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 5d8facf29..a2eabf628 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -7,6 +7,9 @@ import com.apptentive.android.sdk.model.ConversationTokenRequest; import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; +import com.apptentive.android.sdk.notifications.ApptentiveNotification; +import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; +import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; import com.apptentive.android.sdk.serialization.ObjectSerialization; import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.AppReleaseManager; @@ -28,6 +31,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import static com.apptentive.android.sdk.ApptentiveLogTag.*; +import static com.apptentive.android.sdk.ApptentiveNotifications.*; import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; import static com.apptentive.android.sdk.debug.TesterEvent.*; @@ -65,7 +69,7 @@ public class ConversationManager implements DataChangedListener { private final AtomicBoolean isConversationTokenFetchPending = new AtomicBoolean(false); - public ConversationManager(Context context, DispatchQueue operationQueue, File storageDir) { + public ConversationManager(Context context, final DispatchQueue operationQueue, File storageDir) { if (context == null) { throw new IllegalArgumentException("Context is null"); } @@ -77,6 +81,15 @@ public ConversationManager(Context context, DispatchQueue operationQueue, File s this.contextRef = new WeakReference<>(context.getApplicationContext()); this.operationQueue = operationQueue; this.storageDir = storageDir; + + ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_ACTIVITY_STARTED, + new ApptentiveNotificationObserver() { + @Override + public void onReceiveNotification(ApptentiveNotification notification) { + ApptentiveLog.v(CONVERSATION, "Activity 'start' notification received. Trying to fetch interactions..."); + operationQueue.dispatchAsyncOnce(checkFetchInteractionsTask); + } + }); } //region Conversations @@ -149,6 +162,29 @@ private Conversation loadConversation(ConversationMetadataItem item) { //endregion + //region Interactions + + private final DispatchTask checkFetchInteractionsTask = new DispatchTask() { + @Override + protected void execute() { + ApptentiveLog.v(CONVERSATION, "Checking fetch interactions..."); + + if (activeConversation != null) { + Context context = getContext(); + if (context == null) { + ApptentiveLog.w(CONVERSATION, "Unable to check fetch interactions: context is lost"); + return; + } + + activeConversation.checkFetchInteractions(context); + } else { + ApptentiveLog.d(CONVERSATION, "Unable to check interaction fetch: active conversation is missing"); + } + } + }; + + //endregion + //region Conversation fetching private void fetchConversationToken() { @@ -244,8 +280,7 @@ public void onFail(HttpJsonRequest request, String reason) { private void notifyConversationBecameActive() { dispatchDebugEvent(EVT_CONVERSATION_BECAME_ACTIVE); - boolean fetchSucceed = activeConversation.fetchInteractions(); - dispatchDebugEvent(EVT_INTERACTION_FETCH, fetchSucceed); + operationQueue.dispatchAsyncOnce(checkFetchInteractionsTask); } private synchronized void setActiveConversation(Conversation conversation) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java index f1441251f..308a00a6f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + package com.apptentive.android.sdk.notifications; import com.apptentive.android.sdk.util.StringUtils; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java index a288ea9a6..f9f1cd042 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + package com.apptentive.android.sdk.notifications; import com.apptentive.android.sdk.util.threading.DispatchQueue; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserver.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserver.java index ec41f3438..7cda3d744 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserver.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserver.java @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + package com.apptentive.android.sdk.notifications; /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/RuntimeUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/RuntimeUtils.java new file mode 100644 index 000000000..4e05c8db5 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/RuntimeUtils.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; + +import com.apptentive.android.sdk.ApptentiveLog; + +/** + * Collection of helper functions for Android runtime queries. + */ +public class RuntimeUtils { + /** + * Returns true is the app is running in a debug mode + */ + public static boolean isAppDebuggable(Context context) { + if (context == null) { + throw new IllegalArgumentException("Context is null"); + } + + try { + final String appPackageName = context.getPackageName(); + final PackageManager packageManager = context.getPackageManager(); + + PackageInfo packageInfo = packageManager.getPackageInfo(appPackageName, PackageManager.GET_META_DATA | PackageManager.GET_RECEIVERS); + ApplicationInfo ai = packageInfo.applicationInfo; + Bundle metaData = ai.metaData; + if (metaData != null) { + return (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } + } catch (Exception e) { + ApptentiveLog.e("Failed to read app's PackageInfo."); + } + + return false; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueue.java index 7bf13d58c..14e5d02e6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueue.java @@ -56,7 +56,7 @@ public void stop() { @Override public Thread newThread(Runnable r) { - return new Thread(r, name + "-thread-" + threadNumber.getAndIncrement()); + return new Thread(r, name + " (thread-" + threadNumber.getAndIncrement() + ")"); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java index c001f1e96..afcd55694 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java @@ -84,7 +84,14 @@ public boolean dispatchAsyncOnce(DispatchTask task, long delayMillis) { * A global dispatch queue associated with main thread */ public static DispatchQueue mainQueue() { - return Holder.INSTANCE; + return Holder.MAIN_QUEUE; + } + + /** + * A global dispatch concurrent queue + */ + public static DispatchQueue backgroundQueue() { + return Holder.BACKGROUND_QUEUE; } /** @@ -105,7 +112,8 @@ public static DispatchQueue createBackgroundQueue(String name, DispatchQueueType * Thread safe singleton trick */ private static class Holder { - private static final DispatchQueue INSTANCE = createMainQueue(); + private static final DispatchQueue MAIN_QUEUE = createMainQueue(); + private static final DispatchQueue BACKGROUND_QUEUE = createBackgroundQueue(); private static DispatchQueue createMainQueue() { try { @@ -116,5 +124,9 @@ private static DispatchQueue createMainQueue() { return null; } } + + private static DispatchQueue createBackgroundQueue() { + return new ConcurrentDispatchQueue("Apptentive Background Queue"); + } } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java index e854f6dff..6b7ebe7d3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java @@ -26,10 +26,11 @@ public abstract class DispatchTask implements Runnable { @Override public void run() { try { - setScheduled(false); execute(); } catch (Exception e) { ApptentiveLog.e(e, "Exception while executing task"); + } finally { + setScheduled(false); } } From f4bb8c07024a7592d378bd2274be37bb840302c3 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 27 Feb 2017 20:29:31 -0800 Subject: [PATCH 116/465] Send test event when Interaction fetch completes. --- .../android/sdk/conversation/ConversationManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index a2eabf628..50c632297 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -175,8 +175,8 @@ protected void execute() { ApptentiveLog.w(CONVERSATION, "Unable to check fetch interactions: context is lost"); return; } - - activeConversation.checkFetchInteractions(context); + boolean interactionsWereFetched = activeConversation.checkFetchInteractions(context); + dispatchDebugEvent(EVT_INTERACTION_FETCH, interactionsWereFetched); } else { ApptentiveLog.d(CONVERSATION, "Unable to check interaction fetch: active conversation is missing"); } From 41629e13cebf6fb45a1dd77cdd0c36f7c094b75c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 28 Feb 2017 11:36:22 -0800 Subject: [PATCH 117/465] Added functionality for ending active conversation --- .../android/sdk/ApptentiveInternal.java | 4 ++ .../android/sdk/ApptentiveNotifications.java | 5 ++ .../android/sdk/ApptentiveViewActivity.java | 5 +- .../sdk/conversation/ConversationManager.java | 16 ++++++ .../conversation/ConversationMetadata.java | 53 ++++++++++++------- .../ConversationMetadataItem.java | 6 ++- .../notifications/ApptentiveNotification.java | 4 ++ .../android/sdk/util/ObjectUtils.java | 7 --- 8 files changed, 72 insertions(+), 28 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 053594dfe..351179701 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -371,6 +371,10 @@ public ApptentiveTaskManager getApptentiveTaskManager() { return taskManager; } + public ConversationManager getConversationManager() { + return conversationManager; + } + public Resources.Theme getApptentiveToolbarTheme() { return apptentiveToolbarTheme; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java index e7d731075..ae3e3bbd3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -8,6 +8,11 @@ public class ApptentiveNotifications { + /** + * Sent if conversation becomes inactive (user logs out, etc + */ + public static final String NOTIFICATION_CONVERSATION_BECAME_INACTIVE = "CONVERSATION_BECAME_INACTIVE"; + /** * Sent if a new activity is started. */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index e1e0f443b..23658115a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -409,9 +409,10 @@ private void setStatusBarColor() { @Override public void onReceiveNotification(ApptentiveNotification notification) { - if (notification.getName().equals(NOTIFICATION_INTERACTIONS_SHOULD_DISMISS)) { + if (notification.hasName(NOTIFICATION_INTERACTIONS_SHOULD_DISMISS) || + notification.hasName(NOTIFICATION_CONVERSATION_BECAME_INACTIVE)) { if (!isFinishing()) { - exitActivity(ApptentiveViewExitType.NOTIFICATION); + exitActivity(ApptentiveViewExitType.NOTIFICATION); // TODO: different exit types for different notifications? } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 50c632297..f9baf9367 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -160,6 +160,16 @@ private Conversation loadConversation(ConversationMetadataItem item) { return (Conversation) serializer.deserialize(); } + /** Ends active conversation (user logs out, etc) */ + public synchronized boolean endActiveConversation() { + if (activeConversation != null) { + setActiveConversation(null); + ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_CONVERSATION_BECAME_INACTIVE); + return true; + } + return false; + } + //endregion //region Interactions @@ -347,8 +357,10 @@ private ConversationMetadata resolveMetadata() { private void saveMetadata() { try { + long start = System.currentTimeMillis(); File metaFile = new File(storageDir, CONVERSATION_METADATA_PATH); ObjectSerialization.serialize(metaFile, conversationMetadata); + ApptentiveLog.v(CONVERSATION, "Saved metadata (took %d ms)", System.currentTimeMillis() - start); } catch (Exception e) { ApptentiveLog.e(CONVERSATION, "Exception while saving metadata"); } @@ -371,6 +383,10 @@ public Conversation getActiveConversation() { return activeConversation; } + public ConversationMetadata getConversationMetadata() { + return conversationMetadata; + } + private Context getContext() { return contextRef.get(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java index 440b237c5..fbebccbe5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java @@ -7,6 +7,7 @@ import java.io.DataOutput; import java.io.IOException; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import static com.apptentive.android.sdk.conversation.ConversationMetadataItem.*; @@ -14,7 +15,7 @@ /** * Class which represents all conversation entries stored on the disk */ -class ConversationMetadata implements SerializableObject { +public class ConversationMetadata implements SerializableObject, Iterable { private static final byte VERSION = 1; private final List items; @@ -49,27 +50,34 @@ public void writeExternal(DataOutput out) throws IOException { //endregion - //region Conversatiosn + //region Conversation // TODO: replace it with notifications so that the active conversation can send out events and clean itself up. - public void setActiveConversation(final Conversation conversation) - { - // clear 'active' state - boolean found = false; - for (ConversationMetadataItem item : items) { - if (StringUtils.equal(conversation.getConversationId(), item.conversationId)) { - found = true; - item.state = CONVERSATION_STATE_ACTIVE; - } else if (item.state == CONVERSATION_STATE_ACTIVE) { - item.state = CONVERSATION_STATE_INACTIVE; + void setActiveConversation(final Conversation conversation) { + if (conversation != null) { + // clear 'active' state + boolean found = false; + for (ConversationMetadataItem item : items) { + if (StringUtils.equal(conversation.getConversationId(), item.conversationId)) { + found = true; + item.state = CONVERSATION_STATE_ACTIVE; + } else if (item.state == CONVERSATION_STATE_ACTIVE) { + item.state = CONVERSATION_STATE_INACTIVE; + } } - } - // add a new item if it was not found - if (!found) { - final ConversationMetadataItem item = new ConversationMetadataItem(conversation.getConversationId(), conversation.getFilename()); - item.state = CONVERSATION_STATE_ACTIVE; - items.add(item); + // add a new item if it was not found + if (!found) { + final ConversationMetadataItem item = new ConversationMetadataItem(conversation.getConversationId(), conversation.getFilename()); + item.state = CONVERSATION_STATE_ACTIVE; + items.add(item); + } + } else { + for (ConversationMetadataItem item : items) { + if (item.state == CONVERSATION_STATE_ACTIVE) { + item.state = CONVERSATION_STATE_ACTIVE; + } + } } } @@ -88,6 +96,15 @@ public ConversationMetadataItem findItem(Filter filter) { //endregion + //region Iterable + + @Override + public Iterator iterator() { + return items.iterator(); + } + + //endregion + //region Getters/Setters public List getItems() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index 2e8b38532..d0500d710 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -10,7 +10,7 @@ /** * A light weight representation of the conversation object stored on the disk. */ -class ConversationMetadataItem implements SerializableObject { +public class ConversationMetadataItem implements SerializableObject { /** * Conversation state is not known */ @@ -62,6 +62,10 @@ public void writeExternal(DataOutput out) throws IOException { out.writeByte(state); } + public String getConversationId() { + return conversationId; + } + public boolean isActive() { return state == CONVERSATION_STATE_ACTIVE; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java index 308a00a6f..ac251db5d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java @@ -33,6 +33,10 @@ public String getName() { return name; } + public boolean hasName(String name) { + return StringUtils.equal(this.name, name); + } + public Map getUserInfo() { return userInfo; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java index 97b36cd66..dca097d7c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java @@ -21,8 +21,6 @@ package com.apptentive.android.sdk.util; -import static com.apptentive.android.sdk.debug.Assert.*; - import java.util.HashMap; import java.util.Map; @@ -39,11 +37,6 @@ public static T as(Object object, Class cls) { return cls.isInstance(object) ? (T) object : null; } - public static T notNull(T object, String message) { - assertNotNull(object, message); - return object; - } - public static Map toMap(Object... args) { if (args.length % 2 != 0) { throw new IllegalArgumentException("Invalid args"); From 1f081f2a95ea1ff37d499b843ae96e417593baed Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 28 Feb 2017 11:53:18 -0800 Subject: [PATCH 118/465] Fixed ending active conversation + log system improvements --- .../apptentive/android/sdk/ApptentiveLog.java | 57 ++++++++++++------- .../conversation/ConversationMetadata.java | 2 +- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java index fa1450467..4e9b94a04 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java @@ -20,7 +20,7 @@ public static void overrideLogLevel(Level level) { ApptentiveLog.logLevel = level; } - private static void doLog(Level level, Throwable throwable, String message, Object... args){ + private static void doLog(Level level, ApptentiveLogTag tag, Throwable throwable, String message, Object... args){ if(canLog(level) && message != null){ if(args.length > 0){ try{ @@ -30,10 +30,27 @@ private static void doLog(Level level, Throwable throwable, String message, Obje level = Level.ERROR; } } + + String extra = null; + // add thread name if logging of the UI-thread if (Looper.getMainLooper() != null && Looper.getMainLooper().getThread() != Thread.currentThread()) { - message = String.format("[%s] %s", Thread.currentThread().getName(), message); + extra = '[' + Thread.currentThread().getName() + ']'; + } + + // custom tag + if (tag != null) { + if (extra == null) { + extra = '[' + tag.toString() + ']'; + } else { + extra += " [" + tag.toString() + ']'; + } + } + + if (extra != null) { + message = extra + " " + message; } + android.util.Log.println(level.getLevel(), TAG, message); if(throwable != null){ if(throwable.getMessage() != null){ @@ -53,74 +70,74 @@ public static boolean canLog(Level level) { public static void v(ApptentiveLogTag tag, String message, Object... args) { if (tag.enabled) { - doLog(Level.VERBOSE, null, message, args); + doLog(Level.VERBOSE, tag, null, message, args); } } public static void v(String message, Object... args){ - doLog(Level.VERBOSE, null, message, args); + doLog(Level.VERBOSE, null, null, message, args); } public static void v(String message, Throwable throwable, Object... args){ - doLog(Level.VERBOSE, throwable, message, args); + doLog(Level.VERBOSE, null, throwable, message, args); } public static void d(ApptentiveLogTag tag, String message, Object... args){ if (tag.enabled) { - doLog(Level.DEBUG, null, message, args); + doLog(Level.DEBUG, tag, null, message, args); } } public static void d(String message, Object... args){ - doLog(Level.DEBUG, null, message, args); + doLog(Level.DEBUG, null, null, message, args); } public static void d(String message, Throwable throwable, Object... args){ - doLog(Level.DEBUG, throwable, message, args); + doLog(Level.DEBUG, null, throwable, message, args); } public static void i(ApptentiveLogTag tag, String message, Object... args){ if (tag.enabled) { - doLog(Level.INFO, null, message, args); + doLog(Level.INFO, tag, null, message, args); } } public static void i(String message, Object... args){ - doLog(Level.INFO, null, message, args); + doLog(Level.INFO, null, null, message, args); } public static void i(String message, Throwable throwable, Object... args){ - doLog(Level.INFO, throwable, message, args); + doLog(Level.INFO, null, throwable, message, args); } public static void w(ApptentiveLogTag tag, String message, Object... args){ if (tag.enabled) { - doLog(Level.WARN, null, message, args); + doLog(Level.WARN, tag, null, message, args); } } public static void w(String message, Object... args){ - doLog(Level.WARN, null, message, args); + doLog(Level.WARN, null, null, message, args); } public static void w(String message, Throwable throwable, Object... args){ - doLog(Level.WARN, throwable, message, args); + doLog(Level.WARN, null, throwable, message, args); } public static void e(ApptentiveLogTag tag, String message, Object... args){ if (tag.enabled) { - doLog(Level.ERROR, null, message, args); + doLog(Level.ERROR, tag, null, message, args); } } public static void e(String message, Object... args){ - doLog(Level.ERROR, null, message, args); + doLog(Level.ERROR, null, null, message, args); } public static void e(String message, Throwable throwable, Object... args){ - doLog(Level.ERROR, throwable, message, args); + doLog(Level.ERROR, null, throwable, message, args); } public static void e(Throwable throwable, String message, Object... args){ - doLog(Level.ERROR, throwable, message, args); + doLog(Level.ERROR, null, throwable, message, args); } public static void a(String message, Object... args){ - doLog(Level.ASSERT, null, message, args); + doLog(Level.ASSERT, null, null, message, args); } public static void a(String message, Throwable throwable, Object... args){ - doLog(Level.ASSERT, throwable, message, args); + doLog(Level.ASSERT, null, throwable, message, args); } public enum Level { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java index fbebccbe5..d355fa02a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java @@ -75,7 +75,7 @@ void setActiveConversation(final Conversation conversation) { } else { for (ConversationMetadataItem item : items) { if (item.state == CONVERSATION_STATE_ACTIVE) { - item.state = CONVERSATION_STATE_ACTIVE; + item.state = CONVERSATION_STATE_INACTIVE; } } } From 7f3b13ecc4bafd6528785200cf828ce276a303fc Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 28 Feb 2017 12:02:13 -0800 Subject: [PATCH 119/465] Log system improvements --- .../apptentive/android/sdk/ApptentiveLogTag.java | 13 +++++++++---- .../sdk/notifications/ApptentiveNotification.java | 2 +- .../notifications/ApptentiveNotificationCenter.java | 4 ++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java index ad188f8c9..af0d11efa 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java @@ -1,9 +1,14 @@ package com.apptentive.android.sdk; public enum ApptentiveLogTag { - NETWORK, - CONVERSATION, - TESTER_COMMANDS; + NETWORK(true), + CONVERSATION(true), + NOTIFICATIONS(true), + TESTER_COMMANDS(false); - public boolean enabled = true; + ApptentiveLogTag(boolean enabled) { + this.enabled = enabled; + } + + public boolean enabled; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java index ac251db5d..9a1891dfa 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java @@ -43,6 +43,6 @@ public Map getUserInfo() { @Override public String toString() { - return String.format("[%s] name=%s userInfo=%s", name, StringUtils.toString(userInfo)); + return String.format("name=%s userInfo={%s}", name, StringUtils.toString(userInfo)); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java index f9f1cd042..5d65c986c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java @@ -6,6 +6,7 @@ package com.apptentive.android.sdk.notifications; +import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -13,6 +14,8 @@ import java.util.HashMap; import java.util.Map; +import static com.apptentive.android.sdk.ApptentiveLogTag.*; + /** * An {@link ApptentiveNotificationCenter} object (or simply, notification center) provides a * mechanism for broadcasting information within a program. An {@link ApptentiveNotificationCenter} @@ -130,6 +133,7 @@ public void postNotification(String name, Map userInfo) { * Posts a given notification to the receiver. */ public void postNotification(final ApptentiveNotification notification) { + ApptentiveLog.v(NOTIFICATIONS, "Post notification: %s", notification); operationQueue.dispatchAsync(new DispatchTask() { @Override protected void execute() { From 73083f1dafd593ac5da13527ad2607748acada5f Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 28 Feb 2017 14:53:22 -0800 Subject: [PATCH 120/465] Added CONVERSATION_BECAME_ACTIVE notification + extra debug logic --- .../android/sdk/ApptentiveNotifications.java | 8 ++++- .../sdk/conversation/ConversationManager.java | 33 +++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java index ae3e3bbd3..b393c11e4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -9,7 +9,13 @@ public class ApptentiveNotifications { /** - * Sent if conversation becomes inactive (user logs out, etc + * Sent when conversation becomes active (user logs out, etc) + */ + public static final String NOTIFICATION_CONVERSATION_BECAME_ACTIVE = "CONVERSATION_BECAME_ACTIVE"; // { conversation : Conversation } + public static final String NOTIFICATION_CONVERSATION_BECAME_ACTIVE_KEY_CONVERSATION = "conversation"; + + /** + * Sent when conversation becomes inactive (user logs out, etc) */ public static final String NOTIFICATION_CONVERSATION_BECAME_INACTIVE = "CONVERSATION_BECAME_INACTIVE"; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index f9baf9367..e561ed41a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -19,6 +19,7 @@ import com.apptentive.android.sdk.storage.FileSerializer; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; +import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -163,7 +164,7 @@ private Conversation loadConversation(ConversationMetadataItem item) { /** Ends active conversation (user logs out, etc) */ public synchronized boolean endActiveConversation() { if (activeConversation != null) { - setActiveConversation(null); + setActiveConversation((Conversation) null); ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_CONVERSATION_BECAME_INACTIVE); return true; } @@ -289,10 +290,38 @@ public void onFail(HttpJsonRequest request, String reason) { } private void notifyConversationBecameActive() { - dispatchDebugEvent(EVT_CONVERSATION_BECAME_ACTIVE); + dispatchDebugEvent(EVT_CONVERSATION_BECAME_ACTIVE); // TODO: remove debug event and listen to the notification instead + ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_CONVERSATION_BECAME_ACTIVE, + ObjectUtils.toMap(NOTIFICATION_CONVERSATION_BECAME_ACTIVE_KEY_CONVERSATION, activeConversation)); operationQueue.dispatchAsyncOnce(checkFetchInteractionsTask); } + /* For testing purposes */ + public synchronized boolean setActiveConversation(final String conversationId) { + final ConversationMetadataItem item = conversationMetadata.findItem(new ConversationMetadata.Filter() { + @Override + public boolean accept(ConversationMetadataItem item) { + return item.conversationId.equals(conversationId); + } + }); + + if (item == null) { + ApptentiveLog.w(CONVERSATION, "Conversation not found: %s", conversationId); + return false; + } + + final Conversation conversation = loadConversation(item); + if (conversation == null) { + ApptentiveLog.w(CONVERSATION, "Conversation not loaded: %s", conversationId); + return false; + } + + setActiveConversation(conversation); + + notifyConversationBecameActive(); + return true; + } + private synchronized void setActiveConversation(Conversation conversation) { activeConversation = conversation; conversationMetadata.setActiveConversation(conversation); From 82f023e3e40280ad7747ddb38d174e685f4f874d Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 28 Feb 2017 17:35:45 -0800 Subject: [PATCH 121/465] Fixed dismissing UI when active conversation is ended --- .../java/com/apptentive/android/sdk/ApptentiveBaseActivity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java index 809095fe7..5c37af1fe 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java @@ -33,6 +33,7 @@ protected void onDestroy() { protected void registerNotifications() { ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_INTERACTIONS_SHOULD_DISMISS, this); + ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_CONVERSATION_BECAME_INACTIVE, this); } protected void unregisterNotification() { From 2eb518bfcb1298859ace3a5bec8eac756e41d8bc Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 2 Mar 2017 11:39:21 -0800 Subject: [PATCH 122/465] Changed event communication protocol Use map instead of list for payload --- .../apptentive/android/sdk/debug/Tester.java | 22 ++++++++++++------- .../android/sdk/debug/TesterEvent.java | 2 ++ .../sdk/debug/TesterEventListener.java | 4 +++- .../android/sdk/model/EventManager.java | 4 ++-- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java index 508ae63c4..8c3a37a01 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java @@ -1,6 +1,9 @@ package com.apptentive.android.sdk.debug; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.util.ObjectUtils; + +import java.util.Map; import static com.apptentive.android.sdk.debug.TesterEvent.*; @@ -33,19 +36,19 @@ public static boolean isListeningForDebugEvents() { public static void dispatchDebugEvent(String name) { if (isListeningForDebugEvents()) { - notifyEvent(name); + notifyEvent(name, null); } } - public static void dispatchDebugEvent(String name, Object arg) { + public static void dispatchDebugEvent(String name, boolean successful) { if (isListeningForDebugEvents()) { - notifyEvent(name, arg); + notifyEvent(name, ObjectUtils.toMap("successful", successful)); } } - public static void dispatchDebugEvent(String name, boolean arg) { + public static void dispatchDebugEvent(String name, String key, Object value) { if (isListeningForDebugEvents()) { - notifyEvent(name, arg); + notifyEvent(name, ObjectUtils.toMap(key, value)); } } @@ -59,13 +62,16 @@ public static void dispatchException(Throwable e) { stackTrace.append('\n'); } } - notifyEvent(EVT_EXCEPTION, e.getClass().getName(), e.getMessage(), stackTrace.toString()); + notifyEvent(EVT_EXCEPTION, ObjectUtils.toMap( + "class,", e.getClass().getName(), + "message", e.getMessage(), + "stacktrace", stackTrace.toString())); } } - private static void notifyEvent(String name, Object... args) { + private static void notifyEvent(String name, Map userInfo) { try { - instance.listener.onDebugEvent(name, args); + instance.listener.onDebugEvent(name, userInfo); } catch (Exception e) { ApptentiveLog.e(e, "Error while dispatching debug event: %s", name); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java index 2fe0c773d..7fa93ce72 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java @@ -9,5 +9,7 @@ public class TesterEvent { public static final String EVT_INSTANCE_CREATED = "instance_created"; // { successful:boolean } public static final String EVT_EXCEPTION = "exception"; // { class:String, message:String, stackTrace:String } public static final String EVT_APPTENTIVE_EVENT = "apptentive_event"; // { eventLabel:String } + public static final String EVT_APPTENTIVE_EVENT_KEY_EVENT_LABEL = "eventLabel"; + public static final String EVT_CONVERSATION_BECAME_ACTIVE = "conversation_became_active"; // { } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEventListener.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEventListener.java index b262939a6..13dc553aa 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEventListener.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEventListener.java @@ -1,6 +1,8 @@ package com.apptentive.android.sdk.debug; +import java.util.Map; + public interface TesterEventListener { - void onDebugEvent(String name, Object... params); + void onDebugEvent(String name, Map userInfo); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java index 8fd7150f8..3a859d541 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java @@ -10,7 +10,7 @@ import com.apptentive.android.sdk.storage.EventStore; import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; -import static com.apptentive.android.sdk.debug.TesterEvent.EVT_APPTENTIVE_EVENT; +import static com.apptentive.android.sdk.debug.TesterEvent.*; public class EventManager { @@ -19,7 +19,7 @@ private static EventStore getEventStore() { } public static void sendEvent(Event event) { - dispatchDebugEvent(EVT_APPTENTIVE_EVENT, event.getEventLabel()); + dispatchDebugEvent(EVT_APPTENTIVE_EVENT, EVT_APPTENTIVE_EVENT_KEY_EVENT_LABEL, event.getEventLabel()); getEventStore().addPayload(event); } } From 9fe6aba5d7da33dbec9947146a52a412207f1e2e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 15 Mar 2017 17:26:14 -0700 Subject: [PATCH 123/465] Added anonymous conversation support --- .../android/sdk/ApptentiveBaseActivity.java | 2 +- .../android/sdk/ApptentiveInternal.java | 4 +- .../android/sdk/ApptentiveNotifications.java | 11 +- .../android/sdk/ApptentiveViewActivity.java | 36 ++- .../sdk/comm/ApptentiveHttpClient.java | 1 + .../sdk/conversation/Conversation.java | 145 +++++++-- .../sdk/conversation/ConversationManager.java | 303 ++++++++---------- .../conversation/ConversationMetadata.java | 63 ++-- .../ConversationMetadataItem.java | 45 +-- .../sdk/conversation/ConversationState.java | 49 +++ .../apptentive/android/sdk/debug/Assert.java | 65 +--- .../apptentive/android/sdk/debug/Tester.java | 8 +- .../android/sdk/debug/TesterEvent.java | 6 +- .../notifications/ApptentiveNotification.java | 5 + .../android/sdk/storage/FileSerializer.java | 21 +- .../android/sdk/storage/Serializer.java | 4 +- .../sdk/storage/SerializerException.java | 13 + 17 files changed, 431 insertions(+), 350 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationState.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/SerializerException.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java index 5c37af1fe..5efe1d9e6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java @@ -33,7 +33,7 @@ protected void onDestroy() { protected void registerNotifications() { ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_INTERACTIONS_SHOULD_DISMISS, this); - ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_CONVERSATION_BECAME_INACTIVE, this); + ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, this); } protected void unregisterNotification() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 351179701..6da9d9a4b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -157,7 +157,7 @@ private ApptentiveInternal(Context context, String apiKey, String serverUrl) { globalSharedPrefs = context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); backgroundQueue = DispatchQueue.createBackgroundQueue("Apptentive Serial Queue", DispatchQueueType.Serial); apptentiveHttpClient = new ApptentiveHttpClient(apiKey, getEndpointBase(globalSharedPrefs)); - conversationManager = new ConversationManager(appContext, DispatchQueue.backgroundQueue(), Util.getInternalDir(appContext, "conversations", true)); + conversationManager = new ConversationManager(appContext, Util.getInternalDir(appContext, "conversations", true)); appRelease = AppReleaseManager.generateCurrentAppRelease(context, this); messageManager = new MessageManager(); @@ -537,7 +537,7 @@ public boolean init() { */ long start = System.currentTimeMillis(); - boolean conversationLoaded = conversationManager.loadActiveConversation(); + boolean conversationLoaded = conversationManager.loadActiveConversation(getApplicationContext()); ApptentiveLog.i(CONVERSATION, "Active conversation is%s loaded. Took %d ms", conversationLoaded ? " not" : "", System.currentTimeMillis() - start); if (conversationLoaded) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java index b393c11e4..61549d544 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -9,15 +9,10 @@ public class ApptentiveNotifications { /** - * Sent when conversation becomes active (user logs out, etc) + * Sent when conversation state changes (user logs out, etc) */ - public static final String NOTIFICATION_CONVERSATION_BECAME_ACTIVE = "CONVERSATION_BECAME_ACTIVE"; // { conversation : Conversation } - public static final String NOTIFICATION_CONVERSATION_BECAME_ACTIVE_KEY_CONVERSATION = "conversation"; - - /** - * Sent when conversation becomes inactive (user logs out, etc) - */ - public static final String NOTIFICATION_CONVERSATION_BECAME_INACTIVE = "CONVERSATION_BECAME_INACTIVE"; + public static final String NOTIFICATION_CONVERSATION_STATE_DID_CHANGE = "CONVERSATION_STATE_DID_CHANGE"; // { conversation : Conversation } + public static final String NOTIFICATION_CONVERSATION_STATE_DID_CHANGE_KEY_CONVERSATION = "conversation"; /** * Sent if a new activity is started. diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index 23658115a..b4df24400 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -18,7 +18,6 @@ import android.os.Build; import android.os.Bundle; import android.support.v4.app.FragmentManager; - import android.support.v4.content.ContextCompat; import android.support.v4.content.IntentCompat; import android.support.v4.content.res.ResourcesCompat; @@ -31,6 +30,8 @@ import android.view.WindowManager; import com.apptentive.android.sdk.adapter.ApptentiveViewPagerAdapter; +import com.apptentive.android.sdk.conversation.Conversation; +import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.model.FragmentFactory; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.fragment.ApptentiveBaseFragment; @@ -81,8 +82,8 @@ protected void onCreate(Bundle savedInstanceState) { if (fragmentType != Constants.FragmentTypes.UNKNOWN) { if (fragmentType == Constants.FragmentTypes.INTERACTION || - fragmentType == Constants.FragmentTypes.MESSAGE_CENTER_ERROR || - fragmentType == Constants.FragmentTypes.ABOUT) { + fragmentType == Constants.FragmentTypes.MESSAGE_CENTER_ERROR || + fragmentType == Constants.FragmentTypes.ABOUT) { bundle.putInt("toolbarLayoutId", R.id.apptentive_toolbar); if (newFragment == null) { newFragment = FragmentFactory.createFragmentInstance(bundle); @@ -127,14 +128,14 @@ protected void onCreate(Bundle savedInstanceState) { actionBar.setDisplayHomeAsUpEnabled(true); int navIconResId = newFragment.getToolbarNavigationIconResourceId(getTheme()); // Check if fragment may show an alternative navigation icon - if ( navIconResId != 0) { + if (navIconResId != 0) { /* In order for the alternative icon has the same color used by toolbar icon, * need to apply the same color in toolbar theme * By default colorControlNormal has same value as textColorPrimary defined in toolbar theme overlay */ final Drawable alternateUpArrow = ResourcesCompat.getDrawable(getResources(), - navIconResId, - getTheme()); + navIconResId, + getTheme()); int colorControlNormal = Util.getThemeColor(ApptentiveInternal.getInstance().getApptentiveToolbarTheme(), R.attr.colorControlNormal); alternateUpArrow.setColorFilter(colorControlNormal, PorterDuff.Mode.SRC_ATOP); @@ -273,7 +274,7 @@ public void onFragmentTransition(ApptentiveBaseFragment currentFragment) { } private void applyApptentiveTheme(boolean isModalInteraction) { - // Update the activity theme to reflect current attributes + // Update the activity theme to reflect current attributes try { ApptentiveInternal.getInstance().updateApptentiveInteractionTheme(getTheme(), this); @@ -409,13 +410,26 @@ private void setStatusBarColor() { @Override public void onReceiveNotification(ApptentiveNotification notification) { - if (notification.hasName(NOTIFICATION_INTERACTIONS_SHOULD_DISMISS) || - notification.hasName(NOTIFICATION_CONVERSATION_BECAME_INACTIVE)) { - if (!isFinishing()) { - exitActivity(ApptentiveViewExitType.NOTIFICATION); // TODO: different exit types for different notifications? + if (notification.hasName(NOTIFICATION_INTERACTIONS_SHOULD_DISMISS)) { + dismissActivity(); + } else if (notification.hasName(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE)) { + final Conversation conversation = notification.getUserInfo(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE_KEY_CONVERSATION, Conversation.class); + Assert.assertNotNull(conversation, "Conversation expected to be not null"); + if (conversation != null && !conversation.hasActiveState()) { + dismissActivity(); } } } //endregion + + //region Helpers + + private void dismissActivity() { + if (!isFinishing()) { + exitActivity(ApptentiveViewExitType.NOTIFICATION); // TODO: different exit types for different notifications? + } + } + + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 6ecfa9ab7..60f8aebcf 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -62,6 +62,7 @@ private HttpJsonRequest createJsonRequest(String uri, JSONObject jsonObject) { HttpJsonRequest request = new HttpJsonRequest(url, jsonObject); setupRequestDefaults(request); request.setMethod(HttpRequestMethod.POST); + request.setRequestProperty("Content-Type", "application/json"); return request; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 17aa79a36..f82e24961 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -23,6 +23,7 @@ import com.apptentive.android.sdk.storage.DataChangedListener; import com.apptentive.android.sdk.storage.Device; import com.apptentive.android.sdk.storage.EventData; +import com.apptentive.android.sdk.storage.FileSerializer; import com.apptentive.android.sdk.storage.Person; import com.apptentive.android.sdk.storage.Saveable; import com.apptentive.android.sdk.storage.Sdk; @@ -30,10 +31,17 @@ import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.RuntimeUtils; import com.apptentive.android.sdk.util.Util; +import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchTask; import org.json.JSONException; -import static com.apptentive.android.sdk.ApptentiveLogTag.*; +import java.io.File; + +import static com.apptentive.android.sdk.ApptentiveLogTag.CONVERSATION; +import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS; +import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; +import static com.apptentive.android.sdk.debug.TesterEvent.EVT_INTERACTION_FETCH; public class Conversation implements Saveable, DataChangedListener { @@ -61,9 +69,17 @@ public class Conversation implements Saveable, DataChangedListener { private String interactions; private double interactionExpiration; + /** + * File which represents this conversation on the disk + */ + private transient File file; + // TODO: Maybe move this up to a wrapping Conversation class? private transient InteractionManager interactionManager; + // TODO: describe why we don't serialize state + private transient ConversationState state = ConversationState.UNDEFINED; + public Conversation() { this.device = new Device(); this.person = new Person(); @@ -98,13 +114,10 @@ public Interaction getApplicableInteraction(String eventLabel) { return null; } - /** - * Attempts to fetch interactions synchronously. Returns true is successful. - */ - boolean checkFetchInteractions(Context context) { + boolean fetchInteractions(Context context) { boolean cacheExpired = getInteractionExpiration() > Util.currentTimeSeconds(); if (cacheExpired || RuntimeUtils.isAppDebuggable(context)) { - return fetchInteractions(); + return DispatchQueue.backgroundQueue().dispatchAsyncOnce(fetchInteractionsTask); // do not allow multiple fetches at the same time } ApptentiveLog.v(CONVERSATION, "Interaction cache is still valid"); @@ -114,7 +127,7 @@ boolean checkFetchInteractions(Context context) { /** * Fetches interaction synchronously. Returns true if succeed. */ - boolean fetchInteractions() { + private boolean fetchInteractionsSync() { ApptentiveLog.v(CONVERSATION, "Fetching Interactions"); ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); @@ -159,20 +172,67 @@ else if (!response.isSuccessful()) { } ApptentiveLog.v(CONVERSATION, "Fetching new Interactions asyncTask finished. Successful? %b", updateSuccessful); - // Update pending state on UI thread after finishing the task - ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); - return updateSuccessful; } + private transient final DispatchTask fetchInteractionsTask = new DispatchTask() { + @Override + protected void execute() { + final boolean updateSuccessful = fetchInteractionsSync(); + + // Update pending state on UI thread after finishing the task + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + if (hasActiveState()) { + ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); + dispatchDebugEvent(EVT_INTERACTION_FETCH, updateSuccessful); + } + } + }); + } + }; + + //endregion + + //region Saving + + private final transient DispatchTask saveConversationTask = new DispatchTask() { + @Override + protected void execute() { + save(); + } + }; + + /** + * Saves conversation data to the disk synchronously. Returns true + * if succeed. + */ + synchronized boolean save() { + if (file == null) { + ApptentiveLog.e(CONVERSATION, "Unable to save conversation: destination file not specified"); + return false; + } + + ApptentiveLog.d(CONVERSATION, "Saving Conversation"); + ApptentiveLog.v(CONVERSATION, "EventData: %s", getEventData().toString()); // TODO: remove + + try { + FileSerializer serializer = new FileSerializer(file); + serializer.serialize(this); + return true; + } catch (Exception e) { + ApptentiveLog.e(e, "Unable to save conversation"); + return false; + } + } + //endregion //region Listeners - private transient DataChangedListener listener; @Override public void setDataChangedListener(DataChangedListener listener) { - this.listener = listener; device.setDataChangedListener(this); person.setDataChangedListener(this); eventData.setDataChangedListener(this); @@ -181,8 +241,15 @@ public void setDataChangedListener(DataChangedListener listener) { @Override public void notifyDataChanged() { - if (listener != null) { - listener.onDataChanged(); + if (hasFile()) { + boolean scheduled = DispatchQueue.backgroundQueue().dispatchAsyncOnce(saveConversationTask, 100L); + if (scheduled) { + ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); + } else { + ApptentiveLog.d(CONVERSATION, "Conversation save already scheduled."); + } + } else { + ApptentiveLog.v(CONVERSATION, "Can't save conversation data: storage file is not specified"); } } @@ -194,6 +261,41 @@ public void onDataChanged() { //region Getters & Setters + public ConversationState getState() { + return state; + } + + public void setState(ConversationState state) { + // TODO: check if state transition would make sense (for example you should not be able to move from 'logged' state to 'anonymous', etc.) + this.state = state; + } + + /** + * Returns true if conversation is in the given state + */ + public boolean hasState(ConversationState s) { + return state.equals(s); + } + + /** + * Returns true if conversation is in one of the given states + */ + public boolean hasState(ConversationState... states) { + for (ConversationState s : states) { + if (s.equals(state)) { + return true; + } + } + return false; + } + + /** + * Returns true if conversation is in "active" state (after receiving server response) + */ + public boolean hasActiveState() { + return hasState(ConversationState.LOGGED_IN, ANONYMOUS); + } + public String getConversationToken() { return conversationToken; } @@ -421,11 +523,16 @@ public void setInteractionManager(InteractionManager interactionManager) { this.interactionManager = interactionManager; } - /** - * Returns a filename unique to the convesation for persistant storage - */ - String getFilename() { - return String.format("conversation-%s.bin", conversationId); + synchronized boolean hasFile() { + return file != null; + } + + synchronized File getFile() { + return file; + } + + synchronized void setFile(File file) { + this.file = file; } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index e561ed41a..e0a526484 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -4,6 +4,8 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.conversation.ConversationMetadata.Filter; +import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.model.ConversationTokenRequest; import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; @@ -13,28 +15,34 @@ import com.apptentive.android.sdk.serialization.ObjectSerialization; import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.AppReleaseManager; -import com.apptentive.android.sdk.storage.DataChangedListener; import com.apptentive.android.sdk.storage.Device; import com.apptentive.android.sdk.storage.DeviceManager; import com.apptentive.android.sdk.storage.FileSerializer; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; +import com.apptentive.android.sdk.storage.SerializerException; import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; -import com.apptentive.android.sdk.util.threading.DispatchQueue; -import com.apptentive.android.sdk.util.threading.DispatchTask; import org.json.JSONObject; import java.io.File; import java.io.IOException; import java.lang.ref.WeakReference; -import java.util.concurrent.atomic.AtomicBoolean; -import static com.apptentive.android.sdk.ApptentiveLogTag.*; -import static com.apptentive.android.sdk.ApptentiveNotifications.*; +import static com.apptentive.android.sdk.ApptentiveLogTag.CONVERSATION; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_STARTED; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE_KEY_CONVERSATION; +import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS; +import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS_PENDING; +import static com.apptentive.android.sdk.conversation.ConversationState.LOGGED_IN; +import static com.apptentive.android.sdk.conversation.ConversationState.LOGGED_OUT; +import static com.apptentive.android.sdk.conversation.ConversationState.UNDEFINED; import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; -import static com.apptentive.android.sdk.debug.TesterEvent.*; +import static com.apptentive.android.sdk.debug.TesterEvent.EVT_CONVERSATION_CREATE; +import static com.apptentive.android.sdk.debug.TesterEvent.EVT_CONVERSATION_LOAD_ACTIVE; +import static com.apptentive.android.sdk.debug.TesterEvent.EVT_CONVERSATION_METADATA_LOAD; /** * Class responsible for managing conversations. @@ -45,17 +53,12 @@ * - Migrating legacy conversation data. * */ -public class ConversationManager implements DataChangedListener { +public class ConversationManager { protected static final String CONVERSATION_METADATA_PATH = "conversation-v1.meta"; private final WeakReference contextRef; - /** - * Private serial dispatch queue for background operations - */ - private final DispatchQueue operationQueue; - /** * A basic directory for storing conversation-related data. */ @@ -68,27 +71,27 @@ public class ConversationManager implements DataChangedListener { private Conversation activeConversation; - private final AtomicBoolean isConversationTokenFetchPending = new AtomicBoolean(false); - - public ConversationManager(Context context, final DispatchQueue operationQueue, File storageDir) { + public ConversationManager(Context context, File storageDir) { if (context == null) { throw new IllegalArgumentException("Context is null"); } - if (operationQueue == null) { - throw new IllegalArgumentException("Operation queue is null"); - } - this.contextRef = new WeakReference<>(context.getApplicationContext()); - this.operationQueue = operationQueue; this.storageDir = storageDir; ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_ACTIVITY_STARTED, new ApptentiveNotificationObserver() { @Override public void onReceiveNotification(ApptentiveNotification notification) { - ApptentiveLog.v(CONVERSATION, "Activity 'start' notification received. Trying to fetch interactions..."); - operationQueue.dispatchAsyncOnce(checkFetchInteractionsTask); + if (activeConversation != null && activeConversation.hasActiveState()) { + ApptentiveLog.v(CONVERSATION, "Activity 'start' notification received. Trying to fetch interactions..."); + final Context context = getContext(); + if (context != null) { + activeConversation.fetchInteractions(context); + } else { + ApptentiveLog.w(CONVERSATION, "Can't fetch conversation interactions: context is lost"); + } + } } }); } @@ -97,26 +100,26 @@ public void onReceiveNotification(ApptentiveNotification notification) { /** * Attempts to load an active conversation. Returns false if active conversation is - * missing or cannnot be loaded + * missing or cannot be loaded */ - public boolean loadActiveConversation() { + public boolean loadActiveConversation(Context context) { + if (context == null) { + throw new IllegalArgumentException("Context is null"); + } + try { // resolving metadata conversationMetadata = resolveMetadata(); // attempt to load existing conversation - activeConversation = loadActiveConversationGuarded(); + activeConversation = loadActiveConversationGuarded(context); dispatchDebugEvent(EVT_CONVERSATION_LOAD_ACTIVE, activeConversation != null); if (activeConversation != null) { - activeConversation.setDataChangedListener(this); - notifyConversationBecameActive(); + handleConversationStateChange(activeConversation); return true; } - // no conversation - fetch one - fetchConversationToken(); - } catch (Exception e) { ApptentiveLog.e(e, "Exception while loading active conversation"); } @@ -124,48 +127,64 @@ public boolean loadActiveConversation() { return false; } - private Conversation loadActiveConversationGuarded() throws IOException { + private Conversation loadActiveConversationGuarded(Context context) throws IOException, SerializerException { + // we're going to scan metadata in attempt to find existing conversations + ConversationMetadataItem item; + // if the user was logged in previously - we should have an active conversation - ApptentiveLog.v(CONVERSATION, "Loading active conversation..."); - final ConversationMetadataItem activeItem = conversationMetadata.findItem(new ConversationMetadata.Filter() { - @Override - public boolean accept(ConversationMetadataItem item) { - return item.isActive(); - } - }); - if (activeItem != null) { - return loadConversation(activeItem); + item = conversationMetadata.findItem(LOGGED_IN); + if (item != null) { + ApptentiveLog.v(CONVERSATION, "Loading logged-in conversation..."); + return loadConversation(item); } - // if no user was logged in previously - we might have a default conversation - ApptentiveLog.v(CONVERSATION, "Loading default conversation..."); - final ConversationMetadataItem defaultItem = conversationMetadata.findItem(new ConversationMetadata.Filter() { - @Override - public boolean accept(ConversationMetadataItem item) { - return item.isDefault(); - } - }); - if (defaultItem != null) { - return loadConversation(defaultItem); + // if no users were logged in previously - we might have an anonymous conversation + item = conversationMetadata.findItem(ANONYMOUS); + if (item != null) { + ApptentiveLog.v(CONVERSATION, "Loading anonymous conversation..."); + return loadConversation(item); } - // TODO: check for legacy conversations - ApptentiveLog.v(CONVERSATION, "Can't load conversation"); - return null; + // check if we have a 'pending' anonymous conversation + item = conversationMetadata.findItem(ANONYMOUS_PENDING); + if (item != null) { + final Conversation conversation = loadConversation(item); + fetchConversationToken(conversation); + return conversation; + } + + // seems like we only have 'logged-out' conversations + if (conversationMetadata.hasItems()) { + ApptentiveLog.v(CONVERSATION, "Can't load conversation: only 'logged-out' conversations available"); + return null; + } + + // no conversation available: create a new one + ApptentiveLog.v(CONVERSATION, "Can't load conversation: creating anonymous conversation..."); + Conversation anonymousConversation = new Conversation(); + anonymousConversation.setState(ANONYMOUS_PENDING); + fetchConversationToken(anonymousConversation); + return anonymousConversation; } - private Conversation loadConversation(ConversationMetadataItem item) { + private Conversation loadConversation(ConversationMetadataItem item) throws SerializerException { // TODO: use same serialization logic across the project - File file = new File(storageDir, item.filename); - FileSerializer serializer = new FileSerializer(file); - return (Conversation) serializer.deserialize(); + FileSerializer serializer = new FileSerializer(item.file); + final Conversation conversation = (Conversation) serializer.deserialize(); + conversation.setState(item.getState()); // set the state same as the item's state + conversation.setFile(item.file); + return conversation; } - /** Ends active conversation (user logs out, etc) */ + /** + * Ends active conversation (user logs out, etc) + */ public synchronized boolean endActiveConversation() { if (activeConversation != null) { + activeConversation.setState(LOGGED_OUT); + handleConversationStateChange(activeConversation); + setActiveConversation((Conversation) null); - ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_CONVERSATION_BECAME_INACTIVE); return true; } return false; @@ -173,56 +192,31 @@ public synchronized boolean endActiveConversation() { //endregion - //region Interactions + //region Conversation Token Fetching - private final DispatchTask checkFetchInteractionsTask = new DispatchTask() { - @Override - protected void execute() { - ApptentiveLog.v(CONVERSATION, "Checking fetch interactions..."); + private void fetchConversationToken(final Conversation conversation) { + ApptentiveLog.i(CONVERSATION, "Fetching Configuration token task started."); - if (activeConversation != null) { - Context context = getContext(); - if (context == null) { - ApptentiveLog.w(CONVERSATION, "Unable to check fetch interactions: context is lost"); - return; - } - boolean interactionsWereFetched = activeConversation.checkFetchInteractions(context); - dispatchDebugEvent(EVT_INTERACTION_FETCH, interactionsWereFetched); - } else { - ApptentiveLog.d(CONVERSATION, "Unable to check interaction fetch: active conversation is missing"); - } + final Context context = getContext(); + if (context == null) { + ApptentiveLog.w(CONVERSATION, "Unable to fetch conversation token: context reference is lost"); + return; } - }; - - //endregion - //region Conversation fetching - - private void fetchConversationToken() { - if (isConversationTokenFetchPending.compareAndSet(false, true)) { - ApptentiveLog.i(CONVERSATION, "Fetching Configuration token task started."); - - final Context context = getContext(); - if (context == null) { - ApptentiveLog.w(CONVERSATION, "Unable to fetch convesation token: context reference is lost"); - isConversationTokenFetchPending.set(false); - return; - } - - // Try to fetch a new one from the server. - ConversationTokenRequest request = new ConversationTokenRequest(); + // Try to fetch a new one from the server. + ConversationTokenRequest request = new ConversationTokenRequest(); - // Send the Device and Sdk now, so they are available on the server from the start. - final Device device = DeviceManager.generateNewDevice(context); - final Sdk sdk = SdkManager.generateCurrentSdk(); - final AppRelease appRelease = ApptentiveInternal.getInstance().getAppRelease(); + // Send the Device and Sdk now, so they are available on the server from the start. + final Device device = DeviceManager.generateNewDevice(context); + final Sdk sdk = SdkManager.generateCurrentSdk(); + final AppRelease appRelease = ApptentiveInternal.getInstance().getAppRelease(); - request.setDevice(DeviceManager.getDiffPayload(null, device)); - request.setSdk(SdkManager.getPayload(sdk)); - request.setAppRelease(AppReleaseManager.getPayload(appRelease)); + request.setDevice(DeviceManager.getDiffPayload(null, device)); + request.setSdk(SdkManager.getPayload(sdk)); + request.setAppRelease(AppReleaseManager.getPayload(appRelease)); - ApptentiveInternal.getInstance().getApptentiveHttpClient() - .getConversationToken(request, new HttpRequest.Listener() { + ApptentiveInternal.getInstance().getApptentiveHttpClient() + .getConversationToken(request, new HttpRequest.Listener() { @Override public void onFinish(HttpJsonRequest request) { try { @@ -244,8 +238,8 @@ public void onFinish(HttpJsonRequest request) { return; } - // create new conversation - Conversation conversation = new Conversation(); + // set conversation data + conversation.setState(ANONYMOUS); conversation.setConversationToken(conversationToken); conversation.setConversationId(conversationId); conversation.setDevice(device); @@ -255,50 +249,56 @@ public void onFinish(HttpJsonRequest request) { String personId = root.getString("person_id"); ApptentiveLog.d(CONVERSATION, "PersonId: " + personId); conversation.setPersonId(personId); - conversation.setDataChangedListener(ConversationManager.this); + conversation.setFile(getConversationFile(conversation)); - // write conversation to the dist - saveConversation(conversation); + // write conversation to the disk (sync operation) + conversation.save(); - // update active conversation - setActiveConversation(conversation); dispatchDebugEvent(EVT_CONVERSATION_CREATE, true); - notifyConversationBecameActive(); + handleConversationStateChange(conversation); } catch (Exception e) { ApptentiveLog.e(e, "Exception while handling conversation token"); dispatchDebugEvent(EVT_CONVERSATION_CREATE, false); - } finally { - isConversationTokenFetchPending.set(false); } } @Override public void onCancel(HttpJsonRequest request) { - isConversationTokenFetchPending.set(false); dispatchDebugEvent(EVT_CONVERSATION_CREATE, false); } @Override public void onFail(HttpJsonRequest request, String reason) { ApptentiveLog.w("Failed to fetch conversation token: %s", reason); - isConversationTokenFetchPending.set(false); dispatchDebugEvent(EVT_CONVERSATION_CREATE, false); } }); - } } - private void notifyConversationBecameActive() { - dispatchDebugEvent(EVT_CONVERSATION_BECAME_ACTIVE); // TODO: remove debug event and listen to the notification instead - ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_CONVERSATION_BECAME_ACTIVE, - ObjectUtils.toMap(NOTIFICATION_CONVERSATION_BECAME_ACTIVE_KEY_CONVERSATION, activeConversation)); - operationQueue.dispatchAsyncOnce(checkFetchInteractionsTask); + private File getConversationFile(Conversation conversation) { + return new File(storageDir, conversation.getConversationId() + ".bin"); + } + + //endregion + + //region Conversation fetching + + private void handleConversationStateChange(Conversation conversation) { + Assert.assertTrue(conversation != null && !conversation.hasState(UNDEFINED)); // sanity check + + ApptentiveNotificationCenter.defaultCenter() + .postNotification(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, + ObjectUtils.toMap(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE_KEY_CONVERSATION, conversation)); + + if (conversation != null && conversation.hasActiveState()) { + conversation.fetchInteractions(getContext()); + } } /* For testing purposes */ - public synchronized boolean setActiveConversation(final String conversationId) { - final ConversationMetadataItem item = conversationMetadata.findItem(new ConversationMetadata.Filter() { + public synchronized boolean setActiveConversation(final String conversationId) throws SerializerException { + final ConversationMetadataItem item = conversationMetadata.findItem(new Filter() { @Override public boolean accept(ConversationMetadataItem item) { return item.conversationId.equals(conversationId); @@ -318,47 +318,25 @@ public boolean accept(ConversationMetadataItem item) { setActiveConversation(conversation); - notifyConversationBecameActive(); - return true; - } - - private synchronized void setActiveConversation(Conversation conversation) { - activeConversation = conversation; - conversationMetadata.setActiveConversation(conversation); - saveMetadata(); + throw new RuntimeException("Implement me"); } - //endregion - - //region Serialization - - private void scheduleConversationSave() { - boolean scheduled = operationQueue.dispatchAsyncOnce(saveSessionTask, 100L); - if (scheduled) { - ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); - } else { - ApptentiveLog.d(CONVERSATION, "Conversation save already scheduled."); + private synchronized void setActiveConversation(final Conversation conversation) { + // do we have a 'logged-in' conversation already? + if (activeConversation != null && activeConversation.hasState(LOGGED_IN)) { + activeConversation.setState(LOGGED_OUT); // mark it as 'logged-out' + conversationMetadata.setItem(activeConversation); // and update metadata } - } - private final DispatchTask saveSessionTask = new DispatchTask() { - @Override - protected void execute() { - if (activeConversation != null) { - saveConversation(activeConversation); - } else { - ApptentiveLog.w(CONVERSATION, "Can't save conversation: active conversation is missing"); - } - } - }; + // set new active conversation + activeConversation = conversation; - private void saveConversation(Conversation conversation) { - ApptentiveLog.d(CONVERSATION, "Saving Conversation"); - ApptentiveLog.v(CONVERSATION, "EventData: %s", conversation.getEventData().toString()); // TODO: remove + // update metadata (if necessary) + if (activeConversation != null) { + conversationMetadata.setItem(activeConversation); + } - File conversationFile = new File(storageDir, conversation.getFilename()); - FileSerializer serializer = new FileSerializer(conversationFile); - serializer.serialize(conversation); + saveMetadata(); } //endregion @@ -397,15 +375,6 @@ private void saveMetadata() { //endregion - //region DataChangedListener - - @Override - public void onDataChanged() { - scheduleConversationSave(); - } - - //endregion - //region Getters/Setters public Conversation getActiveConversation() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java index d355fa02a..8047eff98 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java @@ -1,7 +1,6 @@ package com.apptentive.android.sdk.conversation; import com.apptentive.android.sdk.serialization.SerializableObject; -import com.apptentive.android.sdk.util.StringUtils; import java.io.DataInput; import java.io.DataOutput; @@ -10,8 +9,6 @@ import java.util.Iterator; import java.util.List; -import static com.apptentive.android.sdk.conversation.ConversationMetadataItem.*; - /** * Class which represents all conversation entries stored on the disk */ @@ -50,42 +47,38 @@ public void writeExternal(DataOutput out) throws IOException { //endregion - //region Conversation - - // TODO: replace it with notifications so that the active conversation can send out events and clean itself up. - void setActiveConversation(final Conversation conversation) { - if (conversation != null) { - // clear 'active' state - boolean found = false; - for (ConversationMetadataItem item : items) { - if (StringUtils.equal(conversation.getConversationId(), item.conversationId)) { - found = true; - item.state = CONVERSATION_STATE_ACTIVE; - } else if (item.state == CONVERSATION_STATE_ACTIVE) { - item.state = CONVERSATION_STATE_INACTIVE; - } - } + //region Items - // add a new item if it was not found - if (!found) { - final ConversationMetadataItem item = new ConversationMetadataItem(conversation.getConversationId(), conversation.getFilename()); - item.state = CONVERSATION_STATE_ACTIVE; - items.add(item); - } - } else { - for (ConversationMetadataItem item : items) { - if (item.state == CONVERSATION_STATE_ACTIVE) { - item.state = CONVERSATION_STATE_INACTIVE; - } - } + public void setItem(Conversation conversation) + { + ConversationMetadataItem item = findItem(conversation); + if (item == null) { + item = new ConversationMetadataItem(conversation.getConversationId(), conversation.getFile()); + items.add(item); } + + item.state = conversation.getState(); } - //endregion + ConversationMetadataItem findItem(final ConversationState state) { + return findItem(new Filter() { + @Override + public boolean accept(ConversationMetadataItem item) { + return state.equals(item.state); + } + }); + } - //region Filtering + ConversationMetadataItem findItem(final Conversation conversation) { + return findItem(new Filter() { + @Override + public boolean accept(ConversationMetadataItem item) { + return item.conversationId.equals(conversation.getConversationId()); + } + }); + } - public ConversationMetadataItem findItem(Filter filter) { + ConversationMetadataItem findItem(Filter filter) { for (ConversationMetadataItem item : items) { if (filter.accept(item)) { return item; @@ -107,6 +100,10 @@ public Iterator iterator() { //region Getters/Setters + public boolean hasItems() { + return items.size() > 0; + } + public List getItems() { return items; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index d0500d710..c19acdbf2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -5,72 +5,61 @@ import java.io.DataInput; import java.io.DataOutput; +import java.io.File; import java.io.IOException; /** * A light weight representation of the conversation object stored on the disk. */ public class ConversationMetadataItem implements SerializableObject { - /** - * Conversation state is not known - */ - public static byte CONVERSATION_STATE_UNDEFINED = 0; /** - * No users have logged-in yet (guest mode) + * The state of the target conversation */ - public static byte CONVERSATION_STATE_DEFAULT = 1; + ConversationState state = ConversationState.UNDEFINED; /** - * The conversation belongs to the currently logged-in user. + * Conversation ID which was received from the backend */ - public static byte CONVERSATION_STATE_ACTIVE = 2; + final String conversationId; /** - * The conversation belongs to a logged-out user. + * Storage filename for conversation serialized data */ - public static byte CONVERSATION_STATE_INACTIVE = 3; - - byte state = CONVERSATION_STATE_UNDEFINED; - String conversationId; - String filename; + final File file; - public ConversationMetadataItem(String conversationId, String filename) + public ConversationMetadataItem(String conversationId, File file) { if (StringUtils.isNullOrEmpty(conversationId)) { throw new IllegalArgumentException("Conversation id is null or empty"); } - if (StringUtils.isNullOrEmpty(filename)) { - throw new IllegalArgumentException("Filename is null or empty"); + if (file == null) { + throw new IllegalArgumentException("File is null"); } this.conversationId = conversationId; - this.filename = filename; + this.file = file; } public ConversationMetadataItem(DataInput in) throws IOException { conversationId = in.readUTF(); - filename = in.readUTF(); - state = in.readByte(); + file = new File(in.readUTF()); + state = ConversationState.valueOf(in.readByte()); } @Override public void writeExternal(DataOutput out) throws IOException { out.writeUTF(conversationId); - out.writeUTF(filename); - out.writeByte(state); + out.writeUTF(file.getAbsolutePath()); + out.writeByte(state.ordinal()); } public String getConversationId() { return conversationId; } - public boolean isActive() { - return state == CONVERSATION_STATE_ACTIVE; - } - - public boolean isDefault() { - return state == CONVERSATION_STATE_DEFAULT; + public ConversationState getState() { + return state; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationState.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationState.java new file mode 100644 index 000000000..faa756d20 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationState.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.conversation; + +public enum ConversationState { + /** + * Conversation state is not known + */ + UNDEFINED, + + /** + * No logged in user and no conversation token + */ + ANONYMOUS_PENDING, + + /** + * No logged in user with conversation token + */ + ANONYMOUS, + + /** + * The conversation belongs to the currently logged-in user + */ + LOGGED_IN, + + /** + * The conversation belongs to a logged-out user + */ + LOGGED_OUT; + + /** + * Returns the {@link ConversationState} object corresponding + * to value or UNDEFINED if value + * is out of range + */ + public static ConversationState valueOf(byte value) { + final ConversationState[] values = ConversationState.values(); + + if (value >= 0 && value < values.length) { + return values[value]; + } + + return UNDEFINED; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java index edcb548b0..2b5607a30 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -22,6 +22,7 @@ package com.apptentive.android.sdk.debug; import java.util.Collection; +import java.util.Objects; /** * A set of assertion methods useful for writing 'runtime' tests. These methods can be used directly: @@ -118,68 +119,8 @@ public static void assertNotNull(Object object, String format, Object... args) { // FIXME: implement me } - /** Asserts that executed is not equal to actual */ - public static void assertNotEquals(int expected, int actual) { + /** Asserts that expected is equal to actual */ + public static void assertEquals(Object expected, Object Object, String format, Object... args) { // FIXME: implement me } - - /** Asserts that executed is not equal to actual */ - public static void assertNotEquals(int expected, int actual, String message) { - // FIXME: implement me - } - - /** Asserts that executed is not equal to actual */ - public static void assertNotEquals(int expected, int actual, String format, Object... args) { - // FIXME: implement me - } - - /** - * Asserts that collection isRegistered an object - */ - public static void assertContains(Collection collection, Object object) { - // FIXME: implement me - } - - /** - * Asserts that collection isRegistered an object - */ - public static void assertContains(Collection collection, Object object, String message) { - // FIXME: implement me - } - - /** - * Asserts that collection isRegistered an object - */ - public static void assertContains(Collection collection, Object object, String format, Object... args) { - // FIXME: implement me - } - - /** - * Asserts that collection doesn't contain an object - */ - public static void assertNotContains(Collection collection, Object object) { - // FIXME: implement me - } - - /** - * Asserts that collection doesn't contain an object - */ - public static void assertNotContains(Collection collection, Object object, String message) { - // FIXME: implement me - } - - /** - * Asserts that collection doesn't contain an object - */ - public static void assertNotContains(Collection collection, Object object, String format, Object... args) { - // FIXME: implement me - } - - /** Asserts that code is executed on the main thread */ - public static void assertMainThread() { - } - - /** Asserts that code is not executed on the main thread */ - public static void assertNotMainThread() { - } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java index 8c3a37a01..485b97112 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java @@ -42,7 +42,7 @@ public static void dispatchDebugEvent(String name) { public static void dispatchDebugEvent(String name, boolean successful) { if (isListeningForDebugEvents()) { - notifyEvent(name, ObjectUtils.toMap("successful", successful)); + notifyEvent(name, ObjectUtils.toMap(EVT_KEY_SUCCESSFUL, successful)); } } @@ -52,6 +52,12 @@ public static void dispatchDebugEvent(String name, String key, Object value) { } } + public static void dispatchDebugEvent(String name, String key1, Object value1, String key2, Object value2) { + if (isListeningForDebugEvents()) { + notifyEvent(name, ObjectUtils.toMap(key1, value1, key2, value2)); + } + } + public static void dispatchException(Throwable e) { if (isListeningForDebugEvents() && e != null) { StringBuilder stackTrace = new StringBuilder(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java index 7fa93ce72..8fedd3439 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java @@ -3,13 +3,15 @@ public class TesterEvent { public static final String EVT_CONVERSATION_LOAD_ACTIVE = "conversation_load_active"; // { successful:boolean } - public static final String EVT_CONVERSATION_CREATE = "conversation_create"; // { successful:boolean } + public static final String EVT_CONVERSATION_CREATE = "conversation_create"; // { successful:boolean, state:String } public static final String EVT_CONVERSATION_METADATA_LOAD = "conversation_metadata_load"; // { successful:boolean } public static final String EVT_INTERACTION_FETCH = "interaction_fetch"; // { successful:boolean } public static final String EVT_INSTANCE_CREATED = "instance_created"; // { successful:boolean } public static final String EVT_EXCEPTION = "exception"; // { class:String, message:String, stackTrace:String } + public static final String EVT_APPTENTIVE_EVENT = "apptentive_event"; // { eventLabel:String } public static final String EVT_APPTENTIVE_EVENT_KEY_EVENT_LABEL = "eventLabel"; - public static final String EVT_CONVERSATION_BECAME_ACTIVE = "conversation_became_active"; // { } + public static final String EVT_KEY_SUCCESSFUL = "successful"; + public static final String EVT_KEY_STATE = "state"; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java index 9a1891dfa..a7c889911 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java @@ -6,6 +6,7 @@ package com.apptentive.android.sdk.notifications; +import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; import java.util.Map; @@ -37,6 +38,10 @@ public boolean hasName(String name) { return StringUtils.equal(this.name, name); } + public T getUserInfo(String key, Class valueClass) { + return userInfo != null ? ObjectUtils.as(userInfo.get(key), valueClass) : null; + } + public Map getUserInfo() { return userInfo; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java index ae35469ba..a02b356b3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java @@ -11,9 +11,7 @@ import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -26,7 +24,7 @@ public FileSerializer(File file) { } @Override - public void serialize(Object object) { + public void serialize(Object object) throws SerializerException { FileOutputStream fos = null; ObjectOutputStream oos = null; try { @@ -34,9 +32,9 @@ public void serialize(Object object) { fos = new FileOutputStream(file); oos = new ObjectOutputStream(fos); oos.writeObject(object); - ApptentiveLog.v("Session data written to file of length: %s",Util.humanReadableByteCount(file.length(),false)); - } catch (IOException e) { - ApptentiveLog.e("Error", e); + ApptentiveLog.v("Session data written to file of length: %s", Util.humanReadableByteCount(file.length(), false)); + } catch (Exception e) { + throw new SerializerException(e); } finally { Util.ensureClosed(fos); Util.ensureClosed(oos); @@ -44,23 +42,18 @@ public void serialize(Object object) { } @Override - public Object deserialize() { + public Object deserialize() throws SerializerException { FileInputStream fis = null; ObjectInputStream ois = null; try { fis = new FileInputStream(file); ois = new ObjectInputStream(fis); return ois.readObject(); - } catch (ClassNotFoundException e) { - ApptentiveLog.e("Error", e); - } catch (FileNotFoundException e) { - ApptentiveLog.e("DataSession file does not yet exist."); - } catch (IOException e) { - ApptentiveLog.e("Error", e); + } catch (Exception e) { + throw new SerializerException(e); } finally { Util.ensureClosed(fis); Util.ensureClosed(ois); } - return null; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Serializer.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Serializer.java index c9636cdbf..4963b06dd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Serializer.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Serializer.java @@ -8,7 +8,7 @@ public interface Serializer { - void serialize(Object object); + void serialize(Object object) throws SerializerException; - Object deserialize(); + Object deserialize() throws SerializerException; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SerializerException.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SerializerException.java new file mode 100644 index 000000000..2d22b2f08 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SerializerException.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +public class SerializerException extends Exception { + public SerializerException(Throwable cause) { + super(cause); + } +} From b0539f31ddf987d8eae22b1aa2bd53621c304a26 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 15 Mar 2017 19:11:37 -0700 Subject: [PATCH 124/465] Fixed updating conversation metadata items --- .../sdk/conversation/ConversationManager.java | 42 +++++++++++-------- .../conversation/ConversationMetadata.java | 11 +---- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index e0a526484..69b504a6f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -6,6 +6,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.conversation.ConversationMetadata.Filter; import com.apptentive.android.sdk.debug.Assert; +import com.apptentive.android.sdk.model.ConversationItem; import com.apptentive.android.sdk.model.ConversationTokenRequest; import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; @@ -112,7 +113,7 @@ public boolean loadActiveConversation(Context context) { conversationMetadata = resolveMetadata(); // attempt to load existing conversation - activeConversation = loadActiveConversationGuarded(context); + activeConversation = loadActiveConversationGuarded(); dispatchDebugEvent(EVT_CONVERSATION_LOAD_ACTIVE, activeConversation != null); if (activeConversation != null) { @@ -127,7 +128,7 @@ public boolean loadActiveConversation(Context context) { return false; } - private Conversation loadActiveConversationGuarded(Context context) throws IOException, SerializerException { + private Conversation loadActiveConversationGuarded() throws IOException, SerializerException { // we're going to scan metadata in attempt to find existing conversations ConversationMetadataItem item; @@ -183,8 +184,7 @@ public synchronized boolean endActiveConversation() { if (activeConversation != null) { activeConversation.setState(LOGGED_OUT); handleConversationStateChange(activeConversation); - - setActiveConversation((Conversation) null); + activeConversation = null; return true; } return false; @@ -294,6 +294,8 @@ private void handleConversationStateChange(Conversation conversation) { if (conversation != null && conversation.hasActiveState()) { conversation.fetchInteractions(getContext()); } + + updateMetadataItems(conversation); } /* For testing purposes */ @@ -316,26 +318,30 @@ public boolean accept(ConversationMetadataItem item) { return false; } - setActiveConversation(conversation); - - throw new RuntimeException("Implement me"); + handleConversationStateChange(conversation); + return true; } - private synchronized void setActiveConversation(final Conversation conversation) { - // do we have a 'logged-in' conversation already? - if (activeConversation != null && activeConversation.hasState(LOGGED_IN)) { - activeConversation.setState(LOGGED_OUT); // mark it as 'logged-out' - conversationMetadata.setItem(activeConversation); // and update metadata - } + private void updateMetadataItems(Conversation conversation) { - // set new active conversation - activeConversation = conversation; + // if the conversation is 'logged-in' we should not have any other 'logged-in' items in metadata + if (conversation.hasState(LOGGED_IN)) { + for (ConversationMetadataItem item : conversationMetadata) { + if (item.state.equals(LOGGED_IN)) { + item.state = LOGGED_OUT; + } + } + } - // update metadata (if necessary) - if (activeConversation != null) { - conversationMetadata.setItem(activeConversation); + // update the state of the corresponding item + ConversationMetadataItem item = conversationMetadata.findItem(conversation); + if (item == null) { + item = new ConversationMetadataItem(conversation.getConversationId(), conversation.getFile()); + conversationMetadata.addItem(item); } + item.state = conversation.getState(); + // apply changes saveMetadata(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java index 8047eff98..192eabe9e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java @@ -49,15 +49,8 @@ public void writeExternal(DataOutput out) throws IOException { //region Items - public void setItem(Conversation conversation) - { - ConversationMetadataItem item = findItem(conversation); - if (item == null) { - item = new ConversationMetadataItem(conversation.getConversationId(), conversation.getFile()); - items.add(item); - } - - item.state = conversation.getState(); + void addItem(ConversationMetadataItem item) { + items.add(item); } ConversationMetadataItem findItem(final ConversationState state) { From 7b59b1b1d5c2c0f76f0380057dbe90b2f54ad376 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 15 Mar 2017 19:47:40 -0700 Subject: [PATCH 125/465] Fixed handling metadata for anonymous pending conversations --- .../android/sdk/conversation/ConversationManager.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 69b504a6f..50c02df93 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -324,6 +324,11 @@ public boolean accept(ConversationMetadataItem item) { private void updateMetadataItems(Conversation conversation) { + if (conversation.hasState(ANONYMOUS_PENDING)) { + ApptentiveLog.v(CONVERSATION, "Skipping updating metadata since conversation is anonymous and pending"); + return; + } + // if the conversation is 'logged-in' we should not have any other 'logged-in' items in metadata if (conversation.hasState(LOGGED_IN)) { for (ConversationMetadataItem item : conversationMetadata) { From f137666c8f97b4a20ce93fe9cc82b7068323790e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 15 Mar 2017 19:48:54 -0700 Subject: [PATCH 126/465] Fixed initialized transient fields in Conversation class --- .../sdk/conversation/Conversation.java | 64 +++++++++++-------- .../android/sdk/storage/CustomData.java | 5 ++ .../android/sdk/storage/Device.java | 4 ++ .../android/sdk/storage/EventData.java | 4 ++ .../android/sdk/storage/FileSerializer.java | 4 +- .../sdk/storage/IntegrationConfig.java | 6 ++ .../android/sdk/storage/Person.java | 4 ++ .../android/sdk/storage/Saveable.java | 1 + .../android/sdk/storage/VersionHistory.java | 5 ++ 9 files changed, 71 insertions(+), 26 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index f82e24961..2a175bcde 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -80,6 +80,10 @@ public class Conversation implements Saveable, DataChangedListener { // TODO: describe why we don't serialize state private transient ConversationState state = ConversationState.UNDEFINED; + // we keep references to the tasks in order to dispatch them only once + private transient DispatchTask fetchInteractionsTask; + private transient DispatchTask saveConversationTask; + public Conversation() { this.device = new Device(); this.person = new Person(); @@ -87,6 +91,35 @@ public Conversation() { this.appRelease = new AppRelease(); this.eventData = new EventData(); this.versionHistory = new VersionHistory(); + + // transient fields might not get properly initialized upon de-serialization + initDispatchTasks(); + } + + private void initDispatchTasks() { + fetchInteractionsTask = new DispatchTask() { + @Override + protected void execute() { + final boolean updateSuccessful = fetchInteractionsSync(); + + // Update pending state on UI thread after finishing the task + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + if (hasActiveState()) { + ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); + dispatchDebugEvent(EVT_INTERACTION_FETCH, updateSuccessful); + } + } + }); + } + }; + saveConversationTask = new DispatchTask() { + @Override + protected void execute() { + save(); + } + }; } //region Interactions @@ -175,35 +208,10 @@ else if (!response.isSuccessful()) { return updateSuccessful; } - private transient final DispatchTask fetchInteractionsTask = new DispatchTask() { - @Override - protected void execute() { - final boolean updateSuccessful = fetchInteractionsSync(); - - // Update pending state on UI thread after finishing the task - DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - if (hasActiveState()) { - ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); - dispatchDebugEvent(EVT_INTERACTION_FETCH, updateSuccessful); - } - } - }); - } - }; - //endregion //region Saving - private final transient DispatchTask saveConversationTask = new DispatchTask() { - @Override - protected void execute() { - save(); - } - }; - /** * Saves conversation data to the disk synchronously. Returns true * if succeed. @@ -253,10 +261,16 @@ public void notifyDataChanged() { } } + @Override + public void onDeserialize() { + initDispatchTasks(); + } + @Override public void onDataChanged() { notifyDataChanged(); } + //endregion //region Getters & Setters diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java index a9da62ff7..5daec3325 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java @@ -30,6 +30,11 @@ public void notifyDataChanged() { listener.onDataChanged(); } } + + @Override + public void onDeserialize() { + } + //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java index bf4cd9ff1..9e6fd3513 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java @@ -59,6 +59,10 @@ public void notifyDataChanged() { } } + @Override + public void onDeserialize() { + } + @Override public void onDataChanged() { notifyDataChanged(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java index 919608154..dc40838b9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java @@ -41,6 +41,10 @@ public void notifyDataChanged() { } } + @Override + public void onDeserialize() { + } + //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java index a02b356b3..f38f21780 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java @@ -48,7 +48,9 @@ public Object deserialize() throws SerializerException { try { fis = new FileInputStream(file); ois = new ObjectInputStream(fis); - return ois.readObject(); + Saveable object = (Saveable) ois.readObject(); + object.onDeserialize(); + return object; } catch (Exception e) { throw new SerializerException(e); } finally { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java index 1c8b1962f..0e8aec598 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java @@ -27,6 +27,7 @@ public class IntegrationConfig implements Saveable { //region Listeners + @Override public void setDataChangedListener(DataChangedListener listener) { this.listener = listener; @@ -38,6 +39,11 @@ public void notifyDataChanged() { listener.onDataChanged(); } } + + @Override + public void onDeserialize() { + } + //endregion //region Getters & Setters diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java index 9efe02d66..15649b977 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java @@ -44,6 +44,10 @@ public void notifyDataChanged() { } } + @Override + public void onDeserialize() { + } + @Override public void onDataChanged() { notifyDataChanged(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Saveable.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Saveable.java index 07b076e43..cbc6b12a7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Saveable.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Saveable.java @@ -12,4 +12,5 @@ public interface Saveable extends Serializable { void setDataChangedListener(DataChangedListener listener); void notifyDataChanged(); + void onDeserialize(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java index 0640be930..1e6064fd0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java @@ -42,6 +42,11 @@ public void notifyDataChanged() { listener.onDataChanged(); } } + + @Override + public void onDeserialize() { + } + //endregion public void updateVersionHistory(double timestamp, Integer newVersionCode, String newVersionName) { From 184717a9f2ce9ce71ffb74e95c2fa181fb48642e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 15 Mar 2017 19:49:08 -0700 Subject: [PATCH 127/465] Fixed log trace --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 6da9d9a4b..d1a2d4558 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -538,7 +538,7 @@ public boolean init() { long start = System.currentTimeMillis(); boolean conversationLoaded = conversationManager.loadActiveConversation(getApplicationContext()); - ApptentiveLog.i(CONVERSATION, "Active conversation is%s loaded. Took %d ms", conversationLoaded ? " not" : "", System.currentTimeMillis() - start); + ApptentiveLog.i(CONVERSATION, "Active conversation is%s loaded. Took %d ms", conversationLoaded ? "" : " not", System.currentTimeMillis() - start); if (conversationLoaded) { Conversation activeConversation = conversationManager.getActiveConversation(); From 6f95d4b527f4a9ae212cbd5b64561d68a3dca42a Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 16 Mar 2017 12:58:44 -0700 Subject: [PATCH 128/465] Added Assert implementation --- .../apptentive/android/sdk/debug/Assert.java | 97 +++++-------------- .../android/sdk/debug/AssertImp.java | 11 +++ .../android/sdk/debug/AssertTest.java | 71 ++++++++++++++ 3 files changed, 105 insertions(+), 74 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/debug/AssertImp.java create mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java index 2b5607a30..b97b6256e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -1,28 +1,6 @@ -// -// Assert.java -// -// Lunar Unity Mobile Console -// https://github.com/SpaceMadness/lunar-unity-console -// -// Copyright 2017 Alex Lementuev, SpaceMadness. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - package com.apptentive.android.sdk.debug; -import java.util.Collection; -import java.util.Objects; +import com.apptentive.android.sdk.util.StringUtils; /** * A set of assertion methods useful for writing 'runtime' tests. These methods can be used directly: @@ -35,92 +13,63 @@ */ public class Assert { + private static AssertImp imp; + /** * Asserts that condition is true */ public static void assertTrue(boolean condition) { - // FIXME: implement me + if (imp != null && !condition) { + imp.assertFailed("Expected 'true' but was 'false'"); + } } /** * Asserts that condition is true */ public static void assertTrue(boolean condition, String message) { - // FIXME: implement me + if (imp != null && !condition) { + imp.assertFailed(message); + } } /** * Asserts that condition is true */ public static void assertTrue(boolean condition, String format, Object... args) { - // FIXME: implement me - } - - /** - * Asserts that condition is false - */ - public static void assertFalse(boolean condition) { - // FIXME: implement me - } - - /** - * Asserts that condition is false - */ - public static void assertFalse(boolean condition, String message) { - // FIXME: implement me - } - - /** - * Asserts that condition is false - */ - public static void assertFalse(boolean condition, String format, Object... args) { - // FIXME: implement me - } - - /** - * Asserts that an object is null - */ - public static void assertNull(Object object) { - // FIXME: implement me - } - - /** - * Asserts that an object is null - */ - public static void assertNull(Object object, String message) { - // FIXME: implement me - } - - /** - * Asserts that an object is null - */ - public static void assertNull(Object object, String format, Object... args) { - // FIXME: implement me + if (imp != null && !condition) { + imp.assertFailed(StringUtils.format(format, args)); + } } /** * Asserts that an object isn't null */ public static void assertNotNull(Object object) { - // FIXME: implement me + if (imp != null && object == null) { + imp.assertFailed("Not-null expected"); + } } /** * Asserts that an object isn't null */ public static void assertNotNull(Object object, String message) { - // FIXME: implement me + if (imp != null && object == null) { + imp.assertFailed(message); + } } /** * Asserts that an object isn't null */ public static void assertNotNull(Object object, String format, Object... args) { - // FIXME: implement me + if (imp != null && object == null) { + imp.assertFailed(String.format(format, args)); + } } - /** Asserts that expected is equal to actual */ - public static void assertEquals(Object expected, Object Object, String format, Object... args) { - // FIXME: implement me + public static void setImp(AssertImp imp) { + Assert.imp = imp; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/AssertImp.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/AssertImp.java new file mode 100644 index 000000000..630f3a473 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/AssertImp.java @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.debug; + +public interface AssertImp { + void assertFailed(String message); +} diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java new file mode 100644 index 000000000..7632eefe4 --- /dev/null +++ b/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.debug; + +import com.apptentive.android.sdk.TestCaseBase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class AssertTest extends TestCaseBase implements AssertImp { + + //region Setup + + @Before + public void setUp() { + AssertEx.setImp(this); + } + + @After + public void tearDown() { + AssertEx.setImp(null); + } + + //endregion + + @Test + public void testAssertTrue() throws Exception { + AssertEx.assertTrue(true); + AssertEx.assertTrue(false); + AssertEx.assertTrue(true, ""); + AssertEx.assertTrue(false, "assertTrue(boolean,String)"); + AssertEx.assertTrue(true, "", new Object()); + AssertEx.assertTrue(false, "assertTrue(boolean,String,Object...)"); + + assertResult( + "Expected 'true' but was 'false'", + "assertTrue(boolean,String)", + "assertTrue(boolean,String,Object...)" + ); + } + + @Test + public void testAssertNotNull() throws Exception { + AssertEx.assertNotNull(new Object()); + AssertEx.assertNotNull(null); + AssertEx.assertNotNull(new Object(), ""); + AssertEx.assertNotNull(null, "assertNotNull(Object,String)"); + AssertEx.assertNotNull(new Object(), "", new Object()); + AssertEx.assertNotNull(null, "assertNotNull(Object,String,Object...)"); + + assertResult( + "Not-null expected", + "assertNotNull(Object,String)", + "assertNotNull(Object,String,Object...)" + ); + } + + @Override + public void assertFailed(String message) { + addResult(message); + } + + private static class AssertEx extends com.apptentive.android.sdk.debug.Assert + { + } +} \ No newline at end of file From 7009fadbabb56969a2d7a5b78616d1ad5725d540 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 16 Mar 2017 14:07:15 -0700 Subject: [PATCH 129/465] Added Assert.assertEquals method --- .../main/java/com/apptentive/android/sdk/debug/Assert.java | 7 +++++++ .../java/com/apptentive/android/sdk/util/ObjectUtils.java | 4 ++++ .../java/com/apptentive/android/sdk/debug/AssertTest.java | 7 +++++++ 3 files changed, 18 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java index b97b6256e..518c1683d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -1,5 +1,6 @@ package com.apptentive.android.sdk.debug; +import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; /** @@ -69,6 +70,12 @@ public static void assertNotNull(Object object, String format, Object... args) { } } + public static void assertEquals(Object expected, Object actual) { + if (imp != null && !ObjectUtils.equal(expected, actual)) { + imp.assertFailed(StringUtils.format("Expected '%s' but was '%s'", expected, actual)); + } + } + public static void setImp(AssertImp imp) { Assert.imp = imp; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java index dca097d7c..45a9c6642 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/ObjectUtils.java @@ -49,4 +49,8 @@ public static Map toMap(Object... args) { return map; } + + public static boolean equal(Object expected, Object actual) { + return expected != null && actual != null && expected.equals(actual); + } } diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java index 7632eefe4..b8a209824 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java @@ -60,6 +60,13 @@ public void testAssertNotNull() throws Exception { ); } + @Test + public void testAssertEquals() throws Exception { + AssertEx.assertEquals("foo", "foo"); + AssertEx.assertEquals("foo", "bar"); + assertResult("Expected 'foo' but was 'bar'"); + } + @Override public void assertFailed(String message) { addResult(message); From 40fa1881deaa869e1d07e4beec05e5f00a8f1708 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 16 Mar 2017 14:33:16 -0700 Subject: [PATCH 130/465] Refactoring Added some helpers to ApptentiveHttpClient --- .../android/sdk/comm/ApptentiveHttpClient.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 60f8aebcf..af5387fe9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -47,22 +47,22 @@ public ApptentiveHttpClient(String oauthToken, String serverURL) { //region API Requests - public void getConversationToken(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { - HttpJsonRequest request = createJsonRequest(ENDPOINT_CONVERSATION, conversationTokenRequest); - request.setListener(listener); - httpRequestManager.startRequest(request); + public HttpJsonRequest getConversationToken(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { + return startJsonRequest(ENDPOINT_CONVERSATION, conversationTokenRequest, listener); } //endregion //region Helpers - private HttpJsonRequest createJsonRequest(String uri, JSONObject jsonObject) { - String url = createEndpointURL(uri); + private HttpJsonRequest startJsonRequest(String endpoint, JSONObject jsonObject, HttpRequest.Listener listener) { + String url = createEndpointURL(endpoint); HttpJsonRequest request = new HttpJsonRequest(url, jsonObject); setupRequestDefaults(request); request.setMethod(HttpRequestMethod.POST); request.setRequestProperty("Content-Type", "application/json"); + request.setListener(listener); + httpRequestManager.startRequest(request); return request; } From 39605f0d6bd80476500ba293948b90648b53f711 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 16 Mar 2017 16:19:36 -0700 Subject: [PATCH 131/465] Added more functionality to Assertion class --- .../apptentive/android/sdk/debug/Assert.java | 45 +++++++++++++++++-- .../android/sdk/debug/AssertTest.java | 16 +++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java index 518c1683d..581beb04b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -16,6 +16,8 @@ public class Assert { private static AssertImp imp; + //region Booleans + /** * Asserts that condition is true */ @@ -43,8 +45,12 @@ public static void assertTrue(boolean condition, String format, Object... args) } } + //endregion + + //region Nullability + /** - * Asserts that an object isn't null + * Asserts that an object isn't null */ public static void assertNotNull(Object object) { if (imp != null && object == null) { @@ -53,7 +59,7 @@ public static void assertNotNull(Object object) { } /** - * Asserts that an object isn't null + * Asserts that an object isn't null */ public static void assertNotNull(Object object, String message) { if (imp != null && object == null) { @@ -62,7 +68,7 @@ public static void assertNotNull(Object object, String message) { } /** - * Asserts that an object isn't null + * Asserts that an object isn't null */ public static void assertNotNull(Object object, String format, Object... args) { if (imp != null && object == null) { @@ -70,12 +76,45 @@ public static void assertNotNull(Object object, String format, Object... args) { } } + /** + * Asserts that an object is null + */ + public static void assertNull(Object object) { + if (imp != null && object != null) { + imp.assertFailed(StringUtils.format("Expected 'null' but was '%s'", object)); + } + } + + /** + * Asserts that an object is null + */ + public static void assertNull(Object object, String message) { + if (imp != null && object != null) { + imp.assertFailed(message); + } + } + + /** + * Asserts that an object is null + */ + public static void assertNull(Object object, String format, Object... args) { + if (imp != null && object != null) { + imp.assertFailed(String.format(format, args)); + } + } + + //endregion + + //region Equality + public static void assertEquals(Object expected, Object actual) { if (imp != null && !ObjectUtils.equal(expected, actual)) { imp.assertFailed(StringUtils.format("Expected '%s' but was '%s'", expected, actual)); } } + //endregion + public static void setImp(AssertImp imp) { Assert.imp = imp; } diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java index b8a209824..703a2a976 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java @@ -60,6 +60,22 @@ public void testAssertNotNull() throws Exception { ); } + @Test + public void testAssertNull() throws Exception { + AssertEx.assertNull(null); + AssertEx.assertNull("foo"); + AssertEx.assertNull(null); + AssertEx.assertNull("foo", "assertNull(Object,String)"); + AssertEx.assertNull(null, "", new Object()); + AssertEx.assertNull("foo", "assertNull(Object,String,Object...)"); + + assertResult( + "Expected 'null' but was 'foo'", + "assertNull(Object,String)", + "assertNull(Object,String,Object...)" + ); + } + @Test public void testAssertEquals() throws Exception { AssertEx.assertEquals("foo", "foo"); From a9d982af1c429032f345718e93ba1a969c634e0d Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 16 Mar 2017 16:38:41 -0700 Subject: [PATCH 132/465] Added more functionality to Assertion class --- .../apptentive/android/sdk/debug/Assert.java | 27 +++++++++++++++++++ .../android/sdk/debug/AssertTest.java | 16 +++++++++++ 2 files changed, 43 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java index 581beb04b..879ac10da 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -45,6 +45,33 @@ public static void assertTrue(boolean condition, String format, Object... args) } } + /** + * Asserts that condition is false + */ + public static void assertFalse(boolean condition) { + if (imp != null && condition) { + imp.assertFailed("Expected 'false' but was 'true'"); + } + } + + /** + * Asserts that condition is false + */ + public static void assertFalse(boolean condition, String message) { + if (imp != null && condition) { + imp.assertFailed(message); + } + } + + /** + * Asserts that condition is false + */ + public static void assertFalse(boolean condition, String format, Object... args) { + if (imp != null && condition) { + imp.assertFailed(StringUtils.format(format, args)); + } + } + //endregion //region Nullability diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java index 703a2a976..8076a0098 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/debug/AssertTest.java @@ -44,6 +44,22 @@ public void testAssertTrue() throws Exception { ); } + @Test + public void testAssertFalse() throws Exception { + AssertEx.assertFalse(false); + AssertEx.assertFalse(true); + AssertEx.assertFalse(false, ""); + AssertEx.assertFalse(true, "assertFalse(boolean,String)"); + AssertEx.assertFalse(false, "", new Object()); + AssertEx.assertFalse(true, "assertFalse(boolean,String,Object...)"); + + assertResult( + "Expected 'false' but was 'true'", + "assertFalse(boolean,String)", + "assertFalse(boolean,String,Object...)" + ); + } + @Test public void testAssertNotNull() throws Exception { AssertEx.assertNotNull(new Object()); From 4bc516c55102787d74a5b223e27c89659891cd46 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 16 Mar 2017 16:49:33 -0700 Subject: [PATCH 133/465] Added HttpRequest retry policy --- .../android/sdk/network/HttpRequest.java | 105 +++++++++++++++--- .../sdk/network/HttpRequestManager.java | 22 ++-- .../sdk/network/HttpRequestRetryPolicy.java | 42 +++++++ 3 files changed, 145 insertions(+), 24 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicy.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index cc783d96d..9fd0ff6f4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -17,7 +17,8 @@ import java.util.Set; import java.util.zip.GZIPInputStream; -import static com.apptentive.android.sdk.ApptentiveLogTag.NETWORK; +import static com.apptentive.android.sdk.ApptentiveLogTag.*; +import static com.apptentive.android.sdk.debug.Assert.*; /** * Class representing async HTTP request @@ -34,11 +35,21 @@ public class HttpRequest { */ private static final long DEFAULT_READ_TIMEOUT_MILLIS = 45 * 1000L; + /** + * Default retry policy (used if a custom one is not specified) + */ + private static final HttpRequestRetryPolicy DEFAULT_RETRY_POLICY = new HttpRequestRetryPolicy(); + /** * Id-number of the next request */ private static int nextRequestId; + /** + * Parent request manager + */ + HttpRequestManager requestManager; + /** * Url-connection for network communications */ @@ -104,6 +115,21 @@ public class HttpRequest { */ private String errorMessage; + /** + * Retry policy for this request + */ + private HttpRequestRetryPolicy retryPolicy = DEFAULT_RETRY_POLICY; + + /** + * How many times request was retried + */ + private int retryCount; + + /** + * Flag indicating if the request is currently retrying + */ + boolean retrying; + @SuppressWarnings("rawtypes") private Listener listener; @@ -157,7 +183,7 @@ protected void handleResponse(String response) throws IOException { //////////////////////////////////////////////////////////////// // Request async task - void dispatchSync() { + void dispatchSync(DispatchQueue networkQueue) { long requestStartTime = System.currentTimeMillis(); try { @@ -172,6 +198,11 @@ void dispatchSync() { ApptentiveLog.d(NETWORK, "Request finished in %d ms", System.currentTimeMillis() - requestStartTime); + // attempt a retry if request failed + if (isFailed() && retryRequest(networkQueue, responseCode)) { + return; + } + // use custom callback queue (if any) if (callbackQueue != null) { callbackQueue.dispatchAsync(new DispatchTask() { @@ -252,6 +283,42 @@ protected void sendRequestSync() throws IOException { } } + //region Retry + + private final DispatchTask retryDispatchTask = new DispatchTask() { + @Override + protected void execute() { + assertTrue(retrying); + retrying = false; + + assertNotNull(requestManager); + requestManager.dispatchRequest(HttpRequest.this); + } + }; + + private boolean retryRequest(DispatchQueue networkQueue, int responseCode) { + final int maxRetryCount = retryPolicy.getMaxRetryCount(); + if (maxRetryCount != HttpRequestRetryPolicy.RETRY_INDEFINITELY && retryCount >= maxRetryCount) { + ApptentiveLog.v(NETWORK, "Request maximum retry limit reached (%d)", maxRetryCount); + return false; + } + + if (!retryPolicy.shouldRetryRequest(responseCode)) { + ApptentiveLog.v(NETWORK, "Retry policy declined request retry"); + return false; + } + + ++retryCount; + + assertFalse(retrying); + retrying = true; + networkQueue.dispatchAsyncOnce(retryDispatchTask, retryPolicy.getRetryTimeoutMillis()); + + return true; + } + + //endregion + //region Connection private void setupRequestProperties(HttpURLConnection connection, Map properties) { @@ -351,23 +418,23 @@ public void setRequestProperty(String key, Object value) { public String toString() { try { return String.format( - "\n" + - "Request:\n" + - "\t%s %s\n" + - "\t%s\n" + - "\t%s\n" + - "Response:\n" + - "\t%d\n" + - "\t%s\n" + - "\t%s", + "\n" + + "Request:\n" + + "\t%s %s\n" + + "\t%s\n" + + "\t%s\n" + + "Response:\n" + + "\t%d\n" + + "\t%s\n" + + "\t%s", /* Request */ - method.name(), urlString, - requestProperties, - new String(createRequestData()), + method.name(), urlString, + requestProperties, + new String(createRequestData()), /* Response */ - responseCode, - responseData, - responseHeaders); + responseCode, + responseData, + responseHeaders); } catch (IOException e) { ApptentiveLog.e("", e); } @@ -398,6 +465,10 @@ public boolean isSuccessful() { return responseCode >= 200 && responseCode < 300; } + public boolean isFailed() { + return !isSuccessful() && !isCancelled(); + } + public int getId() { return id; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java index ef4334edf..5fc9bc40c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java @@ -7,7 +7,7 @@ import java.util.ArrayList; import java.util.List; -import static com.apptentive.android.sdk.debug.Assert.assertTrue; +import static com.apptentive.android.sdk.debug.Assert.*; /** * Class for asynchronous HTTP requests handling. @@ -57,21 +57,26 @@ public synchronized HttpRequest startRequest(final HttpRequest request) { } registerRequest(request); + dispatchRequest(request); + notifyRequestStarted(request); + + return request; + } + /** Handles request synchronously */ + void dispatchRequest(final HttpRequest request) { networkQueue.dispatchAsync(new DispatchTask() { @Override protected void execute() { try { - request.dispatchSync(); + request.dispatchSync(networkQueue); } finally { - unregisterRequest(request); + if (!request.retrying) { + unregisterRequest(request); + } } } }); - - notifyRequestStarted(request); - - return request; } /** @@ -91,6 +96,8 @@ public synchronized void cancelAll() { * Register active request */ synchronized void registerRequest(HttpRequest request) { + assertNull(request.requestManager); + request.requestManager = this; activeRequests.add(request); } @@ -102,6 +109,7 @@ synchronized void unregisterRequest(HttpRequest request) { assertTrue(removed, "Attempted to unregister missing request: %s", request); if (removed) { + request.requestManager = null; notifyRequestFinished(request); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicy.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicy.java new file mode 100644 index 000000000..029efd337 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicy.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.network; + +public class HttpRequestRetryPolicy { + public static final int RETRY_INDEFINITELY = -1; + public static final long DEFAULT_RETRY_TIMEOUT_MILLIS = 5 * 1000; + + /** + * How many times should request retry before giving up + */ + private int maxRetryCount = RETRY_INDEFINITELY; + + /** + * How long should we wait before retrying again + */ + private long retryTimeoutMillis = DEFAULT_RETRY_TIMEOUT_MILLIS; + + protected boolean shouldRetryRequest(int responseCode) { + return false; // TODO: decide based on response code + } + + public int getMaxRetryCount() { + return maxRetryCount; + } + + public void setMaxRetryCount(int maxRetryCount) { + this.maxRetryCount = maxRetryCount; + } + + public long getRetryTimeoutMillis() { + return retryTimeoutMillis; + } + + public void setRetryTimeoutMillis(long retryTimeoutMillis) { + this.retryTimeoutMillis = retryTimeoutMillis; + } +} From 6d53618a399c52bf65a98f8020c4752be2f66cb4 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 16 Mar 2017 17:02:35 -0700 Subject: [PATCH 134/465] Integrate assertions with unit tests --- .../sdk/network/HttpRequestManagerTest.java | 3 +++ .../ConcurrentDispatchQueueTest.java | 2 ++ .../threading/SerialDispatchQueueTest.java | 2 ++ .../ApptentiveNotificationCenterTest.java | 9 ++++++++- ...pptentiveNotificationObserverListTest.java | 12 ++++++++++++ .../sdk/util/threading/DispatchQueueTest.java | 7 +++++++ .../apptentive/android/sdk/TestCaseBase.java | 19 +++++++++++++++++++ 7 files changed, 53 insertions(+), 1 deletion(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java index 737dabb4f..cadc96792 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java @@ -20,6 +20,8 @@ public class HttpRequestManagerTest extends TestCaseBase { @Before public void setUp() { + super.setUp(); + networkQueue = new MockDispatchQueue(false); requestManager = new HttpRequestManager(networkQueue); } @@ -27,6 +29,7 @@ public void setUp() { @After public void tearDown() { requestManager.cancelAll(); + super.tearDown(); } @Test diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueueTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueueTest.java index fb405b4e2..51d9ebec7 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueueTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/ConcurrentDispatchQueueTest.java @@ -21,12 +21,14 @@ public class ConcurrentDispatchQueueTest extends TestCaseBase { @Before public void setUp() { + super.setUp(); dispatchQueue = DispatchQueue.createBackgroundQueue("Test Queue", DispatchQueueType.Concurrent); } @After public void tearDown() { dispatchQueue.stop(); + super.tearDown(); } @Test diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java index dd5cca34a..09b0c39e7 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java @@ -22,12 +22,14 @@ public class SerialDispatchQueueTest extends TestCaseBase { @Before public void setUp() { + super.setUp(); dispatchQueue = DispatchQueue.createBackgroundQueue("Test Queue", DispatchQueueType.Serial); } @After public void tearDown() { dispatchQueue.stop(); + super.tearDown(); } @Test diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java index a55c47f0a..dd10fdbf3 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java @@ -11,6 +11,7 @@ import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.threading.MockDispatchQueue; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -19,10 +20,16 @@ public class ApptentiveNotificationCenterTest extends TestCaseBase { private ApptentiveNotificationCenter notificationCenter; @Before - public void setUp() throws Exception { + public void setUp() { + super.setUp(); notificationCenter = new ApptentiveNotificationCenter(new MockDispatchQueue(true), new MockDispatchQueue(true)); } + @Override + public void tearDown() { + super.tearDown(); + } + private static final boolean WEAK_REFERENCE = true; private static final boolean STRONG_REFERENCE = false; diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverListTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverListTest.java index 836a640ff..3fa478fdc 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverListTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverListTest.java @@ -8,6 +8,8 @@ import com.apptentive.android.sdk.TestCaseBase; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import java.util.HashMap; @@ -18,6 +20,16 @@ public class ApptentiveNotificationObserverListTest extends TestCaseBase { private static final boolean WEAK_REFERENCE = true; private static final boolean STRONG_REFERENCE = false; + @Before + public void setUp() { + super.setUp(); + } + + @After + protected void tearDown() { + super.tearDown(); + } + @Test public void testAddObservers() { ApptentiveNotificationObserverList list = new ApptentiveNotificationObserverList(); diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java index 466fe8324..96418a196 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java @@ -8,6 +8,7 @@ import com.apptentive.android.sdk.TestCaseBase; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -17,9 +18,15 @@ public class DispatchQueueTest extends TestCaseBase { @Before public void setUp() { + super.setUp(); overrideMainQueue(false); } + @After + protected void tearDown() { + super.tearDown(); + } + @Test public void testSchedulingTasks() { DispatchTask task = new DispatchTask() { diff --git a/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java index 12c27b560..ca2517a1e 100644 --- a/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java @@ -8,6 +8,8 @@ import android.os.SystemClock; +import com.apptentive.android.sdk.debug.Assert; +import com.apptentive.android.sdk.debug.AssertImp; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.threading.MockDispatchQueue; @@ -21,6 +23,23 @@ public class TestCaseBase { private List result = new ArrayList<>(); private MockDispatchQueue dispatchQueue; + //region Setup + + protected void setUp() { + Assert.setImp(new AssertImp() { + @Override + public void assertFailed(String message) { + throw new AssertionError(message); + } + }); + } + + protected void tearDown() { + Assert.setImp(null); + } + + //endregion + //region Results protected void addResult(String str) { From d444059e9cac2a324dac1f2dad20bd5efa877a21 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 16 Mar 2017 17:36:35 -0700 Subject: [PATCH 135/465] Fixed Http-request retrying + unit tests --- .../sdk/network/HttpRequestManagerTest.java | 47 ++++++++++++++++++- .../sdk/network/MockHttpURLConnection.java | 1 + .../android/sdk/network/HttpRequest.java | 17 +++++-- .../android/sdk/MockDispatchQueue.java | 5 +- 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java index cadc96792..9822aa13b 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java @@ -23,7 +23,7 @@ public void setUp() { super.setUp(); networkQueue = new MockDispatchQueue(false); - requestManager = new HttpRequestManager(networkQueue); + requestManager = new MockHttpRequestManager(networkQueue); } @After @@ -205,6 +205,35 @@ public void onRequestsCancel(HttpRequestManager manager) { ); } + @Test + public void testRetry() { + HttpRequestRetryPolicy retryPolicy = new HttpRequestRetryPolicy() { + @Override + protected boolean shouldRetryRequest(int responseCode) { + return responseCode == 500; + } + }; + retryPolicy.setMaxRetryCount(2); + retryPolicy.setRetryTimeoutMillis(0); + + startRequest(new MockHttpRequest("1").setMockResponseCode(500).setRetryPolicy(retryPolicy)); + startRequest(new MockHttpRequest("2").setMockResponseCode(400).setRetryPolicy(retryPolicy)); + startRequest(new MockHttpRequest("3").setMockResponseCode(204).setRetryPolicy(retryPolicy)); + startRequest(new MockHttpRequest("4").setThrowsExceptionOnConnect(true).setRetryPolicy(retryPolicy)); + startRequest(new MockHttpRequest("5").setThrowsExceptionOnDisconnect(true).setRetryPolicy(retryPolicy)); + dispatchRequests(); + + assertResult( + "failed: 2 Unexpected response code: 400 (Bad Request)", + "finished: 3", + "failed: 4 Connection error", + "failed: 5 Disconnection error", + "retried: 1", + "retried: 1", + "failed: 1 Unexpected response code: 500 (Internal Server Error)" + ); + } + //region Helpers private void startRequest(HttpRequest request) { @@ -232,4 +261,20 @@ private void dispatchRequests() { } //endregion + + //region Mock HttpRequestManager + + private class MockHttpRequestManager extends HttpRequestManager { + MockHttpRequestManager(MockDispatchQueue networkQueue) { + super(networkQueue); + } + + @Override + void dispatchRequest(HttpRequest request) { + if (request.retrying) { + addResult("retried: " + request); + } + super.dispatchRequest(request); + } + } } \ No newline at end of file diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java index 3c2dc933a..c535bb06e 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java @@ -18,6 +18,7 @@ class MockHttpURLConnection extends HttpURLConnection { statusLookup = new HashMap<>(); statusLookup.put(200, "OK"); statusLookup.put(204, "No Content"); + statusLookup.put(400, "Bad Request"); statusLookup.put(500, "Internal Server Error"); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 9fd0ff6f4..447310500 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -221,6 +221,8 @@ protected void sendRequestSync() throws IOException { URL url = new URL(urlString); ApptentiveLog.d(NETWORK, "Performing request: %s", url); + retrying = false; + connection = openConnection(url); connection.setRequestMethod(method.toString()); connection.setConnectTimeout((int) connectTimeout); @@ -289,8 +291,6 @@ protected void sendRequestSync() throws IOException { @Override protected void execute() { assertTrue(retrying); - retrying = false; - assertNotNull(requestManager); requestManager.dispatchRequest(HttpRequest.this); } @@ -310,7 +310,6 @@ private boolean retryRequest(DispatchQueue networkQueue, int responseCode) { ++retryCount; - assertFalse(retrying); retrying = true; networkQueue.dispatchAsyncOnce(retryDispatchTask, retryPolicy.getRetryTimeoutMillis()); @@ -435,8 +434,8 @@ public String toString() { responseCode, responseData, responseHeaders); - } catch (IOException e) { - ApptentiveLog.e("", e); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while getting request string representation"); } return null; } @@ -493,6 +492,14 @@ public String getResponseData() { return responseData; } + public HttpRequest setRetryPolicy(HttpRequestRetryPolicy retryPolicy) { + if (retryPolicy == null) { + throw new IllegalArgumentException("Retry policy is null"); + } + this.retryPolicy = retryPolicy; + return this; + } + /* For unit testing */ protected void setResponseCode(int code) { responseCode = code; diff --git a/apptentive/src/testCommon/java/com/apptentive/android/sdk/MockDispatchQueue.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/MockDispatchQueue.java index c55a1bcac..569490064 100644 --- a/apptentive/src/testCommon/java/com/apptentive/android/sdk/MockDispatchQueue.java +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/MockDispatchQueue.java @@ -35,10 +35,9 @@ public void stop() { } public void dispatchTasks() { - for (DispatchTask task : tasks) { - task.run(); + while (tasks.size() > 0) { + tasks.poll().run(); } - tasks.clear(); } public static MockDispatchQueue overrideMainQueue(boolean runImmediately) { From b29fabec22132288292c7571110e59160f27a108 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 17 Mar 2017 10:56:45 -0700 Subject: [PATCH 136/465] =?UTF-8?q?HttpRequest=20manager=20=E2=80=9Cretry?= =?UTF-8?q?=E2=80=9D=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sdk/network/HttpRequestManagerTest.java | 41 ++++++++- .../sdk/network/MockHttpJsonRequest.java | 2 +- .../android/sdk/network/MockHttpRequest.java | 9 +- .../sdk/network/MockHttpURLConnection.java | 92 +++++++++++++++++-- .../android/sdk/network/HttpRequest.java | 2 +- .../sdk/network/HttpRequestRetryPolicy.java | 18 +++- .../apptentive/android/sdk/TestCaseBase.java | 13 +-- 7 files changed, 153 insertions(+), 24 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java index 9822aa13b..25b916ce0 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java @@ -1,6 +1,7 @@ package com.apptentive.android.sdk.network; import com.apptentive.android.sdk.TestCaseBase; +import com.apptentive.android.sdk.network.MockHttpURLConnection.AbstractResponseHandler; import com.apptentive.android.sdk.util.threading.MockDispatchQueue; import junit.framework.Assert; @@ -206,7 +207,7 @@ public void onRequestsCancel(HttpRequestManager manager) { } @Test - public void testRetry() { + public void testFailedRetry() { HttpRequestRetryPolicy retryPolicy = new HttpRequestRetryPolicy() { @Override protected boolean shouldRetryRequest(int responseCode) { @@ -234,6 +235,44 @@ protected boolean shouldRetryRequest(int responseCode) { ); } + @Test + public void testSuccessfulRetry() { + HttpRequestRetryPolicy retryPolicy = new HttpRequestRetryPolicy() { + @Override + protected boolean shouldRetryRequest(int responseCode) { + return responseCode == 500; + } + }; + retryPolicy.setMaxRetryCount(3); + retryPolicy.setRetryTimeoutMillis(0); + + // fail this request twice and then finish successfully + startRequest(new MockHttpRequest("1").setMockResponseHandler(new AbstractResponseHandler() { + int requestAttempts = 0; + + @Override + public int getResponseCode() { + return requestAttempts++ < 2 ? 500 : 200; + } + }).setRetryPolicy(retryPolicy)); + + startRequest(new MockHttpRequest("2").setMockResponseCode(400).setRetryPolicy(retryPolicy)); + startRequest(new MockHttpRequest("3").setMockResponseCode(204).setRetryPolicy(retryPolicy)); + startRequest(new MockHttpRequest("4").setThrowsExceptionOnConnect(true).setRetryPolicy(retryPolicy)); + startRequest(new MockHttpRequest("5").setThrowsExceptionOnDisconnect(true).setRetryPolicy(retryPolicy)); + dispatchRequests(); + + assertResult( + "failed: 2 Unexpected response code: 400 (Bad Request)", + "finished: 3", + "failed: 4 Connection error", + "failed: 5 Disconnection error", + "retried: 1", + "retried: 1", + "finished: 1" + ); + } + //region Helpers private void startRequest(HttpRequest request) { diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java index eaf46d939..46ad309e9 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java @@ -33,7 +33,7 @@ public MockHttpJsonRequest setMockResponseData(JSONObject responseData) { } public MockHttpJsonRequest setMockResponseData(String responseData) { - connection.responseData = responseData; + connection.setMockResponseData(responseData); return this; } } diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpRequest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpRequest.java index b7c2270be..e3ab9303b 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpRequest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpRequest.java @@ -1,5 +1,7 @@ package com.apptentive.android.sdk.network; +import com.apptentive.android.sdk.network.MockHttpURLConnection.ResponseHandler; + import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; @@ -30,13 +32,18 @@ public MockHttpRequest setThrowsExceptionOnDisconnect(boolean throwsExceptionOnD return this; } + public MockHttpRequest setMockResponseHandler(ResponseHandler handler) { + connection.setMockResponseHandler(handler); + return this; + } + public MockHttpRequest setMockResponseCode(int mockResponseCode) { connection.setMockResponseCode(mockResponseCode); return this; } public MockHttpRequest setResponseData(String responseData) { - connection.responseData = responseData; + connection.setMockResponseData(responseData); return this; } diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java index c535bb06e..928ceca04 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java @@ -24,10 +24,9 @@ class MockHttpURLConnection extends HttpURLConnection { boolean throwsExceptionOnConnect; boolean throwsExceptionOnDisconnect; - int mockResponseCode = 200; // HTTP OK by default - String mockResponseMessage = "OK"; - String responseData = ""; - String errorData = ""; + + private ResponseHandler responseHandler = new DefaultResponseHandler(200, "", ""); // HTTP OK by default + private int lastResponseCode; // remember the last returned HTTP response code to properly resolve response message protected MockHttpURLConnection() { super(null); @@ -53,13 +52,13 @@ public void disconnect() { @Override public InputStream getInputStream() throws IOException { - return new ByteArrayInputStream(responseData.getBytes("UTF-8")); + return new ByteArrayInputStream(responseHandler.getResponseData().getBytes("UTF-8")); } @Override public InputStream getErrorStream() { try { - return new ByteArrayInputStream(errorData.getBytes("UTF-8")); + return new ByteArrayInputStream(responseHandler.getErrorData().getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new AssertionError(e); } @@ -75,12 +74,13 @@ public int getResponseCode() throws IOException { if (throwsExceptionOnConnect) { throw new IOException("Connection error"); } - return mockResponseCode; + lastResponseCode = responseHandler.getResponseCode(); + return lastResponseCode; } @Override public String getResponseMessage() throws IOException { - return mockResponseMessage; + return statusLookup.get(lastResponseCode); } @Override @@ -88,7 +88,79 @@ public void setRequestMethod(String method) throws ProtocolException { } public void setMockResponseCode(int mockResponseCode) { - this.mockResponseCode = mockResponseCode; - this.mockResponseMessage = statusLookup.get(mockResponseCode); + ((DefaultResponseHandler) responseHandler).setResponseCode(mockResponseCode); + } + + public void setMockResponseData(String responseData) { + ((DefaultResponseHandler) responseHandler).setResponseData(responseData); + } + + public void setMockResponseHandler(ResponseHandler handler) { + responseHandler = handler; + } + + public interface ResponseHandler { + int getResponseCode(); + String getResponseData(); + String getErrorData(); + } + + public static class AbstractResponseHandler implements ResponseHandler { + @Override + public int getResponseCode() { + return 200; + } + + @Override + public String getResponseData() { + return ""; + } + + @Override + public String getErrorData() { + return ""; + } + } + + private static class DefaultResponseHandler implements ResponseHandler { + private int responseCode; + private String responseData; + private String errorData; + + public DefaultResponseHandler(int responseCode, String responseData, String errorData) { + this.responseCode = responseCode; + this.responseData = responseData; + this.errorData = errorData; + } + + public DefaultResponseHandler setResponseCode(int responseCode) { + this.responseCode = responseCode; + return this; + } + + @Override + public int getResponseCode() { + return responseCode; + } + + public DefaultResponseHandler setResponseData(String responseData) { + this.responseData = responseData; + return this; + } + + @Override + public String getResponseData() { + return responseData; + } + + public DefaultResponseHandler setErrorData(String errorData) { + this.errorData = errorData; + return this; + } + + @Override + public String getErrorData() { + return errorData; + } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 447310500..f12ed7426 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -311,7 +311,7 @@ private boolean retryRequest(DispatchQueue networkQueue, int responseCode) { ++retryCount; retrying = true; - networkQueue.dispatchAsyncOnce(retryDispatchTask, retryPolicy.getRetryTimeoutMillis()); + networkQueue.dispatchAsyncOnce(retryDispatchTask, retryPolicy.getRetryTimeoutMillis(retryCount)); return true; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicy.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicy.java index 029efd337..f13fbc4d9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicy.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicy.java @@ -20,10 +20,24 @@ public class HttpRequestRetryPolicy { */ private long retryTimeoutMillis = DEFAULT_RETRY_TIMEOUT_MILLIS; + /** + * Returns true is request should be retried. + * + * @param responseCode - HTTP response code for the request + */ protected boolean shouldRetryRequest(int responseCode) { return false; // TODO: decide based on response code } + /** + * Returns the delay in millis for the next retry + * + * @param retryCount - number of retries attempted already + */ + protected long getRetryTimeoutMillis(int retryCount) { + return retryTimeoutMillis; + } + public int getMaxRetryCount() { return maxRetryCount; } @@ -32,10 +46,6 @@ public void setMaxRetryCount(int maxRetryCount) { this.maxRetryCount = maxRetryCount; } - public long getRetryTimeoutMillis() { - return retryTimeoutMillis; - } - public void setRetryTimeoutMillis(long retryTimeoutMillis) { this.retryTimeoutMillis = retryTimeoutMillis; } diff --git a/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java index ca2517a1e..f9babe56f 100644 --- a/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java @@ -47,13 +47,14 @@ protected void addResult(String str) { } protected void assertResult(String... expected) { - assertEquals("\nExpected: " + StringUtils.join(expected) + - "\nActual: " + StringUtils.join(result), expected.length, result.size()); + // Make sure the expected and result sets contain the same number of items + if (expected.length != result.size()) { + fail(String.format("Expected: [%s], Actual: [%s]", StringUtils.join(expected), StringUtils.join(result))); + } + // Make sure the order and values are the same as well for (int i = 0; i < expected.length; ++i) { - assertEquals("\nExpected: " + StringUtils.join(expected) + - "\nActual: " + StringUtils.join(result), - expected[i], result.get(i)); + assertEquals(String.format("Expected: [%s], Actual: [%s],", StringUtils.join(expected), StringUtils.join(result)), expected[i], result.get(i)); } result.clear(); @@ -79,4 +80,4 @@ protected void sleep(long millis) { } //endregion -} +} \ No newline at end of file From b5140edb959e8caa574d4cd4948c6aea4a993839 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 17 Mar 2017 11:08:05 -0700 Subject: [PATCH 137/465] Refactoring Renamed HttpRequest field --- .../apptentive/android/sdk/network/HttpRequest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index f12ed7426..26ebf082f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -121,9 +121,9 @@ public class HttpRequest { private HttpRequestRetryPolicy retryPolicy = DEFAULT_RETRY_POLICY; /** - * How many times request was retried + * How many times request was retried already */ - private int retryCount; + private int retryAttemptCount; /** * Flag indicating if the request is currently retrying @@ -298,7 +298,7 @@ protected void execute() { private boolean retryRequest(DispatchQueue networkQueue, int responseCode) { final int maxRetryCount = retryPolicy.getMaxRetryCount(); - if (maxRetryCount != HttpRequestRetryPolicy.RETRY_INDEFINITELY && retryCount >= maxRetryCount) { + if (maxRetryCount != HttpRequestRetryPolicy.RETRY_INDEFINITELY && retryAttemptCount >= maxRetryCount) { ApptentiveLog.v(NETWORK, "Request maximum retry limit reached (%d)", maxRetryCount); return false; } @@ -308,10 +308,10 @@ private boolean retryRequest(DispatchQueue networkQueue, int responseCode) { return false; } - ++retryCount; + ++retryAttemptCount; retrying = true; - networkQueue.dispatchAsyncOnce(retryDispatchTask, retryPolicy.getRetryTimeoutMillis(retryCount)); + networkQueue.dispatchAsyncOnce(retryDispatchTask, retryPolicy.getRetryTimeoutMillis(retryAttemptCount)); return true; } From f6ee5e92a83648edcdb0663e66a45ac5ecd71b32 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 17 Mar 2017 11:18:37 -0700 Subject: [PATCH 138/465] Added more comments and assertions to HttpRequest --- .../apptentive/android/sdk/network/HttpRequest.java | 12 +++++++++--- .../android/sdk/util/threading/DispatchTask.java | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 26ebf082f..7c9099df0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -1,6 +1,7 @@ package com.apptentive.android.sdk.network; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -126,7 +127,7 @@ public class HttpRequest { private int retryAttemptCount; /** - * Flag indicating if the request is currently retrying + * Flag indicating if the request is currently scheduled for a retry */ boolean retrying; @@ -183,6 +184,9 @@ protected void handleResponse(String response) throws IOException { //////////////////////////////////////////////////////////////// // Request async task + /** + * Send request synchronously on a background network queue + */ void dispatchSync(DispatchQueue networkQueue) { long requestStartTime = System.currentTimeMillis(); @@ -199,7 +203,7 @@ void dispatchSync(DispatchQueue networkQueue) { ApptentiveLog.d(NETWORK, "Request finished in %d ms", System.currentTimeMillis() - requestStartTime); // attempt a retry if request failed - if (isFailed() && retryRequest(networkQueue, responseCode)) { + if (isFailed() && retryRequest(networkQueue, responseCode)) { // we schedule request retry on the same queue as it was originally dispatched return; } @@ -269,7 +273,7 @@ protected void sendRequestSync() throws IOException { responseData = readResponse(connection.getInputStream(), gzipped); ApptentiveLog.v(NETWORK, "Response: %s", responseData); } else { - errorMessage = String.format("Unexpected response code: %d (%s)", responseCode, connection.getResponseMessage()); + errorMessage = StringUtils.format("Unexpected response code: %d (%s)", responseCode, connection.getResponseMessage()); responseData = readResponse(connection.getErrorStream(), gzipped); ApptentiveLog.w(NETWORK, "Response: %s", responseData); } @@ -297,6 +301,8 @@ protected void execute() { }; private boolean retryRequest(DispatchQueue networkQueue, int responseCode) { + assertFalse(retryDispatchTask.isScheduled()); + final int maxRetryCount = retryPolicy.getMaxRetryCount(); if (maxRetryCount != HttpRequestRetryPolicy.RETRY_INDEFINITELY && retryAttemptCount >= maxRetryCount) { ApptentiveLog.v(NETWORK, "Request maximum retry limit reached (%d)", maxRetryCount); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java index 6b7ebe7d3..f3546f68c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java @@ -38,7 +38,7 @@ synchronized void setScheduled(boolean scheduled) { this.scheduled = scheduled; } - synchronized boolean isScheduled() { + public synchronized boolean isScheduled() { return scheduled; } } \ No newline at end of file From 9a11b7ecedbb98f1e23ee6ac473fce4db989442c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 17 Mar 2017 11:38:26 -0700 Subject: [PATCH 139/465] Fixed Conversation listeners --- .../com/apptentive/android/sdk/conversation/Conversation.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 2a175bcde..c39496973 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -93,6 +93,7 @@ public Conversation() { this.versionHistory = new VersionHistory(); // transient fields might not get properly initialized upon de-serialization + setDataChangedListener(this); initDispatchTasks(); } @@ -263,6 +264,7 @@ public void notifyDataChanged() { @Override public void onDeserialize() { + setDataChangedListener(this); initDispatchTasks(); } From f4600311fc4537edf7755f4957f007b0a9c96d7c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 17 Mar 2017 12:11:30 -0700 Subject: [PATCH 140/465] Added more Conversation tests --- .../sdk/conversation/Conversation.java | 43 ++++++++++++------- .../android/sdk/storage/ConversationTest.java | 37 +++++++++++----- 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index c39496973..fb81eddd4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -8,7 +8,6 @@ import android.content.Context; import android.content.SharedPreferences; -import android.text.TextUtils; import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; @@ -30,6 +29,7 @@ import com.apptentive.android.sdk.storage.VersionHistory; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.RuntimeUtils; +import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -84,6 +84,8 @@ public class Conversation implements Saveable, DataChangedListener { private transient DispatchTask fetchInteractionsTask; private transient DispatchTask saveConversationTask; + private transient DataChangedListener listener; + public Conversation() { this.device = new Device(); this.person = new Person(); @@ -93,7 +95,11 @@ public Conversation() { this.versionHistory = new VersionHistory(); // transient fields might not get properly initialized upon de-serialization - setDataChangedListener(this); + initTransientFields(); + } + + private void initTransientFields() { + initDataChangeListeners(); initDispatchTasks(); } @@ -240,14 +246,18 @@ synchronized boolean save() { //region Listeners - @Override - public void setDataChangedListener(DataChangedListener listener) { + private void initDataChangeListeners() { device.setDataChangedListener(this); person.setDataChangedListener(this); eventData.setDataChangedListener(this); versionHistory.setDataChangedListener(this); } + @Override + public void setDataChangedListener(DataChangedListener listener) { + this.listener = listener; + } + @Override public void notifyDataChanged() { if (hasFile()) { @@ -260,12 +270,15 @@ public void notifyDataChanged() { } else { ApptentiveLog.v(CONVERSATION, "Can't save conversation data: storage file is not specified"); } + + if (listener != null) { + listener.onDataChanged(); + } } @Override public void onDeserialize() { - setDataChangedListener(this); - initDispatchTasks(); + initTransientFields(); } @Override @@ -317,7 +330,7 @@ public String getConversationToken() { } public void setConversationToken(String conversationToken) { - if (!TextUtils.equals(this.conversationToken, conversationToken)) { + if (!StringUtils.equal(this.conversationToken, conversationToken)) { this.conversationToken = conversationToken; notifyDataChanged(); } @@ -328,7 +341,7 @@ public String getConversationId() { } public void setConversationId(String conversationId) { - if (!TextUtils.equals(this.conversationId, conversationId)) { + if (!StringUtils.equal(this.conversationId, conversationId)) { this.conversationId = conversationId; notifyDataChanged(); } @@ -339,7 +352,7 @@ public String getPersonId() { } public void setPersonId(String personId) { - if (!TextUtils.equals(this.personId, personId)) { + if (!StringUtils.equal(this.personId, personId)) { this.personId = personId; notifyDataChanged(); } @@ -350,7 +363,7 @@ public String getPersonEmail() { } public void setPersonEmail(String personEmail) { - if (!TextUtils.equals(this.personEmail, personEmail)) { + if (!StringUtils.equal(this.personEmail, personEmail)) { this.personEmail = personEmail; notifyDataChanged(); } @@ -361,7 +374,7 @@ public String getPersonName() { } public void setPersonName(String personName) { - if (!TextUtils.equals(this.personName, personName)) { + if (!StringUtils.equal(this.personName, personName)) { this.personName = personName; notifyDataChanged(); } @@ -481,7 +494,7 @@ public String getMessageCenterPendingMessage() { } public void setMessageCenterPendingMessage(String messageCenterPendingMessage) { - if (!TextUtils.equals(this.messageCenterPendingMessage, messageCenterPendingMessage)) { + if (!StringUtils.equal(this.messageCenterPendingMessage, messageCenterPendingMessage)) { this.messageCenterPendingMessage = messageCenterPendingMessage; notifyDataChanged(); } @@ -492,7 +505,7 @@ public String getMessageCenterPendingAttachments() { } public void setMessageCenterPendingAttachments(String messageCenterPendingAttachments) { - if (!TextUtils.equals(this.messageCenterPendingAttachments, messageCenterPendingAttachments)) { + if (!StringUtils.equal(this.messageCenterPendingAttachments, messageCenterPendingAttachments)) { this.messageCenterPendingAttachments = messageCenterPendingAttachments; notifyDataChanged(); } @@ -503,7 +516,7 @@ public String getTargets() { } public void setTargets(String targets) { - if (!TextUtils.equals(this.targets, targets)) { + if (!StringUtils.equal(this.targets, targets)) { this.targets = targets; notifyDataChanged(); } @@ -514,7 +527,7 @@ public String getInteractions() { } public void setInteractions(String interactions) { - if (!TextUtils.equals(this.interactions, interactions)) { + if (!StringUtils.equal(this.interactions, interactions)) { this.interactions = interactions; notifyDataChanged(); } diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationTest.java index df8947d51..cab850ee7 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationTest.java @@ -12,7 +12,9 @@ import com.apptentive.android.sdk.util.Util; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -22,6 +24,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -35,6 +38,9 @@ @PrepareForTest(TextUtils.class) public class ConversationTest { + @Rule + public TemporaryFolder conversationFolder = new TemporaryFolder(); + @Before public void setup() { PowerMockito.mockStatic(TextUtils.class); @@ -96,7 +102,7 @@ public void testSerialization() { assertEquals(expected.getMessageCenterPendingAttachments(), result.getMessageCenterPendingAttachments()); assertEquals(expected.getTargets(), result.getTargets()); assertEquals(expected.getInteractions(), result.getInteractions()); - assertEquals(expected.getInteractionExpiration(), result.getInteractionExpiration()); + assertEquals(expected.getInteractionExpiration(), result.getInteractionExpiration(), 0.000001); } catch (Exception e) { fail(e.getMessage()); @@ -108,21 +114,33 @@ public void testSerialization() { } } - private boolean listenerFired; + private boolean listenerFired; // TODO: get rid of this field and make it test "local" @Test - public void testListeners() { + public void testDataChangeListeners() throws Exception { + Conversation conversation = new Conversation(); + testConversationListeners(conversation); + } + + @Test + public void testDataChangeListenersWhenDeserialized() throws Exception { + Conversation conversation = new Conversation(); + + File conversationFile = new File(conversationFolder.getRoot(), "conversation.bin"); + new FileSerializer(conversationFile).serialize(conversation); - DataChangedListener listener = new DataChangedListener() { + conversation = (Conversation) new FileSerializer(conversationFile).deserialize(); + testConversationListeners(conversation); + } + + private void testConversationListeners(Conversation conversation) { + conversation.setDataChangedListener(new DataChangedListener() { @Override public void onDataChanged() { listenerFired = true; } - }; - - Conversation conversation = new Conversation(); - conversation.setDataChangedListener(listener); - assertFalse(listenerFired); + }); + listenerFired = false; conversation.setConversationToken("foo"); assertTrue(listenerFired); @@ -308,7 +326,6 @@ public void onDataChanged() { assertTrue(listenerFired); listenerFired = false; - conversation.getEventData().storeEventForCurrentAppVersion(100D, 10, "1.0", "foo"); assertTrue(listenerFired); listenerFired = false; From 9e5291b81071ce949e695dbb90460b162e4b9e9d Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 17 Mar 2017 12:11:44 -0700 Subject: [PATCH 141/465] Fixed Unit tests --- .../android/sdk/util/threading/SerialDispatchQueueTest.java | 2 +- .../notifications/ApptentiveNotificationObserverListTest.java | 2 +- .../android/sdk/util/threading/DispatchQueueTest.java | 2 +- .../java/com/apptentive/android/sdk/MockDispatchQueue.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java index 09b0c39e7..79a7e1639 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/util/threading/SerialDispatchQueueTest.java @@ -64,6 +64,7 @@ public void testStoppingDispatch() { @Override protected void execute() { sleep(500); + dispatchQueue.stop(); addResult("task-1"); } }); @@ -73,7 +74,6 @@ protected void execute() { addResult("task-2"); } }); - dispatchQueue.stop(); sleep(1000); // wait for the first task to finish assertResult("task-1"); // task-2 should not run diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverListTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverListTest.java index 3fa478fdc..80c955076 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverListTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationObserverListTest.java @@ -26,7 +26,7 @@ public void setUp() { } @After - protected void tearDown() { + public void tearDown() { super.tearDown(); } diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java index 96418a196..aaabf2984 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/util/threading/DispatchQueueTest.java @@ -23,7 +23,7 @@ public void setUp() { } @After - protected void tearDown() { + public void tearDown() { super.tearDown(); } diff --git a/apptentive/src/testCommon/java/com/apptentive/android/sdk/MockDispatchQueue.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/MockDispatchQueue.java index 569490064..e7c17b6f1 100644 --- a/apptentive/src/testCommon/java/com/apptentive/android/sdk/MockDispatchQueue.java +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/MockDispatchQueue.java @@ -49,7 +49,7 @@ public static MockDispatchQueue overrideMainQueue(boolean runImmediately) { private static void overrideMainQueue(DispatchQueue queue) { try { Class holderClass = DispatchQueue.class.getDeclaredClasses()[0]; - Field instanceField = holderClass.getDeclaredField("INSTANCE"); + Field instanceField = holderClass.getDeclaredField("MAIN_QUEUE"); instanceField.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); From a7a38809f0eeee01008e4bbdd2ee0f4ce72a8aac Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 11:18:24 -0700 Subject: [PATCH 142/465] Refactoring Extracted Conversation data into a separate class --- .../sdk/conversation/Conversation.java | 939 ++++++++---------- .../sdk/conversation/ConversationData.java | 302 ++++++ .../android/sdk/storage/CustomData.java | 4 - .../android/sdk/storage/Device.java | 4 - .../android/sdk/storage/EventData.java | 4 - .../android/sdk/storage/FileSerializer.java | 4 +- .../sdk/storage/IntegrationConfig.java | 4 - .../android/sdk/storage/Person.java | 4 - .../android/sdk/storage/Saveable.java | 1 - .../android/sdk/storage/VersionHistory.java | 4 - 10 files changed, 714 insertions(+), 556 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index fb81eddd4..f3943d1d3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -24,12 +24,10 @@ import com.apptentive.android.sdk.storage.EventData; import com.apptentive.android.sdk.storage.FileSerializer; import com.apptentive.android.sdk.storage.Person; -import com.apptentive.android.sdk.storage.Saveable; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.VersionHistory; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.RuntimeUtils; -import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -38,531 +36,416 @@ import java.io.File; -import static com.apptentive.android.sdk.ApptentiveLogTag.CONVERSATION; -import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS; import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; -import static com.apptentive.android.sdk.debug.TesterEvent.EVT_INTERACTION_FETCH; - -public class Conversation implements Saveable, DataChangedListener { - - private static final long serialVersionUID = 1L; - - private String conversationToken; - private String conversationId; - private String personId; - private String personEmail; - private String personName; - private Device device; - private Device lastSentDevice; - private Person person; - private Person lastSentPerson; - private Sdk sdk; - private AppRelease appRelease; - private EventData eventData; - private String lastSeenSdkVersion; - private VersionHistory versionHistory; - private boolean messageCenterFeatureUsed; - private boolean messageCenterWhoCardPreviouslyDisplayed; - private String messageCenterPendingMessage; - private String messageCenterPendingAttachments; - private String targets; - private String interactions; - private double interactionExpiration; - - /** - * File which represents this conversation on the disk - */ - private transient File file; - - // TODO: Maybe move this up to a wrapping Conversation class? - private transient InteractionManager interactionManager; - - // TODO: describe why we don't serialize state - private transient ConversationState state = ConversationState.UNDEFINED; - - // we keep references to the tasks in order to dispatch them only once - private transient DispatchTask fetchInteractionsTask; - private transient DispatchTask saveConversationTask; - - private transient DataChangedListener listener; - - public Conversation() { - this.device = new Device(); - this.person = new Person(); - this.sdk = new Sdk(); - this.appRelease = new AppRelease(); - this.eventData = new EventData(); - this.versionHistory = new VersionHistory(); - - // transient fields might not get properly initialized upon de-serialization - initTransientFields(); - } - - private void initTransientFields() { - initDataChangeListeners(); - initDispatchTasks(); - } - - private void initDispatchTasks() { - fetchInteractionsTask = new DispatchTask() { - @Override - protected void execute() { - final boolean updateSuccessful = fetchInteractionsSync(); - - // Update pending state on UI thread after finishing the task - DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - if (hasActiveState()) { - ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); - dispatchDebugEvent(EVT_INTERACTION_FETCH, updateSuccessful); - } - } - }); - } - }; - saveConversationTask = new DispatchTask() { - @Override - protected void execute() { - save(); - } - }; - } - - //region Interactions - - /** - * Returns an Interaction for eventLabel if there is one that can be displayed. - */ - public Interaction getApplicableInteraction(String eventLabel) { - String targetsString = getTargets(); - if (targetsString != null) { - try { - Targets targets = new Targets(getTargets()); - String interactionId = targets.getApplicableInteraction(eventLabel); - if (interactionId != null) { - String interactionsString = getInteractions(); - if (interactionsString != null) { - Interactions interactions = new Interactions(interactionsString); - return interactions.getInteraction(interactionId); - } - } - } catch (JSONException e) { - ApptentiveLog.e(e, "Exception while getting applicable interaction: %s", eventLabel); - } - } - return null; - } - - boolean fetchInteractions(Context context) { - boolean cacheExpired = getInteractionExpiration() > Util.currentTimeSeconds(); - if (cacheExpired || RuntimeUtils.isAppDebuggable(context)) { - return DispatchQueue.backgroundQueue().dispatchAsyncOnce(fetchInteractionsTask); // do not allow multiple fetches at the same time - } - - ApptentiveLog.v(CONVERSATION, "Interaction cache is still valid"); - return false; - } - - /** - * Fetches interaction synchronously. Returns true if succeed. - */ - private boolean fetchInteractionsSync() { - ApptentiveLog.v(CONVERSATION, "Fetching Interactions"); - ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); - - // TODO: Move this to global config - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - boolean updateSuccessful = true; - - // We weren't able to connect to the internet. - if (response.isException()) { - prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, false).apply(); - updateSuccessful = false; - } - // We got a server error. - else if (!response.isSuccessful()) { - prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, true).apply(); - updateSuccessful = false; - } - - if (updateSuccessful) { - String interactionsPayloadString = response.getContent(); - - // Store new integration cache expiration. - String cacheControl = response.getHeaders().get("Cache-Control"); - Integer cacheSeconds = Util.parseCacheControlHeader(cacheControl); - if (cacheSeconds == null) { - cacheSeconds = Constants.CONFIG_DEFAULT_INTERACTION_CACHE_EXPIRATION_DURATION_SECONDS; - } - setInteractionExpiration(Util.currentTimeSeconds() + cacheSeconds); - try { - InteractionManifest payload = new InteractionManifest(interactionsPayloadString); - Interactions interactions = payload.getInteractions(); - Targets targets = payload.getTargets(); - if (interactions != null && targets != null) { - setTargets(targets.toString()); - setInteractions(interactions.toString()); - } else { - ApptentiveLog.e(CONVERSATION, "Unable to save interactionManifest."); - } - } catch (JSONException e) { - ApptentiveLog.e(e, "Invalid InteractionManifest received."); - } - } - ApptentiveLog.v(CONVERSATION, "Fetching new Interactions asyncTask finished. Successful? %b", updateSuccessful); - - return updateSuccessful; - } - - //endregion - - //region Saving - - /** - * Saves conversation data to the disk synchronously. Returns true - * if succeed. - */ - synchronized boolean save() { - if (file == null) { - ApptentiveLog.e(CONVERSATION, "Unable to save conversation: destination file not specified"); - return false; - } - - ApptentiveLog.d(CONVERSATION, "Saving Conversation"); - ApptentiveLog.v(CONVERSATION, "EventData: %s", getEventData().toString()); // TODO: remove - - try { - FileSerializer serializer = new FileSerializer(file); - serializer.serialize(this); - return true; - } catch (Exception e) { - ApptentiveLog.e(e, "Unable to save conversation"); - return false; - } - } - - //endregion - - //region Listeners - - private void initDataChangeListeners() { - device.setDataChangedListener(this); - person.setDataChangedListener(this); - eventData.setDataChangedListener(this); - versionHistory.setDataChangedListener(this); - } - - @Override - public void setDataChangedListener(DataChangedListener listener) { - this.listener = listener; - } - - @Override - public void notifyDataChanged() { - if (hasFile()) { - boolean scheduled = DispatchQueue.backgroundQueue().dispatchAsyncOnce(saveConversationTask, 100L); - if (scheduled) { - ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); - } else { - ApptentiveLog.d(CONVERSATION, "Conversation save already scheduled."); - } - } else { - ApptentiveLog.v(CONVERSATION, "Can't save conversation data: storage file is not specified"); - } - - if (listener != null) { - listener.onDataChanged(); - } - } - - @Override - public void onDeserialize() { - initTransientFields(); - } - - @Override - public void onDataChanged() { - notifyDataChanged(); - } - - //endregion - - //region Getters & Setters - - public ConversationState getState() { - return state; - } - - public void setState(ConversationState state) { - // TODO: check if state transition would make sense (for example you should not be able to move from 'logged' state to 'anonymous', etc.) - this.state = state; - } - - /** - * Returns true if conversation is in the given state - */ - public boolean hasState(ConversationState s) { - return state.equals(s); - } - - /** - * Returns true if conversation is in one of the given states - */ - public boolean hasState(ConversationState... states) { - for (ConversationState s : states) { - if (s.equals(state)) { - return true; - } - } - return false; - } - - /** - * Returns true if conversation is in "active" state (after receiving server response) - */ - public boolean hasActiveState() { - return hasState(ConversationState.LOGGED_IN, ANONYMOUS); - } - - public String getConversationToken() { - return conversationToken; - } - - public void setConversationToken(String conversationToken) { - if (!StringUtils.equal(this.conversationToken, conversationToken)) { - this.conversationToken = conversationToken; - notifyDataChanged(); - } - } - - public String getConversationId() { - return conversationId; - } - - public void setConversationId(String conversationId) { - if (!StringUtils.equal(this.conversationId, conversationId)) { - this.conversationId = conversationId; - notifyDataChanged(); - } - } - - public String getPersonId() { - return personId; - } - - public void setPersonId(String personId) { - if (!StringUtils.equal(this.personId, personId)) { - this.personId = personId; - notifyDataChanged(); - } - } - - public String getPersonEmail() { - return personEmail; - } - - public void setPersonEmail(String personEmail) { - if (!StringUtils.equal(this.personEmail, personEmail)) { - this.personEmail = personEmail; - notifyDataChanged(); - } - } - - public String getPersonName() { - return personName; - } - - public void setPersonName(String personName) { - if (!StringUtils.equal(this.personName, personName)) { - this.personName = personName; - notifyDataChanged(); - } - } - - public Device getDevice() { - return device; - } - - public void setDevice(Device device) { - this.device = device; - device.setDataChangedListener(this); - notifyDataChanged(); - } - - public Device getLastSentDevice() { - return lastSentDevice; - } - - public void setLastSentDevice(Device lastSentDevice) { - this.lastSentDevice = lastSentDevice; - this.lastSentDevice.setDataChangedListener(this); - notifyDataChanged(); - } - - public Person getPerson() { - return person; - } - - public void setPerson(Person person) { - this.person = person; - this.person.setDataChangedListener(this); - notifyDataChanged(); - } - - public Person getLastSentPerson() { - return lastSentPerson; - } - - public void setLastSentPerson(Person lastSentPerson) { - this.lastSentPerson = lastSentPerson; - this.lastSentPerson.setDataChangedListener(this); - notifyDataChanged(); - } - - public Sdk getSdk() { - return sdk; - } - - public void setSdk(Sdk sdk) { - this.sdk = sdk; - notifyDataChanged(); - } - - public AppRelease getAppRelease() { - return appRelease; - } - - public void setAppRelease(AppRelease appRelease) { - this.appRelease = appRelease; - notifyDataChanged(); - } - - public EventData getEventData() { - return eventData; - } - - public void setEventData(EventData eventData) { - this.eventData = eventData; - this.eventData.setDataChangedListener(this); - notifyDataChanged(); - } - - public String getLastSeenSdkVersion() { - return lastSeenSdkVersion; - } - - public void setLastSeenSdkVersion(String lastSeenSdkVersion) { - this.lastSeenSdkVersion = lastSeenSdkVersion; - notifyDataChanged(); - } - - public VersionHistory getVersionHistory() { - return versionHistory; - } - - public void setVersionHistory(VersionHistory versionHistory) { - this.versionHistory = versionHistory; - this.versionHistory.setDataChangedListener(this); - notifyDataChanged(); - } - - public boolean isMessageCenterFeatureUsed() { - return messageCenterFeatureUsed; - } - - public void setMessageCenterFeatureUsed(boolean messageCenterFeatureUsed) { - if (this.messageCenterFeatureUsed != messageCenterFeatureUsed) { - this.messageCenterFeatureUsed = messageCenterFeatureUsed; - notifyDataChanged(); - } - } - - public boolean isMessageCenterWhoCardPreviouslyDisplayed() { - return messageCenterWhoCardPreviouslyDisplayed; - } - - public void setMessageCenterWhoCardPreviouslyDisplayed(boolean messageCenterWhoCardPreviouslyDisplayed) { - if (this.messageCenterWhoCardPreviouslyDisplayed != messageCenterWhoCardPreviouslyDisplayed) { - this.messageCenterWhoCardPreviouslyDisplayed = messageCenterWhoCardPreviouslyDisplayed; - notifyDataChanged(); - } - } - - public String getMessageCenterPendingMessage() { - return messageCenterPendingMessage; - } - - public void setMessageCenterPendingMessage(String messageCenterPendingMessage) { - if (!StringUtils.equal(this.messageCenterPendingMessage, messageCenterPendingMessage)) { - this.messageCenterPendingMessage = messageCenterPendingMessage; - notifyDataChanged(); - } - } - - public String getMessageCenterPendingAttachments() { - return messageCenterPendingAttachments; - } - - public void setMessageCenterPendingAttachments(String messageCenterPendingAttachments) { - if (!StringUtils.equal(this.messageCenterPendingAttachments, messageCenterPendingAttachments)) { - this.messageCenterPendingAttachments = messageCenterPendingAttachments; - notifyDataChanged(); - } - } - - public String getTargets() { - return targets; - } - - public void setTargets(String targets) { - if (!StringUtils.equal(this.targets, targets)) { - this.targets = targets; - notifyDataChanged(); - } - } - - public String getInteractions() { - return interactions; - } - - public void setInteractions(String interactions) { - if (!StringUtils.equal(this.interactions, interactions)) { - this.interactions = interactions; - notifyDataChanged(); - } - } - - public double getInteractionExpiration() { - return interactionExpiration; - } - - public void setInteractionExpiration(double interactionExpiration) { - if (this.interactionExpiration != interactionExpiration) { - this.interactionExpiration = interactionExpiration; - notifyDataChanged(); - } - } - - public InteractionManager getInteractionManager() { - return interactionManager; - } - - public void setInteractionManager(InteractionManager interactionManager) { - this.interactionManager = interactionManager; - } - - synchronized boolean hasFile() { - return file != null; - } - - synchronized File getFile() { - return file; - } - - synchronized void setFile(File file) { - this.file = file; - } - - //endregion +import static com.apptentive.android.sdk.ApptentiveLogTag.*; +import static com.apptentive.android.sdk.conversation.ConversationState.*; +import static com.apptentive.android.sdk.debug.TesterEvent.*; + +public class Conversation implements DataChangedListener { + + /** + * Conversation data for this class to manage + */ + private ConversationData data; + + /** + * File which represents this conversation on the disk + */ + private File file; + + // TODO: Maybe move this up to a wrapping Conversation class? + private InteractionManager interactionManager; + + private ConversationState state = ConversationState.UNDEFINED; + + // we keep references to the tasks in order to dispatch them only once + private final DispatchTask fetchInteractionsTask = new DispatchTask() { + @Override + protected void execute() { + final boolean updateSuccessful = fetchInteractionsSync(); + + // Update pending state on UI thread after finishing the task + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + if (hasActiveState()) { + ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); + dispatchDebugEvent(EVT_INTERACTION_FETCH, updateSuccessful); + } + } + }); + } + }; + + // we keep references to the tasks in order to dispatch them only once + private final DispatchTask saveConversationTask = new DispatchTask() { + @Override + protected void execute() { + save(); + } + }; + + public Conversation() { + data = new ConversationData(); + } + + //region Interactions + + /** + * Returns an Interaction for eventLabel if there is one that can be displayed. + */ + public Interaction getApplicableInteraction(String eventLabel) { + String targetsString = getTargets(); + if (targetsString != null) { + try { + Targets targets = new Targets(getTargets()); + String interactionId = targets.getApplicableInteraction(eventLabel); + if (interactionId != null) { + String interactionsString = getInteractions(); + if (interactionsString != null) { + Interactions interactions = new Interactions(interactionsString); + return interactions.getInteraction(interactionId); + } + } + } catch (JSONException e) { + ApptentiveLog.e(e, "Exception while getting applicable interaction: %s", eventLabel); + } + } + return null; + } + + boolean fetchInteractions(Context context) { + boolean cacheExpired = getInteractionExpiration() > Util.currentTimeSeconds(); + if (cacheExpired || RuntimeUtils.isAppDebuggable(context)) { + return DispatchQueue.backgroundQueue().dispatchAsyncOnce(fetchInteractionsTask); // do not allow multiple fetches at the same time + } + + ApptentiveLog.v(CONVERSATION, "Interaction cache is still valid"); + return false; + } + + /** + * Fetches interaction synchronously. Returns true if succeed. + */ + private boolean fetchInteractionsSync() { + ApptentiveLog.v(CONVERSATION, "Fetching Interactions"); + ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); + + // TODO: Move this to global config + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); + boolean updateSuccessful = true; + + // We weren't able to connect to the internet. + if (response.isException()) { + prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, false).apply(); + updateSuccessful = false; + } + // We got a server error. + else if (!response.isSuccessful()) { + prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, true).apply(); + updateSuccessful = false; + } + + if (updateSuccessful) { + String interactionsPayloadString = response.getContent(); + + // Store new integration cache expiration. + String cacheControl = response.getHeaders().get("Cache-Control"); + Integer cacheSeconds = Util.parseCacheControlHeader(cacheControl); + if (cacheSeconds == null) { + cacheSeconds = Constants.CONFIG_DEFAULT_INTERACTION_CACHE_EXPIRATION_DURATION_SECONDS; + } + setInteractionExpiration(Util.currentTimeSeconds() + cacheSeconds); + try { + InteractionManifest payload = new InteractionManifest(interactionsPayloadString); + Interactions interactions = payload.getInteractions(); + Targets targets = payload.getTargets(); + if (interactions != null && targets != null) { + setTargets(targets.toString()); + setInteractions(interactions.toString()); + } else { + ApptentiveLog.e(CONVERSATION, "Unable to save interactionManifest."); + } + } catch (JSONException e) { + ApptentiveLog.e(e, "Invalid InteractionManifest received."); + } + } + ApptentiveLog.v(CONVERSATION, "Fetching new Interactions asyncTask finished. Successful? %b", updateSuccessful); + + return updateSuccessful; + } + + //endregion + + //region Saving + + /** + * Saves conversation data to the disk synchronously. Returns true + * if succeed. + */ + synchronized boolean save() { + if (file == null) { + ApptentiveLog.e(CONVERSATION, "Unable to save conversation: destination file not specified"); + return false; + } + + ApptentiveLog.d(CONVERSATION, "Saving Conversation"); + ApptentiveLog.v(CONVERSATION, "EventData: %s", getEventData().toString()); // TODO: remove + + try { + FileSerializer serializer = new FileSerializer(file); + serializer.serialize(this); + return true; + } catch (Exception e) { + ApptentiveLog.e(e, "Unable to save conversation"); + return false; + } + } + + //endregion + + //region Listeners + + @Override + public void onDataChanged() { + if (hasFile()) { + boolean scheduled = DispatchQueue.backgroundQueue().dispatchAsyncOnce(saveConversationTask, 100L); + if (scheduled) { + ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); + } else { + ApptentiveLog.d(CONVERSATION, "Conversation save already scheduled."); + } + } else { + ApptentiveLog.v(CONVERSATION, "Can't save conversation data: storage file is not specified"); + } + } + + //endregion + + //region Getters & Setters + + public ConversationState getState() { + return state; + } + + public void setState(ConversationState state) { + // TODO: check if state transition would make sense (for example you should not be able to move from 'logged' state to 'anonymous', etc.) + this.state = state; + } + + /** + * Returns true if conversation is in the given state + */ + public boolean hasState(ConversationState s) { + return state.equals(s); + } + + /** + * Returns true if conversation is in one of the given states + */ + public boolean hasState(ConversationState... states) { + for (ConversationState s : states) { + if (s.equals(state)) { + return true; + } + } + return false; + } + + /** + * Returns true if conversation is in "active" state (after receiving server response) + */ + public boolean hasActiveState() { + return hasState(ConversationState.LOGGED_IN, ANONYMOUS); + } + + public String getConversationToken() { + return data.getConversationToken(); + } + + public void setConversationToken(String conversationToken) { + data.setConversationToken(conversationToken); + } + + public String getConversationId() { + return data.getConversationId(); + } + + public void setConversationId(String conversationId) { + data.setConversationId(conversationId); + } + + public String getPersonId() { + return data.getPersonId(); + } + + public void setPersonId(String personId) { + data.setPersonId(personId); + } + + public String getPersonEmail() { + return data.getPersonEmail(); + } + + public void setPersonEmail(String personEmail) { + data.setPersonEmail(personEmail); + } + + public String getPersonName() { + return data.getPersonName(); + } + + public void setPersonName(String personName) { + data.setPersonName(personName); + } + + public Device getDevice() { + return data.getDevice(); + } + + public void setDevice(Device device) { + data.setDevice(device); + } + + public Device getLastSentDevice() { + return data.getLastSentDevice(); + } + + public void setLastSentDevice(Device lastSentDevice) { + data.setLastSentDevice(lastSentDevice); + } + + public Person getPerson() { + return data.getPerson(); + } + + public void setPerson(Person person) { + data.setPerson(person); + } + + public Person getLastSentPerson() { + return data.getLastSentPerson(); + } + + public void setLastSentPerson(Person lastSentPerson) { + data.setLastSentPerson(lastSentPerson); + } + + public Sdk getSdk() { + return data.getSdk(); + } + + public void setSdk(Sdk sdk) { + data.setSdk(sdk); + } + + public AppRelease getAppRelease() { + return data.getAppRelease(); + } + + public void setAppRelease(AppRelease appRelease) { + data.setAppRelease(appRelease); + } + + public EventData getEventData() { + return data.getEventData(); + } + + public void setEventData(EventData eventData) { + data.setEventData(eventData); + } + + public String getLastSeenSdkVersion() { + return data.getLastSeenSdkVersion(); + } + + public void setLastSeenSdkVersion(String lastSeenSdkVersion) { + data.setLastSeenSdkVersion(lastSeenSdkVersion); + } + + public VersionHistory getVersionHistory() { + return data.getVersionHistory(); + } + + public void setVersionHistory(VersionHistory versionHistory) { + data.setVersionHistory(versionHistory); + } + + public boolean isMessageCenterFeatureUsed() { + return data.isMessageCenterFeatureUsed(); + } + + public void setMessageCenterFeatureUsed(boolean messageCenterFeatureUsed) { + data.setMessageCenterFeatureUsed(messageCenterFeatureUsed); + } + + public boolean isMessageCenterWhoCardPreviouslyDisplayed() { + return data.isMessageCenterWhoCardPreviouslyDisplayed(); + } + + public void setMessageCenterWhoCardPreviouslyDisplayed(boolean messageCenterWhoCardPreviouslyDisplayed) { + data.setMessageCenterWhoCardPreviouslyDisplayed(messageCenterWhoCardPreviouslyDisplayed); + } + + public String getMessageCenterPendingMessage() { + return data.getMessageCenterPendingMessage(); + } + + public void setMessageCenterPendingMessage(String messageCenterPendingMessage) { + data.setMessageCenterPendingMessage(messageCenterPendingMessage); + } + + public String getMessageCenterPendingAttachments() { + return data.getMessageCenterPendingAttachments(); + } + + public void setMessageCenterPendingAttachments(String messageCenterPendingAttachments) { + data.setMessageCenterPendingAttachments(messageCenterPendingAttachments); + } + + public String getTargets() { + return data.getTargets(); + } + + public void setTargets(String targets) { + data.setTargets(targets); + } + + public String getInteractions() { + return data.getInteractions(); + } + + public void setInteractions(String interactions) { + data.setInteractions(interactions); + } + + public double getInteractionExpiration() { + return data.getInteractionExpiration(); + } + + public void setInteractionExpiration(double interactionExpiration) { + data.setInteractionExpiration(interactionExpiration); + } + + public InteractionManager getInteractionManager() { + return interactionManager; + } + + public void setInteractionManager(InteractionManager interactionManager) { + this.interactionManager = interactionManager; + } + + synchronized boolean hasFile() { + return file != null; + } + + synchronized File getFile() { + return file; + } + + synchronized void setFile(File file) { + this.file = file; + } + + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java new file mode 100644 index 000000000..e8113deac --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.conversation; + +import com.apptentive.android.sdk.storage.AppRelease; +import com.apptentive.android.sdk.storage.DataChangedListener; +import com.apptentive.android.sdk.storage.Device; +import com.apptentive.android.sdk.storage.EventData; +import com.apptentive.android.sdk.storage.Person; +import com.apptentive.android.sdk.storage.Saveable; +import com.apptentive.android.sdk.storage.Sdk; +import com.apptentive.android.sdk.storage.VersionHistory; +import com.apptentive.android.sdk.util.StringUtils; + +public class ConversationData implements Saveable, DataChangedListener { + + private static final long serialVersionUID = 1L; + + private String conversationToken; + private String conversationId; + private String personId; + private String personEmail; + private String personName; + private Device device; + private Device lastSentDevice; + private Person person; + private Person lastSentPerson; + private Sdk sdk; + private AppRelease appRelease; + private EventData eventData; + private String lastSeenSdkVersion; + private VersionHistory versionHistory; + private boolean messageCenterFeatureUsed; + private boolean messageCenterWhoCardPreviouslyDisplayed; + private String messageCenterPendingMessage; + private String messageCenterPendingAttachments; + private String targets; + private String interactions; + private double interactionExpiration; + + public ConversationData() { + this.device = new Device(); + this.person = new Person(); + this.sdk = new Sdk(); + this.appRelease = new AppRelease(); + this.eventData = new EventData(); + this.versionHistory = new VersionHistory(); + } + + //region Listeners + + private transient DataChangedListener listener; + + @Override + public void setDataChangedListener(DataChangedListener listener) { + this.listener = listener; + device.setDataChangedListener(this); + person.setDataChangedListener(this); + eventData.setDataChangedListener(this); + versionHistory.setDataChangedListener(this); + } + + @Override + public void notifyDataChanged() { + if (listener != null) { + listener.onDataChanged(); + } + } + + @Override + public void onDataChanged() { + notifyDataChanged(); + } + //endregion + + //region Getters & Setters + + public String getConversationToken() { + return conversationToken; + } + + public void setConversationToken(String conversationToken) { + if (!StringUtils.equal(this.conversationToken, conversationToken)) { + this.conversationToken = conversationToken; + notifyDataChanged(); + } + } + + public String getConversationId() { + return conversationId; + } + + public void setConversationId(String conversationId) { + if (!StringUtils.equal(this.conversationId, conversationId)) { + this.conversationId = conversationId; + notifyDataChanged(); + } + } + + public String getPersonId() { + return personId; + } + + public void setPersonId(String personId) { + if (!StringUtils.equal(this.personId, personId)) { + this.personId = personId; + notifyDataChanged(); + } + } + + public String getPersonEmail() { + return personEmail; + } + + public void setPersonEmail(String personEmail) { + if (!StringUtils.equal(this.personEmail, personEmail)) { + this.personEmail = personEmail; + notifyDataChanged(); + } + } + + public String getPersonName() { + return personName; + } + + public void setPersonName(String personName) { + if (!StringUtils.equal(this.personName, personName)) { + this.personName = personName; + notifyDataChanged(); + } + } + + public Device getDevice() { + return device; + } + + public void setDevice(Device device) { + this.device = device; + device.setDataChangedListener(this); + notifyDataChanged(); + } + + public Device getLastSentDevice() { + return lastSentDevice; + } + + public void setLastSentDevice(Device lastSentDevice) { + this.lastSentDevice = lastSentDevice; + this.lastSentDevice.setDataChangedListener(this); + notifyDataChanged(); + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + this.person.setDataChangedListener(this); + notifyDataChanged(); + } + + public Person getLastSentPerson() { + return lastSentPerson; + } + + public void setLastSentPerson(Person lastSentPerson) { + this.lastSentPerson = lastSentPerson; + this.lastSentPerson.setDataChangedListener(this); + notifyDataChanged(); + } + + public Sdk getSdk() { + return sdk; + } + + public void setSdk(Sdk sdk) { + this.sdk = sdk; + notifyDataChanged(); + } + + public AppRelease getAppRelease() { + return appRelease; + } + + public void setAppRelease(AppRelease appRelease) { + this.appRelease = appRelease; + notifyDataChanged(); + } + + public EventData getEventData() { + return eventData; + } + + public void setEventData(EventData eventData) { + this.eventData = eventData; + this.eventData.setDataChangedListener(this); + notifyDataChanged(); + } + + public String getLastSeenSdkVersion() { + return lastSeenSdkVersion; + } + + public void setLastSeenSdkVersion(String lastSeenSdkVersion) { + this.lastSeenSdkVersion = lastSeenSdkVersion; + notifyDataChanged(); + } + + public VersionHistory getVersionHistory() { + return versionHistory; + } + + public void setVersionHistory(VersionHistory versionHistory) { + this.versionHistory = versionHistory; + this.versionHistory.setDataChangedListener(this); + notifyDataChanged(); + } + + public boolean isMessageCenterFeatureUsed() { + return messageCenterFeatureUsed; + } + + public void setMessageCenterFeatureUsed(boolean messageCenterFeatureUsed) { + if (this.messageCenterFeatureUsed != messageCenterFeatureUsed) { + this.messageCenterFeatureUsed = messageCenterFeatureUsed; + notifyDataChanged(); + } + } + + public boolean isMessageCenterWhoCardPreviouslyDisplayed() { + return messageCenterWhoCardPreviouslyDisplayed; + } + + public void setMessageCenterWhoCardPreviouslyDisplayed(boolean messageCenterWhoCardPreviouslyDisplayed) { + if (this.messageCenterWhoCardPreviouslyDisplayed != messageCenterWhoCardPreviouslyDisplayed) { + this.messageCenterWhoCardPreviouslyDisplayed = messageCenterWhoCardPreviouslyDisplayed; + notifyDataChanged(); + } + } + + public String getMessageCenterPendingMessage() { + return messageCenterPendingMessage; + } + + public void setMessageCenterPendingMessage(String messageCenterPendingMessage) { + if (!StringUtils.equal(this.messageCenterPendingMessage, messageCenterPendingMessage)) { + this.messageCenterPendingMessage = messageCenterPendingMessage; + notifyDataChanged(); + } + } + + public String getMessageCenterPendingAttachments() { + return messageCenterPendingAttachments; + } + + public void setMessageCenterPendingAttachments(String messageCenterPendingAttachments) { + if (!StringUtils.equal(this.messageCenterPendingAttachments, messageCenterPendingAttachments)) { + this.messageCenterPendingAttachments = messageCenterPendingAttachments; + notifyDataChanged(); + } + } + + public String getTargets() { + return targets; + } + + public void setTargets(String targets) { + if (!StringUtils.equal(this.targets, targets)) { + this.targets = targets; + notifyDataChanged(); + } + } + + public String getInteractions() { + return interactions; + } + + public void setInteractions(String interactions) { + if (!StringUtils.equal(this.interactions, interactions)) { + this.interactions = interactions; + notifyDataChanged(); + } + } + + public double getInteractionExpiration() { + return interactionExpiration; + } + + public void setInteractionExpiration(double interactionExpiration) { + if (this.interactionExpiration != interactionExpiration) { + this.interactionExpiration = interactionExpiration; + notifyDataChanged(); + } + } + + //endregion +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java index 5daec3325..4a454caed 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java @@ -31,10 +31,6 @@ public void notifyDataChanged() { } } - @Override - public void onDeserialize() { - } - //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java index 9e6fd3513..bf4cd9ff1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java @@ -59,10 +59,6 @@ public void notifyDataChanged() { } } - @Override - public void onDeserialize() { - } - @Override public void onDataChanged() { notifyDataChanged(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java index dc40838b9..919608154 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java @@ -41,10 +41,6 @@ public void notifyDataChanged() { } } - @Override - public void onDeserialize() { - } - //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java index f38f21780..a02b356b3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java @@ -48,9 +48,7 @@ public Object deserialize() throws SerializerException { try { fis = new FileInputStream(file); ois = new ObjectInputStream(fis); - Saveable object = (Saveable) ois.readObject(); - object.onDeserialize(); - return object; + return ois.readObject(); } catch (Exception e) { throw new SerializerException(e); } finally { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java index 0e8aec598..063c9cb00 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java @@ -40,10 +40,6 @@ public void notifyDataChanged() { } } - @Override - public void onDeserialize() { - } - //endregion //region Getters & Setters diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java index 15649b977..9efe02d66 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java @@ -44,10 +44,6 @@ public void notifyDataChanged() { } } - @Override - public void onDeserialize() { - } - @Override public void onDataChanged() { notifyDataChanged(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Saveable.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Saveable.java index cbc6b12a7..07b076e43 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Saveable.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Saveable.java @@ -12,5 +12,4 @@ public interface Saveable extends Serializable { void setDataChangedListener(DataChangedListener listener); void notifyDataChanged(); - void onDeserialize(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java index 1e6064fd0..30e1a9452 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/VersionHistory.java @@ -43,10 +43,6 @@ public void notifyDataChanged() { } } - @Override - public void onDeserialize() { - } - //endregion public void updateVersionHistory(double timestamp, Integer newVersionCode, String newVersionName) { From b17c435cfb8e7003c0773269bc5deae10bd2fab5 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 11:23:25 -0700 Subject: [PATCH 143/465] Fixed unit tests --- ...ionTest.java => ConversationDataTest.java} | 178 ++++++++---------- 1 file changed, 76 insertions(+), 102 deletions(-) rename apptentive/src/test/java/com/apptentive/android/sdk/storage/{ConversationTest.java => ConversationDataTest.java} (51%) diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationDataTest.java similarity index 51% rename from apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationTest.java rename to apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationDataTest.java index cab850ee7..26f7c9a1c 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationDataTest.java @@ -6,21 +6,13 @@ package com.apptentive.android.sdk.storage; -import android.text.TextUtils; - import com.apptentive.android.sdk.conversation.Conversation; +import com.apptentive.android.sdk.conversation.ConversationData; import com.apptentive.android.sdk.util.Util; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -28,34 +20,16 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; +import static org.junit.Assert.*; -@RunWith(PowerMockRunner.class) -@PrepareForTest(TextUtils.class) -public class ConversationTest { +public class ConversationDataTest { @Rule public TemporaryFolder conversationFolder = new TemporaryFolder(); - @Before - public void setup() { - PowerMockito.mockStatic(TextUtils.class); - PowerMockito.when(TextUtils.isEmpty(any(CharSequence.class))).thenAnswer(new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - CharSequence a = (CharSequence) invocation.getArguments()[0]; - return !(a != null && a.length() > 0); - } - }); - } - @Test public void testSerialization() { - Conversation expected = new Conversation(); + ConversationData expected = new ConversationData(); expected.setConversationId("jvnuveanesndndnadldbj"); expected.setConversationToken("watgsiovncsagjmcneiusdolnfcs"); expected.setPersonId("sijngmkmvewsnblkfmsd"); @@ -89,20 +63,20 @@ public void testSerialization() { bais = new ByteArrayInputStream(baos.toByteArray()); ois = new ObjectInputStream(bais); - Conversation result = (Conversation) ois.readObject(); - assertEquals(expected.getConversationId(), result.getConversationId()); - assertEquals(expected.getConversationToken(), result.getConversationToken()); - assertEquals(expected.getPersonId(), result.getPersonId()); - assertEquals(expected.getPersonName(), result.getPersonName()); - assertEquals(expected.getPersonEmail(), result.getPersonEmail()); - assertEquals(expected.getLastSeenSdkVersion(), result.getLastSeenSdkVersion()); - assertEquals(expected.isMessageCenterFeatureUsed(), result.isMessageCenterFeatureUsed()); - assertEquals(expected.isMessageCenterWhoCardPreviouslyDisplayed(), result.isMessageCenterWhoCardPreviouslyDisplayed()); - assertEquals(expected.getMessageCenterPendingMessage(), result.getMessageCenterPendingMessage()); - assertEquals(expected.getMessageCenterPendingAttachments(), result.getMessageCenterPendingAttachments()); - assertEquals(expected.getTargets(), result.getTargets()); - assertEquals(expected.getInteractions(), result.getInteractions()); - assertEquals(expected.getInteractionExpiration(), result.getInteractionExpiration(), 0.000001); + ConversationData actual = (ConversationData) ois.readObject(); + assertEquals(expected.getConversationId(), actual.getConversationId()); + assertEquals(expected.getConversationToken(), actual.getConversationToken()); + assertEquals(expected.getPersonId(), actual.getPersonId()); + assertEquals(expected.getPersonName(), actual.getPersonName()); + assertEquals(expected.getPersonEmail(), actual.getPersonEmail()); + assertEquals(expected.getLastSeenSdkVersion(), actual.getLastSeenSdkVersion()); + assertEquals(expected.isMessageCenterFeatureUsed(), actual.isMessageCenterFeatureUsed()); + assertEquals(expected.isMessageCenterWhoCardPreviouslyDisplayed(), actual.isMessageCenterWhoCardPreviouslyDisplayed()); + assertEquals(expected.getMessageCenterPendingMessage(), actual.getMessageCenterPendingMessage()); + assertEquals(expected.getMessageCenterPendingAttachments(), actual.getMessageCenterPendingAttachments()); + assertEquals(expected.getTargets(), actual.getTargets()); + assertEquals(expected.getInteractions(), actual.getInteractions()); + assertEquals(expected.getInteractionExpiration(), actual.getInteractionExpiration(), 0.000001); } catch (Exception e) { fail(e.getMessage()); @@ -118,23 +92,23 @@ public void testSerialization() { @Test public void testDataChangeListeners() throws Exception { - Conversation conversation = new Conversation(); - testConversationListeners(conversation); + ConversationData data = new ConversationData(); + testConversationListeners(data); } @Test public void testDataChangeListenersWhenDeserialized() throws Exception { - Conversation conversation = new Conversation(); + ConversationData data = new ConversationData(); File conversationFile = new File(conversationFolder.getRoot(), "conversation.bin"); - new FileSerializer(conversationFile).serialize(conversation); + new FileSerializer(conversationFile).serialize(data); - conversation = (Conversation) new FileSerializer(conversationFile).deserialize(); - testConversationListeners(conversation); + data = (ConversationData) new FileSerializer(conversationFile).deserialize(); + testConversationListeners(data); } - private void testConversationListeners(Conversation conversation) { - conversation.setDataChangedListener(new DataChangedListener() { + private void testConversationListeners(ConversationData data) { + data.setDataChangedListener(new DataChangedListener() { @Override public void onDataChanged() { listenerFired = true; @@ -142,207 +116,207 @@ public void onDataChanged() { }); listenerFired = false; - conversation.setConversationToken("foo"); + data.setConversationToken("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setConversationId("foo"); + data.setConversationId("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setPersonId("foo"); + data.setPersonId("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setPersonEmail("foo"); + data.setPersonEmail("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setPersonName("foo"); + data.setPersonName("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setLastSeenSdkVersion("foo"); + data.setLastSeenSdkVersion("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setMessageCenterFeatureUsed(true); + data.setMessageCenterFeatureUsed(true); assertTrue(listenerFired); listenerFired = false; - conversation.setMessageCenterWhoCardPreviouslyDisplayed(true); + data.setMessageCenterWhoCardPreviouslyDisplayed(true); assertTrue(listenerFired); listenerFired = false; - conversation.setMessageCenterPendingMessage("foo"); + data.setMessageCenterPendingMessage("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setMessageCenterPendingAttachments("foo"); + data.setMessageCenterPendingAttachments("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setInteractions("foo"); + data.setInteractions("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setTargets("foo"); + data.setTargets("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setInteractionExpiration(1000L); + data.setInteractionExpiration(1000L); assertTrue(listenerFired); listenerFired = false; - conversation.getDevice().getCustomData().put("foo", "bar"); + data.getDevice().getCustomData().put("foo", "bar"); assertTrue(listenerFired); listenerFired = false; - conversation.getDevice().getCustomData().remove("foo"); + data.getDevice().getCustomData().remove("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setDevice(new Device()); + data.setDevice(new Device()); assertTrue(listenerFired); listenerFired = false; - conversation.getDevice().getCustomData().put("foo", "bar"); + data.getDevice().getCustomData().put("foo", "bar"); assertTrue(listenerFired); listenerFired = false; - conversation.getDevice().getIntegrationConfig().setAmazonAwsSns(new IntegrationConfigItem()); + data.getDevice().getIntegrationConfig().setAmazonAwsSns(new IntegrationConfigItem()); assertTrue(listenerFired); listenerFired = false; - conversation.getDevice().setIntegrationConfig(new IntegrationConfig()); + data.getDevice().setIntegrationConfig(new IntegrationConfig()); assertTrue(listenerFired); listenerFired = false; - conversation.getDevice().getIntegrationConfig().setAmazonAwsSns(new IntegrationConfigItem()); + data.getDevice().getIntegrationConfig().setAmazonAwsSns(new IntegrationConfigItem()); assertTrue(listenerFired); listenerFired = false; - conversation.getDevice().setOsApiLevel(5); + data.getDevice().setOsApiLevel(5); assertTrue(listenerFired); listenerFired = false; - conversation.getDevice().setUuid("foo"); + data.getDevice().setUuid("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setLastSentDevice(new Device()); + data.setLastSentDevice(new Device()); assertTrue(listenerFired); listenerFired = false; - conversation.getLastSentDevice().setUuid("foo"); + data.getLastSentDevice().setUuid("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setPerson(new Person()); + data.setPerson(new Person()); assertTrue(listenerFired); listenerFired = false; - conversation.getPerson().setId("foo"); + data.getPerson().setId("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.getPerson().setEmail("foo"); + data.getPerson().setEmail("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.getPerson().setName("foo"); + data.getPerson().setName("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.getPerson().setFacebookId("foo"); + data.getPerson().setFacebookId("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.getPerson().setPhoneNumber("foo"); + data.getPerson().setPhoneNumber("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.getPerson().setStreet("foo"); + data.getPerson().setStreet("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.getPerson().setCity("foo"); + data.getPerson().setCity("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.getPerson().setZip("foo"); + data.getPerson().setZip("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.getPerson().setCountry("foo"); + data.getPerson().setCountry("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.getPerson().setBirthday("foo"); + data.getPerson().setBirthday("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.getPerson().setCustomData(new CustomData()); + data.getPerson().setCustomData(new CustomData()); assertTrue(listenerFired); listenerFired = false; - conversation.getPerson().getCustomData().put("foo", "bar"); + data.getPerson().getCustomData().put("foo", "bar"); assertTrue(listenerFired); listenerFired = false; - conversation.getPerson().getCustomData().remove("foo"); + data.getPerson().getCustomData().remove("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setLastSentPerson(new Person()); + data.setLastSentPerson(new Person()); assertTrue(listenerFired); listenerFired = false; - conversation.getLastSentPerson().setId("foo"); + data.getLastSentPerson().setId("foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setSdk(new Sdk()); + data.setSdk(new Sdk()); assertTrue(listenerFired); listenerFired = false; - conversation.setAppRelease(new AppRelease()); + data.setAppRelease(new AppRelease()); assertTrue(listenerFired); listenerFired = false; - conversation.getVersionHistory().updateVersionHistory(100D, 1, "1"); + data.getVersionHistory().updateVersionHistory(100D, 1, "1"); assertTrue(listenerFired); listenerFired = false; - conversation.setVersionHistory(new VersionHistory()); + data.setVersionHistory(new VersionHistory()); assertTrue(listenerFired); listenerFired = false; - conversation.getVersionHistory().updateVersionHistory(100D, 1, "1"); + data.getVersionHistory().updateVersionHistory(100D, 1, "1"); assertTrue(listenerFired); listenerFired = false; - conversation.getEventData().storeEventForCurrentAppVersion(100D, 10, "1.0", "foo"); + data.getEventData().storeEventForCurrentAppVersion(100D, 10, "1.0", "foo"); assertTrue(listenerFired); listenerFired = false; - conversation.getEventData().storeInteractionForCurrentAppVersion(100D, 10, "1.0", "foo"); + data.getEventData().storeInteractionForCurrentAppVersion(100D, 10, "1.0", "foo"); assertTrue(listenerFired); listenerFired = false; - conversation.setEventData(new EventData()); + data.setEventData(new EventData()); assertTrue(listenerFired); listenerFired = false; - conversation.getEventData().storeEventForCurrentAppVersion(100D, 10, "1.0", "foo"); + data.getEventData().storeEventForCurrentAppVersion(100D, 10, "1.0", "foo"); assertTrue(listenerFired); listenerFired = false; - conversation.getEventData().storeInteractionForCurrentAppVersion(100D, 10, "1.0", "foo"); + data.getEventData().storeInteractionForCurrentAppVersion(100D, 10, "1.0", "foo"); assertTrue(listenerFired); listenerFired = false; } From 02eca7a53ca8a64749cfce95191e3f34ae099b36 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 11:26:30 -0700 Subject: [PATCH 144/465] Removed some unused code --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index d1a2d4558..5c5265eb1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -100,9 +100,6 @@ public class ApptentiveInternal { String androidId; String appPackageName; - // private background serial dispatch queue for internal SDK tasks - private final DispatchQueue backgroundQueue; // TODO: replace with a global concurrent queue? - // toolbar theme specified in R.attr.apptentiveToolbarTheme Resources.Theme apptentiveToolbarTheme; @@ -113,8 +110,6 @@ public class ApptentiveInternal { String defaultAppDisplayName = "this app"; // booleans to prevent starting multiple fetching asyncTasks simultaneously - AtomicBoolean isConfigurationFetchPending = new AtomicBoolean(false); // TODO: remove me! - IRatingProvider ratingProvider; Map ratingProviderArgs; WeakReference onSurveyFinishedListener; @@ -155,7 +150,6 @@ private ApptentiveInternal(Context context, String apiKey, String serverUrl) { appContext = context.getApplicationContext(); globalSharedPrefs = context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); - backgroundQueue = DispatchQueue.createBackgroundQueue("Apptentive Serial Queue", DispatchQueueType.Serial); apptentiveHttpClient = new ApptentiveHttpClient(apiKey, getEndpointBase(globalSharedPrefs)); conversationManager = new ConversationManager(appContext, Util.getInternalDir(appContext, "conversations", true)); From 6e02209864d287c84a867d99b871280955627fad Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 11:57:51 -0700 Subject: [PATCH 145/465] Refactoring Replace handler message with dispatch task --- .../module/messagecenter/MessageManager.java | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index a27b1454e..78281b0b7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -30,6 +30,8 @@ import com.apptentive.android.sdk.module.metric.MetricModule; import com.apptentive.android.sdk.storage.MessageStore; import com.apptentive.android.sdk.util.Util; +import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchTask; import org.json.JSONArray; import org.json.JSONException; @@ -51,7 +53,6 @@ public class MessageManager { private static int TOAST_TYPE_UNREAD_MESSAGE = 1; - private static final int UI_THREAD_MESSAGE_ON_UNREAD_HOST = 1; private static final int UI_THREAD_MESSAGE_ON_UNREAD_INTERNAL = 2; private static final int UI_THREAD_MESSAGE_ON_TOAST_NOTIFICATION = 3; @@ -72,6 +73,13 @@ public class MessageManager { private MessagePollingWorker pollingWorker; + private final MessageCountDispatchTask unreadHostNotifierTask = new MessageCountDispatchTask() { + @Override + protected void execute() { + notifyHostUnreadMessagesListeners(getMessageCount()); + } + }; + public MessageManager() { } @@ -83,9 +91,6 @@ public void init() { @Override public void handleMessage(android.os.Message msg) { switch (msg.what) { - case UI_THREAD_MESSAGE_ON_UNREAD_HOST: - notifyHostUnreadMessagesListeners(msg.arg1); - break; case UI_THREAD_MESSAGE_ON_UNREAD_INTERNAL: { // Notify internal listeners such as Message Center CompoundMessage msgToAdd = (CompoundMessage) msg.obj; @@ -210,9 +215,8 @@ public synchronized boolean fetchAndStoreMessages(boolean isMessageCenterForegro } } // Send message to notify host app, such as unread message badge - msg = uiHandler.obtainMessage(UI_THREAD_MESSAGE_ON_UNREAD_HOST, getUnreadMessageCount(), 0); - uiHandler.removeMessages(UI_THREAD_MESSAGE_ON_UNREAD_HOST); - msg.sendToTarget(); + DispatchQueue.mainQueue() + .dispatchAsyncOnce(unreadHostNotifierTask.setMessageCount(getUnreadMessageCount())); return incomingUnreadMessages > 0; } @@ -395,7 +399,7 @@ public void addInternalOnMessagesUpdatedListener(OnNewIncomingMessagesListener n iterator.remove(); } } - internalNewMessagesListeners.add(new WeakReference(newlistener)); + internalNewMessagesListeners.add(new WeakReference<>(newlistener)); } } @@ -416,7 +420,7 @@ public void notifyInternalNewMessagesListeners(final CompoundMessage apptentiveM public void setHostUnreadMessagesListener(UnreadMessagesListener listener) { clearHostUnreadMessagesListeners(); if (listener != null) { - hostUnreadMessagesListeners.add(new WeakReference(listener)); + hostUnreadMessagesListeners.add(new WeakReference<>(listener)); } } @@ -433,7 +437,7 @@ public void addHostUnreadMessagesListener(UnreadMessagesListener newListener) { iterator.remove(); } } - hostUnreadMessagesListeners.add(new WeakReference(newListener)); + hostUnreadMessagesListeners.add(new WeakReference<>(newListener)); } } @@ -510,4 +514,22 @@ public void appWentToBackground() { pollingWorker.appWentToBackground(); } } + + //region Message Dispatch Task + + private abstract static class MessageCountDispatchTask extends DispatchTask + { + private int messageCount; + + public MessageCountDispatchTask setMessageCount(int messageCount) { + this.messageCount = messageCount; + return this; + } + + protected int getMessageCount() { + return messageCount; + } + } + + //endregion } From ff7d28860e527b96b9f8ad10917add55273d8f8e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 12:00:40 -0700 Subject: [PATCH 146/465] Refactoring Replace handler message with dispatch task --- .../module/messagecenter/MessageManager.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 78281b0b7..c0d021961 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -53,7 +53,6 @@ public class MessageManager { private static int TOAST_TYPE_UNREAD_MESSAGE = 1; - private static final int UI_THREAD_MESSAGE_ON_UNREAD_INTERNAL = 2; private static final int UI_THREAD_MESSAGE_ON_TOAST_NOTIFICATION = 3; private WeakReference currentForegroundApptentiveActivity; @@ -91,12 +90,6 @@ public void init() { @Override public void handleMessage(android.os.Message msg) { switch (msg.what) { - case UI_THREAD_MESSAGE_ON_UNREAD_INTERNAL: { - // Notify internal listeners such as Message Center - CompoundMessage msgToAdd = (CompoundMessage) msg.obj; - notifyInternalNewMessagesListeners(msgToAdd); - break; - } case UI_THREAD_MESSAGE_ON_TOAST_NOTIFICATION: { CompoundMessage msgToShow = (CompoundMessage) msg.obj; showUnreadMessageToastNotification(msgToShow); @@ -188,7 +181,7 @@ public synchronized boolean fetchAndStoreMessages(boolean isMessageCenterForegro // Also get the count of incoming unread messages. int incomingUnreadMessages = 0; // Mark messages from server where sender is the app user as read. - for (ApptentiveMessage apptentiveMessage : messagesToSave) { + for (final ApptentiveMessage apptentiveMessage : messagesToSave) { if (apptentiveMessage.isOutgoingMessage()) { apptentiveMessage.setRead(true); } else { @@ -198,9 +191,14 @@ public synchronized boolean fetchAndStoreMessages(boolean isMessageCenterForegro } } incomingUnreadMessages++; + // for every new message received, notify Message Center - Message msg = uiHandler.obtainMessage(UI_THREAD_MESSAGE_ON_UNREAD_INTERNAL, apptentiveMessage); - msg.sendToTarget(); + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + notifyInternalNewMessagesListeners((CompoundMessage) apptentiveMessage); + } + }); } } getMessageStore().addOrUpdateMessages(messagesToSave.toArray(new ApptentiveMessage[messagesToSave.size()])); From 61bdf35f926413a06f5aadc20eb91dd9b7ec4c9d Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 12:13:07 -0700 Subject: [PATCH 147/465] Refactoring Replace handler message with dispatch task --- .../module/messagecenter/MessageManager.java | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index c0d021961..b4b26cfa0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -14,7 +14,6 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; -import android.os.Message; import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; @@ -53,8 +52,6 @@ public class MessageManager { private static int TOAST_TYPE_UNREAD_MESSAGE = 1; - private static final int UI_THREAD_MESSAGE_ON_TOAST_NOTIFICATION = 3; - private WeakReference currentForegroundApptentiveActivity; private WeakReference afterSendMessageListener; @@ -71,11 +68,17 @@ public class MessageManager { private Handler uiHandler; private MessagePollingWorker pollingWorker; + private final MessageDispatchTask toastMessageNotifierTask = new MessageDispatchTask() { + @Override + protected void execute(CompoundMessage message) { + showUnreadMessageToastNotification(message); + } + }; - private final MessageCountDispatchTask unreadHostNotifierTask = new MessageCountDispatchTask() { + private final MessageCountDispatchTask hostMessageNotifierTask = new MessageCountDispatchTask() { @Override - protected void execute() { - notifyHostUnreadMessagesListeners(getMessageCount()); + protected void execute(int messageCount) { + notifyHostUnreadMessagesListeners(messageCount); } }; @@ -202,19 +205,16 @@ protected void execute() { } } getMessageStore().addOrUpdateMessages(messagesToSave.toArray(new ApptentiveMessage[messagesToSave.size()])); - Message msg; if (incomingUnreadMessages > 0) { // Show toast notification only if the foreground activity is not already message center activity if (!isMessageCenterForeground && showToast) { - msg = uiHandler.obtainMessage(UI_THREAD_MESSAGE_ON_TOAST_NOTIFICATION, messageOnToast); - // Only show the latest new message on toast - uiHandler.removeMessages(UI_THREAD_MESSAGE_ON_TOAST_NOTIFICATION); - msg.sendToTarget(); + DispatchQueue.mainQueue().dispatchAsyncOnce(toastMessageNotifierTask.setMessage(messageOnToast)); } } + // Send message to notify host app, such as unread message badge DispatchQueue.mainQueue() - .dispatchAsyncOnce(unreadHostNotifierTask.setMessageCount(getUnreadMessageCount())); + .dispatchAsyncOnce(hostMessageNotifierTask.setMessageCount(getUnreadMessageCount())); return incomingUnreadMessages > 0; } @@ -515,17 +515,41 @@ public void appWentToBackground() { //region Message Dispatch Task + private abstract static class MessageDispatchTask extends DispatchTask + { + private CompoundMessage message; + + protected abstract void execute(CompoundMessage message); + + @Override + protected void execute() { + try { + execute(message); + } finally { + message = null; + } + } + + MessageDispatchTask setMessage(CompoundMessage message) { + this.message = message; + return this; + } + } + private abstract static class MessageCountDispatchTask extends DispatchTask { private int messageCount; - public MessageCountDispatchTask setMessageCount(int messageCount) { - this.messageCount = messageCount; - return this; + protected abstract void execute(int messageCount); + + @Override + protected void execute() { + execute(messageCount); } - protected int getMessageCount() { - return messageCount; + MessageCountDispatchTask setMessageCount(int messageCount) { + this.messageCount = messageCount; + return this; } } From 976b3d2e022605e607875f3ffa6f52a5ee3332cc Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 12:14:18 -0700 Subject: [PATCH 148/465] Refactoring Remove handler from MessageManager --- .../module/messagecenter/MessageManager.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index b4b26cfa0..1c9758646 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -65,7 +65,6 @@ public class MessageManager { private final List> hostUnreadMessagesListeners = new ArrayList>(); AtomicBoolean appInForeground = new AtomicBoolean(false); - private Handler uiHandler; private MessagePollingWorker pollingWorker; private final MessageDispatchTask toastMessageNotifierTask = new MessageDispatchTask() { @@ -88,22 +87,6 @@ public MessageManager() { // init() will start polling worker. public void init() { - if (uiHandler == null) { - uiHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(android.os.Message msg) { - switch (msg.what) { - case UI_THREAD_MESSAGE_ON_TOAST_NOTIFICATION: { - CompoundMessage msgToShow = (CompoundMessage) msg.obj; - showUnreadMessageToastNotification(msgToShow); - break; - } - default: - super.handleMessage(msg); - } - } - }; - } if (pollingWorker == null) { pollingWorker = new MessagePollingWorker(this); /* Set SharePreference to indicate Message Center feature is desired. It will always be checked From 5b445fa552e9e342129c979a542bfa10cbea4ed2 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 12:20:33 -0700 Subject: [PATCH 149/465] Refactoring Replaced AsyncTask with DispatchQueue --- .../module/messagecenter/MessageManager.java | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 1c9758646..2a165a5c8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -105,35 +105,26 @@ public void init() { */ public void startMessagePreFetchTask() { // Defer message polling thread creation, if not created yet and host app receives a new message push + init(); - AsyncTask task = new AsyncTask() { - private Exception e = null; + final boolean updateMC = isMessageCenterInForeground(); + DispatchQueue.backgroundQueue().dispatchAsync(new DispatchTask() { @Override - protected Void doInBackground(Object... params) { - boolean updateMC = (Boolean) params[0]; + protected void execute() { try { fetchAndStoreMessages(updateMC, false); - } catch (Exception e) { - this.e = e; - } - return null; - } - - @Override - protected void onPostExecute(Void v) { - if (e != null) { - ApptentiveLog.w("Unhandled Exception thrown from fetching new message asyncTask", e); - MetricModule.sendError(e, null, null); + } catch (final Exception e) { + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + ApptentiveLog.w("Unhandled Exception thrown from fetching new message task", e); + MetricModule.sendError(e, null, null); + } + }); } } - }; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, isMessageCenterInForeground()); - } else { - task.execute(isMessageCenterInForeground()); - } + }); } /** @@ -196,8 +187,7 @@ protected void execute() { } // Send message to notify host app, such as unread message badge - DispatchQueue.mainQueue() - .dispatchAsyncOnce(hostMessageNotifierTask.setMessageCount(getUnreadMessageCount())); + DispatchQueue.mainQueue().dispatchAsyncOnce(hostMessageNotifierTask.setMessageCount(getUnreadMessageCount())); return incomingUnreadMessages > 0; } From 0ababdb06905d9c8067f5b73f399383841b9aa5e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 12:21:39 -0700 Subject: [PATCH 150/465] Refactoring Organized imports --- .../android/sdk/module/messagecenter/MessageManager.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 2a165a5c8..c8d1523ff 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -10,10 +10,6 @@ import android.app.Notification; import android.app.PendingIntent; import android.content.Context; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; From c69f1fa57c814f9c50431fc7e341450730be60eb Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 13:39:27 -0700 Subject: [PATCH 151/465] Moved MessageManager to Conversation class --- .../android/sdk/ApptentiveInternal.java | 40 +- .../android/sdk/ApptentiveNotifications.java | 23 +- .../android/sdk/ApptentiveViewActivity.java | 2 +- .../sdk/conversation/Conversation.java | 782 +++++++++--------- .../sdk/conversation/ConversationManager.java | 5 +- .../fragment/MessageCenterFragment.java | 2 +- .../module/messagecenter/MessageManager.java | 116 +-- .../notifications/ApptentiveNotification.java | 7 + .../android/sdk/util/Destroyable.java | 11 + 9 files changed, 525 insertions(+), 463 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/Destroyable.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 5c5265eb1..fceec4e5d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -40,8 +40,8 @@ import com.apptentive.android.sdk.module.rating.IRatingProvider; import com.apptentive.android.sdk.module.rating.impl.GooglePlayRatingProvider; import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; -import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; +import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.AppReleaseManager; import com.apptentive.android.sdk.storage.ApptentiveTaskManager; import com.apptentive.android.sdk.storage.PayloadSendWorker; @@ -51,8 +51,6 @@ import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.Util; -import com.apptentive.android.sdk.util.threading.DispatchQueue; -import com.apptentive.android.sdk.util.threading.DispatchQueueType; import org.json.JSONException; import org.json.JSONObject; @@ -78,7 +76,6 @@ public class ApptentiveInternal { static AtomicBoolean isApptentiveInitialized = new AtomicBoolean(false); - private final MessageManager messageManager; private final PayloadSendWorker payloadWorker; private final ApptentiveTaskManager taskManager; @@ -154,7 +151,6 @@ private ApptentiveInternal(Context context, String apiKey, String serverUrl) { conversationManager = new ConversationManager(appContext, Util.getInternalDir(appContext, "conversations", true)); appRelease = AppReleaseManager.generateCurrentAppRelease(context, this); - messageManager = new MessageManager(); payloadWorker = new PayloadSendWorker(); taskManager = new ApptentiveTaskManager(appContext); cachedExecutor = Executors.newCachedThreadPool(); @@ -177,7 +173,7 @@ public static boolean isApptentiveRegistered() { * @param context the context of the app that is creating the instance * @return An non-null instance of the Apptentive SDK */ - public static ApptentiveInternal createInstance(Context context, String apptentiveApiKey, final String serverUrl) { + public static ApptentiveInternal createInstance(Context context, String apptentiveApiKey, final String serverUrl) { if (sApptentiveInternal == null) { synchronized (ApptentiveInternal.class) { if (sApptentiveInternal == null && context != null) { @@ -354,7 +350,8 @@ public Activity getCurrentTaskStackTopActivity() { } public MessageManager getMessageManager() { - return messageManager; + final Conversation conversation = getConversation(); + return conversation != null ? conversation.getMessageManager() : null; } public PayloadSendWorker getPayloadWorker() { @@ -433,11 +430,10 @@ public void onActivityStarted(Activity activity) { if (activity != null) { // Set current foreground activity reference whenever a new activity is started currentTaskStackTopActivity = new WeakReference<>(activity); - messageManager.setCurrentForegroundActivity(activity); - // Fire a notification + // Post a notification ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_ACTIVITY_STARTED, - ObjectUtils.toMap(NOTIFICATION_ACTIVITY_STARTED_KEY_ACTIVITY_CLASS, activity.getClass())); + ObjectUtils.toMap(NOTIFICATION_KEY_ACTIVITY, activity)); } } @@ -445,23 +441,28 @@ public void onActivityResumed(Activity activity) { if (activity != null) { // Set current foreground activity reference whenever a new activity is started currentTaskStackTopActivity = new WeakReference<>(activity); - messageManager.setCurrentForegroundActivity(activity); - } + // Post a notification + ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_ACTIVITY_RESUMED, + ObjectUtils.toMap(NOTIFICATION_KEY_ACTIVITY, activity)); + } } public void onAppEnterForeground() { appIsInForeground = true; payloadWorker.appWentToForeground(); - messageManager.appWentToForeground(); + + // Post a notification + ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_APP_ENTER_FOREGROUND); } public void onAppEnterBackground() { appIsInForeground = false; currentTaskStackTopActivity = null; - messageManager.setCurrentForegroundActivity(null); payloadWorker.appWentToBackground(); - messageManager.appWentToBackground(); + + // Post a notification + ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_APP_ENTER_BACKGROUND); } /* Apply Apptentive styling layers to the theme to be used by interaction. The layers include @@ -536,10 +537,11 @@ public boolean init() { if (conversationLoaded) { Conversation activeConversation = conversationManager.getActiveConversation(); - boolean featureEverUsed = activeConversation.isMessageCenterFeatureUsed(); - if (featureEverUsed) { - messageManager.init(); - } + // FIXME: don't accept the pull request before this one is resolved + // boolean featureEverUsed = activeConversation.isMessageCenterFeatureUsed(); + // if (featureEverUsed) { + // messageManager.init(); + // } activeConversation.setInteractionManager(new InteractionManager(activeConversation)); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java index 61549d544..c09e3a9dc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -12,16 +12,33 @@ public class ApptentiveNotifications { * Sent when conversation state changes (user logs out, etc) */ public static final String NOTIFICATION_CONVERSATION_STATE_DID_CHANGE = "CONVERSATION_STATE_DID_CHANGE"; // { conversation : Conversation } - public static final String NOTIFICATION_CONVERSATION_STATE_DID_CHANGE_KEY_CONVERSATION = "conversation"; /** * Sent if a new activity is started. */ - public static final String NOTIFICATION_ACTIVITY_STARTED = "NOTIFICATION_ACTIVITY_STARTED"; // { activityClass : Class } - public static final String NOTIFICATION_ACTIVITY_STARTED_KEY_ACTIVITY_CLASS = "activityClass"; + public static final String NOTIFICATION_ACTIVITY_STARTED = "NOTIFICATION_ACTIVITY_STARTED"; // { activity : Activity } + + /** + * Sent if activity is resumed. + */ + public static final String NOTIFICATION_ACTIVITY_RESUMED = "NOTIFICATION_ACTIVITY_RESUMED"; // { activity : Activity } + + /** + * Sent if app enter foreground + */ + public static final String NOTIFICATION_APP_ENTER_FOREGROUND = "NOTIFICATION_APP_ENTER_FOREGROUND"; + + /** + * Sent if app enter background + */ + public static final String NOTIFICATION_APP_ENTER_BACKGROUND = "NOTIFICATION_APP_ENTER_BACKGROUND"; /** * Sent if user requested to close all interactions. */ public static final String NOTIFICATION_INTERACTIONS_SHOULD_DISMISS = "NOTIFICATION_INTERACTIONS_SHOULD_DISMISS"; + + // keys + public static final String NOTIFICATION_KEY_ACTIVITY = "activity"; + public static final String NOTIFICATION_KEY_CONVERSATION = "conversation"; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index b4df24400..ab1145950 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -413,7 +413,7 @@ public void onReceiveNotification(ApptentiveNotification notification) { if (notification.hasName(NOTIFICATION_INTERACTIONS_SHOULD_DISMISS)) { dismissActivity(); } else if (notification.hasName(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE)) { - final Conversation conversation = notification.getUserInfo(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE_KEY_CONVERSATION, Conversation.class); + final Conversation conversation = notification.getUserInfo(NOTIFICATION_KEY_CONVERSATION, Conversation.class); Assert.assertNotNull(conversation, "Conversation expected to be not null"); if (conversation != null && !conversation.hasActiveState()) { dismissActivity(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index f3943d1d3..3d433b104 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -18,6 +18,7 @@ import com.apptentive.android.sdk.module.engagement.interaction.model.InteractionManifest; import com.apptentive.android.sdk.module.engagement.interaction.model.Interactions; import com.apptentive.android.sdk.module.engagement.interaction.model.Targets; +import com.apptentive.android.sdk.module.messagecenter.MessageManager; import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.DataChangedListener; import com.apptentive.android.sdk.storage.Device; @@ -27,6 +28,7 @@ import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.VersionHistory; import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.util.Destroyable; import com.apptentive.android.sdk.util.RuntimeUtils; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; @@ -41,411 +43,427 @@ import static com.apptentive.android.sdk.conversation.ConversationState.*; import static com.apptentive.android.sdk.debug.TesterEvent.*; -public class Conversation implements DataChangedListener { - - /** - * Conversation data for this class to manage - */ - private ConversationData data; - - /** - * File which represents this conversation on the disk - */ - private File file; - - // TODO: Maybe move this up to a wrapping Conversation class? - private InteractionManager interactionManager; - - private ConversationState state = ConversationState.UNDEFINED; - - // we keep references to the tasks in order to dispatch them only once - private final DispatchTask fetchInteractionsTask = new DispatchTask() { - @Override - protected void execute() { - final boolean updateSuccessful = fetchInteractionsSync(); - - // Update pending state on UI thread after finishing the task - DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - if (hasActiveState()) { - ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); - dispatchDebugEvent(EVT_INTERACTION_FETCH, updateSuccessful); - } - } - }); - } - }; - - // we keep references to the tasks in order to dispatch them only once - private final DispatchTask saveConversationTask = new DispatchTask() { - @Override - protected void execute() { - save(); - } - }; - - public Conversation() { - data = new ConversationData(); - } - - //region Interactions - - /** - * Returns an Interaction for eventLabel if there is one that can be displayed. - */ - public Interaction getApplicableInteraction(String eventLabel) { - String targetsString = getTargets(); - if (targetsString != null) { - try { - Targets targets = new Targets(getTargets()); - String interactionId = targets.getApplicableInteraction(eventLabel); - if (interactionId != null) { - String interactionsString = getInteractions(); - if (interactionsString != null) { - Interactions interactions = new Interactions(interactionsString); - return interactions.getInteraction(interactionId); - } - } - } catch (JSONException e) { - ApptentiveLog.e(e, "Exception while getting applicable interaction: %s", eventLabel); - } - } - return null; - } - - boolean fetchInteractions(Context context) { - boolean cacheExpired = getInteractionExpiration() > Util.currentTimeSeconds(); - if (cacheExpired || RuntimeUtils.isAppDebuggable(context)) { - return DispatchQueue.backgroundQueue().dispatchAsyncOnce(fetchInteractionsTask); // do not allow multiple fetches at the same time - } - - ApptentiveLog.v(CONVERSATION, "Interaction cache is still valid"); - return false; - } - - /** - * Fetches interaction synchronously. Returns true if succeed. - */ - private boolean fetchInteractionsSync() { - ApptentiveLog.v(CONVERSATION, "Fetching Interactions"); - ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); - - // TODO: Move this to global config - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - boolean updateSuccessful = true; - - // We weren't able to connect to the internet. - if (response.isException()) { - prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, false).apply(); - updateSuccessful = false; - } - // We got a server error. - else if (!response.isSuccessful()) { - prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, true).apply(); - updateSuccessful = false; - } - - if (updateSuccessful) { - String interactionsPayloadString = response.getContent(); - - // Store new integration cache expiration. - String cacheControl = response.getHeaders().get("Cache-Control"); - Integer cacheSeconds = Util.parseCacheControlHeader(cacheControl); - if (cacheSeconds == null) { - cacheSeconds = Constants.CONFIG_DEFAULT_INTERACTION_CACHE_EXPIRATION_DURATION_SECONDS; - } - setInteractionExpiration(Util.currentTimeSeconds() + cacheSeconds); - try { - InteractionManifest payload = new InteractionManifest(interactionsPayloadString); - Interactions interactions = payload.getInteractions(); - Targets targets = payload.getTargets(); - if (interactions != null && targets != null) { - setTargets(targets.toString()); - setInteractions(interactions.toString()); - } else { - ApptentiveLog.e(CONVERSATION, "Unable to save interactionManifest."); - } - } catch (JSONException e) { - ApptentiveLog.e(e, "Invalid InteractionManifest received."); - } - } - ApptentiveLog.v(CONVERSATION, "Fetching new Interactions asyncTask finished. Successful? %b", updateSuccessful); - - return updateSuccessful; - } - - //endregion - - //region Saving - - /** - * Saves conversation data to the disk synchronously. Returns true - * if succeed. - */ - synchronized boolean save() { - if (file == null) { - ApptentiveLog.e(CONVERSATION, "Unable to save conversation: destination file not specified"); - return false; - } - - ApptentiveLog.d(CONVERSATION, "Saving Conversation"); - ApptentiveLog.v(CONVERSATION, "EventData: %s", getEventData().toString()); // TODO: remove - - try { - FileSerializer serializer = new FileSerializer(file); - serializer.serialize(this); - return true; - } catch (Exception e) { - ApptentiveLog.e(e, "Unable to save conversation"); - return false; - } - } - - //endregion - - //region Listeners - - @Override - public void onDataChanged() { - if (hasFile()) { - boolean scheduled = DispatchQueue.backgroundQueue().dispatchAsyncOnce(saveConversationTask, 100L); - if (scheduled) { - ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); - } else { - ApptentiveLog.d(CONVERSATION, "Conversation save already scheduled."); - } - } else { - ApptentiveLog.v(CONVERSATION, "Can't save conversation data: storage file is not specified"); - } - } - - //endregion - - //region Getters & Setters - - public ConversationState getState() { - return state; - } - - public void setState(ConversationState state) { - // TODO: check if state transition would make sense (for example you should not be able to move from 'logged' state to 'anonymous', etc.) - this.state = state; - } - - /** - * Returns true if conversation is in the given state - */ - public boolean hasState(ConversationState s) { - return state.equals(s); - } - - /** - * Returns true if conversation is in one of the given states - */ - public boolean hasState(ConversationState... states) { - for (ConversationState s : states) { - if (s.equals(state)) { - return true; - } - } - return false; - } - - /** - * Returns true if conversation is in "active" state (after receiving server response) - */ - public boolean hasActiveState() { - return hasState(ConversationState.LOGGED_IN, ANONYMOUS); - } - - public String getConversationToken() { - return data.getConversationToken(); - } - - public void setConversationToken(String conversationToken) { - data.setConversationToken(conversationToken); - } - - public String getConversationId() { - return data.getConversationId(); - } - - public void setConversationId(String conversationId) { - data.setConversationId(conversationId); - } - - public String getPersonId() { - return data.getPersonId(); - } - - public void setPersonId(String personId) { - data.setPersonId(personId); - } - - public String getPersonEmail() { - return data.getPersonEmail(); - } - - public void setPersonEmail(String personEmail) { - data.setPersonEmail(personEmail); - } - - public String getPersonName() { - return data.getPersonName(); - } - - public void setPersonName(String personName) { - data.setPersonName(personName); - } - - public Device getDevice() { - return data.getDevice(); - } - - public void setDevice(Device device) { - data.setDevice(device); - } - - public Device getLastSentDevice() { - return data.getLastSentDevice(); - } - - public void setLastSentDevice(Device lastSentDevice) { - data.setLastSentDevice(lastSentDevice); - } - - public Person getPerson() { - return data.getPerson(); - } - - public void setPerson(Person person) { - data.setPerson(person); - } - - public Person getLastSentPerson() { - return data.getLastSentPerson(); - } - - public void setLastSentPerson(Person lastSentPerson) { - data.setLastSentPerson(lastSentPerson); - } - - public Sdk getSdk() { - return data.getSdk(); - } - - public void setSdk(Sdk sdk) { - data.setSdk(sdk); - } - - public AppRelease getAppRelease() { - return data.getAppRelease(); - } - - public void setAppRelease(AppRelease appRelease) { - data.setAppRelease(appRelease); - } - - public EventData getEventData() { - return data.getEventData(); - } +public class Conversation implements DataChangedListener, Destroyable { + + /** + * Conversation data for this class to manage + */ + private ConversationData data; + + /** + * File which represents this conversation on the disk + */ + private File file; + + // TODO: remove this class + private InteractionManager interactionManager; + + private ConversationState state = ConversationState.UNDEFINED; + + private final MessageManager messageManager; + + // we keep references to the tasks in order to dispatch them only once + private final DispatchTask fetchInteractionsTask = new DispatchTask() { + @Override + protected void execute() { + final boolean updateSuccessful = fetchInteractionsSync(); + + // Update pending state on UI thread after finishing the task + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + if (hasActiveState()) { + ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); + dispatchDebugEvent(EVT_INTERACTION_FETCH, updateSuccessful); + } + } + }); + } + }; + + // we keep references to the tasks in order to dispatch them only once + private final DispatchTask saveConversationTask = new DispatchTask() { + @Override + protected void execute() { + save(); + } + }; + + public Conversation() { + data = new ConversationData(); + messageManager = new MessageManager(); + } + + //region Interactions + + /** + * Returns an Interaction for eventLabel if there is one that can be displayed. + */ + public Interaction getApplicableInteraction(String eventLabel) { + String targetsString = getTargets(); + if (targetsString != null) { + try { + Targets targets = new Targets(getTargets()); + String interactionId = targets.getApplicableInteraction(eventLabel); + if (interactionId != null) { + String interactionsString = getInteractions(); + if (interactionsString != null) { + Interactions interactions = new Interactions(interactionsString); + return interactions.getInteraction(interactionId); + } + } + } catch (JSONException e) { + ApptentiveLog.e(e, "Exception while getting applicable interaction: %s", eventLabel); + } + } + return null; + } + + boolean fetchInteractions(Context context) { + boolean cacheExpired = getInteractionExpiration() > Util.currentTimeSeconds(); + if (cacheExpired || RuntimeUtils.isAppDebuggable(context)) { + return DispatchQueue.backgroundQueue().dispatchAsyncOnce(fetchInteractionsTask); // do not allow multiple fetches at the same time + } + + ApptentiveLog.v(CONVERSATION, "Interaction cache is still valid"); + return false; + } + + /** + * Fetches interaction synchronously. Returns true if succeed. + */ + private boolean fetchInteractionsSync() { + ApptentiveLog.v(CONVERSATION, "Fetching Interactions"); + ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); + + // TODO: Move this to global config + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); + boolean updateSuccessful = true; + + // We weren't able to connect to the internet. + if (response.isException()) { + prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, false).apply(); + updateSuccessful = false; + } + // We got a server error. + else if (!response.isSuccessful()) { + prefs.edit().putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT, true).apply(); + updateSuccessful = false; + } + + if (updateSuccessful) { + String interactionsPayloadString = response.getContent(); + + // Store new integration cache expiration. + String cacheControl = response.getHeaders().get("Cache-Control"); + Integer cacheSeconds = Util.parseCacheControlHeader(cacheControl); + if (cacheSeconds == null) { + cacheSeconds = Constants.CONFIG_DEFAULT_INTERACTION_CACHE_EXPIRATION_DURATION_SECONDS; + } + setInteractionExpiration(Util.currentTimeSeconds() + cacheSeconds); + try { + InteractionManifest payload = new InteractionManifest(interactionsPayloadString); + Interactions interactions = payload.getInteractions(); + Targets targets = payload.getTargets(); + if (interactions != null && targets != null) { + setTargets(targets.toString()); + setInteractions(interactions.toString()); + } else { + ApptentiveLog.e(CONVERSATION, "Unable to save interactionManifest."); + } + } catch (JSONException e) { + ApptentiveLog.e(e, "Invalid InteractionManifest received."); + } + } + ApptentiveLog.v(CONVERSATION, "Fetching new Interactions asyncTask finished. Successful? %b", updateSuccessful); + + return updateSuccessful; + } + + //endregion + + //region Saving + + /** + * Saves conversation data to the disk synchronously. Returns true + * if succeed. + */ + synchronized boolean save() { + if (file == null) { + ApptentiveLog.e(CONVERSATION, "Unable to save conversation: destination file not specified"); + return false; + } + + ApptentiveLog.d(CONVERSATION, "Saving Conversation"); + ApptentiveLog.v(CONVERSATION, "EventData: %s", getEventData().toString()); // TODO: remove + + try { + FileSerializer serializer = new FileSerializer(file); + serializer.serialize(this); + return true; + } catch (Exception e) { + ApptentiveLog.e(e, "Unable to save conversation"); + return false; + } + } + + //endregion + + //region Listeners + + @Override + public void onDataChanged() { + if (hasFile()) { + boolean scheduled = DispatchQueue.backgroundQueue().dispatchAsyncOnce(saveConversationTask, 100L); + if (scheduled) { + ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); + } else { + ApptentiveLog.d(CONVERSATION, "Conversation save already scheduled."); + } + } else { + ApptentiveLog.v(CONVERSATION, "Can't save conversation data: storage file is not specified"); + } + } + + //endregion + + //region Destroyable + + @Override + public void destroy() { + messageManager.destroy(); + } + + //endregion + + //region Getters & Setters + + public ConversationState getState() { + return state; + } + + public void setState(ConversationState state) { + // TODO: check if state transition would make sense (for example you should not be able to move from 'logged' state to 'anonymous', etc.) + this.state = state; + } + + /** + * Returns true if conversation is in the given state + */ + public boolean hasState(ConversationState s) { + return state.equals(s); + } + + /** + * Returns true if conversation is in one of the given states + */ + public boolean hasState(ConversationState... states) { + for (ConversationState s : states) { + if (s.equals(state)) { + return true; + } + } + return false; + } + + /** + * Returns true if conversation is in "active" state (after receiving server response) + */ + public boolean hasActiveState() { + return hasState(ConversationState.LOGGED_IN, ANONYMOUS); + } + + public String getConversationToken() { + return data.getConversationToken(); + } + + public void setConversationToken(String conversationToken) { + data.setConversationToken(conversationToken); + } + + public String getConversationId() { + return data.getConversationId(); + } + + public void setConversationId(String conversationId) { + data.setConversationId(conversationId); + } + + public String getPersonId() { + return data.getPersonId(); + } + + public void setPersonId(String personId) { + data.setPersonId(personId); + } + + public String getPersonEmail() { + return data.getPersonEmail(); + } + + public void setPersonEmail(String personEmail) { + data.setPersonEmail(personEmail); + } + + public String getPersonName() { + return data.getPersonName(); + } + + public void setPersonName(String personName) { + data.setPersonName(personName); + } + + public Device getDevice() { + return data.getDevice(); + } + + public void setDevice(Device device) { + data.setDevice(device); + } + + public Device getLastSentDevice() { + return data.getLastSentDevice(); + } + + public void setLastSentDevice(Device lastSentDevice) { + data.setLastSentDevice(lastSentDevice); + } + + public Person getPerson() { + return data.getPerson(); + } + + public void setPerson(Person person) { + data.setPerson(person); + } + + public Person getLastSentPerson() { + return data.getLastSentPerson(); + } + + public void setLastSentPerson(Person lastSentPerson) { + data.setLastSentPerson(lastSentPerson); + } + + public Sdk getSdk() { + return data.getSdk(); + } + + public void setSdk(Sdk sdk) { + data.setSdk(sdk); + } + + public AppRelease getAppRelease() { + return data.getAppRelease(); + } + + public void setAppRelease(AppRelease appRelease) { + data.setAppRelease(appRelease); + } + + public EventData getEventData() { + return data.getEventData(); + } - public void setEventData(EventData eventData) { - data.setEventData(eventData); - } + public void setEventData(EventData eventData) { + data.setEventData(eventData); + } - public String getLastSeenSdkVersion() { - return data.getLastSeenSdkVersion(); - } + public String getLastSeenSdkVersion() { + return data.getLastSeenSdkVersion(); + } - public void setLastSeenSdkVersion(String lastSeenSdkVersion) { - data.setLastSeenSdkVersion(lastSeenSdkVersion); - } + public void setLastSeenSdkVersion(String lastSeenSdkVersion) { + data.setLastSeenSdkVersion(lastSeenSdkVersion); + } - public VersionHistory getVersionHistory() { - return data.getVersionHistory(); - } + public VersionHistory getVersionHistory() { + return data.getVersionHistory(); + } - public void setVersionHistory(VersionHistory versionHistory) { - data.setVersionHistory(versionHistory); - } + public void setVersionHistory(VersionHistory versionHistory) { + data.setVersionHistory(versionHistory); + } - public boolean isMessageCenterFeatureUsed() { - return data.isMessageCenterFeatureUsed(); - } + public boolean isMessageCenterFeatureUsed() { + return data.isMessageCenterFeatureUsed(); + } - public void setMessageCenterFeatureUsed(boolean messageCenterFeatureUsed) { - data.setMessageCenterFeatureUsed(messageCenterFeatureUsed); - } + public void setMessageCenterFeatureUsed(boolean messageCenterFeatureUsed) { + data.setMessageCenterFeatureUsed(messageCenterFeatureUsed); + } - public boolean isMessageCenterWhoCardPreviouslyDisplayed() { - return data.isMessageCenterWhoCardPreviouslyDisplayed(); - } + public boolean isMessageCenterWhoCardPreviouslyDisplayed() { + return data.isMessageCenterWhoCardPreviouslyDisplayed(); + } - public void setMessageCenterWhoCardPreviouslyDisplayed(boolean messageCenterWhoCardPreviouslyDisplayed) { - data.setMessageCenterWhoCardPreviouslyDisplayed(messageCenterWhoCardPreviouslyDisplayed); - } + public void setMessageCenterWhoCardPreviouslyDisplayed(boolean messageCenterWhoCardPreviouslyDisplayed) { + data.setMessageCenterWhoCardPreviouslyDisplayed(messageCenterWhoCardPreviouslyDisplayed); + } - public String getMessageCenterPendingMessage() { - return data.getMessageCenterPendingMessage(); - } + public String getMessageCenterPendingMessage() { + return data.getMessageCenterPendingMessage(); + } - public void setMessageCenterPendingMessage(String messageCenterPendingMessage) { - data.setMessageCenterPendingMessage(messageCenterPendingMessage); - } + public void setMessageCenterPendingMessage(String messageCenterPendingMessage) { + data.setMessageCenterPendingMessage(messageCenterPendingMessage); + } - public String getMessageCenterPendingAttachments() { - return data.getMessageCenterPendingAttachments(); - } + public String getMessageCenterPendingAttachments() { + return data.getMessageCenterPendingAttachments(); + } - public void setMessageCenterPendingAttachments(String messageCenterPendingAttachments) { - data.setMessageCenterPendingAttachments(messageCenterPendingAttachments); - } + public void setMessageCenterPendingAttachments(String messageCenterPendingAttachments) { + data.setMessageCenterPendingAttachments(messageCenterPendingAttachments); + } - public String getTargets() { - return data.getTargets(); - } + public String getTargets() { + return data.getTargets(); + } - public void setTargets(String targets) { - data.setTargets(targets); - } + public void setTargets(String targets) { + data.setTargets(targets); + } - public String getInteractions() { - return data.getInteractions(); - } + public String getInteractions() { + return data.getInteractions(); + } - public void setInteractions(String interactions) { - data.setInteractions(interactions); - } + public void setInteractions(String interactions) { + data.setInteractions(interactions); + } - public double getInteractionExpiration() { - return data.getInteractionExpiration(); - } + public double getInteractionExpiration() { + return data.getInteractionExpiration(); + } - public void setInteractionExpiration(double interactionExpiration) { - data.setInteractionExpiration(interactionExpiration); - } + public void setInteractionExpiration(double interactionExpiration) { + data.setInteractionExpiration(interactionExpiration); + } - public InteractionManager getInteractionManager() { - return interactionManager; - } + public MessageManager getMessageManager() { + return messageManager; + } - public void setInteractionManager(InteractionManager interactionManager) { - this.interactionManager = interactionManager; - } + public InteractionManager getInteractionManager() { + return interactionManager; + } - synchronized boolean hasFile() { - return file != null; - } + public void setInteractionManager(InteractionManager interactionManager) { + this.interactionManager = interactionManager; + } - synchronized File getFile() { - return file; - } + synchronized boolean hasFile() { + return file != null; + } - synchronized void setFile(File file) { - this.file = file; - } + synchronized File getFile() { + return file; + } - //endregion + synchronized void setFile(File file) { + this.file = file; + } + + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 50c02df93..069e3c9a9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -6,7 +6,6 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.conversation.ConversationMetadata.Filter; import com.apptentive.android.sdk.debug.Assert; -import com.apptentive.android.sdk.model.ConversationItem; import com.apptentive.android.sdk.model.ConversationTokenRequest; import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; @@ -34,7 +33,7 @@ import static com.apptentive.android.sdk.ApptentiveLogTag.CONVERSATION; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_STARTED; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE_KEY_CONVERSATION; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS; import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS_PENDING; import static com.apptentive.android.sdk.conversation.ConversationState.LOGGED_IN; @@ -289,7 +288,7 @@ private void handleConversationStateChange(Conversation conversation) { ApptentiveNotificationCenter.defaultCenter() .postNotification(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, - ObjectUtils.toMap(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE_KEY_CONVERSATION, conversation)); + ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, conversation)); if (conversation != null && conversation.hasActiveState()) { conversation.fetchInteractions(getContext()); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index 41ec10d4f..7e07bcb42 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -325,7 +325,7 @@ public void onResume() { ApptentiveInternal.getInstance().getMessageManager().resumeSending(); /* imagePickerStillOpen was set true when the picker intent was launched. If user had picked an image, - * it woud have been set to false. Otherwise, it indicates the user tried to attach an image but + * it would have been set to false. Otherwise, it indicates the user tried to attach an image but * abandoned the image picker without picking anything */ if (imagePickerStillOpen) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index c8d1523ff..6b77d58da 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -16,14 +16,17 @@ import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; -import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveToastNotification; import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem; import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; import com.apptentive.android.sdk.module.metric.MetricModule; +import com.apptentive.android.sdk.notifications.ApptentiveNotification; +import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; +import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; import com.apptentive.android.sdk.storage.MessageStore; +import com.apptentive.android.sdk.util.Destroyable; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -39,7 +42,13 @@ import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; -public class MessageManager { +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_RESUMED; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_STARTED; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_BACKGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_ACTIVITY; + +public class MessageManager implements Destroyable, ApptentiveNotificationObserver { // The reason of pause message sending public static int SEND_PAUSE_REASON_ACTIVITY_PAUSE = 0; @@ -52,16 +61,16 @@ public class MessageManager { private WeakReference afterSendMessageListener; - private final List> internalNewMessagesListeners = new ArrayList>(); + private final List> internalNewMessagesListeners = new ArrayList<>(); /* UnreadMessagesListener is set by external hosting app, and its lifecycle is managed by the app. * Use WeakReference to prevent memory leak */ - private final List> hostUnreadMessagesListeners = new ArrayList>(); + private final List> hostUnreadMessagesListeners = new ArrayList<>(); AtomicBoolean appInForeground = new AtomicBoolean(false); - private MessagePollingWorker pollingWorker; + private final MessagePollingWorker pollingWorker; private final MessageDispatchTask toastMessageNotifierTask = new MessageDispatchTask() { @Override @@ -78,21 +87,8 @@ protected void execute(int messageCount) { }; public MessageManager() { - - } - - // init() will start polling worker. - public void init() { - if (pollingWorker == null) { - pollingWorker = new MessagePollingWorker(this); - /* Set SharePreference to indicate Message Center feature is desired. It will always be checked - * during Apptentive initialization. - */ - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.setMessageCenterFeatureUsed(true); - } - } + pollingWorker = new MessagePollingWorker(this); + // conversation.setMessageCenterFeatureUsed(true); FIXME: figure out what to do with this call } /* @@ -100,10 +96,6 @@ public void init() { * when push is received on the device. */ public void startMessagePreFetchTask() { - // Defer message polling thread creation, if not created yet and host app receives a new message push - - init(); - final boolean updateMC = isMessageCenterInForeground(); DispatchQueue.backgroundQueue().dispatchAsync(new DispatchTask() { @Override @@ -129,11 +121,7 @@ protected void execute() { * * @return true if messages were returned, else false. */ - public synchronized boolean fetchAndStoreMessages(boolean isMessageCenterForeground, boolean showToast) { - if (ApptentiveInternal.getInstance().getConversation().getConversationToken() == null) { - ApptentiveLog.d("Can't fetch messages because the conversation has not yet been initialized."); - return false; - } + synchronized boolean fetchAndStoreMessages(boolean isMessageCenterForeground, boolean showToast) { if (!Util.isNetworkConnectionPresent()) { ApptentiveLog.d("Can't fetch messages because a network connection is not present."); return false; @@ -191,7 +179,7 @@ protected void execute() { } public List getMessageCenterListItems() { - List messagesToShow = new ArrayList(); + List messagesToShow = new ArrayList<>(); try { List messagesAll = getMessageStore().getAllMessages().get(); // Do not display hidden messages on Message Center @@ -225,7 +213,7 @@ private List fetchMessages(String afterId) { ApptentiveHttpResponse response = ApptentiveClient.getMessages(null, afterId, null); - List ret = new ArrayList(); + List ret = new ArrayList<>(); if (!response.isSuccessful()) { return ret; } @@ -243,8 +231,8 @@ public void updateMessage(ApptentiveMessage apptentiveMessage) { getMessageStore().updateMessage(apptentiveMessage); } - public List parseMessagesString(String messageString) throws JSONException { - List ret = new ArrayList(); + private List parseMessagesString(String messageString) throws JSONException { + List ret = new ArrayList<>(); JSONObject root = new JSONObject(messageString); if (!root.isNull("items")) { JSONArray items = root.getJSONArray("items"); @@ -331,6 +319,34 @@ public int getUnreadMessageCount() { return msgCount; } + //region Notification Observer + + @Override + public void onReceiveNotification(ApptentiveNotification notification) { + if (notification.hasName(NOTIFICATION_ACTIVITY_STARTED) || + notification.hasName(NOTIFICATION_ACTIVITY_RESUMED)) { + + final Activity activity = notification.getRequiredUserInfo(NOTIFICATION_KEY_ACTIVITY, Activity.class); + setCurrentForegroundActivity(activity); + } else if (notification.hasName(NOTIFICATION_APP_ENTER_FOREGROUND)) { + appWentToForeground(); + } else if (notification.hasName(NOTIFICATION_APP_ENTER_BACKGROUND)) { + setCurrentForegroundActivity(null); + appWentToBackground(); + } + } + + //endregion + + //region Destroyable + + @Override + public void destroy() { + ApptentiveNotificationCenter.defaultCenter().removeObserver(this); + } + + //endregion + // Listeners public interface AfterSendMessageListener { @@ -347,7 +363,7 @@ public interface OnNewIncomingMessagesListener { public void setAfterSendMessageListener(AfterSendMessageListener listener) { if (listener != null) { - afterSendMessageListener = new WeakReference(listener); + afterSendMessageListener = new WeakReference<>(listener); } else { afterSendMessageListener = null; } @@ -356,7 +372,6 @@ public void setAfterSendMessageListener(AfterSendMessageListener listener) { public void addInternalOnMessagesUpdatedListener(OnNewIncomingMessagesListener newlistener) { if (newlistener != null) { - init(); for (Iterator> iterator = internalNewMessagesListeners.iterator(); iterator.hasNext(); ) { WeakReference listenerRef = iterator.next(); OnNewIncomingMessagesListener listener = listenerRef.get(); @@ -374,7 +389,7 @@ public void clearInternalOnMessagesUpdatedListeners() { internalNewMessagesListeners.clear(); } - public void notifyInternalNewMessagesListeners(final CompoundMessage apptentiveMsg) { + private void notifyInternalNewMessagesListeners(final CompoundMessage apptentiveMsg) { for (WeakReference listenerRef : internalNewMessagesListeners) { OnNewIncomingMessagesListener listener = listenerRef.get(); if (listener != null) { @@ -393,8 +408,7 @@ public void setHostUnreadMessagesListener(UnreadMessagesListener listener) { public void addHostUnreadMessagesListener(UnreadMessagesListener newListener) { if (newListener != null) { - // Defer message polling thread creation, if not created yet, and host app adds an unread message listener - init(); + // Defer message polling thread creation, if not created yet, and host app adds an unread message listenerinit(); for (Iterator> iterator = hostUnreadMessagesListeners.iterator(); iterator.hasNext(); ) { WeakReference listenerRef = iterator.next(); UnreadMessagesListener listener = listenerRef.get(); @@ -408,7 +422,7 @@ public void addHostUnreadMessagesListener(UnreadMessagesListener newListener) { } } - public void clearHostUnreadMessagesListeners() { + private void clearHostUnreadMessagesListeners() { hostUnreadMessagesListeners.clear(); } @@ -422,9 +436,9 @@ public void notifyHostUnreadMessagesListeners(int unreadMessages) { } // Set when Activity.onStart() and onStop() are called - public void setCurrentForegroundActivity(Activity activity) { + private void setCurrentForegroundActivity(Activity activity) { if (activity != null) { - currentForegroundApptentiveActivity = new WeakReference(activity); + currentForegroundApptentiveActivity = new WeakReference<>(activity); } else { ApptentiveToastNotificationManager manager = ApptentiveToastNotificationManager.getInstance(null, false); if (manager != null) { @@ -438,7 +452,7 @@ public void setMessageCenterInForeground(boolean bInForeground) { pollingWorker.setMessageCenterInForeground(bInForeground); } - public boolean isMessageCenterInForeground() { + private boolean isMessageCenterInForeground() { return pollingWorker.messageCenterInForeground.get(); } @@ -468,24 +482,19 @@ public void run() { } } - public void appWentToForeground() { + private void appWentToForeground() { appInForeground.set(true); - if (pollingWorker != null) { - pollingWorker.appWentToForeground(); - } + pollingWorker.appWentToForeground(); } - public void appWentToBackground() { + private void appWentToBackground() { appInForeground.set(false); - if (pollingWorker != null) { - pollingWorker.appWentToBackground(); - } + pollingWorker.appWentToBackground(); } //region Message Dispatch Task - private abstract static class MessageDispatchTask extends DispatchTask - { + private abstract static class MessageDispatchTask extends DispatchTask { private CompoundMessage message; protected abstract void execute(CompoundMessage message); @@ -505,8 +514,7 @@ MessageDispatchTask setMessage(CompoundMessage message) { } } - private abstract static class MessageCountDispatchTask extends DispatchTask - { + private abstract static class MessageCountDispatchTask extends DispatchTask { private int messageCount; protected abstract void execute(int messageCount); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java index a7c889911..7549492d7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java @@ -6,6 +6,7 @@ package com.apptentive.android.sdk.notifications; +import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; @@ -38,6 +39,12 @@ public boolean hasName(String name) { return StringUtils.equal(this.name, name); } + public T getRequiredUserInfo(String key, Class valueClass) { + final T userInfo = getUserInfo(key, valueClass); + Assert.assertNotNull(userInfo, "Missing required user info '%s' for '%s' notification", key, name); + return userInfo; + } + public T getUserInfo(String key, Class valueClass) { return userInfo != null ? ObjectUtils.as(userInfo.get(key), valueClass) : null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Destroyable.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Destroyable.java new file mode 100644 index 000000000..7a599c097 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Destroyable.java @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util; + +public interface Destroyable { + void destroy(); +} From 322c8e501a3b7d18909e221b031410931172ac30 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 13:50:33 -0700 Subject: [PATCH 152/465] Added a default message store --- .../sdk/conversation/Conversation.java | 5 +- .../sdk/conversation/DefaultMessageStore.java | 82 +++++++++++++++++++ .../module/messagecenter/MessageManager.java | 38 +++++---- 3 files changed, 106 insertions(+), 19 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/conversation/DefaultMessageStore.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 3d433b104..d1c55149a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -32,6 +32,7 @@ import com.apptentive.android.sdk.util.RuntimeUtils; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchQueueType; import com.apptentive.android.sdk.util.threading.DispatchTask; import org.json.JSONException; @@ -91,7 +92,9 @@ protected void execute() { public Conversation() { data = new ConversationData(); - messageManager = new MessageManager(); + + final DispatchQueue messageStoreQueue = DispatchQueue.createBackgroundQueue("Message Store", DispatchQueueType.Concurrent); + messageManager = new MessageManager(new DefaultMessageStore(messageStoreQueue)); } //region Interactions diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/DefaultMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/DefaultMessageStore.java new file mode 100644 index 000000000..3faacf661 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/DefaultMessageStore.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.conversation; + +import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; +import com.apptentive.android.sdk.storage.MessageStore; +import com.apptentive.android.sdk.util.threading.DispatchQueue; + +import java.util.List; +import java.util.concurrent.Future; + +class DefaultMessageStore implements MessageStore { + + private final DispatchQueue operationQueue; + + public DefaultMessageStore(DispatchQueue operationQueue) { + if (operationQueue == null) { + throw new IllegalArgumentException("Operation queue is null"); + } + this.operationQueue = operationQueue; + } + + @Override + public void addPayload(Payload... payloads) { + + } + + @Override + public void deletePayload(Payload payload) { + + } + + @Override + public void deleteAllPayloads() { + + } + + @Override + public Future getOldestUnsentPayload() throws Exception { + return null; + } + + @Override + public void addOrUpdateMessages(ApptentiveMessage... apptentiveMessage) { + + } + + @Override + public void updateMessage(ApptentiveMessage apptentiveMessage) { + + } + + @Override + public Future> getAllMessages() throws Exception { + return null; + } + + @Override + public Future getLastReceivedMessageId() throws Exception { + return null; + } + + @Override + public Future getUnreadMessageCount() throws Exception { + return null; + } + + @Override + public void deleteAllMessages() { + + } + + @Override + public void deleteMessage(String nonce) { + + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 6b77d58da..48f80481a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -57,19 +57,20 @@ public class MessageManager implements Destroyable, ApptentiveNotificationObserv private static int TOAST_TYPE_UNREAD_MESSAGE = 1; + private final MessageStore messageStore; + private WeakReference currentForegroundApptentiveActivity; private WeakReference afterSendMessageListener; private final List> internalNewMessagesListeners = new ArrayList<>(); - /* UnreadMessagesListener is set by external hosting app, and its lifecycle is managed by the app. * Use WeakReference to prevent memory leak */ private final List> hostUnreadMessagesListeners = new ArrayList<>(); - AtomicBoolean appInForeground = new AtomicBoolean(false); + private final AtomicBoolean appInForeground = new AtomicBoolean(false); private final MessagePollingWorker pollingWorker; private final MessageDispatchTask toastMessageNotifierTask = new MessageDispatchTask() { @@ -86,8 +87,13 @@ protected void execute(int messageCount) { } }; - public MessageManager() { - pollingWorker = new MessagePollingWorker(this); + public MessageManager(MessageStore messageStore) { + if (messageStore == null) { + throw new IllegalArgumentException("Message store is null"); + } + + this.messageStore = messageStore; + this.pollingWorker = new MessagePollingWorker(this); // conversation.setMessageCenterFeatureUsed(true); FIXME: figure out what to do with this call } @@ -130,7 +136,7 @@ synchronized boolean fetchAndStoreMessages(boolean isMessageCenterForeground, bo // Fetch the messages. List messagesToSave = null; try { - Future future = getMessageStore().getLastReceivedMessageId(); + Future future = messageStore.getLastReceivedMessageId(); messagesToSave = fetchMessages(future.get()); } catch (Exception e) { ApptentiveLog.e("Error retrieving last received message id from worker thread"); @@ -162,7 +168,7 @@ protected void execute() { }); } } - getMessageStore().addOrUpdateMessages(messagesToSave.toArray(new ApptentiveMessage[messagesToSave.size()])); + messageStore.addOrUpdateMessages(messagesToSave.toArray(new ApptentiveMessage[messagesToSave.size()])); if (incomingUnreadMessages > 0) { // Show toast notification only if the foreground activity is not already message center activity if (!isMessageCenterForeground && showToast) { @@ -181,7 +187,7 @@ protected void execute() { public List getMessageCenterListItems() { List messagesToShow = new ArrayList<>(); try { - List messagesAll = getMessageStore().getAllMessages().get(); + List messagesAll = messageStore.getAllMessages().get(); // Do not display hidden messages on Message Center for (ApptentiveMessage message : messagesAll) { if (!message.isHidden()) { @@ -196,7 +202,7 @@ public List getMessageCenterListItems() { } public void sendMessage(ApptentiveMessage apptentiveMessage) { - getMessageStore().addOrUpdateMessages(apptentiveMessage); + messageStore.addOrUpdateMessages(apptentiveMessage); ApptentiveInternal.getInstance().getApptentiveTaskManager().addPayload(apptentiveMessage); } @@ -205,7 +211,7 @@ public void sendMessage(ApptentiveMessage apptentiveMessage) { */ public void deleteAllMessages(Context context) { ApptentiveLog.d("Deleting all messages."); - getMessageStore().deleteAllMessages(); + messageStore.deleteAllMessages(); } private List fetchMessages(String afterId) { @@ -228,7 +234,7 @@ private List fetchMessages(String afterId) { } public void updateMessage(ApptentiveMessage apptentiveMessage) { - getMessageStore().updateMessage(apptentiveMessage); + messageStore.updateMessage(apptentiveMessage); } private List parseMessagesString(String messageString) throws JSONException { @@ -266,7 +272,7 @@ public void onSentMessage(ApptentiveMessage apptentiveMessage, ApptentiveHttpRes if (response.isRejectedPermanently() || response.isBadPayload()) { if (apptentiveMessage instanceof CompoundMessage) { apptentiveMessage.setCreatedAt(Double.MIN_VALUE); - getMessageStore().updateMessage(apptentiveMessage); + messageStore.updateMessage(apptentiveMessage); if (afterSendMessageListener != null && afterSendMessageListener.get() != null) { afterSendMessageListener.get().onMessageSent(response, apptentiveMessage); } @@ -284,7 +290,7 @@ public void onSentMessage(ApptentiveMessage apptentiveMessage, ApptentiveHttpRes // Don't store hidden messages once sent. Delete them. if (apptentiveMessage.isHidden()) { ((CompoundMessage) apptentiveMessage).deleteAssociatedFiles(); - getMessageStore().deleteMessage(apptentiveMessage.getNonce()); + messageStore.deleteMessage(apptentiveMessage.getNonce()); return; } try { @@ -297,7 +303,7 @@ public void onSentMessage(ApptentiveMessage apptentiveMessage, ApptentiveHttpRes } catch (JSONException e) { ApptentiveLog.e("Error parsing sent apptentiveMessage response.", e); } - getMessageStore().updateMessage(apptentiveMessage); + messageStore.updateMessage(apptentiveMessage); if (afterSendMessageListener != null && afterSendMessageListener.get() != null) { afterSendMessageListener.get().onMessageSent(response, apptentiveMessage); @@ -305,14 +311,10 @@ public void onSentMessage(ApptentiveMessage apptentiveMessage, ApptentiveHttpRes } } - private MessageStore getMessageStore() { - return ApptentiveInternal.getInstance().getApptentiveTaskManager(); - } - public int getUnreadMessageCount() { int msgCount = 0; try { - msgCount = getMessageStore().getUnreadMessageCount().get(); + msgCount = messageStore.getUnreadMessageCount().get(); } catch (Exception e) { ApptentiveLog.e("Error getting unread messages count in worker thread"); } From 50717674361c3a07581eec9b43fd8da429094a96 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 13:59:30 -0700 Subject: [PATCH 153/465] Small fix --- .../android/sdk/module/messagecenter/MessageManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 48f80481a..871e40e62 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -70,7 +70,7 @@ public class MessageManager implements Destroyable, ApptentiveNotificationObserv */ private final List> hostUnreadMessagesListeners = new ArrayList<>(); - private final AtomicBoolean appInForeground = new AtomicBoolean(false); + final AtomicBoolean appInForeground = new AtomicBoolean(false); // FIXME: get rid of that private final MessagePollingWorker pollingWorker; private final MessageDispatchTask toastMessageNotifierTask = new MessageDispatchTask() { From 0c802ec322b58bf6bbeb38f1191e9697cafec3e9 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 14:00:30 -0700 Subject: [PATCH 154/465] Refactoring Renamed DefaultMessagStore to FileMessageStore --- .../com/apptentive/android/sdk/conversation/Conversation.java | 2 +- .../{DefaultMessageStore.java => FileMessageStore.java} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/conversation/{DefaultMessageStore.java => FileMessageStore.java} (93%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index d1c55149a..730f61eed 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -94,7 +94,7 @@ public Conversation() { data = new ConversationData(); final DispatchQueue messageStoreQueue = DispatchQueue.createBackgroundQueue("Message Store", DispatchQueueType.Concurrent); - messageManager = new MessageManager(new DefaultMessageStore(messageStoreQueue)); + messageManager = new MessageManager(new FileMessageStore(messageStoreQueue)); } //region Interactions diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/DefaultMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java similarity index 93% rename from apptentive/src/main/java/com/apptentive/android/sdk/conversation/DefaultMessageStore.java rename to apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index 3faacf661..4f8c5cd31 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/DefaultMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -14,11 +14,11 @@ import java.util.List; import java.util.concurrent.Future; -class DefaultMessageStore implements MessageStore { +class FileMessageStore implements MessageStore { private final DispatchQueue operationQueue; - public DefaultMessageStore(DispatchQueue operationQueue) { + public FileMessageStore(DispatchQueue operationQueue) { if (operationQueue == null) { throw new IllegalArgumentException("Operation queue is null"); } From 33593a1b2375cd075b42754e6b6235b204124aba Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 14:12:31 -0700 Subject: [PATCH 155/465] File message store progress --- .../sdk/conversation/Conversation.java | 4 +- .../sdk/conversation/FileMessageStore.java | 43 ++--------- .../module/messagecenter/MessageManager.java | 8 +- .../sdk/storage/ApptentiveTaskManager.java | 73 +------------------ .../android/sdk/storage/MessageStore.java | 8 +- 5 files changed, 17 insertions(+), 119 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 730f61eed..7c322ee74 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -92,9 +92,7 @@ protected void execute() { public Conversation() { data = new ConversationData(); - - final DispatchQueue messageStoreQueue = DispatchQueue.createBackgroundQueue("Message Store", DispatchQueueType.Concurrent); - messageManager = new MessageManager(new FileMessageStore(messageStoreQueue)); + messageManager = new MessageManager(new FileMessageStore()); } //region Interactions diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index 4f8c5cd31..daf8b944e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -6,44 +6,13 @@ package com.apptentive.android.sdk.conversation; -import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.storage.MessageStore; -import com.apptentive.android.sdk.util.threading.DispatchQueue; import java.util.List; -import java.util.concurrent.Future; class FileMessageStore implements MessageStore { - - private final DispatchQueue operationQueue; - - public FileMessageStore(DispatchQueue operationQueue) { - if (operationQueue == null) { - throw new IllegalArgumentException("Operation queue is null"); - } - this.operationQueue = operationQueue; - } - - @Override - public void addPayload(Payload... payloads) { - - } - - @Override - public void deletePayload(Payload payload) { - - } - - @Override - public void deleteAllPayloads() { - - } - - @Override - public Future getOldestUnsentPayload() throws Exception { - return null; - } + //region MessageStore @Override public void addOrUpdateMessages(ApptentiveMessage... apptentiveMessage) { @@ -56,18 +25,18 @@ public void updateMessage(ApptentiveMessage apptentiveMessage) { } @Override - public Future> getAllMessages() throws Exception { + public List getAllMessages() throws Exception { return null; } @Override - public Future getLastReceivedMessageId() throws Exception { + public String getLastReceivedMessageId() throws Exception { return null; } @Override - public Future getUnreadMessageCount() throws Exception { - return null; + public int getUnreadMessageCount() throws Exception { + return 0; } @Override @@ -79,4 +48,6 @@ public void deleteAllMessages() { public void deleteMessage(String nonce) { } + + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 871e40e62..c892aec38 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -136,8 +136,8 @@ synchronized boolean fetchAndStoreMessages(boolean isMessageCenterForeground, bo // Fetch the messages. List messagesToSave = null; try { - Future future = messageStore.getLastReceivedMessageId(); - messagesToSave = fetchMessages(future.get()); + String lastMessageId = messageStore.getLastReceivedMessageId(); + messagesToSave = fetchMessages(lastMessageId); } catch (Exception e) { ApptentiveLog.e("Error retrieving last received message id from worker thread"); } @@ -187,7 +187,7 @@ protected void execute() { public List getMessageCenterListItems() { List messagesToShow = new ArrayList<>(); try { - List messagesAll = messageStore.getAllMessages().get(); + List messagesAll = messageStore.getAllMessages(); // Do not display hidden messages on Message Center for (ApptentiveMessage message : messagesAll) { if (!message.isHidden()) { @@ -314,7 +314,7 @@ public void onSentMessage(ApptentiveMessage apptentiveMessage, ApptentiveHttpRes public int getUnreadMessageCount() { int msgCount = 0; try { - msgCount = messageStore.getUnreadMessageCount().get(); + msgCount = messageStore.getUnreadMessageCount(); } catch (Exception e) { ApptentiveLog.e("Error getting unread messages count in worker thread"); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index bf014f0c6..a6da7ff15 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -20,7 +20,7 @@ import java.util.concurrent.TimeUnit; -public class ApptentiveTaskManager implements PayloadStore, EventStore, MessageStore { +public class ApptentiveTaskManager implements PayloadStore, EventStore { private ApptentiveDatabaseHelper dbHelper; private ThreadPoolExecutor singleThreadExecutor; @@ -104,77 +104,6 @@ public Payload call() throws Exception { }); } - @Override - public void addOrUpdateMessages(final ApptentiveMessage... apptentiveMessages) { - singleThreadExecutor.execute(new Runnable() { - @Override - public void run() { - dbHelper.addOrUpdateMessages(apptentiveMessages); - } - }); - } - - @Override - public void updateMessage(final ApptentiveMessage apptentiveMessage) { - singleThreadExecutor.execute(new Runnable() { - @Override - public void run() { - dbHelper.updateMessage(apptentiveMessage); - } - }); - } - - @Override - public Future> getAllMessages() throws Exception { - return singleThreadExecutor.submit(new Callable>() { - @Override - public List call() throws Exception { - List result = dbHelper.getAllMessages(); - return result; - } - }); - } - - @Override - public Future getLastReceivedMessageId() throws Exception { - return singleThreadExecutor.submit(new Callable() { - @Override - public String call() throws Exception { - return dbHelper.getLastReceivedMessageId(); - } - }); - } - - @Override - public Future getUnreadMessageCount() throws Exception { - return singleThreadExecutor.submit(new Callable() { - @Override - public Integer call() throws Exception { - return dbHelper.getUnreadMessageCount(); - } - }); - } - - @Override - public void deleteAllMessages() { - singleThreadExecutor.execute(new Runnable() { - @Override - public void run() { - dbHelper.deleteAllMessages(); - } - }); - } - - @Override - public void deleteMessage(final String nonce) { - singleThreadExecutor.execute(new Runnable() { - @Override - public void run() { - dbHelper.deleteMessage(nonce); - } - }); - } - public void deleteAssociatedFiles(final String messageNonce) { singleThreadExecutor.execute(new Runnable() { @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/MessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/MessageStore.java index 3452acad9..820dc9890 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/MessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/MessageStore.java @@ -15,17 +15,17 @@ /** * @author Sky Kelsey */ -public interface MessageStore extends PayloadStore { +public interface MessageStore { void addOrUpdateMessages(ApptentiveMessage... apptentiveMessage); void updateMessage(ApptentiveMessage apptentiveMessage); - Future> getAllMessages() throws Exception; + List getAllMessages() throws Exception; - Future getLastReceivedMessageId() throws Exception; + String getLastReceivedMessageId() throws Exception; - Future getUnreadMessageCount() throws Exception; + int getUnreadMessageCount() throws Exception; void deleteAllMessages(); From 2c2b1c722139e4a9e4bd679e63458022f86908f2 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 15:51:58 -0700 Subject: [PATCH 156/465] File message store progress --- .../android/sdk/ApptentiveLogTag.java | 1 + .../sdk/conversation/FileMessageStore.java | 252 +++++++++++++++++- .../model/ApptentiveMessage.java | 2 +- .../sdk/storage/ApptentiveDatabaseHelper.java | 117 -------- 4 files changed, 244 insertions(+), 128 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java index af0d11efa..7e3135ffc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java @@ -4,6 +4,7 @@ public enum ApptentiveLogTag { NETWORK(true), CONVERSATION(true), NOTIFICATIONS(true), + MESSAGES(true), TESTER_COMMANDS(false); ApptentiveLogTag(boolean enabled) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index daf8b944e..048de2ae6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -6,47 +6,279 @@ package com.apptentive.android.sdk.conversation; +import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; +import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; +import com.apptentive.android.sdk.serialization.SerializableObject; import com.apptentive.android.sdk.storage.MessageStore; +import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.util.Util; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; import java.util.List; class FileMessageStore implements MessageStore { + /** + * Binary format version + */ + private static final byte VERSION = 1; + + private final File file; + private final List messageEntries; + private boolean shouldFetchFromFile; + + FileMessageStore(File file) { + this.file = file; + this.messageEntries = new ArrayList<>(); // we need a random access + this.shouldFetchFromFile = true; // we would lazily read it from a file later + } + //region MessageStore @Override - public void addOrUpdateMessages(ApptentiveMessage... apptentiveMessage) { + public synchronized void addOrUpdateMessages(ApptentiveMessage... apptentiveMessages) { + fetchEntries(); + + for (ApptentiveMessage apptentiveMessage : apptentiveMessages) { + MessageEntry existing = findMessageEntry(apptentiveMessage); + if (existing != null) { + // Update + existing.id = apptentiveMessage.getId(); + existing.state = apptentiveMessage.getState().name(); + if (apptentiveMessage.isRead()) { // A apptentiveMessage can't be unread after being read. + existing.isRead = true; + } + existing.json = apptentiveMessage.toString(); + } else { + // Insert + MessageEntry entry = new MessageEntry(); + entry.id = apptentiveMessage.getId(); + entry.clientCreatedAt = apptentiveMessage.getClientCreatedAt(); + entry.nonce = apptentiveMessage.getNonce(); + entry.state = apptentiveMessage.getState().name(); + entry.isRead = apptentiveMessage.isRead(); + entry.json = apptentiveMessage.toString(); + } + } + writeToFile(); } @Override - public void updateMessage(ApptentiveMessage apptentiveMessage) { + public synchronized void updateMessage(ApptentiveMessage apptentiveMessage) { + fetchEntries(); + MessageEntry entry = findMessageEntry(apptentiveMessage); + if (entry != null) { + entry.id = apptentiveMessage.getId(); + entry.clientCreatedAt = apptentiveMessage.getClientCreatedAt(); + entry.nonce = apptentiveMessage.getNonce(); + entry.state = apptentiveMessage.getState().name(); + if (apptentiveMessage.isRead()) { // A apptentiveMessage can't be unread after being read. + entry.isRead = true; + } + entry.json = apptentiveMessage.toString(); + writeToFile(); + } } @Override - public List getAllMessages() throws Exception { - return null; + public synchronized List getAllMessages() throws Exception { + fetchEntries(); + + List apptentiveMessages = new ArrayList<>(); + for (MessageEntry entry : messageEntries) { + ApptentiveMessage apptentiveMessage = MessageFactory.fromJson(entry.json); + if (apptentiveMessage == null) { + ApptentiveLog.e("Error parsing Record json from database: %s", entry.json); + continue; + } + apptentiveMessage.setState(ApptentiveMessage.State.parse(entry.state)); + apptentiveMessage.setRead(entry.isRead); + apptentiveMessages.add(apptentiveMessage); + } + return apptentiveMessages; } @Override - public String getLastReceivedMessageId() throws Exception { + public synchronized String getLastReceivedMessageId() throws Exception { + fetchEntries(); + + final String savedState = ApptentiveMessage.State.saved.name(); + for (int i = messageEntries.size() - 1; i >= 0; --i) { + final MessageEntry entry = messageEntries.get(i); + if (StringUtils.equal(entry.state, savedState) && entry.id != null) { + return entry.id; + } + } return null; } @Override - public int getUnreadMessageCount() throws Exception { - return 0; + public synchronized int getUnreadMessageCount() throws Exception { + fetchEntries(); + + int count = 0; + for (MessageEntry entry : messageEntries) { + if (!entry.isRead && entry.id != null) { + ++count; + } + } + return count; } @Override - public void deleteAllMessages() { - + public synchronized void deleteAllMessages() { + messageEntries.clear(); + writeToFile(); } @Override - public void deleteMessage(String nonce) { + public synchronized void deleteMessage(String nonce) { + fetchEntries(); + + for (int i = 0; i < messageEntries.size(); ++i) { + if (StringUtils.equal(nonce, messageEntries.get(i).nonce)) { + messageEntries.remove(i); + writeToFile(); + break; + } + } + } + + //endregion + + //region File save/load + + private synchronized void fetchEntries() { + if (shouldFetchFromFile) { + readFromFile(); + shouldFetchFromFile = false; + } + } + + private synchronized void readFromFile() { + messageEntries.clear(); + try { + List entries = readFromFileGuarded(); + messageEntries.addAll(entries); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while reading entries"); + } + } + + private List readFromFileGuarded() throws IOException { + DataInputStream dis = null; + try { + dis = new DataInputStream(new FileInputStream(file)); + byte version = dis.readByte(); + if (version != VERSION) { + throw new IOException("Unsupported binary version: " + version); + } + int entryCount = dis.readInt(); + List entries = new ArrayList<>(); + for (int i = 0; i < entryCount; ++i) { + entries.add(new MessageEntry(dis)); + } + return entries; + } finally { + Util.ensureClosed(dis); + } + } + + private synchronized void writeToFile() { + try { + writeToFileGuarded(); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while saving messages"); + } + shouldFetchFromFile = false; // mark it as not shouldFetchFromFile to keep a memory version + } + + private void writeToFileGuarded() throws IOException { + DataOutputStream dos = null; + try { + dos = new DataOutputStream(new FileOutputStream(file)); + dos.writeByte(VERSION); + dos.writeInt(messageEntries.size()); + for (MessageEntry entry : messageEntries) { + entry.writeExternal(dos); + } + } finally { + Util.ensureClosed(dos); + } + } + + //endregion + + //region Filtering + + private MessageEntry findMessageEntry(ApptentiveMessage message) { + return findMessageEntry(message.getNonce()); + } + + private MessageEntry findMessageEntry(String nonce) { + for (MessageEntry entry : messageEntries) { + if (StringUtils.equal(entry.nonce, nonce)) { + return entry; + } + } + return null; + } + + //endregion + + //region Message Entry + + private static class MessageEntry implements SerializableObject { + String id; + double clientCreatedAt; + String nonce; + String state; + boolean isRead; + String json; + + MessageEntry() { + } + + MessageEntry(DataInput in) throws IOException { + id = readNullableUTF(in); + clientCreatedAt = in.readDouble(); + nonce = readNullableUTF(in); + state = readNullableUTF(in); + isRead = in.readBoolean(); + json = readNullableUTF(in); + } + + @Override + public void writeExternal(DataOutput out) throws IOException { + writeNullableUTF(out, id); + out.writeDouble(clientCreatedAt); + writeNullableUTF(out, nonce); + writeNullableUTF(out, state); + out.writeBoolean(isRead); + writeNullableUTF(out, json); + } + + private static void writeNullableUTF(DataOutput out, String value) throws IOException { + out.writeBoolean(value != null); + if (value != null) { + out.writeUTF(value); + } + } + private static String readNullableUTF(DataInput in) throws IOException { + boolean notNull = in.readBoolean(); + return notNull ? in.readUTF() : null; + } } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java index 6f82cc376..4c8037694 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java @@ -299,7 +299,7 @@ public static Type parse(String rawType) { } } - public static enum State { + public enum State { sending, // The item is either being sent, or is queued for sending. sent, // The item has been posted to the server successfully. saved, // The item has been returned from the server during a fetch. diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 35c08a82e..e5ceedc3c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -269,123 +269,6 @@ public Payload getOldestUnsentPayload() { // MessageStore - /** - * Saves the message into the message table, and also into the payload table so it can be sent to the server. - */ - public void addOrUpdateMessages(ApptentiveMessage... apptentiveMessages) { - SQLiteDatabase db = null; - try { - db = getWritableDatabase(); - for (ApptentiveMessage apptentiveMessage : apptentiveMessages) { - Cursor cursor = null; - try { - cursor = db.rawQuery(QUERY_MESSAGE_GET_BY_NONCE, new String[]{apptentiveMessage.getNonce()}); - if (cursor.moveToFirst()) { - // Update - String databaseId = cursor.getString(0); - ContentValues messageValues = new ContentValues(); - messageValues.put(MESSAGE_KEY_ID, apptentiveMessage.getId()); - messageValues.put(MESSAGE_KEY_STATE, apptentiveMessage.getState().name()); - if (apptentiveMessage.isRead()) { // A apptentiveMessage can't be unread after being read. - messageValues.put(MESSAGE_KEY_READ, TRUE); - } - messageValues.put(MESSAGE_KEY_JSON, apptentiveMessage.toString()); - db.update(TABLE_MESSAGE, messageValues, MESSAGE_KEY_DB_ID + " = ?", new String[]{databaseId}); - } else { - // Insert - db.beginTransaction(); - ContentValues messageValues = new ContentValues(); - messageValues.put(MESSAGE_KEY_ID, apptentiveMessage.getId()); - messageValues.put(MESSAGE_KEY_CLIENT_CREATED_AT, apptentiveMessage.getClientCreatedAt()); - messageValues.put(MESSAGE_KEY_NONCE, apptentiveMessage.getNonce()); - messageValues.put(MESSAGE_KEY_STATE, apptentiveMessage.getState().name()); - messageValues.put(MESSAGE_KEY_READ, apptentiveMessage.isRead() ? TRUE : FALSE); - messageValues.put(MESSAGE_KEY_JSON, apptentiveMessage.toString()); - db.insert(TABLE_MESSAGE, null, messageValues); - db.setTransactionSuccessful(); - db.endTransaction(); - } - } finally { - ensureClosed(cursor); - } - } - } catch (SQLException sqe) { - ApptentiveLog.e("addOrUpdateMessages EXCEPTION: " + sqe.getMessage()); - } - } - - public void updateMessage(ApptentiveMessage apptentiveMessage) { - SQLiteDatabase db = null; - try { - db = getWritableDatabase(); - db.beginTransaction(); - ContentValues values = new ContentValues(); - values.put(MESSAGE_KEY_ID, apptentiveMessage.getId()); - values.put(MESSAGE_KEY_CLIENT_CREATED_AT, apptentiveMessage.getClientCreatedAt()); - values.put(MESSAGE_KEY_NONCE, apptentiveMessage.getNonce()); - values.put(MESSAGE_KEY_STATE, apptentiveMessage.getState().name()); - if (apptentiveMessage.isRead()) { // A apptentiveMessage can't be unread after being read. - values.put(MESSAGE_KEY_READ, TRUE); - } - values.put(MESSAGE_KEY_JSON, apptentiveMessage.toString()); - db.update(TABLE_MESSAGE, values, MESSAGE_KEY_NONCE + " = ?", new String[]{apptentiveMessage.getNonce()}); - db.setTransactionSuccessful(); - } catch (SQLException sqe) { - ApptentiveLog.e("updateMessage EXCEPTION: " + sqe.getMessage()); - } finally { - if (db != null) { - db.endTransaction(); - } - } - } - - public List getAllMessages() { - List apptentiveMessages = new ArrayList(); - SQLiteDatabase db = null; - Cursor cursor = null; - try { - db = getReadableDatabase(); - cursor = db.rawQuery(QUERY_MESSAGE_GET_ALL_IN_ORDER, null); - if (cursor.moveToFirst()) { - do { - String json = cursor.getString(6); - ApptentiveMessage apptentiveMessage = MessageFactory.fromJson(json); - if (apptentiveMessage == null) { - ApptentiveLog.e("Error parsing Record json from database: %s", json); - continue; - } - apptentiveMessage.setDatabaseId(cursor.getLong(0)); - apptentiveMessage.setState(ApptentiveMessage.State.parse(cursor.getString(4))); - apptentiveMessage.setRead(cursor.getInt(5) == TRUE); - apptentiveMessages.add(apptentiveMessage); - } while (cursor.moveToNext()); - } - } catch (SQLException sqe) { - ApptentiveLog.e("getAllMessages EXCEPTION: " + sqe.getMessage()); - } finally { - ensureClosed(cursor); - } - return apptentiveMessages; - } - - public synchronized String getLastReceivedMessageId() { - SQLiteDatabase db = null; - Cursor cursor = null; - String ret = null; - try { - db = getReadableDatabase(); - cursor = db.rawQuery(QUERY_MESSAGE_GET_LAST_ID, null); - if (cursor.moveToFirst()) { - ret = cursor.getString(0); - } - } catch (SQLException sqe) { - ApptentiveLog.e("getLastReceivedMessageId EXCEPTION: " + sqe.getMessage()); - } finally { - ensureClosed(cursor); - } - return ret; - } - public synchronized int getUnreadMessageCount() { SQLiteDatabase db = null; Cursor cursor = null; From 4ca95f4757d3628d6d1421c4794b0a2a5eb61376 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 16:29:14 -0700 Subject: [PATCH 157/465] Added a temporary solution for initializing file message store --- .../com/apptentive/android/sdk/conversation/Conversation.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 7c322ee74..e9832f010 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -32,7 +32,6 @@ import com.apptentive.android.sdk.util.RuntimeUtils; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; -import com.apptentive.android.sdk.util.threading.DispatchQueueType; import com.apptentive.android.sdk.util.threading.DispatchTask; import org.json.JSONException; @@ -92,7 +91,7 @@ protected void execute() { public Conversation() { data = new ConversationData(); - messageManager = new MessageManager(new FileMessageStore()); + messageManager = new MessageManager(new FileMessageStore(new File(""))); // FIXME: figure out a filename } //region Interactions From d859a55d647e9ad2b49123395032e85d32f07393 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 21 Mar 2017 16:47:44 -0700 Subject: [PATCH 158/465] File message store testing progress --- .../conversation/FileMessageStoreTest.java | 84 +++++++++++++++++++ .../sdk/conversation/FileMessageStore.java | 4 +- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java new file mode 100644 index 000000000..3142c21e2 --- /dev/null +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.conversation; + +import com.apptentive.android.sdk.TestCaseBase; +import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; +import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; +import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; + +import org.json.JSONException; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.*; + +public class FileMessageStoreTest extends TestCaseBase { + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Before + public void setUp() { + super.setUp(); + } + + @After + public void tearDown() { + super.tearDown(); + } + + @Test + public void addOrUpdateMessages() throws Exception { + File file = getTempFile(); + FileMessageStore store = new FileMessageStore(file); + store.addOrUpdateMessages(createMessage()); + } + + @Test + public void updateMessage() throws Exception { + + } + + @Test + public void getAllMessages() throws Exception { + + } + + @Test + public void getLastReceivedMessageId() throws Exception { + + } + + @Test + public void getUnreadMessageCount() throws Exception { + + } + + @Test + public void deleteAllMessages() throws Exception { + + } + + @Test + public void deleteMessage() throws Exception { + + } + + private ApptentiveMessage createMessage() throws JSONException { + return new CompoundMessage("{}", true); + } + + private File getTempFile() throws IOException { + return tempFolder.newFile("data.bin"); + } +} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index 048de2ae6..7ac0bbdb5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -7,6 +7,7 @@ package com.apptentive.android.sdk.conversation; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; import com.apptentive.android.sdk.serialization.SerializableObject; @@ -222,7 +223,8 @@ private void writeToFileGuarded() throws IOException { //region Filtering private MessageEntry findMessageEntry(ApptentiveMessage message) { - return findMessageEntry(message.getNonce()); + Assert.assertNotNull(message); + return message != null ? findMessageEntry(message.getNonce()) : null; } private MessageEntry findMessageEntry(String nonce) { From 47f4878503f21dc6b563046587744f2db26ce612 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 22 Mar 2017 11:16:29 -0700 Subject: [PATCH 159/465] Apptentive file message store tests --- .../conversation/FileMessageStoreTest.java | 33 +++++++++++++++---- .../android/sdk/ApptentiveInternal.java | 17 ++++++++-- .../sdk/conversation/FileMessageStore.java | 7 ++-- .../android/sdk/ApptentiveInternalMock.java | 18 ++++++++++ 4 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 apptentive/src/testCommon/java/com/apptentive/android/sdk/ApptentiveInternalMock.java diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java index 3142c21e2..10bee00e5 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java @@ -6,12 +6,14 @@ package com.apptentive.android.sdk.conversation; +import com.apptentive.android.sdk.ApptentiveInternal; +import com.apptentive.android.sdk.ApptentiveInternalMock; import com.apptentive.android.sdk.TestCaseBase; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; -import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; import org.json.JSONException; +import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -20,8 +22,9 @@ import java.io.File; import java.io.IOException; +import java.util.List; -import static org.junit.Assert.*; +import static junit.framework.Assert.assertEquals; public class FileMessageStoreTest extends TestCaseBase { @Rule @@ -30,6 +33,7 @@ public class FileMessageStoreTest extends TestCaseBase { @Before public void setUp() { super.setUp(); + ApptentiveInternal.setInstance(new ApptentiveInternalMock(), true); } @After @@ -38,10 +42,15 @@ public void tearDown() { } @Test - public void addOrUpdateMessages() throws Exception { + public void testAddingAndLoadingMessages() throws Exception { File file = getTempFile(); FileMessageStore store = new FileMessageStore(file); - store.addOrUpdateMessages(createMessage()); + store.addOrUpdateMessages(createMessage("1")); + store.addOrUpdateMessages(createMessage("2")); + store.addOrUpdateMessages(createMessage("3")); + + store = new FileMessageStore(file); + assertEquals("1,2,3", toString(store.getAllMessages())); } @Test @@ -74,11 +83,23 @@ public void deleteMessage() throws Exception { } - private ApptentiveMessage createMessage() throws JSONException { - return new CompoundMessage("{}", true); + private ApptentiveMessage createMessage(String nonce) throws JSONException { + JSONObject object = new JSONObject(); + object.put("nonce", nonce); + object.put("client_created_at", 0.0); + return new CompoundMessage(object.toString(), true); } private File getTempFile() throws IOException { return tempFolder.newFile("data.bin"); } + + private String toString(List messages) { + String result = ""; + for (ApptentiveMessage message : messages) { + if (result.length() > 0) result += ","; + result += message.getNonce(); + } + return result; + } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index fceec4e5d..668323b61 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -140,6 +140,19 @@ public static PushAction parse(String name) { @SuppressLint("StaticFieldLeak") private static volatile ApptentiveInternal sApptentiveInternal; + // for unit testing + protected ApptentiveInternal() { + payloadWorker = null; + taskManager = null; + globalSharedPrefs = null; + apiKey = null; + apptentiveHttpClient = null; + conversationManager = null; + appContext = null; + appRelease = null; + cachedExecutor = null; + } + private ApptentiveInternal(Context context, String apiKey, String serverUrl) { this.apiKey = apiKey; this.serverUrl = serverUrl; @@ -256,9 +269,9 @@ public static ApptentiveInternal getInstance() { * * @param instance the internal instance to be set to */ - public static void setInstance(ApptentiveInternal instance) { + public static void setInstance(ApptentiveInternal instance, boolean initialized) { sApptentiveInternal = instance; - isApptentiveInitialized.set(false); + isApptentiveInitialized.set(initialized); } /* Called by {@link #Apptentive.register()} to register global lifecycle diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index 7ac0bbdb5..8cfac58ad 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -67,6 +67,7 @@ public synchronized void addOrUpdateMessages(ApptentiveMessage... apptentiveMess entry.state = apptentiveMessage.getState().name(); entry.isRead = apptentiveMessage.isRead(); entry.json = apptentiveMessage.toString(); + messageEntries.add(entry); } } @@ -169,8 +170,10 @@ private synchronized void fetchEntries() { private synchronized void readFromFile() { messageEntries.clear(); try { - List entries = readFromFileGuarded(); - messageEntries.addAll(entries); + if (file.exists()) { + List entries = readFromFileGuarded(); + messageEntries.addAll(entries); + } } catch (Exception e) { ApptentiveLog.e(e, "Exception while reading entries"); } diff --git a/apptentive/src/testCommon/java/com/apptentive/android/sdk/ApptentiveInternalMock.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/ApptentiveInternalMock.java new file mode 100644 index 000000000..2a616bc0d --- /dev/null +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/ApptentiveInternalMock.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk; + +import android.content.Context; + +/** + * Created by alementuev on 3/22/17. + */ + +public class ApptentiveInternalMock extends ApptentiveInternal { + public ApptentiveInternalMock() { + } +} From 89d64373c3ccaff610977d3bc40cd2816ee594a5 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 22 Mar 2017 11:37:17 -0700 Subject: [PATCH 160/465] Apptentive file message store tests --- .../conversation/FileMessageStoreTest.java | 48 ++++++++++++++----- .../android/sdk/ApptentiveInternalMock.java | 6 --- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java index 10bee00e5..5ea2161ae 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java @@ -11,6 +11,7 @@ import com.apptentive.android.sdk.TestCaseBase; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; +import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; import org.json.JSONObject; @@ -22,11 +23,16 @@ import java.io.File; import java.io.IOException; +import java.util.Iterator; import java.util.List; +import static com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage.State; import static junit.framework.Assert.assertEquals; public class FileMessageStoreTest extends TestCaseBase { + private static final boolean READ = true; + private static final boolean UNREAD = false; + @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); @@ -45,12 +51,17 @@ public void tearDown() { public void testAddingAndLoadingMessages() throws Exception { File file = getTempFile(); FileMessageStore store = new FileMessageStore(file); - store.addOrUpdateMessages(createMessage("1")); - store.addOrUpdateMessages(createMessage("2")); - store.addOrUpdateMessages(createMessage("3")); + store.addOrUpdateMessages(createMessage("1", State.sending, READ, 10.0)); + store.addOrUpdateMessages(createMessage("2", State.sent, UNREAD, 20.0)); + store.addOrUpdateMessages(createMessage("3", State.saved, READ, 30.0)); store = new FileMessageStore(file); - assertEquals("1,2,3", toString(store.getAllMessages())); + addResult(store.getAllMessages()); + + assertResult( + "{'nonce':'1','client_created_at':'10','state':'sending','read':'true'}", + "{'nonce':'2','client_created_at':'20','state':'sent','read':'false'}", + "{'nonce':'3','client_created_at':'30','state':'saved','read':'true'}"); } @Test @@ -83,23 +94,38 @@ public void deleteMessage() throws Exception { } - private ApptentiveMessage createMessage(String nonce) throws JSONException { + private ApptentiveMessage createMessage(String nonce, State state, boolean read, double clientCreatedAt) throws JSONException { JSONObject object = new JSONObject(); object.put("nonce", nonce); - object.put("client_created_at", 0.0); - return new CompoundMessage(object.toString(), true); + object.put("client_created_at", clientCreatedAt); + CompoundMessage message = new CompoundMessage(object.toString(), true); + message.setState(state); + message.setRead(read); + return message; } private File getTempFile() throws IOException { return tempFolder.newFile("data.bin"); } - private String toString(List messages) { - String result = ""; + private void addResult(List messages) throws JSONException { for (ApptentiveMessage message : messages) { - if (result.length() > 0) result += ","; - result += message.getNonce(); + addResult(toString(message)); } + } + + private String toString(ApptentiveMessage message) throws JSONException { + String result = "{"; + final Iterator keys = message.keys(); + while (keys.hasNext()) { + String key = keys.next(); + result += StringUtils.format("'%s':'%s',", key, message.get(key)); + } + + result += StringUtils.format("'state':'%s',", message.getState().name()); + result += StringUtils.format("'read':'%s'", message.isRead()); + result += "}"; + return result; } } \ No newline at end of file diff --git a/apptentive/src/testCommon/java/com/apptentive/android/sdk/ApptentiveInternalMock.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/ApptentiveInternalMock.java index 2a616bc0d..b1a1adaf0 100644 --- a/apptentive/src/testCommon/java/com/apptentive/android/sdk/ApptentiveInternalMock.java +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/ApptentiveInternalMock.java @@ -6,12 +6,6 @@ package com.apptentive.android.sdk; -import android.content.Context; - -/** - * Created by alementuev on 3/22/17. - */ - public class ApptentiveInternalMock extends ApptentiveInternal { public ApptentiveInternalMock() { } From 8cc33a06d949f5d07f88473a56abf3318b50877e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 22 Mar 2017 11:50:47 -0700 Subject: [PATCH 161/465] Apptentive file message store tests --- .../conversation/FileMessageStoreTest.java | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java index 5ea2161ae..10700cea7 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java @@ -27,7 +27,6 @@ import java.util.List; import static com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage.State; -import static junit.framework.Assert.assertEquals; public class FileMessageStoreTest extends TestCaseBase { private static final boolean READ = true; @@ -50,11 +49,14 @@ public void tearDown() { @Test public void testAddingAndLoadingMessages() throws Exception { File file = getTempFile(); + + // create a few messages and add them to the store FileMessageStore store = new FileMessageStore(file); store.addOrUpdateMessages(createMessage("1", State.sending, READ, 10.0)); store.addOrUpdateMessages(createMessage("2", State.sent, UNREAD, 20.0)); store.addOrUpdateMessages(createMessage("3", State.saved, READ, 30.0)); + // reload store and check saved messages store = new FileMessageStore(file); addResult(store.getAllMessages()); @@ -62,11 +64,49 @@ public void testAddingAndLoadingMessages() throws Exception { "{'nonce':'1','client_created_at':'10','state':'sending','read':'true'}", "{'nonce':'2','client_created_at':'20','state':'sent','read':'false'}", "{'nonce':'3','client_created_at':'30','state':'saved','read':'true'}"); + + // reload the store again and add another message + store = new FileMessageStore(file); + store.addOrUpdateMessages(createMessage("4", State.sent, UNREAD, 40.0)); + addResult(store.getAllMessages()); + + assertResult( + "{'nonce':'1','client_created_at':'10','state':'sending','read':'true'}", + "{'nonce':'2','client_created_at':'20','state':'sent','read':'false'}", + "{'nonce':'3','client_created_at':'30','state':'saved','read':'true'}", + "{'nonce':'4','client_created_at':'40','state':'sent','read':'false'}"); } @Test public void updateMessage() throws Exception { + File file = getTempFile(); + + // create a few messages and add them to the store + FileMessageStore store = new FileMessageStore(file); + store.addOrUpdateMessages(createMessage("1", State.sending, READ, 10.0)); + store.addOrUpdateMessages(createMessage("2", State.sent, UNREAD, 20.0)); + store.addOrUpdateMessages(createMessage("3", State.saved, READ, 30.0)); + + // reload the store and change a single message + store = new FileMessageStore(file); + store.updateMessage(createMessage("2", State.saved, READ, 40.0)); + addResult(store.getAllMessages()); + + assertResult( + "{'nonce':'1','client_created_at':'10','state':'sending','read':'true'}", + "{'nonce':'2','client_created_at':'40','state':'saved','read':'true'}", + "{'nonce':'3','client_created_at':'30','state':'saved','read':'true'}"); + + + // reload the store and check the stored messages + store = new FileMessageStore(file); + addResult(store.getAllMessages()); + + assertResult( + "{'nonce':'1','client_created_at':'10','state':'sending','read':'true'}", + "{'nonce':'2','client_created_at':'40','state':'saved','read':'true'}", + "{'nonce':'3','client_created_at':'30','state':'saved','read':'true'}"); } @Test @@ -105,7 +145,7 @@ private ApptentiveMessage createMessage(String nonce, State state, boolean read, } private File getTempFile() throws IOException { - return tempFolder.newFile("data.bin"); + return tempFolder.newFile(); } private void addResult(List messages) throws JSONException { From 05999e1d91476c828d59341b45f85b81dc38ea1e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 22 Mar 2017 12:00:07 -0700 Subject: [PATCH 162/465] Apptentive file message store tests --- .../conversation/FileMessageStoreTest.java | 62 +++++++++++++++++-- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java index 10700cea7..29686cffe 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java @@ -25,8 +25,10 @@ import java.io.IOException; import java.util.Iterator; import java.util.List; +import java.util.UUID; import static com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage.State; +import static junit.framework.Assert.assertEquals; public class FileMessageStoreTest extends TestCaseBase { private static final boolean READ = true; @@ -110,23 +112,69 @@ public void updateMessage() throws Exception { } @Test - public void getAllMessages() throws Exception { - + public void getLastReceivedMessageId() throws Exception { } @Test - public void getLastReceivedMessageId() throws Exception { + public void getUnreadMessageCount() throws Exception { + File file = getTempFile(); + + // create a few messages and add them to the store + FileMessageStore store = new FileMessageStore(file); + store.addOrUpdateMessages(createMessage("1", State.sending, READ, 10.0)); + store.addOrUpdateMessages(createMessage("2", State.sent, UNREAD, 20.0)); + store.addOrUpdateMessages(createMessage("3", State.saved, READ, 30.0)); + store.addOrUpdateMessages(createMessage("4", State.sending, UNREAD, 40.0)); + + assertEquals(2, store.getUnreadMessageCount()); + // reload store and check saved messages + store = new FileMessageStore(file); + assertEquals(2, store.getUnreadMessageCount()); } @Test - public void getUnreadMessageCount() throws Exception { + public void deleteAllMessages() throws Exception { + File file = getTempFile(); + // create a few messages and add them to the store + FileMessageStore store = new FileMessageStore(file); + store.addOrUpdateMessages(createMessage("1", State.sending, READ, 10.0)); + store.addOrUpdateMessages(createMessage("2", State.sent, UNREAD, 20.0)); + store.addOrUpdateMessages(createMessage("3", State.saved, READ, 30.0)); + store.addOrUpdateMessages(createMessage("4", State.sending, UNREAD, 40.0)); + + // delete all messages + store.deleteAllMessages(); + + // check stored messages + addResult(store.getAllMessages()); + assertResult(); + + // reload the store and check for messages + store = new FileMessageStore(file); + addResult(store.getAllMessages()); + assertResult(); } @Test - public void deleteAllMessages() throws Exception { + public void deleteAllMessagesAfterReload() throws Exception { + File file = getTempFile(); + // create a few messages and add them to the store + FileMessageStore store = new FileMessageStore(file); + store.addOrUpdateMessages(createMessage("1", State.sending, READ, 10.0)); + store.addOrUpdateMessages(createMessage("2", State.sent, UNREAD, 20.0)); + store.addOrUpdateMessages(createMessage("3", State.saved, READ, 30.0)); + store.addOrUpdateMessages(createMessage("4", State.sending, UNREAD, 40.0)); + + // delete all messages + store.deleteAllMessages(); + + // reload the store and check for messages + store = new FileMessageStore(file); + addResult(store.getAllMessages()); + assertResult(); } @Test @@ -139,6 +187,7 @@ private ApptentiveMessage createMessage(String nonce, State state, boolean read, object.put("nonce", nonce); object.put("client_created_at", clientCreatedAt); CompoundMessage message = new CompoundMessage(object.toString(), true); + message.setId(UUID.randomUUID().toString()); message.setState(state); message.setRead(read); return message; @@ -159,6 +208,9 @@ private String toString(ApptentiveMessage message) throws JSONException { final Iterator keys = message.keys(); while (keys.hasNext()) { String key = keys.next(); + if (key.equals("id")) { // 'id' is randomly generated each time (so don't test it) + continue; + } result += StringUtils.format("'%s':'%s',", key, message.get(key)); } From fcf4cf7e9fe9d4ce1bc10bac021952632584c154 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 22 Mar 2017 12:49:00 -0700 Subject: [PATCH 163/465] Apptentive file message store tests --- .../conversation/FileMessageStoreTest.java | 72 ++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java index 29686cffe..2a3de95d9 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java @@ -46,6 +46,7 @@ public void setUp() { @After public void tearDown() { super.tearDown(); + ApptentiveInternal.setInstance(null, false); } @Test @@ -113,6 +114,20 @@ public void updateMessage() throws Exception { @Test public void getLastReceivedMessageId() throws Exception { + File file = getTempFile(); + + // create a few messages and add them to the store + FileMessageStore store = new FileMessageStore(file); + store.addOrUpdateMessages(createMessage("1", State.saved, READ, 10.0, "111")); + store.addOrUpdateMessages(createMessage("2", State.saved, UNREAD, 20.0, "222")); + store.addOrUpdateMessages(createMessage("3", State.sending, READ, 30.0, "333")); + store.addOrUpdateMessages(createMessage("4", State.sent, UNREAD, 40.0, "444")); + + assertEquals("222", store.getLastReceivedMessageId()); + + // reload the store and check again + store = new FileMessageStore(file); + assertEquals("222", store.getLastReceivedMessageId()); } @Test @@ -179,15 +194,70 @@ public void deleteAllMessagesAfterReload() throws Exception { @Test public void deleteMessage() throws Exception { + File file = getTempFile(); + // create a few messages and add them to the store + FileMessageStore store = new FileMessageStore(file); + store.addOrUpdateMessages(createMessage("1", State.sending, READ, 10.0)); + store.addOrUpdateMessages(createMessage("2", State.sent, UNREAD, 20.0)); + store.addOrUpdateMessages(createMessage("3", State.saved, READ, 30.0)); + store.addOrUpdateMessages(createMessage("4", State.sending, UNREAD, 40.0)); + + store.deleteMessage("2"); + store.deleteMessage("4"); + + addResult(store.getAllMessages()); + + assertResult( + "{'nonce':'1','client_created_at':'10','state':'sending','read':'true'}", + "{'nonce':'3','client_created_at':'30','state':'saved','read':'true'}"); + } + + @Test + public void deleteMessageAndReload() throws Exception { + File file = getTempFile(); + + // create a few messages and add them to the store + FileMessageStore store = new FileMessageStore(file); + store.addOrUpdateMessages(createMessage("1", State.sending, READ, 10.0)); + store.addOrUpdateMessages(createMessage("2", State.sent, UNREAD, 20.0)); + store.addOrUpdateMessages(createMessage("3", State.saved, READ, 30.0)); + store.addOrUpdateMessages(createMessage("4", State.sending, UNREAD, 40.0)); + + store.deleteMessage("2"); + store.deleteMessage("4"); + + // reload store + store = new FileMessageStore(file); + addResult(store.getAllMessages()); + + assertResult( + "{'nonce':'1','client_created_at':'10','state':'sending','read':'true'}", + "{'nonce':'3','client_created_at':'30','state':'saved','read':'true'}"); + + // delete more + store.deleteMessage("1"); + addResult(store.getAllMessages()); + + assertResult("{'nonce':'3','client_created_at':'30','state':'saved','read':'true'}"); + + // reload store + store = new FileMessageStore(file); + addResult(store.getAllMessages()); + + assertResult("{'nonce':'3','client_created_at':'30','state':'saved','read':'true'}"); } private ApptentiveMessage createMessage(String nonce, State state, boolean read, double clientCreatedAt) throws JSONException { + return createMessage(nonce, state, read, clientCreatedAt, UUID.randomUUID().toString()); + } + + private ApptentiveMessage createMessage(String nonce, State state, boolean read, double clientCreatedAt, String id) throws JSONException { JSONObject object = new JSONObject(); object.put("nonce", nonce); object.put("client_created_at", clientCreatedAt); CompoundMessage message = new CompoundMessage(object.toString(), true); - message.setId(UUID.randomUUID().toString()); + message.setId(id); message.setState(state); message.setRead(read); return message; From 8704d4b5d790fb04dce82e1025a35f5e90db44e5 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 22 Mar 2017 13:52:39 -0700 Subject: [PATCH 164/465] Fixed message store initialization --- .../sdk/conversation/Conversation.java | 50 ++++++++++++++----- .../sdk/conversation/ConversationManager.java | 18 ++++--- .../ConversationMetadataItem.java | 26 +++++++--- .../sdk/conversation/FileMessageStore.java | 27 +++++++++- .../module/messagecenter/MessageManager.java | 1 - 5 files changed, 94 insertions(+), 28 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index e9832f010..083b9f658 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -51,9 +51,14 @@ public class Conversation implements DataChangedListener, Destroyable { private ConversationData data; /** - * File which represents this conversation on the disk + * File which represents serialized conversation data on the disk */ - private File file; + private File dataFile; + + /** + * File which represents serialized messages data on the disk + */ + private File messagesFile; // TODO: remove this class private InteractionManager interactionManager; @@ -61,6 +66,7 @@ public class Conversation implements DataChangedListener, Destroyable { private ConversationState state = ConversationState.UNDEFINED; private final MessageManager messageManager; + private final FileMessageStore messageStore; // we keep references to the tasks in order to dispatch them only once private final DispatchTask fetchInteractionsTask = new DispatchTask() { @@ -91,7 +97,8 @@ protected void execute() { public Conversation() { data = new ConversationData(); - messageManager = new MessageManager(new FileMessageStore(new File(""))); // FIXME: figure out a filename + messageStore = new FileMessageStore(); // create a store in a "detached" state (we can't save anything until dest file is specified) + messageManager = new MessageManager(messageStore); // it's important to initialize message manager in a constructor since other SDK parts depend on it via Apptentive singleton } //region Interactions @@ -189,7 +196,7 @@ else if (!response.isSuccessful()) { * if succeed. */ synchronized boolean save() { - if (file == null) { + if (dataFile == null) { ApptentiveLog.e(CONVERSATION, "Unable to save conversation: destination file not specified"); return false; } @@ -198,7 +205,7 @@ synchronized boolean save() { ApptentiveLog.v(CONVERSATION, "EventData: %s", getEventData().toString()); // TODO: remove try { - FileSerializer serializer = new FileSerializer(file); + FileSerializer serializer = new FileSerializer(dataFile); serializer.serialize(this); return true; } catch (Exception e) { @@ -213,7 +220,7 @@ synchronized boolean save() { @Override public void onDataChanged() { - if (hasFile()) { + if (hasDataFile()) { boolean scheduled = DispatchQueue.backgroundQueue().dispatchAsyncOnce(saveConversationTask, 100L); if (scheduled) { ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); @@ -453,16 +460,35 @@ public void setInteractionManager(InteractionManager interactionManager) { this.interactionManager = interactionManager; } - synchronized boolean hasFile() { - return file != null; + private synchronized boolean hasDataFile() { + return dataFile != null; } - synchronized File getFile() { - return file; + synchronized File getDataFile() { + return dataFile; + } + + synchronized void setDataFile(File dataFile) { + if (dataFile == null) { + throw new IllegalArgumentException("Data file is null"); + } + this.dataFile = dataFile; } - synchronized void setFile(File file) { - this.file = file; + synchronized boolean hasMessagesFile() { + return messagesFile != null; + } + + synchronized File getMessagesFile() { + return messagesFile; + } + + synchronized void setMessagesFile(File messagesFile) { + if (messagesFile == null) { + throw new IllegalArgumentException("Messages file is null"); + } + this.messagesFile = messagesFile; + messageStore.setFile(messagesFile); // now we can save/load messages } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 069e3c9a9..157f92932 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -169,10 +169,11 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali private Conversation loadConversation(ConversationMetadataItem item) throws SerializerException { // TODO: use same serialization logic across the project - FileSerializer serializer = new FileSerializer(item.file); + FileSerializer serializer = new FileSerializer(item.dataFile); final Conversation conversation = (Conversation) serializer.deserialize(); conversation.setState(item.getState()); // set the state same as the item's state - conversation.setFile(item.file); + conversation.setDataFile(item.dataFile); // at this point we can save conversation data to the disk + conversation.setMessagesFile(item.messagesFile); // at this point we can save messages to the disk return conversation; } @@ -248,7 +249,8 @@ public void onFinish(HttpJsonRequest request) { String personId = root.getString("person_id"); ApptentiveLog.d(CONVERSATION, "PersonId: " + personId); conversation.setPersonId(personId); - conversation.setFile(getConversationFile(conversation)); + conversation.setDataFile(getConversationDataFile(conversation)); // at this point we can save conversation data to the disk + conversation.setMessagesFile(getConversationMessagesFile(conversation)); // at this point we can save messages to the disk // write conversation to the disk (sync operation) conversation.save(); @@ -275,8 +277,12 @@ public void onFail(HttpJsonRequest request, String reason) { }); } - private File getConversationFile(Conversation conversation) { - return new File(storageDir, conversation.getConversationId() + ".bin"); + private File getConversationDataFile(Conversation conversation) { + return new File(storageDir, conversation.getConversationId() + "-conversation.bin"); + } + + private File getConversationMessagesFile(Conversation conversation) { + return new File(storageDir, conversation.getConversationId() + "-messages.bin"); } //endregion @@ -340,7 +346,7 @@ private void updateMetadataItems(Conversation conversation) { // update the state of the corresponding item ConversationMetadataItem item = conversationMetadata.findItem(conversation); if (item == null) { - item = new ConversationMetadataItem(conversation.getConversationId(), conversation.getFile()); + item = new ConversationMetadataItem(conversation.getConversationId(), conversation.getDataFile(), conversation.getMessagesFile()); conversationMetadata.addItem(item); } item.state = conversation.getState(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index c19acdbf2..3c59c9a0e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -26,32 +26,44 @@ public class ConversationMetadataItem implements SerializableObject { /** * Storage filename for conversation serialized data */ - final File file; + final File dataFile; - public ConversationMetadataItem(String conversationId, File file) + /** + * Storage filename for conversation serialized messages + */ + final File messagesFile; + + public ConversationMetadataItem(String conversationId, File dataFile, File messagesFile) { if (StringUtils.isNullOrEmpty(conversationId)) { throw new IllegalArgumentException("Conversation id is null or empty"); } - if (file == null) { - throw new IllegalArgumentException("File is null"); + if (dataFile == null) { + throw new IllegalArgumentException("Data file is null"); + } + + if (messagesFile == null) { + throw new IllegalArgumentException("Messages file is null"); } this.conversationId = conversationId; - this.file = file; + this.dataFile = dataFile; + this.messagesFile = messagesFile; } public ConversationMetadataItem(DataInput in) throws IOException { conversationId = in.readUTF(); - file = new File(in.readUTF()); + dataFile = new File(in.readUTF()); + messagesFile = new File(in.readUTF()); state = ConversationState.valueOf(in.readByte()); } @Override public void writeExternal(DataOutput out) throws IOException { out.writeUTF(conversationId); - out.writeUTF(file.getAbsolutePath()); + out.writeUTF(dataFile.getAbsolutePath()); + out.writeUTF(messagesFile.getAbsolutePath()); out.writeByte(state.ordinal()); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index 8cfac58ad..6623abce2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -32,16 +32,20 @@ class FileMessageStore implements MessageStore { */ private static final byte VERSION = 1; - private final File file; + private File file; private final List messageEntries; private boolean shouldFetchFromFile; FileMessageStore(File file) { + this(); this.file = file; - this.messageEntries = new ArrayList<>(); // we need a random access this.shouldFetchFromFile = true; // we would lazily read it from a file later } + FileMessageStore() { + this.messageEntries = new ArrayList<>(); // we need a random access + } + //region MessageStore @Override @@ -199,6 +203,11 @@ private List readFromFileGuarded() throws IOException { } private synchronized void writeToFile() { + Assert.assertFalse(file != null, "File is not specified"); + if (file == null) { + return; + } + try { writeToFileGuarded(); } catch (Exception e) { @@ -239,6 +248,20 @@ private MessageEntry findMessageEntry(String nonce) { return null; } + //endregion + + //region Properties + + public synchronized void setFile(File file) { + if (file == null) { + throw new IllegalArgumentException("File is null"); + } + + this.file = file; + shouldFetchFromFile = true; + } + + //endregion //region Message Entry diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index c892aec38..e6ee64200 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -39,7 +39,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_RESUMED; From 89c7a08014e8cbb48266554889f3e12aebc5476c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 22 Mar 2017 14:12:16 -0700 Subject: [PATCH 165/465] Fixed saving/load --- .../android/sdk/conversation/Conversation.java | 13 ++++++++++--- .../sdk/conversation/ConversationManager.java | 9 ++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 083b9f658..dcc50602e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -26,6 +26,7 @@ import com.apptentive.android.sdk.storage.FileSerializer; import com.apptentive.android.sdk.storage.Person; import com.apptentive.android.sdk.storage.Sdk; +import com.apptentive.android.sdk.storage.SerializerException; import com.apptentive.android.sdk.storage.VersionHistory; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Destroyable; @@ -91,7 +92,7 @@ protected void execute() { private final DispatchTask saveConversationTask = new DispatchTask() { @Override protected void execute() { - save(); + saveData(); } }; @@ -195,7 +196,7 @@ else if (!response.isSuccessful()) { * Saves conversation data to the disk synchronously. Returns true * if succeed. */ - synchronized boolean save() { + synchronized boolean saveData() { if (dataFile == null) { ApptentiveLog.e(CONVERSATION, "Unable to save conversation: destination file not specified"); return false; @@ -206,7 +207,7 @@ synchronized boolean save() { try { FileSerializer serializer = new FileSerializer(dataFile); - serializer.serialize(this); + serializer.serialize(data); return true; } catch (Exception e) { ApptentiveLog.e(e, "Unable to save conversation"); @@ -214,6 +215,12 @@ synchronized boolean save() { } } + synchronized void loadData() throws SerializerException { + ApptentiveLog.d(CONVERSATION, "Loading conversation data"); + FileSerializer serializer = new FileSerializer(dataFile); + data = (ConversationData) serializer.deserialize(); + } + //endregion //region Listeners diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 157f92932..93d50fb85 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -17,7 +17,6 @@ import com.apptentive.android.sdk.storage.AppReleaseManager; import com.apptentive.android.sdk.storage.Device; import com.apptentive.android.sdk.storage.DeviceManager; -import com.apptentive.android.sdk.storage.FileSerializer; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.storage.SerializerException; @@ -169,11 +168,11 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali private Conversation loadConversation(ConversationMetadataItem item) throws SerializerException { // TODO: use same serialization logic across the project - FileSerializer serializer = new FileSerializer(item.dataFile); - final Conversation conversation = (Conversation) serializer.deserialize(); - conversation.setState(item.getState()); // set the state same as the item's state + final Conversation conversation = new Conversation(); conversation.setDataFile(item.dataFile); // at this point we can save conversation data to the disk + conversation.loadData(); conversation.setMessagesFile(item.messagesFile); // at this point we can save messages to the disk + conversation.setState(item.getState()); // set the state same as the item's state return conversation; } @@ -253,7 +252,7 @@ public void onFinish(HttpJsonRequest request) { conversation.setMessagesFile(getConversationMessagesFile(conversation)); // at this point we can save messages to the disk // write conversation to the disk (sync operation) - conversation.save(); + conversation.saveData(); dispatchDebugEvent(EVT_CONVERSATION_CREATE, true); From a38ca5a519e3b8edbac612d2e2d26787991293b6 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 22 Mar 2017 15:20:23 -0700 Subject: [PATCH 166/465] Changed conversation storage file naming schema --- .../sdk/conversation/Conversation.java | 181 ++++++++---------- .../sdk/conversation/ConversationManager.java | 17 +- .../sdk/conversation/FileMessageStore.java | 27 +-- .../com/apptentive/android/sdk/util/Util.java | 4 + 4 files changed, 92 insertions(+), 137 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index dcc50602e..707cde0a5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -49,17 +49,17 @@ public class Conversation implements DataChangedListener, Destroyable { /** * Conversation data for this class to manage */ - private ConversationData data; + private ConversationData conversationData; /** * File which represents serialized conversation data on the disk */ - private File dataFile; + private final File conversationDataFile; /** * File which represents serialized messages data on the disk */ - private File messagesFile; + private final File conversationMessagesFile; // TODO: remove this class private InteractionManager interactionManager; @@ -67,7 +67,6 @@ public class Conversation implements DataChangedListener, Destroyable { private ConversationState state = ConversationState.UNDEFINED; private final MessageManager messageManager; - private final FileMessageStore messageStore; // we keep references to the tasks in order to dispatch them only once private final DispatchTask fetchInteractionsTask = new DispatchTask() { @@ -92,13 +91,27 @@ protected void execute() { private final DispatchTask saveConversationTask = new DispatchTask() { @Override protected void execute() { - saveData(); + try { + saveConversationData(); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while saving conversation data"); + } } }; - public Conversation() { - data = new ConversationData(); - messageStore = new FileMessageStore(); // create a store in a "detached" state (we can't save anything until dest file is specified) + public Conversation(File conversationDataFile, File conversationMessagesFile) { + if (conversationDataFile == null) { + throw new IllegalArgumentException("Data file is null"); + } + if (conversationMessagesFile == null) { + throw new IllegalArgumentException("Messages file is null"); + } + + this.conversationDataFile = conversationDataFile; + this.conversationMessagesFile = conversationMessagesFile; + + conversationData = new ConversationData(); + FileMessageStore messageStore = new FileMessageStore(conversationMessagesFile); messageManager = new MessageManager(messageStore); // it's important to initialize message manager in a constructor since other SDK parts depend on it via Apptentive singleton } @@ -183,7 +196,7 @@ else if (!response.isSuccessful()) { ApptentiveLog.e(e, "Invalid InteractionManifest received."); } } - ApptentiveLog.v(CONVERSATION, "Fetching new Interactions asyncTask finished. Successful? %b", updateSuccessful); + ApptentiveLog.v(CONVERSATION, "Fetching new Interactions task finished. Successful: %b", updateSuccessful); return updateSuccessful; } @@ -196,29 +209,18 @@ else if (!response.isSuccessful()) { * Saves conversation data to the disk synchronously. Returns true * if succeed. */ - synchronized boolean saveData() { - if (dataFile == null) { - ApptentiveLog.e(CONVERSATION, "Unable to save conversation: destination file not specified"); - return false; - } - + synchronized void saveConversationData() throws SerializerException { ApptentiveLog.d(CONVERSATION, "Saving Conversation"); ApptentiveLog.v(CONVERSATION, "EventData: %s", getEventData().toString()); // TODO: remove - try { - FileSerializer serializer = new FileSerializer(dataFile); - serializer.serialize(data); - return true; - } catch (Exception e) { - ApptentiveLog.e(e, "Unable to save conversation"); - return false; - } + FileSerializer serializer = new FileSerializer(conversationDataFile); + serializer.serialize(conversationData); } - synchronized void loadData() throws SerializerException { + synchronized void loadConversationData() throws SerializerException { ApptentiveLog.d(CONVERSATION, "Loading conversation data"); - FileSerializer serializer = new FileSerializer(dataFile); - data = (ConversationData) serializer.deserialize(); + FileSerializer serializer = new FileSerializer(conversationDataFile); + conversationData = (ConversationData) serializer.deserialize(); } //endregion @@ -227,15 +229,11 @@ synchronized void loadData() throws SerializerException { @Override public void onDataChanged() { - if (hasDataFile()) { - boolean scheduled = DispatchQueue.backgroundQueue().dispatchAsyncOnce(saveConversationTask, 100L); - if (scheduled) { - ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); - } else { - ApptentiveLog.d(CONVERSATION, "Conversation save already scheduled."); - } + boolean scheduled = DispatchQueue.backgroundQueue().dispatchAsyncOnce(saveConversationTask, 100L); + if (scheduled) { + ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); } else { - ApptentiveLog.v(CONVERSATION, "Can't save conversation data: storage file is not specified"); + ApptentiveLog.d(CONVERSATION, "Conversation save already scheduled."); } } @@ -288,171 +286,171 @@ public boolean hasActiveState() { } public String getConversationToken() { - return data.getConversationToken(); + return conversationData.getConversationToken(); } public void setConversationToken(String conversationToken) { - data.setConversationToken(conversationToken); + conversationData.setConversationToken(conversationToken); } public String getConversationId() { - return data.getConversationId(); + return conversationData.getConversationId(); } public void setConversationId(String conversationId) { - data.setConversationId(conversationId); + conversationData.setConversationId(conversationId); } public String getPersonId() { - return data.getPersonId(); + return conversationData.getPersonId(); } public void setPersonId(String personId) { - data.setPersonId(personId); + conversationData.setPersonId(personId); } public String getPersonEmail() { - return data.getPersonEmail(); + return conversationData.getPersonEmail(); } public void setPersonEmail(String personEmail) { - data.setPersonEmail(personEmail); + conversationData.setPersonEmail(personEmail); } public String getPersonName() { - return data.getPersonName(); + return conversationData.getPersonName(); } public void setPersonName(String personName) { - data.setPersonName(personName); + conversationData.setPersonName(personName); } public Device getDevice() { - return data.getDevice(); + return conversationData.getDevice(); } public void setDevice(Device device) { - data.setDevice(device); + conversationData.setDevice(device); } public Device getLastSentDevice() { - return data.getLastSentDevice(); + return conversationData.getLastSentDevice(); } public void setLastSentDevice(Device lastSentDevice) { - data.setLastSentDevice(lastSentDevice); + conversationData.setLastSentDevice(lastSentDevice); } public Person getPerson() { - return data.getPerson(); + return conversationData.getPerson(); } public void setPerson(Person person) { - data.setPerson(person); + conversationData.setPerson(person); } public Person getLastSentPerson() { - return data.getLastSentPerson(); + return conversationData.getLastSentPerson(); } public void setLastSentPerson(Person lastSentPerson) { - data.setLastSentPerson(lastSentPerson); + conversationData.setLastSentPerson(lastSentPerson); } public Sdk getSdk() { - return data.getSdk(); + return conversationData.getSdk(); } public void setSdk(Sdk sdk) { - data.setSdk(sdk); + conversationData.setSdk(sdk); } public AppRelease getAppRelease() { - return data.getAppRelease(); + return conversationData.getAppRelease(); } public void setAppRelease(AppRelease appRelease) { - data.setAppRelease(appRelease); + conversationData.setAppRelease(appRelease); } public EventData getEventData() { - return data.getEventData(); + return conversationData.getEventData(); } public void setEventData(EventData eventData) { - data.setEventData(eventData); + conversationData.setEventData(eventData); } public String getLastSeenSdkVersion() { - return data.getLastSeenSdkVersion(); + return conversationData.getLastSeenSdkVersion(); } public void setLastSeenSdkVersion(String lastSeenSdkVersion) { - data.setLastSeenSdkVersion(lastSeenSdkVersion); + conversationData.setLastSeenSdkVersion(lastSeenSdkVersion); } public VersionHistory getVersionHistory() { - return data.getVersionHistory(); + return conversationData.getVersionHistory(); } public void setVersionHistory(VersionHistory versionHistory) { - data.setVersionHistory(versionHistory); + conversationData.setVersionHistory(versionHistory); } public boolean isMessageCenterFeatureUsed() { - return data.isMessageCenterFeatureUsed(); + return conversationData.isMessageCenterFeatureUsed(); } public void setMessageCenterFeatureUsed(boolean messageCenterFeatureUsed) { - data.setMessageCenterFeatureUsed(messageCenterFeatureUsed); + conversationData.setMessageCenterFeatureUsed(messageCenterFeatureUsed); } public boolean isMessageCenterWhoCardPreviouslyDisplayed() { - return data.isMessageCenterWhoCardPreviouslyDisplayed(); + return conversationData.isMessageCenterWhoCardPreviouslyDisplayed(); } public void setMessageCenterWhoCardPreviouslyDisplayed(boolean messageCenterWhoCardPreviouslyDisplayed) { - data.setMessageCenterWhoCardPreviouslyDisplayed(messageCenterWhoCardPreviouslyDisplayed); + conversationData.setMessageCenterWhoCardPreviouslyDisplayed(messageCenterWhoCardPreviouslyDisplayed); } public String getMessageCenterPendingMessage() { - return data.getMessageCenterPendingMessage(); + return conversationData.getMessageCenterPendingMessage(); } public void setMessageCenterPendingMessage(String messageCenterPendingMessage) { - data.setMessageCenterPendingMessage(messageCenterPendingMessage); + conversationData.setMessageCenterPendingMessage(messageCenterPendingMessage); } public String getMessageCenterPendingAttachments() { - return data.getMessageCenterPendingAttachments(); + return conversationData.getMessageCenterPendingAttachments(); } public void setMessageCenterPendingAttachments(String messageCenterPendingAttachments) { - data.setMessageCenterPendingAttachments(messageCenterPendingAttachments); + conversationData.setMessageCenterPendingAttachments(messageCenterPendingAttachments); } public String getTargets() { - return data.getTargets(); + return conversationData.getTargets(); } public void setTargets(String targets) { - data.setTargets(targets); + conversationData.setTargets(targets); } public String getInteractions() { - return data.getInteractions(); + return conversationData.getInteractions(); } public void setInteractions(String interactions) { - data.setInteractions(interactions); + conversationData.setInteractions(interactions); } public double getInteractionExpiration() { - return data.getInteractionExpiration(); + return conversationData.getInteractionExpiration(); } public void setInteractionExpiration(double interactionExpiration) { - data.setInteractionExpiration(interactionExpiration); + conversationData.setInteractionExpiration(interactionExpiration); } public MessageManager getMessageManager() { @@ -467,35 +465,12 @@ public void setInteractionManager(InteractionManager interactionManager) { this.interactionManager = interactionManager; } - private synchronized boolean hasDataFile() { - return dataFile != null; + synchronized File getConversationDataFile() { + return conversationDataFile; } - synchronized File getDataFile() { - return dataFile; - } - - synchronized void setDataFile(File dataFile) { - if (dataFile == null) { - throw new IllegalArgumentException("Data file is null"); - } - this.dataFile = dataFile; - } - - synchronized boolean hasMessagesFile() { - return messagesFile != null; - } - - synchronized File getMessagesFile() { - return messagesFile; - } - - synchronized void setMessagesFile(File messagesFile) { - if (messagesFile == null) { - throw new IllegalArgumentException("Messages file is null"); - } - this.messagesFile = messagesFile; - messageStore.setFile(messagesFile); // now we can save/load messages + synchronized File getConversationMessagesFile() { + return conversationMessagesFile; } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 93d50fb85..ce29d3374 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -22,6 +22,7 @@ import com.apptentive.android.sdk.storage.SerializerException; import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.util.Util; import org.json.JSONObject; @@ -160,7 +161,9 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali // no conversation available: create a new one ApptentiveLog.v(CONVERSATION, "Can't load conversation: creating anonymous conversation..."); - Conversation anonymousConversation = new Conversation(); + File dataFile = new File(storageDir, Util.generateRandomFilename()); + File messagesFile = new File(storageDir, Util.generateRandomFilename()); + Conversation anonymousConversation = new Conversation(dataFile, messagesFile); anonymousConversation.setState(ANONYMOUS_PENDING); fetchConversationToken(anonymousConversation); return anonymousConversation; @@ -168,10 +171,8 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali private Conversation loadConversation(ConversationMetadataItem item) throws SerializerException { // TODO: use same serialization logic across the project - final Conversation conversation = new Conversation(); - conversation.setDataFile(item.dataFile); // at this point we can save conversation data to the disk - conversation.loadData(); - conversation.setMessagesFile(item.messagesFile); // at this point we can save messages to the disk + final Conversation conversation = new Conversation(item.dataFile, item.messagesFile); + conversation.loadConversationData(); conversation.setState(item.getState()); // set the state same as the item's state return conversation; } @@ -248,11 +249,9 @@ public void onFinish(HttpJsonRequest request) { String personId = root.getString("person_id"); ApptentiveLog.d(CONVERSATION, "PersonId: " + personId); conversation.setPersonId(personId); - conversation.setDataFile(getConversationDataFile(conversation)); // at this point we can save conversation data to the disk - conversation.setMessagesFile(getConversationMessagesFile(conversation)); // at this point we can save messages to the disk // write conversation to the disk (sync operation) - conversation.saveData(); + conversation.saveConversationData(); dispatchDebugEvent(EVT_CONVERSATION_CREATE, true); @@ -345,7 +344,7 @@ private void updateMetadataItems(Conversation conversation) { // update the state of the corresponding item ConversationMetadataItem item = conversationMetadata.findItem(conversation); if (item == null) { - item = new ConversationMetadataItem(conversation.getConversationId(), conversation.getDataFile(), conversation.getMessagesFile()); + item = new ConversationMetadataItem(conversation.getConversationId(), conversation.getConversationDataFile(), conversation.getConversationMessagesFile()); conversationMetadata.addItem(item); } item.state = conversation.getState(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index 6623abce2..8cfac58ad 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -32,18 +32,14 @@ class FileMessageStore implements MessageStore { */ private static final byte VERSION = 1; - private File file; + private final File file; private final List messageEntries; private boolean shouldFetchFromFile; FileMessageStore(File file) { - this(); this.file = file; - this.shouldFetchFromFile = true; // we would lazily read it from a file later - } - - FileMessageStore() { this.messageEntries = new ArrayList<>(); // we need a random access + this.shouldFetchFromFile = true; // we would lazily read it from a file later } //region MessageStore @@ -203,11 +199,6 @@ private List readFromFileGuarded() throws IOException { } private synchronized void writeToFile() { - Assert.assertFalse(file != null, "File is not specified"); - if (file == null) { - return; - } - try { writeToFileGuarded(); } catch (Exception e) { @@ -248,20 +239,6 @@ private MessageEntry findMessageEntry(String nonce) { return null; } - //endregion - - //region Properties - - public synchronized void setFile(File file) { - if (file == null) { - throw new IllegalArgumentException("File is null"); - } - - this.file = file; - shouldFetchFromFile = true; - } - - //endregion //region Message Entry diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java index 147009dba..12e875155 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java @@ -520,6 +520,10 @@ private static String md5(String s) { return null; } + public static String generateRandomFilename() { + return UUID.randomUUID().toString(); + } + /* * Generate cached file name use md5 from image originalPath and image created time */ From dd90103e26435eee20dfa469d50fe1346e8a500b Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 23 Mar 2017 10:44:19 -0700 Subject: [PATCH 167/465] Added ability to post notifications sync --- .../ApptentiveNotificationCenter.java | 105 ++++++------------ .../ApptentiveNotificationCenterTest.java | 33 +++++- 2 files changed, 68 insertions(+), 70 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java index 5d65c986c..9ca275df2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java @@ -38,24 +38,13 @@ public class ApptentiveNotificationCenter { */ private final DispatchQueue notificationQueue; - /** - * Dispatch queue for the concurrent access to the internal data structures - * (adding/removing observers, etc). - */ - private final DispatchQueue operationQueue; - - ApptentiveNotificationCenter(DispatchQueue notificationQueue, DispatchQueue operationQueue) { + ApptentiveNotificationCenter(DispatchQueue notificationQueue) { if (notificationQueue == null) { throw new IllegalArgumentException("Notification queue is not defined"); } - if (operationQueue == null) { - throw new IllegalArgumentException("Operation queue is not defined"); - } - this.observerListLookup = new HashMap<>(); this.notificationQueue = notificationQueue; - this.operationQueue = operationQueue; } //region Observers @@ -63,7 +52,7 @@ public class ApptentiveNotificationCenter { /** * Adds an entry to the receiver’s dispatch table with an observer using strong reference. */ - public void addObserver(final String notification, final ApptentiveNotificationObserver observer) { + public synchronized void addObserver(String notification, ApptentiveNotificationObserver observer) { addObserver(notification, observer, false); } @@ -72,43 +61,28 @@ public void addObserver(final String notification, final ApptentiveNotificationO * * @param useWeakReference - weak reference is used if true */ - public void addObserver(final String notification, final ApptentiveNotificationObserver observer, final boolean useWeakReference) { - operationQueue.dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - final ApptentiveNotificationObserverList list = resolveObserverList(notification); - list.addObserver(observer, useWeakReference); - } - }); + public synchronized void addObserver(String notification, ApptentiveNotificationObserver observer, boolean useWeakReference) { + final ApptentiveNotificationObserverList list = resolveObserverList(notification); + list.addObserver(observer, useWeakReference); } /** * Removes matching entries from the receiver’s dispatch table. */ - public void removeObserver(final String notification, final ApptentiveNotificationObserver observer) { - operationQueue.dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - final ApptentiveNotificationObserverList list = findObserverList(notification); - if (list != null) { - list.removeObserver(observer); - } - } - }); + public synchronized void removeObserver(final String notification, final ApptentiveNotificationObserver observer) { + final ApptentiveNotificationObserverList list = findObserverList(notification); + if (list != null) { + list.removeObserver(observer); + } } /** * Removes all the entries specifying a given observer from the receiver’s dispatch table. */ - public void removeObserver(final ApptentiveNotificationObserver observer) { - operationQueue.dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - for (ApptentiveNotificationObserverList observers : observerListLookup.values()) { - observers.removeObserver(observer); - } - } - }); + public synchronized void removeObserver(final ApptentiveNotificationObserver observer) { + for (ApptentiveNotificationObserverList observers : observerListLookup.values()) { + observers.removeObserver(observer); + } } //endregion @@ -116,43 +90,38 @@ protected void execute() { //region Notifications /** - * Posts a given notification to the receiver. + * Creates a notification with a given name and posts it to the receiver. */ - public void postNotification(String name) { + public synchronized void postNotification(String name) { postNotification(name, EMPTY_USER_INFO); } /** - * Creates a notification with a given name and information and posts it to the receiver. - */ - public void postNotification(String name, Map userInfo) { - postNotification(new ApptentiveNotification(name, userInfo)); - } - - /** - * Posts a given notification to the receiver. + * Creates a notification with a given name and user info and posts it to the receiver. */ - public void postNotification(final ApptentiveNotification notification) { - ApptentiveLog.v(NOTIFICATIONS, "Post notification: %s", notification); - operationQueue.dispatchAsync(new DispatchTask() { + public synchronized void postNotification(final String name, final Map userInfo) { + notificationQueue.dispatchAsync(new DispatchTask() { @Override protected void execute() { - if (notificationQueue == operationQueue) { // is it the same queue? - postNotificationSync(notification); - } else { - notificationQueue.dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - postNotificationSync(notification); - } - }); - } + postNotificationSync(name, userInfo); } }); } - // this method is not thread-safe - private void postNotificationSync(ApptentiveNotification notification) { + /** + * Creates a notification with a given name and information and posts it to the receiver synchronously. + */ + public synchronized void postNotificationSync(String name) { + postNotificationSync(name, EMPTY_USER_INFO); + } + + /** + * Creates a notification with a given name and information and posts it to the receiver synchronously. + */ + public synchronized void postNotificationSync(String name, Map userInfo) { + final ApptentiveNotification notification = new ApptentiveNotification(name, userInfo); + ApptentiveLog.v(NOTIFICATIONS, "Post notification: %s", notification); + final ApptentiveNotificationObserverList list = findObserverList(notification.getName()); if (list != null) { list.notifyObservers(notification); @@ -168,14 +137,14 @@ private void postNotificationSync(ApptentiveNotification notification) { * * @return null is not found */ - private ApptentiveNotificationObserverList findObserverList(String name) { + private synchronized ApptentiveNotificationObserverList findObserverList(String name) { return observerListLookup.get(name); } /** * Find an observer list for the specified name or creates a new one if not found. */ - private ApptentiveNotificationObserverList resolveObserverList(String name) { + private synchronized ApptentiveNotificationObserverList resolveObserverList(String name) { ApptentiveNotificationObserverList list = observerListLookup.get(name); if (list == null) { list = new ApptentiveNotificationObserverList(); @@ -199,7 +168,7 @@ public static ApptentiveNotificationCenter defaultCenter() { * Thread-safe initialization trick */ private static class Holder { - static final ApptentiveNotificationCenter INSTANCE = new ApptentiveNotificationCenter(DispatchQueue.mainQueue(), DispatchQueue.mainQueue()); + static final ApptentiveNotificationCenter INSTANCE = new ApptentiveNotificationCenter(DispatchQueue.mainQueue()); } //endregion diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java index dd10fdbf3..ca712b385 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java @@ -22,10 +22,10 @@ public class ApptentiveNotificationCenterTest extends TestCaseBase { @Before public void setUp() { super.setUp(); - notificationCenter = new ApptentiveNotificationCenter(new MockDispatchQueue(true), new MockDispatchQueue(true)); + notificationCenter = new ApptentiveNotificationCenter(new MockDispatchQueue(true)); } - @Override + @After public void tearDown() { super.tearDown(); } @@ -101,6 +101,35 @@ public void testPostNotifications() { assertResult(); } + @Test + public void testPostNotificationsSync() { + final Observer observer1 = new Observer("observer1"); + final Observer observer3 = new Observer("observer3"); + final Observer observer2 = new Observer("observer2") { + @Override + public void onReceiveNotification(ApptentiveNotification notification) { + super.onReceiveNotification(notification); + notificationCenter.removeObserver(observer3); + } + }; + notificationCenter.addObserver("notification", observer1); + notificationCenter.addObserver("notification", observer2); + notificationCenter.addObserver("notification", observer3); + notificationCenter.postNotificationSync("notification", ObjectUtils.toMap("key", "value")); + + assertResult( + "observer1: notification {'key':'value'}", + "observer2: notification {'key':'value'}", + "observer3: notification {'key':'value'}" + ); + + notificationCenter.postNotificationSync("notification", ObjectUtils.toMap("key", "value")); + assertResult( + "observer1: notification {'key':'value'}", + "observer2: notification {'key':'value'}" + ); + } + private class Observer implements ApptentiveNotificationObserver { private final String name; From 83f77a8c6af13d0ea50dc7eafe0ef3e2e1aa26e6 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 23 Mar 2017 12:56:45 -0700 Subject: [PATCH 168/465] Moved ApptentiveInternal instance initialization to the Apptentive.register() function --- .../conversation/FileMessageStoreTest.java | 4 +- .../apptentive/android/sdk/Apptentive.java | 7 +- .../android/sdk/ApptentiveInternal.java | 154 ++++++++---------- .../ApptentiveActivityLifecycleCallbacks.java | 10 +- 4 files changed, 73 insertions(+), 102 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java index 2a3de95d9..4ef9b50f7 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java @@ -40,13 +40,13 @@ public class FileMessageStoreTest extends TestCaseBase { @Before public void setUp() { super.setUp(); - ApptentiveInternal.setInstance(new ApptentiveInternalMock(), true); + ApptentiveInternal.setInstance(new ApptentiveInternalMock()); } @After public void tearDown() { super.tearDown(); - ApptentiveInternal.setInstance(null, false); + ApptentiveInternal.setInstance(null); } @Test diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 8ac80644d..874b104a2 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -57,15 +57,12 @@ public static void register(Application application) { } public static void register(Application application, String apptentiveApiKey) { - ApptentiveLog.i("Registering Apptentive."); - ApptentiveInternal.createInstance(application, apptentiveApiKey, null); - ApptentiveInternal.setLifeCycleCallback(); + register(application, apptentiveApiKey, null); } - public static void register(Application application, String apptentiveApiKey, String serverUrl) { + private static void register(Application application, String apptentiveApiKey, String serverUrl) { ApptentiveLog.i("Registering Apptentive."); ApptentiveInternal.createInstance(application, apptentiveApiKey, serverUrl); - ApptentiveInternal.setLifeCycleCallback(); } // **************************************************************************************** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 668323b61..adbeac38e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -50,6 +50,7 @@ import com.apptentive.android.sdk.storage.VersionHistoryItem; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.ObjectUtils; +import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; import org.json.JSONException; @@ -63,23 +64,24 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicBoolean; -import static com.apptentive.android.sdk.ApptentiveLogTag.*; -import static com.apptentive.android.sdk.ApptentiveNotifications.*; -import static com.apptentive.android.sdk.debug.Tester.*; -import static com.apptentive.android.sdk.debug.TesterEvent.*; +import static com.apptentive.android.sdk.ApptentiveLogTag.CONVERSATION; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_RESUMED; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_STARTED; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_BACKGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_INTERACTIONS_SHOULD_DISMISS; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_ACTIVITY; /** * This class contains only internal methods. These methods should not be access directly by the host app. */ public class ApptentiveInternal { - static AtomicBoolean isApptentiveInitialized = new AtomicBoolean(false); private final PayloadSendWorker payloadWorker; private final ApptentiveTaskManager taskManager; - ApptentiveActivityLifecycleCallbacks lifecycleCallbacks; + private final ApptentiveActivityLifecycleCallbacks lifecycleCallbacks; private final ApptentiveHttpClient apptentiveHttpClient; private final ConversationManager conversationManager; @@ -89,29 +91,29 @@ public class ApptentiveInternal { // We keep a readonly reference to AppRelease object since it won't change at runtime private final AppRelease appRelease; - boolean appIsInForeground; + private boolean appIsInForeground; private final SharedPreferences globalSharedPrefs; private final String apiKey; - String serverUrl; - String personId; - String androidId; - String appPackageName; + private String serverUrl; + private String personId; + private String androidId; // FIXME: remove this field (never used) + private String appPackageName; // toolbar theme specified in R.attr.apptentiveToolbarTheme - Resources.Theme apptentiveToolbarTheme; + private Resources.Theme apptentiveToolbarTheme; // app default appcompat theme res id, if specified in app AndroidManifest - int appDefaultAppCompatThemeId; + private int appDefaultAppCompatThemeId; - int statusBarColorDefault; - String defaultAppDisplayName = "this app"; + private int statusBarColorDefault; + private String defaultAppDisplayName = "this app"; // booleans to prevent starting multiple fetching asyncTasks simultaneously - IRatingProvider ratingProvider; - Map ratingProviderArgs; - WeakReference onSurveyFinishedListener; + private IRatingProvider ratingProvider; + private Map ratingProviderArgs; + private WeakReference onSurveyFinishedListener; - final LinkedBlockingQueue interactionUpdateListeners = new LinkedBlockingQueue(); + private final LinkedBlockingQueue interactionUpdateListeners = new LinkedBlockingQueue(); private final ExecutorService cachedExecutor; @@ -121,9 +123,9 @@ public class ApptentiveInternal { // Used for temporarily holding customData that needs to be sent on the next message the consumer sends. private Map customData; - public static final String PUSH_ACTION = "action"; + private static final String PUSH_ACTION = "action"; - public enum PushAction { + private enum PushAction { pmc, // Present Message Center. unknown; // Anything unknown will not be handled. @@ -151,22 +153,26 @@ protected ApptentiveInternal() { appContext = null; appRelease = null; cachedExecutor = null; + lifecycleCallbacks = null; } - private ApptentiveInternal(Context context, String apiKey, String serverUrl) { + private ApptentiveInternal(Application application, String apiKey, String serverUrl) { this.apiKey = apiKey; this.serverUrl = serverUrl; - appContext = context.getApplicationContext(); + appContext = application.getApplicationContext(); - globalSharedPrefs = context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); + globalSharedPrefs = application.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); apptentiveHttpClient = new ApptentiveHttpClient(apiKey, getEndpointBase(globalSharedPrefs)); conversationManager = new ConversationManager(appContext, Util.getInternalDir(appContext, "conversations", true)); - appRelease = AppReleaseManager.generateCurrentAppRelease(context, this); + appRelease = AppReleaseManager.generateCurrentAppRelease(application, this); payloadWorker = new PayloadSendWorker(); taskManager = new ApptentiveTaskManager(appContext); cachedExecutor = Executors.newCachedThreadPool(); + + lifecycleCallbacks = new ApptentiveActivityLifecycleCallbacks(); + application.registerActivityLifecycleCallbacks(lifecycleCallbacks); } public static boolean isApptentiveRegistered() { @@ -183,28 +189,35 @@ public static boolean isApptentiveRegistered() { * service, or receiver in the hosting app's process, the initialization of Apptentive is deferred to the first time * {@link #getInstance()} is called. * - * @param context the context of the app that is creating the instance - * @return An non-null instance of the Apptentive SDK + * @param application the context of the app that is creating the instance */ - public static ApptentiveInternal createInstance(Context context, String apptentiveApiKey, final String serverUrl) { - if (sApptentiveInternal == null) { - synchronized (ApptentiveInternal.class) { - if (sApptentiveInternal == null && context != null) { + static void createInstance(Application application, String apptentiveApiKey, final String serverUrl) { + if (application == null) { + throw new IllegalArgumentException("Application is null"); + } - // trim spaces - apptentiveApiKey = Util.trim(apptentiveApiKey); + synchronized (ApptentiveInternal.class) { + if (sApptentiveInternal == null) { - // if API key is not defined - try loading from AndroidManifest.xml - if (TextUtils.isEmpty(apptentiveApiKey)) { - apptentiveApiKey = resolveManifestApiKey(context); - } + // trim spaces + apptentiveApiKey = Util.trim(apptentiveApiKey); - sApptentiveInternal = new ApptentiveInternal(context, apptentiveApiKey, serverUrl); - isApptentiveInitialized.set(false); + // if API key is not defined - try loading from AndroidManifest.xml + if (StringUtils.isNullOrEmpty(apptentiveApiKey)) { + apptentiveApiKey = resolveManifestApiKey(application); + // TODO: check if apptentive key is still empty + } + + try { + sApptentiveInternal = new ApptentiveInternal(application, apptentiveApiKey, serverUrl); + sApptentiveInternal.start(); // TODO: check the result of this call + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while initializing ApptentiveInternal instance"); } + } else { + ApptentiveLog.w("Apptentive instance is already initialized"); } } - return sApptentiveInternal; } /** @@ -228,16 +241,6 @@ private static String resolveManifestApiKey(Context context) { return null; } - /** - * Retrieve the existing instance of the Apptentive class. If {@link Apptentive#register(Application)} is - * not called prior to this, it will only return null if context is null - * - * @return the existing instance of the Apptentive SDK fully initialized with API key, or a new instance if context is not null - */ - public static ApptentiveInternal getInstance(Context context) { - return createInstance((context == null) ? null : context, null, null); - } - /** * Retrieve the existing instance of the Apptentive class. If {@link Apptentive#register(Application)} is * not called prior to this, it will return null; Otherwise, it will return the singleton instance initialized. @@ -245,22 +248,9 @@ public static ApptentiveInternal getInstance(Context context) { * @return the existing instance of the Apptentive SDK fully initialized with API key, or null */ public static ApptentiveInternal getInstance() { - // Lazy initialization, only once for each application launch when getInstance() is called for the 1st time - if (sApptentiveInternal != null && !isApptentiveInitialized.get()) { - synchronized (ApptentiveInternal.class) { - if (sApptentiveInternal != null && !isApptentiveInitialized.get()) { - isApptentiveInitialized.set(true); - - final boolean successful = sApptentiveInternal.init(); - dispatchDebugEvent(EVT_INSTANCE_CREATED, successful); - - if (!successful) { - ApptentiveLog.e("Apptentive init() failed"); - } - } - } + synchronized (ApptentiveInternal.class) { + return sApptentiveInternal; } - return sApptentiveInternal; } /** @@ -269,24 +259,8 @@ public static ApptentiveInternal getInstance() { * * @param instance the internal instance to be set to */ - public static void setInstance(ApptentiveInternal instance, boolean initialized) { + public static void setInstance(ApptentiveInternal instance) { sApptentiveInternal = instance; - isApptentiveInitialized.set(initialized); - } - - /* Called by {@link #Apptentive.register()} to register global lifecycle - * callbacks, only if the callback hasn't been set yet. - */ - static void setLifeCycleCallback() { - if (sApptentiveInternal != null && sApptentiveInternal.lifecycleCallbacks == null) { - synchronized (ApptentiveInternal.class) { - if (sApptentiveInternal != null && sApptentiveInternal.lifecycleCallbacks == null && - sApptentiveInternal.appContext instanceof Application) { - sApptentiveInternal.lifecycleCallbacks = new ApptentiveActivityLifecycleCallbacks(); - ((Application) sApptentiveInternal.appContext).registerActivityLifecycleCallbacks(sApptentiveInternal.lifecycleCallbacks); - } - } - } } /* @@ -295,7 +269,7 @@ static void setLifeCycleCallback() { * @param themeResId : resource id of the theme style definition, such as R.style.MyAppTheme * @return true if the theme is set for inheritance successfully. */ - public boolean setApplicationDefaultTheme(int themeResId) { + private boolean setApplicationDefaultTheme(int themeResId) { try { if (themeResId != 0) { // If passed theme res id does not exist, an exception would be thrown and caught @@ -383,7 +357,7 @@ public Resources.Theme getApptentiveToolbarTheme() { return apptentiveToolbarTheme; } - public int getDefaultStatusBarColor() { + int getDefaultStatusBarColor() { return statusBarColorDefault; } @@ -535,7 +509,7 @@ public void updateApptentiveInteractionTheme(Resources.Theme interactionTheme, C apptentiveToolbarTheme.applyStyle(toolbarThemeId, true); } - public boolean init() { + private boolean start() { boolean bRet = true; /* If Message Center feature has never been used before, don't initialize message polling thread. * Message Center feature will be seen as used, if one of the following conditions has been met: @@ -731,7 +705,7 @@ public IRatingProvider getRatingProvider() { return ratingProvider; } - public void setRatingProvider(IRatingProvider ratingProvider) { + void setRatingProvider(IRatingProvider ratingProvider) { this.ratingProvider = ratingProvider; } @@ -739,14 +713,14 @@ public Map getRatingProviderArgs() { return ratingProviderArgs; } - public void putRatingProviderArg(String key, String value) { + void putRatingProviderArg(String key, String value) { if (ratingProviderArgs == null) { ratingProviderArgs = new HashMap<>(); } ratingProviderArgs.put(key, value); } - public void setOnSurveyFinishedListener(OnSurveyFinishedListener onSurveyFinishedListener) { + void setOnSurveyFinishedListener(OnSurveyFinishedListener onSurveyFinishedListener) { if (onSurveyFinishedListener != null) { this.onSurveyFinishedListener = new WeakReference<>(onSurveyFinishedListener); } else { @@ -769,7 +743,7 @@ public void removeInteractionUpdateListener(InteractionManager.InteractionUpdate /** * Pass in a log level to override the default, which is {@link ApptentiveLog.Level#INFO} */ - public void setMinimumLogLevel(ApptentiveLog.Level level) { + private void setMinimumLogLevel(ApptentiveLog.Level level) { ApptentiveLog.overrideLogLevel(level); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/lifecycle/ApptentiveActivityLifecycleCallbacks.java b/apptentive/src/main/java/com/apptentive/android/sdk/lifecycle/ApptentiveActivityLifecycleCallbacks.java index af647a443..e6d6c0aac 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/lifecycle/ApptentiveActivityLifecycleCallbacks.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/lifecycle/ApptentiveActivityLifecycleCallbacks.java @@ -55,12 +55,12 @@ public void onActivityStarted(Activity activity) { appEnteredForeground(); } - ApptentiveInternal.getInstance().onActivityStarted(activity); + ApptentiveInternal.getInstance().onActivityStarted(activity); // TODO: post a notification here } @Override public void onActivityResumed(Activity activity) { - ApptentiveInternal.getInstance().onActivityResumed(activity); + ApptentiveInternal.getInstance().onActivityResumed(activity); // TODO: post a notification here } @Override @@ -123,16 +123,16 @@ private void appEnteredForeground() { private void appEnteredBackground() { ApptentiveLog.d("App went to background."); - ApptentiveInternal.getInstance().onAppEnterBackground(); + ApptentiveInternal.getInstance().onAppEnterBackground(); // TODO: post a notification here // Mark entering background as app exit appExited(ApptentiveInternal.getInstance().getApplicationContext()); } private void appLaunched(Context appContext) { - ApptentiveInternal.getInstance().onAppLaunch(appContext); + ApptentiveInternal.getInstance().onAppLaunch(appContext); // TODO: post a notification here } private void appExited(Context appContext) { - ApptentiveInternal.getInstance().onAppExit(appContext); + ApptentiveInternal.getInstance().onAppExit(appContext); // TODO: post a notification here } } From ca3d91a0f4ddb5325afdbbb8e2735a1963d0bb3c Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 20 Mar 2017 11:53:40 -0700 Subject: [PATCH 169/465] Whitespace refactor, clean up imports, rearrange parts, add code regions. --- .../sdk/storage/ApptentiveDatabaseHelper.java | 332 +++++++++--------- 1 file changed, 174 insertions(+), 158 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index e5ceedc3c..b5b4719ac 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -16,7 +16,9 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.model.*; +import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PayloadFactory; +import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; @@ -30,37 +32,38 @@ /** * There can be only one. SQLiteOpenHelper per database name that is. All new Apptentive tables must be defined here. - * - * @author Sky Kelsey */ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { - // COMMON private static final int DATABASE_VERSION = 2; public static final String DATABASE_NAME = "apptentive"; private static final int TRUE = 1; private static final int FALSE = 0; + private File fileDir; // data dir of the application + + //region Payload SQL - // PAYLOAD public static final String TABLE_PAYLOAD = "payload"; public static final String PAYLOAD_KEY_DB_ID = "_id"; // 0 public static final String PAYLOAD_KEY_BASE_TYPE = "base_type"; // 1 public static final String PAYLOAD_KEY_JSON = "json"; // 2 private static final String TABLE_CREATE_PAYLOAD = - "CREATE TABLE " + TABLE_PAYLOAD + - " (" + - PAYLOAD_KEY_DB_ID + " INTEGER PRIMARY KEY, " + - PAYLOAD_KEY_BASE_TYPE + " TEXT, " + - PAYLOAD_KEY_JSON + " TEXT" + - ");"; + "CREATE TABLE " + TABLE_PAYLOAD + + " (" + + PAYLOAD_KEY_DB_ID + " INTEGER PRIMARY KEY, " + + PAYLOAD_KEY_BASE_TYPE + " TEXT, " + + PAYLOAD_KEY_JSON + " TEXT" + + ");"; public static final String QUERY_PAYLOAD_GET_NEXT_TO_SEND = "SELECT * FROM " + TABLE_PAYLOAD + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC LIMIT 1"; private static final String QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER = "SELECT * FROM " + TABLE_PAYLOAD + " WHERE " + PAYLOAD_KEY_BASE_TYPE + " = ?" + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC"; + //endregion + + //region Message SQL - // MESSAGE private static final String TABLE_MESSAGE = "message"; private static final String MESSAGE_KEY_DB_ID = "_id"; // 0 private static final String MESSAGE_KEY_ID = "id"; // 1 @@ -71,16 +74,16 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { private static final String MESSAGE_KEY_JSON = "json"; // 6 private static final String TABLE_CREATE_MESSAGE = - "CREATE TABLE " + TABLE_MESSAGE + - " (" + - MESSAGE_KEY_DB_ID + " INTEGER PRIMARY KEY, " + - MESSAGE_KEY_ID + " TEXT, " + - MESSAGE_KEY_CLIENT_CREATED_AT + " DOUBLE, " + - MESSAGE_KEY_NONCE + " TEXT, " + - MESSAGE_KEY_STATE + " TEXT, " + - MESSAGE_KEY_READ + " INTEGER, " + - MESSAGE_KEY_JSON + " TEXT" + - ");"; + "CREATE TABLE " + TABLE_MESSAGE + + " (" + + MESSAGE_KEY_DB_ID + " INTEGER PRIMARY KEY, " + + MESSAGE_KEY_ID + " TEXT, " + + MESSAGE_KEY_CLIENT_CREATED_AT + " DOUBLE, " + + MESSAGE_KEY_NONCE + " TEXT, " + + MESSAGE_KEY_STATE + " TEXT, " + + MESSAGE_KEY_READ + " INTEGER, " + + MESSAGE_KEY_JSON + " TEXT" + + ");"; private static final String QUERY_MESSAGE_GET_BY_NONCE = "SELECT * FROM " + TABLE_MESSAGE + " WHERE " + MESSAGE_KEY_NONCE + " = ?"; // Coalesce returns the second arg if the first is null. This forces the entries with null IDs to be ordered last in the list until they do have IDs because they were sent and retrieved from the server. @@ -88,7 +91,10 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { private static final String QUERY_MESSAGE_GET_LAST_ID = "SELECT " + MESSAGE_KEY_ID + " FROM " + TABLE_MESSAGE + " WHERE " + MESSAGE_KEY_STATE + " = '" + ApptentiveMessage.State.saved + "' AND " + MESSAGE_KEY_ID + " NOTNULL ORDER BY " + MESSAGE_KEY_ID + " DESC LIMIT 1"; private static final String QUERY_MESSAGE_UNREAD = "SELECT " + MESSAGE_KEY_ID + " FROM " + TABLE_MESSAGE + " WHERE " + MESSAGE_KEY_READ + " = " + FALSE + " AND " + MESSAGE_KEY_ID + " NOTNULL"; - // FileStore + //endregion + + //region File SQL + private static final String TABLE_FILESTORE = "file_store"; private static final String FILESTORE_KEY_ID = "id"; // 0 private static final String FILESTORE_KEY_MIME_TYPE = "mime_type"; // 1 @@ -96,15 +102,18 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { private static final String FILESTORE_KEY_LOCAL_URL = "local_uri"; // 3 private static final String FILESTORE_KEY_APPTENTIVE_URL = "apptentive_uri"; // 4 private static final String TABLE_CREATE_FILESTORE = - "CREATE TABLE " + TABLE_FILESTORE + - " (" + - FILESTORE_KEY_ID + " TEXT PRIMARY KEY, " + - FILESTORE_KEY_MIME_TYPE + " TEXT, " + - FILESTORE_KEY_ORIGINAL_URL + " TEXT, " + - FILESTORE_KEY_LOCAL_URL + " TEXT, " + - FILESTORE_KEY_APPTENTIVE_URL + " TEXT" + - ");"; - + "CREATE TABLE " + TABLE_FILESTORE + + " (" + + FILESTORE_KEY_ID + " TEXT PRIMARY KEY, " + + FILESTORE_KEY_MIME_TYPE + " TEXT, " + + FILESTORE_KEY_ORIGINAL_URL + " TEXT, " + + FILESTORE_KEY_LOCAL_URL + " TEXT, " + + FILESTORE_KEY_APPTENTIVE_URL + " TEXT" + + ");"; + + //endregion + + //region Compound Message FileStore SQL /* Compound Message FileStore: * For Compound Messages stored in TABLE_MESSAGE, each associated file will add a row to this table * using the message's "nonce" key @@ -119,43 +128,23 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { private static final String COMPOUND_FILESTORE_KEY_CREATION_TIME = "creation_time"; // creation time of the original file // Create the initial table. Use nonce and local cache path as primary key because both sent/received files will have a local cached copy private static final String TABLE_CREATE_COMPOUND_FILESTORE = - "CREATE TABLE " + TABLE_COMPOUND_MESSAGE_FILESTORE + - " (" + - COMPOUND_FILESTORE_KEY_DB_ID + " INTEGER PRIMARY KEY, " + - COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " TEXT, " + - COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH + " TEXT, " + - COMPOUND_FILESTORE_KEY_MIME_TYPE + " TEXT, " + - COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI + " TEXT, " + - COMPOUND_FILESTORE_KEY_REMOTE_URL + " TEXT, " + - COMPOUND_FILESTORE_KEY_CREATION_TIME + " LONG" + - ");"; + "CREATE TABLE " + TABLE_COMPOUND_MESSAGE_FILESTORE + + " (" + + COMPOUND_FILESTORE_KEY_DB_ID + " INTEGER PRIMARY KEY, " + + COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " TEXT, " + + COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH + " TEXT, " + + COMPOUND_FILESTORE_KEY_MIME_TYPE + " TEXT, " + + COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI + " TEXT, " + + COMPOUND_FILESTORE_KEY_REMOTE_URL + " TEXT, " + + COMPOUND_FILESTORE_KEY_CREATION_TIME + " LONG" + + ");"; // Query all files associated with a given compound message nonce id private static final String QUERY_MESSAGE_FILES_GET_BY_NONCE = "SELECT * FROM " + TABLE_COMPOUND_MESSAGE_FILESTORE + " WHERE " + COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?"; - private File fileDir; // data dir of the application + // endregion - public void ensureClosed(SQLiteDatabase db) { - try { - if (db != null) { - db.close(); - } - } catch (Exception e) { - ApptentiveLog.w("Error closing SQLite database.", e); - } - } - - public void ensureClosed(Cursor cursor) { - try { - if (cursor != null) { - cursor.close(); - } - } catch (Exception e) { - ApptentiveLog.w("Error closing SQLite cursor.", e); - } - } - public ApptentiveDatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); fileDir = context.getFilesDir(); @@ -174,23 +163,27 @@ public void onCreate(SQLiteDatabase db) { db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); } - /** - * This method is called when an app is upgraded. Add alter table statements here for each version in a non-breaking - * switch, so that all the necessary upgrades occur for each older version. - */ - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - ApptentiveLog.d("ApptentiveDatabase.onUpgrade(db, %d, %d)", oldVersion, newVersion); - switch (oldVersion) { - case 1: - if (newVersion == 2) { - db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); - migrateToCompoundMessage(db); - } + public void ensureClosed(SQLiteDatabase db) { + try { + if (db != null) { + db.close(); + } + } catch (Exception e) { + ApptentiveLog.w("Error closing SQLite database.", e); + } + } + + public void ensureClosed(Cursor cursor) { + try { + if (cursor != null) { + cursor.close(); + } + } catch (Exception e) { + ApptentiveLog.w("Error closing SQLite cursor.", e); } } - // PAYLOAD: This table is used to store all the Payloads we want to send to the server. + //region Payloads /** * If an item with the same nonce as an item passed in already exists, it is overwritten by the item. Otherwise @@ -267,7 +260,9 @@ public Payload getOldestUnsentPayload() { } } - // MessageStore + //endregion + + //region Messages public synchronized int getUnreadMessageCount() { SQLiteDatabase db = null; @@ -305,10 +300,105 @@ public synchronized void deleteMessage(String nonce) { } } + //endregion + + //region Files + + public void deleteAssociatedFiles(String messageNonce) { + SQLiteDatabase db = null; + try { + db = getWritableDatabase(); + int deleted = db.delete(TABLE_COMPOUND_MESSAGE_FILESTORE, COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?", new String[]{messageNonce}); + ApptentiveLog.d("Deleted %d stored files.", deleted); + } catch (SQLException sqe) { + ApptentiveLog.e("deleteAssociatedFiles EXCEPTION: " + sqe.getMessage()); + } + } + + public List getAssociatedFiles(String nonce) { + SQLiteDatabase db = null; + Cursor cursor = null; + List associatedFiles = new ArrayList(); + try { + db = getReadableDatabase(); + cursor = db.rawQuery(QUERY_MESSAGE_FILES_GET_BY_NONCE, new String[]{nonce}); + StoredFile ret; + if (cursor.moveToFirst()) { + do { + ret = new StoredFile(); + ret.setId(nonce); + ret.setLocalFilePath(cursor.getString(2)); + ret.setMimeType(cursor.getString(3)); + ret.setSourceUriOrPath(cursor.getString(4)); + ret.setApptentiveUri(cursor.getString(5)); + ret.setCreationTime(cursor.getLong(6)); + associatedFiles.add(ret); + } while (cursor.moveToNext()); + } + } catch (SQLException sqe) { + ApptentiveLog.e("getAssociatedFiles EXCEPTION: " + sqe.getMessage()); + } finally { + ensureClosed(cursor); + } + return associatedFiles.size() > 0 ? associatedFiles : null; + } + + /* + * Add a list of associated files to compound message file storage + * Caller of this method should ensure all associated files have the same message nonce + * @param associatedFiles list of associated files + * @return true if succeed + */ + public boolean addCompoundMessageFiles(List associatedFiles) { + String messageNonce = associatedFiles.get(0).getId(); + SQLiteDatabase db = null; + long ret = -1; + try { + + db = getWritableDatabase(); + db.beginTransaction(); + // Always delete existing rows with the same nonce to ensure add/update both work + db.delete(TABLE_COMPOUND_MESSAGE_FILESTORE, COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?", new String[]{messageNonce}); + + for (StoredFile file : associatedFiles) { + ContentValues values = new ContentValues(); + values.put(COMPOUND_FILESTORE_KEY_MESSAGE_NONCE, file.getId()); + values.put(COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH, file.getLocalFilePath()); + values.put(COMPOUND_FILESTORE_KEY_MIME_TYPE, file.getMimeType()); + values.put(COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI, file.getSourceUriOrPath()); + values.put(COMPOUND_FILESTORE_KEY_REMOTE_URL, file.getApptentiveUri()); + values.put(COMPOUND_FILESTORE_KEY_CREATION_TIME, file.getCreationTime()); + ret = db.insert(TABLE_COMPOUND_MESSAGE_FILESTORE, null, values); + } + db.setTransactionSuccessful(); + db.endTransaction(); + } catch (SQLException sqe) { + ApptentiveLog.e("addCompoundMessageFiles EXCEPTION: " + sqe.getMessage()); + } finally { + return ret != -1; + } + } + + //endregion + + //region Upgrade + + /** + * This method is called when an app is upgraded. Add alter table statements here for each version in a non-breaking + * switch, so that all the necessary upgrades occur for each older version. + */ + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + ApptentiveLog.d("ApptentiveDatabase.onUpgrade(db, %d, %d)", oldVersion, newVersion); + switch (oldVersion) { + case 1: + if (newVersion == 2) { + db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); + migrateToCompoundMessage(db); + } + } + } - // - // File Store - // private void migrateToCompoundMessage(SQLiteDatabase db) { Cursor cursor = null; // Migrate legacy stored files to compound message associated files @@ -441,80 +531,7 @@ private void migrateToCompoundMessage(SQLiteDatabase db) { } } - public void deleteAssociatedFiles(String messageNonce) { - SQLiteDatabase db = null; - try { - db = getWritableDatabase(); - int deleted = db.delete(TABLE_COMPOUND_MESSAGE_FILESTORE, COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?", new String[]{messageNonce}); - ApptentiveLog.d("Deleted %d stored files.", deleted); - } catch (SQLException sqe) { - ApptentiveLog.e("deleteAssociatedFiles EXCEPTION: " + sqe.getMessage()); - } - } - - public List getAssociatedFiles(String nonce) { - SQLiteDatabase db = null; - Cursor cursor = null; - List associatedFiles = new ArrayList(); - try { - db = getReadableDatabase(); - cursor = db.rawQuery(QUERY_MESSAGE_FILES_GET_BY_NONCE, new String[]{nonce}); - StoredFile ret; - if (cursor.moveToFirst()) { - do { - ret = new StoredFile(); - ret.setId(nonce); - ret.setLocalFilePath(cursor.getString(2)); - ret.setMimeType(cursor.getString(3)); - ret.setSourceUriOrPath(cursor.getString(4)); - ret.setApptentiveUri(cursor.getString(5)); - ret.setCreationTime(cursor.getLong(6)); - associatedFiles.add(ret); - } while (cursor.moveToNext()); - } - } catch (SQLException sqe) { - ApptentiveLog.e("getAssociatedFiles EXCEPTION: " + sqe.getMessage()); - } finally { - ensureClosed(cursor); - } - return associatedFiles.size() > 0 ? associatedFiles : null; - } - - /* - * Add a list of associated files to compound message file storage - * Caller of this method should ensure all associated files have the same message nonce - * @param associatedFiles list of associated files - * @return true if succeed - */ - public boolean addCompoundMessageFiles(List associatedFiles) { - String messageNonce = associatedFiles.get(0).getId(); - SQLiteDatabase db = null; - long ret = -1; - try { - - db = getWritableDatabase(); - db.beginTransaction(); - // Always delete existing rows with the same nonce to ensure add/update both work - db.delete(TABLE_COMPOUND_MESSAGE_FILESTORE, COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?", new String[]{messageNonce}); - - for (StoredFile file : associatedFiles) { - ContentValues values = new ContentValues(); - values.put(COMPOUND_FILESTORE_KEY_MESSAGE_NONCE, file.getId()); - values.put(COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH, file.getLocalFilePath()); - values.put(COMPOUND_FILESTORE_KEY_MIME_TYPE, file.getMimeType()); - values.put(COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI, file.getSourceUriOrPath()); - values.put(COMPOUND_FILESTORE_KEY_REMOTE_URL, file.getApptentiveUri()); - values.put(COMPOUND_FILESTORE_KEY_CREATION_TIME, file.getCreationTime()); - ret = db.insert(TABLE_COMPOUND_MESSAGE_FILESTORE, null, values); - } - db.setTransactionSuccessful(); - db.endTransaction(); - } catch (SQLException sqe) { - ApptentiveLog.e("addCompoundMessageFiles EXCEPTION: " + sqe.getMessage()); - } finally { - return ret != -1; - } - } + //endregion public void reset(Context context) { /** @@ -523,5 +540,4 @@ public void reset(Context context) { */ context.deleteDatabase(DATABASE_NAME); } - -} +} \ No newline at end of file From 1f75d167dac9b8cb59b9949eee344484730509f4 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 20 Mar 2017 12:01:18 -0700 Subject: [PATCH 170/465] Move some more methods around in Database. --- .../sdk/storage/ApptentiveDatabaseHelper.java | 308 +++++++++--------- 1 file changed, 151 insertions(+), 157 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index b5b4719ac..b0d8c885d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -150,6 +150,8 @@ public ApptentiveDatabaseHelper(Context context) { fileDir = context.getFilesDir(); } + //region Create & Upgrade + /** * This function is called only for new installs, and onUpgrade is not called in that case. Therefore, you must include the * latest complete set of DDL here. @@ -163,26 +165,156 @@ public void onCreate(SQLiteDatabase db) { db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); } - public void ensureClosed(SQLiteDatabase db) { + /** + * This method is called when an app is upgraded. Add alter table statements here for each version in a non-breaking + * switch, so that all the necessary upgrades occur for each older version. + */ + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + ApptentiveLog.d("ApptentiveDatabase.onUpgrade(db, %d, %d)", oldVersion, newVersion); + switch (oldVersion) { + case 1: + if (newVersion == 2) { + db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); + migrateToCompoundMessage(db); + } + } + } + + private void migrateToCompoundMessage(SQLiteDatabase db) { + Cursor cursor = null; + // Migrate legacy stored files to compound message associated files try { - if (db != null) { - db.close(); + cursor = db.rawQuery("SELECT * FROM " + TABLE_FILESTORE, null); + if (cursor.moveToFirst()) { + do { + String file_nonce = cursor.getString(0); + // Stored File id was in the format of "apptentive-file-nonce" + String patten = "apptentive-file-"; + String nonce = file_nonce.substring(file_nonce.indexOf(patten) + patten.length()); + ContentValues values = new ContentValues(); + values.put(COMPOUND_FILESTORE_KEY_MESSAGE_NONCE, nonce); + // Legacy file was stored in db by name only. Need to get the full path when migrated + String localFileName = cursor.getString(3); + values.put(COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH, (new File(fileDir, localFileName).getAbsolutePath())); + values.put(COMPOUND_FILESTORE_KEY_MIME_TYPE, cursor.getString(1)); + // Original file name might not be stored, i.e. sent by API, in which case, local stored file name will be used. + String originalFileName = cursor.getString(2); + if (TextUtils.isEmpty(originalFileName)) { + originalFileName = localFileName; + } + values.put(COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI, originalFileName); + values.put(COMPOUND_FILESTORE_KEY_REMOTE_URL, cursor.getString(4)); + values.put(COMPOUND_FILESTORE_KEY_CREATION_TIME, 0); // we didn't store creation time of legacy file message + db.insert(TABLE_COMPOUND_MESSAGE_FILESTORE, null, values); + + } while (cursor.moveToNext()); } - } catch (Exception e) { - ApptentiveLog.w("Error closing SQLite database.", e); + } catch (SQLException sqe) { + ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); + } finally { + ensureClosed(cursor); + } + // Migrate legacy message types to CompoundMessage Type + try { + cursor = db.rawQuery(QUERY_MESSAGE_GET_ALL_IN_ORDER, null); + if (cursor.moveToFirst()) { + do { + String json = cursor.getString(6); + JSONObject root = null; + boolean bUpdateRecord = false; + try { + root = new JSONObject(json); + ApptentiveMessage.Type type = ApptentiveMessage.Type.valueOf(root.getString(ApptentiveMessage.KEY_TYPE)); + switch (type) { + case TextMessage: + root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); + root.put(CompoundMessage.KEY_TEXT_ONLY, true); + bUpdateRecord = true; + break; + case FileMessage: + root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); + root.put(CompoundMessage.KEY_TEXT_ONLY, false); + bUpdateRecord = true; + break; + case AutomatedMessage: + root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); + root.put(CompoundMessage.KEY_TEXT_ONLY, true); + root.put(ApptentiveMessage.KEY_AUTOMATED, true); + bUpdateRecord = true; + break; + default: + break; + } + if (bUpdateRecord) { + String databaseId = cursor.getString(0); + ContentValues messageValues = new ContentValues(); + messageValues.put(MESSAGE_KEY_JSON, root.toString()); + db.update(TABLE_MESSAGE, messageValues, MESSAGE_KEY_DB_ID + " = ?", new String[]{databaseId}); + } + } catch (JSONException e) { + ApptentiveLog.v("Error parsing json as Message: %s", e, json); + } + } while (cursor.moveToNext()); + } + } catch (SQLException sqe) { + ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); + } finally { + ensureClosed(cursor); } - } - public void ensureClosed(Cursor cursor) { + // Migrate all pending payload messages + // Migrate legacy message types to CompoundMessage Type try { - if (cursor != null) { - cursor.close(); + cursor = db.rawQuery(QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER, new String[]{Payload.BaseType.message.name()}); + if (cursor.moveToFirst()) { + do { + String json = cursor.getString(2); + JSONObject root; + boolean bUpdateRecord = false; + try { + root = new JSONObject(json); + ApptentiveMessage.Type type = ApptentiveMessage.Type.valueOf(root.getString(ApptentiveMessage.KEY_TYPE)); + switch (type) { + case TextMessage: + root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); + root.put(CompoundMessage.KEY_TEXT_ONLY, true); + bUpdateRecord = true; + break; + case FileMessage: + root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); + root.put(CompoundMessage.KEY_TEXT_ONLY, false); + bUpdateRecord = true; + break; + case AutomatedMessage: + root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); + root.put(CompoundMessage.KEY_TEXT_ONLY, true); + root.put(ApptentiveMessage.KEY_AUTOMATED, true); + bUpdateRecord = true; + break; + default: + break; + } + if (bUpdateRecord) { + String databaseId = cursor.getString(0); + ContentValues messageValues = new ContentValues(); + messageValues.put(PAYLOAD_KEY_JSON, root.toString()); + db.update(TABLE_PAYLOAD, messageValues, PAYLOAD_KEY_DB_ID + " = ?", new String[]{databaseId}); + } + } catch (JSONException e) { + ApptentiveLog.v("Error parsing json as Message: %s", e, json); + } + } while (cursor.moveToNext()); } - } catch (Exception e) { - ApptentiveLog.w("Error closing SQLite cursor.", e); + } catch (SQLException sqe) { + ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); + } finally { + ensureClosed(cursor); } } + //endregion + //region Payloads /** @@ -381,158 +513,18 @@ public boolean addCompoundMessageFiles(List associatedFiles) { //endregion - //region Upgrade + // region Helpers - /** - * This method is called when an app is upgraded. Add alter table statements here for each version in a non-breaking - * switch, so that all the necessary upgrades occur for each older version. - */ - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - ApptentiveLog.d("ApptentiveDatabase.onUpgrade(db, %d, %d)", oldVersion, newVersion); - switch (oldVersion) { - case 1: - if (newVersion == 2) { - db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); - migrateToCompoundMessage(db); - } - } - } - - private void migrateToCompoundMessage(SQLiteDatabase db) { - Cursor cursor = null; - // Migrate legacy stored files to compound message associated files + private void ensureClosed(Cursor cursor) { try { - cursor = db.rawQuery("SELECT * FROM " + TABLE_FILESTORE, null); - if (cursor.moveToFirst()) { - do { - String file_nonce = cursor.getString(0); - // Stored File id was in the format of "apptentive-file-nonce" - String patten = "apptentive-file-"; - String nonce = file_nonce.substring(file_nonce.indexOf(patten) + patten.length()); - ContentValues values = new ContentValues(); - values.put(COMPOUND_FILESTORE_KEY_MESSAGE_NONCE, nonce); - // Legacy file was stored in db by name only. Need to get the full path when migrated - String localFileName = cursor.getString(3); - values.put(COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH, (new File(fileDir, localFileName).getAbsolutePath())); - values.put(COMPOUND_FILESTORE_KEY_MIME_TYPE, cursor.getString(1)); - // Original file name might not be stored, i.e. sent by API, in which case, local stored file name will be used. - String originalFileName = cursor.getString(2); - if (TextUtils.isEmpty(originalFileName)) { - originalFileName = localFileName; - } - values.put(COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI, originalFileName); - values.put(COMPOUND_FILESTORE_KEY_REMOTE_URL, cursor.getString(4)); - values.put(COMPOUND_FILESTORE_KEY_CREATION_TIME, 0); // we didn't store creation time of legacy file message - db.insert(TABLE_COMPOUND_MESSAGE_FILESTORE, null, values); - - } while (cursor.moveToNext()); - } - } catch (SQLException sqe) { - ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); - } finally { - ensureClosed(cursor); - } - // Migrate legacy message types to CompoundMessage Type - try { - cursor = db.rawQuery(QUERY_MESSAGE_GET_ALL_IN_ORDER, null); - if (cursor.moveToFirst()) { - do { - String json = cursor.getString(6); - JSONObject root = null; - boolean bUpdateRecord = false; - try { - root = new JSONObject(json); - ApptentiveMessage.Type type = ApptentiveMessage.Type.valueOf(root.getString(ApptentiveMessage.KEY_TYPE)); - switch (type) { - case TextMessage: - root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); - root.put(CompoundMessage.KEY_TEXT_ONLY, true); - bUpdateRecord = true; - break; - case FileMessage: - root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); - root.put(CompoundMessage.KEY_TEXT_ONLY, false); - bUpdateRecord = true; - break; - case AutomatedMessage: - root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); - root.put(CompoundMessage.KEY_TEXT_ONLY, true); - root.put(ApptentiveMessage.KEY_AUTOMATED, true); - bUpdateRecord = true; - break; - default: - break; - } - if (bUpdateRecord) { - String databaseId = cursor.getString(0); - ContentValues messageValues = new ContentValues(); - messageValues.put(MESSAGE_KEY_JSON, root.toString()); - db.update(TABLE_MESSAGE, messageValues, MESSAGE_KEY_DB_ID + " = ?", new String[]{databaseId}); - } - } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as Message: %s", e, json); - } - } while (cursor.moveToNext()); - } - } catch (SQLException sqe) { - ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); - } finally { - ensureClosed(cursor); - } - - // Migrate all pending payload messages - // Migrate legacy message types to CompoundMessage Type - try { - cursor = db.rawQuery(QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER, new String[]{Payload.BaseType.message.name()}); - if (cursor.moveToFirst()) { - do { - String json = cursor.getString(2); - JSONObject root; - boolean bUpdateRecord = false; - try { - root = new JSONObject(json); - ApptentiveMessage.Type type = ApptentiveMessage.Type.valueOf(root.getString(ApptentiveMessage.KEY_TYPE)); - switch (type) { - case TextMessage: - root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); - root.put(CompoundMessage.KEY_TEXT_ONLY, true); - bUpdateRecord = true; - break; - case FileMessage: - root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); - root.put(CompoundMessage.KEY_TEXT_ONLY, false); - bUpdateRecord = true; - break; - case AutomatedMessage: - root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); - root.put(CompoundMessage.KEY_TEXT_ONLY, true); - root.put(ApptentiveMessage.KEY_AUTOMATED, true); - bUpdateRecord = true; - break; - default: - break; - } - if (bUpdateRecord) { - String databaseId = cursor.getString(0); - ContentValues messageValues = new ContentValues(); - messageValues.put(PAYLOAD_KEY_JSON, root.toString()); - db.update(TABLE_PAYLOAD, messageValues, PAYLOAD_KEY_DB_ID + " = ?", new String[]{databaseId}); - } - } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as Message: %s", e, json); - } - } while (cursor.moveToNext()); + if (cursor != null) { + cursor.close(); } - } catch (SQLException sqe) { - ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); - } finally { - ensureClosed(cursor); + } catch (Exception e) { + ApptentiveLog.w("Error closing SQLite cursor.", e); } } - //endregion - public void reset(Context context) { /** * The following ONLY be used during development and testing. It will delete the database, including all saved @@ -540,4 +532,6 @@ public void reset(Context context) { */ context.deleteDatabase(DATABASE_NAME); } + + //endregion } \ No newline at end of file From 9ba3dced31767d1042064f6be6eab1bc078bdf19 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 20 Mar 2017 19:29:15 -0700 Subject: [PATCH 171/465] Rename upgrading method. --- .../android/sdk/storage/ApptentiveDatabaseHelper.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index b0d8c885d..8174b1106 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -174,14 +174,12 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { ApptentiveLog.d("ApptentiveDatabase.onUpgrade(db, %d, %d)", oldVersion, newVersion); switch (oldVersion) { case 1: - if (newVersion == 2) { - db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); - migrateToCompoundMessage(db); - } + db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); + upgradeVersion1to2(db); } } - private void migrateToCompoundMessage(SQLiteDatabase db) { + private void upgradeVersion1to2(SQLiteDatabase db) { Cursor cursor = null; // Migrate legacy stored files to compound message associated files try { From b8405a5aa360e79c4862987476398670097d861e Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 23 Mar 2017 14:59:30 -0700 Subject: [PATCH 172/465] Migrate payload database to add Conversation ID String to payloads. ANDROID-902 --- .../android/sdk/ApptentiveLogTag.java | 2 +- .../apptentive/android/sdk/model/Payload.java | 21 +++++++++++++----- .../sdk/storage/ApptentiveDatabaseHelper.java | 22 ++++++++++++++++--- .../sdk/storage/PayloadSendWorker.java | 1 + 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java index 7e3135ffc..828e98af6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java @@ -5,7 +5,7 @@ public enum ApptentiveLogTag { CONVERSATION(true), NOTIFICATIONS(true), MESSAGES(true), - TESTER_COMMANDS(false); + DATABASE(true); ApptentiveLogTag(boolean enabled) { this.enabled = enabled; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index d9b7d0a1c..72d4eb802 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -11,14 +11,12 @@ import org.json.JSONException; import org.json.JSONObject; -/** - * @author Sky Kelsey - */ public abstract class Payload extends JSONObject { - // These two are not stored in the JSON, only the DB. + // These three are not stored in the JSON, only the DB. private Long databaseId; private BaseType baseType; + private String conversationId; public Payload() { initBaseType(); @@ -29,6 +27,11 @@ public Payload(String json) throws JSONException { initBaseType(); } + public Payload(String json, String conversationId) throws JSONException { + this(json); + this.conversationId = conversationId; + } + /** * Each subclass must set its type in this method. */ @@ -66,6 +69,14 @@ protected void setBaseType(BaseType baseType) { this.baseType = baseType; } + public String getConversationId() { + return conversationId; + } + + public void setConversationId(String conversationId) { + this.conversationId = conversationId; + } + public enum BaseType { message, event, diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 8174b1106..fca411105 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -30,12 +30,14 @@ import java.util.ArrayList; import java.util.List; +import static com.apptentive.android.sdk.ApptentiveLogTag.DATABASE; + /** * There can be only one. SQLiteOpenHelper per database name that is. All new Apptentive tables must be defined here. */ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { - private static final int DATABASE_VERSION = 2; + private static final int DATABASE_VERSION = 3; public static final String DATABASE_NAME = "apptentive"; private static final int TRUE = 1; private static final int FALSE = 0; @@ -47,18 +49,21 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { public static final String PAYLOAD_KEY_DB_ID = "_id"; // 0 public static final String PAYLOAD_KEY_BASE_TYPE = "base_type"; // 1 public static final String PAYLOAD_KEY_JSON = "json"; // 2 + private static final String PAYLOAD_KEY_CONVERSATION_ID = "conversation_id"; // 3 private static final String TABLE_CREATE_PAYLOAD = "CREATE TABLE " + TABLE_PAYLOAD + " (" + PAYLOAD_KEY_DB_ID + " INTEGER PRIMARY KEY, " + PAYLOAD_KEY_BASE_TYPE + " TEXT, " + - PAYLOAD_KEY_JSON + " TEXT" + + PAYLOAD_KEY_JSON + " TEXT," + + PAYLOAD_KEY_CONVERSATION_ID + " TEXT" + ");"; public static final String QUERY_PAYLOAD_GET_NEXT_TO_SEND = "SELECT * FROM " + TABLE_PAYLOAD + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC LIMIT 1"; private static final String QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER = "SELECT * FROM " + TABLE_PAYLOAD + " WHERE " + PAYLOAD_KEY_BASE_TYPE + " = ?" + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC"; + private static final String UPGRADE_V2_to_v3_ALTER_PAYLOAD = "ALTER TABLE " + TABLE_PAYLOAD + " ADD COLUMN " + PAYLOAD_KEY_CONVERSATION_ID + " TEXT"; //endregion @@ -174,12 +179,15 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { ApptentiveLog.d("ApptentiveDatabase.onUpgrade(db, %d, %d)", oldVersion, newVersion); switch (oldVersion) { case 1: - db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); upgradeVersion1to2(db); + case 2: + upgradeVersion2to3(db); } } private void upgradeVersion1to2(SQLiteDatabase db) { + ApptentiveLog.i(DATABASE, "Upgrading Database from v1 to v2"); + db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); Cursor cursor = null; // Migrate legacy stored files to compound message associated files try { @@ -311,6 +319,11 @@ private void upgradeVersion1to2(SQLiteDatabase db) { } } + private void upgradeVersion2to3(SQLiteDatabase db) { + ApptentiveLog.i(DATABASE, "Upgrading Database from v2 to v3"); + db.execSQL(UPGRADE_V2_to_v3_ALTER_PAYLOAD); + } + //endregion //region Payloads @@ -328,6 +341,7 @@ public void addPayload(Payload... payloads) { ContentValues values = new ContentValues(); values.put(PAYLOAD_KEY_BASE_TYPE, payload.getBaseType().name()); values.put(PAYLOAD_KEY_JSON, payload.toString()); + values.put(PAYLOAD_KEY_CONVERSATION_ID, payload.getConversationId()); db.insert(TABLE_PAYLOAD, null, values); } db.setTransactionSuccessful(); @@ -376,9 +390,11 @@ public Payload getOldestUnsentPayload() { long databaseId = Long.parseLong(cursor.getString(0)); Payload.BaseType baseType = Payload.BaseType.parse(cursor.getString(1)); String json = cursor.getString(2); + String conversationId = cursor.getString(3); payload = PayloadFactory.fromJson(json, baseType); if (payload != null) { payload.setDatabaseId(databaseId); + payload.setConversationId(conversationId); } } return payload; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java index c34179ecb..a0d30756c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java @@ -142,6 +142,7 @@ public void run() { break; } ApptentiveLog.d("Got a payload to send: %s:%d", payload.getBaseType(), payload.getDatabaseId()); + ApptentiveLog.v("Payload Conversation ID: %s", payload.getConversationId()); ApptentiveHttpResponse response = null; From f5695292d5e6ea2841e982dc818082d2cad22e2d Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 23 Mar 2017 15:01:14 -0700 Subject: [PATCH 173/465] When enqueing a payload, include the Conversation ID, if set. Set it when the conversation state changes. ANDROID-904 --- .../sdk/storage/ApptentiveTaskManager.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index a6da7ff15..6520d8089 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -8,9 +8,15 @@ import android.content.Context; +import com.apptentive.android.sdk.ApptentiveNotifications; +import com.apptentive.android.sdk.conversation.Conversation; +import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; +import com.apptentive.android.sdk.notifications.ApptentiveNotification; +import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; +import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; import java.util.List; import java.util.concurrent.Callable; @@ -19,12 +25,18 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; +import static com.apptentive.android.sdk.conversation.ConversationState.UNDEFINED; -public class ApptentiveTaskManager implements PayloadStore, EventStore { +public class ApptentiveTaskManager implements PayloadStore, EventStore, ApptentiveNotificationObserver { private ApptentiveDatabaseHelper dbHelper; private ThreadPoolExecutor singleThreadExecutor; + // Set when receiving an ApptentiveNotification + private String currentConversationId; + /* * Creates an asynchronous task manager with one worker thread. This constructor must be invoked on the UI thread. */ @@ -43,6 +55,8 @@ public ApptentiveTaskManager(Context context) { // If no new task arrives in 30 seconds, the worker thread terminates; otherwise it will be reused singleThreadExecutor.allowCoreThreadTimeOut(true); + + ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, this); } @@ -67,6 +81,9 @@ private static class ApptentiveTaskResult { * a new message is added. */ public void addPayload(final Payload... payloads) { + for (Payload payload : payloads) { + payload.setConversationId(currentConversationId); + } singleThreadExecutor.execute(new Runnable() { @Override public void run() { @@ -135,4 +152,16 @@ public void reset(Context context) { dbHelper.reset(context); } + @Override + public void onReceiveNotification(ApptentiveNotification notification) { + if (notification.hasName(ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE)) { + Conversation conversation = notification.getUserInfo(NOTIFICATION_KEY_CONVERSATION, Conversation.class); + Assert.assertTrue(conversation != null && !conversation.hasState(UNDEFINED)); // sanity check + if (conversation.hasActiveState()) { + currentConversationId = conversation.getConversationId(); + } else { + currentConversationId = null; + } + } + } } \ No newline at end of file From ed9b5f80119e32ecbb4d1d32fec3eb830d1f7412 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 23 Mar 2017 15:50:32 -0700 Subject: [PATCH 174/465] Send Conversation state changed notification at correct time. 1. Make observers process it synchonously, before the SDK starts enqueing payloads. 2. Don't register lifecycle callbacks until the SDK is globally initialized. ANDROID-904 --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 2 +- .../android/sdk/conversation/ConversationManager.java | 2 +- .../apptentive/android/sdk/storage/ApptentiveTaskManager.java | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index adbeac38e..e802a82f7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -172,7 +172,6 @@ private ApptentiveInternal(Application application, String apiKey, String server cachedExecutor = Executors.newCachedThreadPool(); lifecycleCallbacks = new ApptentiveActivityLifecycleCallbacks(); - application.registerActivityLifecycleCallbacks(lifecycleCallbacks); } public static boolean isApptentiveRegistered() { @@ -211,6 +210,7 @@ static void createInstance(Application application, String apptentiveApiKey, fin try { sApptentiveInternal = new ApptentiveInternal(application, apptentiveApiKey, serverUrl); sApptentiveInternal.start(); // TODO: check the result of this call + application.registerActivityLifecycleCallbacks(sApptentiveInternal.lifecycleCallbacks); } catch (Exception e) { ApptentiveLog.e(e, "Exception while initializing ApptentiveInternal instance"); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index ce29d3374..254c64509 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -291,7 +291,7 @@ private void handleConversationStateChange(Conversation conversation) { Assert.assertTrue(conversation != null && !conversation.hasState(UNDEFINED)); // sanity check ApptentiveNotificationCenter.defaultCenter() - .postNotification(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, + .postNotificationSync(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, conversation)); if (conversation != null && conversation.hasActiveState()) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 6520d8089..3d0edafca 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -8,12 +8,10 @@ import android.content.Context; -import com.apptentive.android.sdk.ApptentiveNotifications; import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.StoredFile; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.notifications.ApptentiveNotification; import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; @@ -154,7 +152,7 @@ public void reset(Context context) { @Override public void onReceiveNotification(ApptentiveNotification notification) { - if (notification.hasName(ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE)) { + if (notification.hasName(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE)) { Conversation conversation = notification.getUserInfo(NOTIFICATION_KEY_CONVERSATION, Conversation.class); Assert.assertTrue(conversation != null && !conversation.hasState(UNDEFINED)); // sanity check if (conversation.hasActiveState()) { From 13d0dd6a155edf2015530282b79ef3c5e3ecca2d Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 28 Mar 2017 13:55:17 -0700 Subject: [PATCH 175/465] Update Apptentive to use the newest gradle / plugin. --- apptentive/build.gradle | 6 +++--- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- samples/apptentive-example/build.gradle | 12 ++++++------ tests/test-app/build.gradle | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apptentive/build.gradle b/apptentive/build.gradle index 933d33e8c..6aad3a927 100644 --- a/apptentive/build.gradle +++ b/apptentive/build.gradle @@ -24,12 +24,12 @@ dependencies { } android { - compileSdkVersion 24 - buildToolsVersion '24.0.3' + compileSdkVersion 25 + buildToolsVersion '25.0.2' defaultConfig { minSdkVersion 14 - targetSdkVersion 24 + targetSdkVersion 25 // BUILD_NUMBER is provided by Jenkins. Default to 1 in dev builds. versionCode System.getenv("BUILD_NUMBER") as Integer ?: 1 versionName project.version diff --git a/build.gradle b/build.gradle index 21ec059b0..d4a9d338e 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,6 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:2.3.0' } } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 50e3929ff..42156b3bf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Sep 21 15:22:15 PDT 2016 +#Tue Mar 28 10:38:53 PDT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/samples/apptentive-example/build.gradle b/samples/apptentive-example/build.gradle index d064ff50c..c05b1eff4 100644 --- a/samples/apptentive-example/build.gradle +++ b/samples/apptentive-example/build.gradle @@ -15,19 +15,19 @@ repositories { dependencies { compile project(':apptentive') - compile 'com.android.support:support-v4:23.3.0' - compile 'com.android.support:appcompat-v7:23.3.0' - compile 'com.android.support:cardview-v7:23.3.0' + compile 'com.android.support:support-v4:24.2.1' + compile 'com.android.support:appcompat-v7:24.2.1' + compile 'com.android.support:cardview-v7:24.2.1' compile 'com.google.android.gms:play-services-gcm:8.4.0' } android { - compileSdkVersion 23 - buildToolsVersion '23.0.3' + compileSdkVersion 25 + buildToolsVersion '25.0.2' defaultConfig { minSdkVersion 14 - targetSdkVersion 23 + targetSdkVersion 24 versionCode 1 versionName "1.0" multiDexEnabled true diff --git a/tests/test-app/build.gradle b/tests/test-app/build.gradle index 14148ca99..4e1a8545f 100644 --- a/tests/test-app/build.gradle +++ b/tests/test-app/build.gradle @@ -5,13 +5,13 @@ repositories { } android { - compileSdkVersion 23 + compileSdkVersion 24 - buildToolsVersion '23.0.3' + buildToolsVersion '25.0.2' defaultConfig { minSdkVersion 14 - targetSdkVersion 23 + targetSdkVersion 24 versionCode 4 versionName "2.0" } From 226dc27bcb19d47aeebf36760b955c8a0b4f3ce8 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 28 Mar 2017 14:01:58 -0700 Subject: [PATCH 176/465] Remove unneeded permissions from Example App. --- samples/apptentive-example/src/main/AndroidManifest.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/samples/apptentive-example/src/main/AndroidManifest.xml b/samples/apptentive-example/src/main/AndroidManifest.xml index d3c84f1d9..b2cbf566d 100755 --- a/samples/apptentive-example/src/main/AndroidManifest.xml +++ b/samples/apptentive-example/src/main/AndroidManifest.xml @@ -2,12 +2,6 @@ - - - - - - From 5b9f9a70141f17307fe959e47c35b9f0d9b932a7 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 28 Mar 2017 14:58:25 -0700 Subject: [PATCH 177/465] Combine SDK and AppRelease Updates --- .../sdk/model/SdkAndAppReleasePayload.java | 225 ++++-------------- 1 file changed, 40 insertions(+), 185 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index a07ca18b5..b71fe708b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -27,27 +27,8 @@ */ public class SdkAndAppReleasePayload extends Payload { - //region Sdk payload keys - private static final String KEY_VERSION = "version"; - private static final String KEY_PROGRAMMING_LANGUAGE = "programming_language"; - private static final String KEY_AUTHOR_NAME = "author_name"; - private static final String KEY_AUTHOR_EMAIL = "author_email"; - private static final String KEY_PLATFORM = "platform"; - private static final String KEY_DISTRIBUTION = "distribution"; - private static final String KEY_DISTRIBUTION_VERSION = "distribution_version"; - //endregion - - //region AppRelease keys - private static final String KEY_TYPE = "type"; - private static final String KEY_VERSION_NAME = "version_name"; - private static final String KEY_VERSION_CODE = "version_code"; - private static final String KEY_IDENTIFIER = "identifier"; - private static final String KEY_TARGET_SDK_VERSION = "target_sdk_version"; - private static final String KEY_APP_STORE = "app_store"; - private static final String KEY_STYLE_INHERIT = "inheriting_styles"; - private static final String KEY_STYLE_OVERRIDE = "overriding_styles"; - private static final String KEY_DEBUG = "debug"; - //endregion + private final com.apptentive.android.sdk.model.Sdk sdk; + private final com.apptentive.android.sdk.model.AppRelease appRelease; public static SdkAndAppReleasePayload fromJson(String json) { try { @@ -60,12 +41,17 @@ public static SdkAndAppReleasePayload fromJson(String json) { return null; } - public SdkAndAppReleasePayload(String json) throws JSONException { + private SdkAndAppReleasePayload(String json) throws JSONException { super(json); + + sdk = new com.apptentive.android.sdk.model.Sdk(getJSONObject("sdk").toString()); + appRelease = new com.apptentive.android.sdk.model.AppRelease(getJSONObject("app_release").toString()); } public SdkAndAppReleasePayload() { super(); + sdk = new com.apptentive.android.sdk.model.Sdk(); + appRelease = new com.apptentive.android.sdk.model.AppRelease(); } //region Inheritance @@ -76,266 +62,135 @@ public void initBaseType() { //region Sdk getters/setters public String getVersion() { - try { - if(!isNull(KEY_VERSION)) { - return getString(KEY_VERSION); - } - } catch (JSONException e) { - // Ignore - } - return null; + return sdk.getVersion(); } public void setVersion(String version) { - try { - put(KEY_VERSION, version); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_VERSION); - } + sdk.setVersion(version); } public String getProgrammingLanguage() { - try { - if(!isNull(KEY_PROGRAMMING_LANGUAGE)) { - return getString(KEY_PROGRAMMING_LANGUAGE); - } - } catch (JSONException e) { - // Ignore - } - return null; + return sdk.getProgrammingLanguage(); } public void setProgrammingLanguage(String programmingLanguage) { - try { - put(KEY_PROGRAMMING_LANGUAGE, programmingLanguage); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_PROGRAMMING_LANGUAGE); - } + sdk.setProgrammingLanguage(programmingLanguage); } public String getAuthorName() { - try { - if(!isNull(KEY_AUTHOR_NAME)) { - return getString(KEY_AUTHOR_NAME); - } - } catch (JSONException e) { - // Ignore - } - return null; + return sdk.getAuthorName(); } public void setAuthorName(String authorName) { - try { - put(KEY_AUTHOR_NAME, authorName); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_AUTHOR_NAME); - } + sdk.setAuthorName(authorName); } public String getAuthorEmail() { - try { - if(!isNull(KEY_AUTHOR_EMAIL)) { - return getString(KEY_AUTHOR_EMAIL); - } - } catch (JSONException e) { - // Ignore - } - return null; + return sdk.getAuthorEmail(); } public void setAuthorEmail(String authorEmail) { - try { - put(KEY_AUTHOR_EMAIL, authorEmail); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_AUTHOR_EMAIL); - } + sdk.setAuthorEmail(authorEmail); } public String getPlatform() { - try { - if(!isNull(KEY_PLATFORM)) { - return getString(KEY_PLATFORM); - } - } catch (JSONException e) { - // Ignore - } - return null; + return sdk.getPlatform(); } public void setPlatform(String platform) { - try { - put(KEY_PLATFORM, platform); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_PLATFORM); - } + sdk.setPlatform(platform); } public String getDistribution() { - try { - if(!isNull(KEY_DISTRIBUTION)) { - return getString(KEY_DISTRIBUTION); - } - } catch (JSONException e) { - // Ignore - } - return null; + return sdk.getDistribution(); } public void setDistribution(String distribution) { - try { - put(KEY_DISTRIBUTION, distribution); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_DISTRIBUTION); - } + sdk.setDistribution(distribution); } public String getDistributionVersion() { - try { - if(!isNull(KEY_DISTRIBUTION_VERSION)) { - return getString(KEY_DISTRIBUTION_VERSION); - } - } catch (JSONException e) { - // Ignore - } - return null; + return sdk.getDistributionVersion(); } public void setDistributionVersion(String distributionVersion) { - try { - put(KEY_DISTRIBUTION_VERSION, distributionVersion); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_DISTRIBUTION_VERSION); - } + sdk.setDistributionVersion(distributionVersion); } //endregion //region AppRelease getters/setters public String getType() { - if (!isNull(KEY_TYPE)) { - return optString(KEY_TYPE, null); - } - return null; + return appRelease.getType(); } public void setType(String type) { - try { - put(KEY_TYPE, type); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_TYPE); - } + appRelease.setType(type); } public String getVersionName() { - if (!isNull(KEY_VERSION_NAME)) { - return optString(KEY_VERSION_NAME, null); - } - return null; + return appRelease.getVersionName(); } public void setVersionName(String versionName) { - try { - put(KEY_VERSION_NAME, versionName); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_VERSION_NAME); - } + appRelease.setVersionName(versionName); } public int getVersionCode() { - if (!isNull(KEY_VERSION_CODE)) { - return optInt(KEY_VERSION_CODE, -1); - } - return -1; + return appRelease.getVersionCode(); } public void setVersionCode(int versionCode) { - try { - put(KEY_VERSION_CODE, versionCode); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_VERSION_CODE); - } + appRelease.setVersionCode(versionCode); } public String getIdentifier() { - if (!isNull(KEY_IDENTIFIER)) { - return optString(KEY_IDENTIFIER, null); - } - return null; + return appRelease.getIdentifier(); } public void setIdentifier(String identifier) { - try { - put(KEY_IDENTIFIER, identifier); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_IDENTIFIER); - } + appRelease.setIdentifier(identifier); } public String getTargetSdkVersion() { - if (!isNull(KEY_TARGET_SDK_VERSION)) { - return optString(KEY_TARGET_SDK_VERSION); - } - return null; + return appRelease.getTargetSdkVersion(); } public void setTargetSdkVersion(String targetSdkVersion) { - try { - put(KEY_TARGET_SDK_VERSION, targetSdkVersion); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_TARGET_SDK_VERSION); - } + appRelease.setTargetSdkVersion(targetSdkVersion); } public String getAppStore() { - if (!isNull(KEY_APP_STORE)) { - return optString(KEY_APP_STORE, null); - } - return null; + return appRelease.getAppStore(); } public void setAppStore(String appStore) { - try { - put(KEY_APP_STORE, appStore); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_APP_STORE); - } + appRelease.setAppStore(appStore); } // Flag for whether the apptentive is inheriting styles from the host app public boolean getInheritStyle() { - return optBoolean(KEY_STYLE_INHERIT); + return appRelease.getInheritStyle(); } public void setInheritStyle(boolean inheritStyle) { - try { - put(KEY_STYLE_INHERIT, inheritStyle); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_STYLE_INHERIT); - } + appRelease.setInheritStyle(inheritStyle); } // Flag for whether the app is overriding any Apptentive Styles public boolean getOverrideStyle() { - return optBoolean(KEY_STYLE_OVERRIDE); + return appRelease.getOverrideStyle(); } public void setOverrideStyle(boolean overrideStyle) { - try { - put(KEY_STYLE_OVERRIDE, overrideStyle); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_STYLE_OVERRIDE); - } + appRelease.setOverrideStyle(overrideStyle); } public boolean getDebug() { - return optBoolean(KEY_DEBUG); + return appRelease.getDebug(); } public void setDebug(boolean debug) { - try { - put(KEY_DEBUG, debug); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_DEBUG); - } + appRelease.setDebug(debug); } public static AppRelease generateCurrentAppRelease(Context context) { From 07d8f6035310fb1dde562ca0fd64106a24ffd8d9 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 28 Mar 2017 15:09:00 -0700 Subject: [PATCH 178/465] Fixed initializing SdkAndAppReleasePayload --- .../android/sdk/model/SdkAndAppReleasePayload.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index b71fe708b..3a1e25e81 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -52,6 +52,13 @@ public SdkAndAppReleasePayload() { super(); sdk = new com.apptentive.android.sdk.model.Sdk(); appRelease = new com.apptentive.android.sdk.model.AppRelease(); + + try { + put("sdk", sdk); + put("app_release", appRelease); + } catch (JSONException e) { + throw new IllegalStateException(e); // that should not happen but we can't ignore that + } } //region Inheritance From 2fddf64a4146661fd1bc6efdff6a2c109e8153ec Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 28 Mar 2017 15:45:29 -0700 Subject: [PATCH 179/465] Updated build tools version to 25.0.0 --- apptentive/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/build.gradle b/apptentive/build.gradle index 933d33e8c..cb99af4a6 100644 --- a/apptentive/build.gradle +++ b/apptentive/build.gradle @@ -25,7 +25,7 @@ dependencies { android { compileSdkVersion 24 - buildToolsVersion '24.0.3' + buildToolsVersion '25.0.0' defaultConfig { minSdkVersion 14 From a517a66819e0ceb0bfa08d41acdb10a40d929555 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 28 Mar 2017 16:21:37 -0700 Subject: [PATCH 180/465] Update missing conversation ids --- .../sdk/storage/ApptentiveDatabaseHelper.java | 23 +++++++++++++++++-- .../sdk/storage/ApptentiveTaskManager.java | 23 +++++++++++++++---- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index fca411105..b1b2e8743 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -21,7 +21,7 @@ import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; -import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; +import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; import org.json.JSONObject; @@ -60,7 +60,8 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { PAYLOAD_KEY_CONVERSATION_ID + " TEXT" + ");"; - public static final String QUERY_PAYLOAD_GET_NEXT_TO_SEND = "SELECT * FROM " + TABLE_PAYLOAD + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC LIMIT 1"; + public static final String QUERY_PAYLOAD_GET_NEXT_TO_SEND = "SELECT * FROM " + TABLE_PAYLOAD + " WHERE " + PAYLOAD_KEY_CONVERSATION_ID + " IS NOT NULL ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC LIMIT 1"; + public static final String QUERY_UPDATE_MISSING_CONVERSATION_IDS = StringUtils.format("UPDATE %s SET %s = ? WHERE %s IS NULL", TABLE_PAYLOAD, PAYLOAD_KEY_CONVERSATION_ID, PAYLOAD_KEY_CONVERSATION_ID); private static final String QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER = "SELECT * FROM " + TABLE_PAYLOAD + " WHERE " + PAYLOAD_KEY_BASE_TYPE + " = ?" + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC"; private static final String UPGRADE_V2_to_v3_ALTER_PAYLOAD = "ALTER TABLE " + TABLE_PAYLOAD + " ADD COLUMN " + PAYLOAD_KEY_CONVERSATION_ID + " TEXT"; @@ -406,6 +407,24 @@ public Payload getOldestUnsentPayload() { } } + public void updateMissingConversationIds(String conversationId) { + if (StringUtils.isNullOrEmpty(conversationId)) { + throw new IllegalArgumentException("Conversation id is null or empty"); + } + + Cursor cursor = null; + try { + SQLiteDatabase db = getWritableDatabase(); + cursor = db.rawQuery(QUERY_UPDATE_MISSING_CONVERSATION_IDS, new String[] { conversationId }); + cursor.moveToFirst(); // we need to move a cursor in order to update database + ApptentiveLog.v(DATABASE, "Updated missing conversation ids"); + } catch (SQLException e) { + ApptentiveLog.e(e, "Exception while updating missing conversation ids"); + } finally { + ensureClosed(cursor); + } + } + //endregion //region Messages diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 3d0edafca..064b41143 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -9,7 +9,6 @@ import android.content.Context; import com.apptentive.android.sdk.conversation.Conversation; -import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.notifications.ApptentiveNotification; @@ -23,9 +22,9 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; -import static com.apptentive.android.sdk.conversation.ConversationState.UNDEFINED; +import static com.apptentive.android.sdk.ApptentiveNotifications.*; +import static com.apptentive.android.sdk.conversation.ConversationState.*; +import static com.apptentive.android.sdk.debug.Assert.*; public class ApptentiveTaskManager implements PayloadStore, EventStore, ApptentiveNotificationObserver { @@ -154,9 +153,23 @@ public void reset(Context context) { public void onReceiveNotification(ApptentiveNotification notification) { if (notification.hasName(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE)) { Conversation conversation = notification.getUserInfo(NOTIFICATION_KEY_CONVERSATION, Conversation.class); - Assert.assertTrue(conversation != null && !conversation.hasState(UNDEFINED)); // sanity check + assertTrue(conversation != null && !conversation.hasState(UNDEFINED)); // sanity check if (conversation.hasActiveState()) { + assertNotNull(conversation.getConversationId()); currentConversationId = conversation.getConversationId(); + + // when the Conversation ID comes back from the server, we need to update + // the payloads that may have already been enqueued so + // that they each have the Conversation ID. + if (conversation.hasState(ANONYMOUS)) { + singleThreadExecutor.execute(new Runnable() { + @Override + public void run() { + dbHelper.updateMissingConversationIds(currentConversationId); + } + }); + } + } else { currentConversationId = null; } From f0d0198f5f0744ebc8d326db60281faad93f4b9a Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 28 Mar 2017 16:26:37 -0700 Subject: [PATCH 181/465] =?UTF-8?q?List=20every=20payload=20(including=20t?= =?UTF-8?q?hose=20with=20=E2=80=98null=E2=80=99=20conversation=20ids)=20wh?= =?UTF-8?q?en=20sending=20data=20to=20the=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/sdk/storage/ApptentiveDatabaseHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index b1b2e8743..c26d9be58 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -60,7 +60,7 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { PAYLOAD_KEY_CONVERSATION_ID + " TEXT" + ");"; - public static final String QUERY_PAYLOAD_GET_NEXT_TO_SEND = "SELECT * FROM " + TABLE_PAYLOAD + " WHERE " + PAYLOAD_KEY_CONVERSATION_ID + " IS NOT NULL ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC LIMIT 1"; + public static final String QUERY_PAYLOAD_GET_NEXT_TO_SEND = "SELECT * FROM " + TABLE_PAYLOAD + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC LIMIT 1"; public static final String QUERY_UPDATE_MISSING_CONVERSATION_IDS = StringUtils.format("UPDATE %s SET %s = ? WHERE %s IS NULL", TABLE_PAYLOAD, PAYLOAD_KEY_CONVERSATION_ID, PAYLOAD_KEY_CONVERSATION_ID); private static final String QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER = "SELECT * FROM " + TABLE_PAYLOAD + " WHERE " + PAYLOAD_KEY_BASE_TYPE + " = ?" + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC"; From 6668129b3fa19645d37999c8055c04e9d28b09a7 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 29 Mar 2017 09:55:39 -0700 Subject: [PATCH 182/465] Update Example app to use latest Firebase FCM, and latest Apptentive maven dependency. --- samples/apptentive-example/build.gradle | 11 ++-- .../apptentive-example/google-services.json | 35 ++++++---- .../src/main/AndroidManifest.xml | 40 +++--------- .../android/example/ExampleActivity.java | 25 +++---- .../android/example/ExampleApplication.java | 4 +- .../push/MyFirebaseInstanceIdService.java | 23 +++++++ .../push/MyFirebaseMessagingService.java | 65 +++++++++++++++++++ .../example/push/MyGcmListenerService.java | 56 ---------------- .../push/MyInstanceIdListenerService.java | 22 ------- .../push/RegistrationIntentService.java | 41 ------------ 10 files changed, 133 insertions(+), 189 deletions(-) create mode 100644 samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyFirebaseInstanceIdService.java create mode 100644 samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyFirebaseMessagingService.java delete mode 100644 samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyGcmListenerService.java delete mode 100644 samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyInstanceIdListenerService.java delete mode 100644 samples/apptentive-example/src/main/java/com/apptentive/android/example/push/RegistrationIntentService.java diff --git a/samples/apptentive-example/build.gradle b/samples/apptentive-example/build.gradle index c05b1eff4..dc277ef7c 100644 --- a/samples/apptentive-example/build.gradle +++ b/samples/apptentive-example/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.google.gms:google-services:2.1.0' + classpath 'com.google.gms:google-services:3.0.0' } } @@ -14,11 +14,8 @@ repositories { } dependencies { - compile project(':apptentive') - compile 'com.android.support:support-v4:24.2.1' - compile 'com.android.support:appcompat-v7:24.2.1' - compile 'com.android.support:cardview-v7:24.2.1' - compile 'com.google.android.gms:play-services-gcm:8.4.0' + compile 'com.apptentive:apptentive-android:3.4.1' + compile 'com.google.firebase:firebase-messaging:10.2.1' } android { @@ -27,7 +24,7 @@ android { defaultConfig { minSdkVersion 14 - targetSdkVersion 24 + targetSdkVersion 25 versionCode 1 versionName "1.0" multiDexEnabled true diff --git a/samples/apptentive-example/google-services.json b/samples/apptentive-example/google-services.json index 327ee48f5..1d392aa22 100644 --- a/samples/apptentive-example/google-services.json +++ b/samples/apptentive-example/google-services.json @@ -1,39 +1,46 @@ { "project_info": { - "project_id": "apptentive-example-app", "project_number": "864112465884", - "name": "Apptentive Example App" + "firebase_url": "https://apptentive-example-app.firebaseio.com", + "project_id": "apptentive-example-app", + "storage_bucket": "apptentive-example-app.appspot.com" }, "client": [ { "client_info": { - "client_id": "android:com.apptentive.android.example", - "client_type": 1, + "mobilesdk_app_id": "1:864112465884:android:f009d8e2b2851a1c", "android_client_info": { "package_name": "com.apptentive.android.example" } }, - "oauth_client": [], + "oauth_client": [ + { + "client_id": "864112465884-vdhbg3arakm2n71a9rhuuui843dq0q2j.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "864112465884-5d35u6lus8uqmgieh30aeg645ofkr5ni.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyBki4xmuidZeU9UjTJscOKIysSGUWGb-Ps" + } + ], "services": { "analytics_service": { "status": 1 }, - "cloud_messaging_service": { - "status": 2, - "apns_config": [] - }, "appinvite_service": { "status": 1, "other_platform_oauth_client": [] }, - "google_signin_service": { - "status": 1 - }, "ads_service": { - "status": 1 + "status": 2 } } } ], - "ARTIFACT_VERSION": "1" + "configuration_version": "1" } \ No newline at end of file diff --git a/samples/apptentive-example/src/main/AndroidManifest.xml b/samples/apptentive-example/src/main/AndroidManifest.xml index b2cbf566d..9245f8732 100755 --- a/samples/apptentive-example/src/main/AndroidManifest.xml +++ b/samples/apptentive-example/src/main/AndroidManifest.xml @@ -1,17 +1,6 @@ - - - - - - - - - - - - - - - - - - - - + + - + - + + - + - - - \ No newline at end of file diff --git a/samples/apptentive-example/src/main/java/com/apptentive/android/example/ExampleActivity.java b/samples/apptentive-example/src/main/java/com/apptentive/android/example/ExampleActivity.java index dabe620ed..1751a2190 100755 --- a/samples/apptentive-example/src/main/java/com/apptentive/android/example/ExampleActivity.java +++ b/samples/apptentive-example/src/main/java/com/apptentive/android/example/ExampleActivity.java @@ -1,17 +1,15 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ package com.apptentive.android.example; -import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; -import com.apptentive.android.example.push.RegistrationIntentService; import com.apptentive.android.sdk.Apptentive; /** @@ -23,19 +21,6 @@ public class ExampleActivity extends AppCompatActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); - - // GCM: Start IntentService to register this application. - Intent intent = new Intent(this, RegistrationIntentService.class); - startService(intent); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - // Only engage if this window is gaining focus. - if (hasFocus) { - Apptentive.handleOpenedPushNotification(this); - } } /** @@ -44,4 +29,12 @@ public void onWindowFocusChanged(boolean hasFocus) { public void onMessageCenterButtonPressed(@SuppressWarnings("unused") View view) { Apptentive.showMessageCenter(this); } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (true) { + Apptentive.engage(this, "main_activity_focused"); + } + } } diff --git a/samples/apptentive-example/src/main/java/com/apptentive/android/example/ExampleApplication.java b/samples/apptentive-example/src/main/java/com/apptentive/android/example/ExampleApplication.java index c1955369e..1784bd29c 100644 --- a/samples/apptentive-example/src/main/java/com/apptentive/android/example/ExampleApplication.java +++ b/samples/apptentive-example/src/main/java/com/apptentive/android/example/ExampleApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -11,6 +11,8 @@ import com.apptentive.android.sdk.Apptentive; public class ExampleApplication extends Application { + public static final String TAG = "ApptentiveExample"; + @Override public void onCreate() { super.onCreate(); diff --git a/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyFirebaseInstanceIdService.java b/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyFirebaseInstanceIdService.java new file mode 100644 index 000000000..7575739d0 --- /dev/null +++ b/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyFirebaseInstanceIdService.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.example.push; + +import android.util.Log; + +import com.apptentive.android.example.ExampleApplication; +import com.apptentive.android.sdk.Apptentive; +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.FirebaseInstanceIdService; + +public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService { + @Override + public void onTokenRefresh() { + String token = FirebaseInstanceId.getInstance().getToken(); + Log.e(ExampleApplication.TAG, "Refreshed InstanceId token: " + token); + Apptentive.setPushNotificationIntegration(Apptentive.PUSH_PROVIDER_APPTENTIVE, token); + } +} diff --git a/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyFirebaseMessagingService.java b/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyFirebaseMessagingService.java new file mode 100644 index 000000000..f52008c27 --- /dev/null +++ b/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyFirebaseMessagingService.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.example.push; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.media.RingtoneManager; +import android.net.Uri; +import android.support.v4.app.NotificationCompat; +import android.util.Log; + +import com.apptentive.android.example.ExampleApplication; +import com.apptentive.android.example.R; +import com.apptentive.android.sdk.Apptentive; +import com.apptentive.android.sdk.ApptentiveLog; +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + +import java.util.Map; + +public class MyFirebaseMessagingService extends FirebaseMessagingService { + + @Override + public void onMessageReceived(RemoteMessage remoteMessage) { + super.onMessageReceived(remoteMessage); + Log.e(ExampleApplication.TAG, "onMessageReceived()"); + logPushBundle(remoteMessage); + Map data = remoteMessage.getData(); + + PendingIntent pendingIntent = Apptentive.buildPendingIntentFromPushNotification(data); + String title = Apptentive.getTitleFromApptentivePush(data); + String body = Apptentive.getBodyFromApptentivePush(data); + + if (pendingIntent != null) { + ApptentiveLog.e("Title: " + title); + ApptentiveLog.e("Body: " + body); + + Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.notification) + .setContentTitle(title) + .setContentText(body) + .setAutoCancel(true) + .setSound(defaultSoundUri) + .setContentIntent(pendingIntent); + NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.notify(0, notificationBuilder.build()); + } + } + + private static void logPushBundle(RemoteMessage remoteMessage) { + Map data = remoteMessage.getData(); + Log.e(ExampleApplication.TAG, "Push Data:"); + for (String key : data.keySet()) { + String value = data.get(key); + Log.e(ExampleApplication.TAG, " " + key + " : " + value); + } + Log.e(ExampleApplication.TAG, data.get("title") + ": " + data.get("body")); + } +} diff --git a/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyGcmListenerService.java b/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyGcmListenerService.java deleted file mode 100644 index 1f3553ad5..000000000 --- a/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyGcmListenerService.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.example.push; - -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.NotificationCompat; - -import com.apptentive.android.example.ExampleActivity; -import com.apptentive.android.example.R; -import com.apptentive.android.sdk.Apptentive; -import com.apptentive.android.sdk.ApptentiveLog; -import com.google.android.gms.gcm.GcmListenerService; - -/** - * @author Sky Kelsey - */ -public class MyGcmListenerService extends GcmListenerService { - @Override - public void onMessageReceived(String from, Bundle data) { - String title = data.getString("gcm.notification.title"); - String body = data.getString("gcm.notification.body"); - ApptentiveLog.e("From: " + from); - ApptentiveLog.e("Title: " + title); - ApptentiveLog.e("Body: " + body); - - Intent intent = new Intent(this, ExampleActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - - Apptentive.setPendingPushNotification(data); - - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent, PendingIntent.FLAG_ONE_SHOT); - - Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); - NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) - .setSmallIcon(R.drawable.notification) - .setContentTitle(title) - .setContentText(body) - .setAutoCancel(true) - .setSound(defaultSoundUri) - .setContentIntent(pendingIntent); - - NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - - notificationManager.notify(0 /* ID of notification */, notificationBuilder.build()); - } -} diff --git a/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyInstanceIdListenerService.java b/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyInstanceIdListenerService.java deleted file mode 100644 index c666513dd..000000000 --- a/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyInstanceIdListenerService.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.example.push; - -import android.content.Intent; - -import com.google.android.gms.iid.InstanceIDListenerService; - -/** - * @author Sky Kelsey - */ -public class MyInstanceIdListenerService extends InstanceIDListenerService { - @Override - public void onTokenRefresh() { - Intent intent = new Intent(this, RegistrationIntentService.class); - startService(intent); - } -} diff --git a/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/RegistrationIntentService.java b/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/RegistrationIntentService.java deleted file mode 100644 index 6a1008fad..000000000 --- a/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/RegistrationIntentService.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.example.push; - -import android.app.IntentService; -import android.content.Intent; - -import com.apptentive.android.example.R; -import com.apptentive.android.sdk.Apptentive; -import com.apptentive.android.sdk.ApptentiveLog; -import com.google.android.gms.gcm.GoogleCloudMessaging; -import com.google.android.gms.iid.InstanceID; - -import java.io.IOException; - -/** - * @author Sky Kelsey - */ -public class RegistrationIntentService extends IntentService { - - private static final String TAG = "RegistrationIntentService"; - - public RegistrationIntentService() { - super(TAG); - } - - @Override - protected void onHandleIntent(Intent intent) { - InstanceID instanceID = InstanceID.getInstance(this); - try { - String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); - Apptentive.setPushNotificationIntegration(Apptentive.PUSH_PROVIDER_APPTENTIVE, token); - } catch (IOException e) { - ApptentiveLog.e("Unable to get instanceId token.", e); - } - } -} From c44a2e53ac40628ce9829f16bed6f54cd47b75c7 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 29 Mar 2017 10:08:03 -0700 Subject: [PATCH 183/465] Stop tracking `.idea/runConfigurations` --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index cd6997439..65a64d0fe 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ build/ .idea/vcs.xml .idea/workspace.xml .idea/libraries +.idea/runConfigurations projectFilesBackup/ # Eclipse From a952f2c224c8b48f10987cf3e11f50578540f962 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 29 Mar 2017 10:09:55 -0700 Subject: [PATCH 184/465] Remove runConfigurations files from repo --- .idea/runConfigurations/Apptentive_Tests.xml | 46 -------------------- 1 file changed, 46 deletions(-) delete mode 100644 .idea/runConfigurations/Apptentive_Tests.xml diff --git a/.idea/runConfigurations/Apptentive_Tests.xml b/.idea/runConfigurations/Apptentive_Tests.xml deleted file mode 100644 index ea16eeb5a..000000000 --- a/.idea/runConfigurations/Apptentive_Tests.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - \ No newline at end of file From 2de021e624145e7a642ad8ace7c45382df3e23db Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 31 Mar 2017 14:25:47 -0700 Subject: [PATCH 185/465] Fixed conversation loading events --- .../com/apptentive/android/sdk/ApptentiveLogTag.java | 3 ++- .../android/sdk/conversation/ConversationManager.java | 9 +++++++-- .../java/com/apptentive/android/sdk/debug/Tester.java | 6 ++++++ .../com/apptentive/android/sdk/debug/TesterEvent.java | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java index 828e98af6..2fc888887 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java @@ -5,7 +5,8 @@ public enum ApptentiveLogTag { CONVERSATION(true), NOTIFICATIONS(true), MESSAGES(true), - DATABASE(true); + DATABASE(true), + TESTER_COMMANDS(true); ApptentiveLogTag(boolean enabled) { this.enabled = enabled; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 254c64509..87f9ce76b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -41,7 +41,7 @@ import static com.apptentive.android.sdk.conversation.ConversationState.UNDEFINED; import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; import static com.apptentive.android.sdk.debug.TesterEvent.EVT_CONVERSATION_CREATE; -import static com.apptentive.android.sdk.debug.TesterEvent.EVT_CONVERSATION_LOAD_ACTIVE; +import static com.apptentive.android.sdk.debug.TesterEvent.EVT_CONVERSATION_LOAD; import static com.apptentive.android.sdk.debug.TesterEvent.EVT_CONVERSATION_METADATA_LOAD; /** @@ -113,9 +113,13 @@ public boolean loadActiveConversation(Context context) { // attempt to load existing conversation activeConversation = loadActiveConversationGuarded(); - dispatchDebugEvent(EVT_CONVERSATION_LOAD_ACTIVE, activeConversation != null); if (activeConversation != null) { + dispatchDebugEvent(EVT_CONVERSATION_LOAD, + "successful", Boolean.TRUE, + "conversation_state", activeConversation.getState().toString(), + "conversation_identifier", activeConversation.getConversationId()); + handleConversationStateChange(activeConversation); return true; } @@ -124,6 +128,7 @@ public boolean loadActiveConversation(Context context) { ApptentiveLog.e(e, "Exception while loading active conversation"); } + dispatchDebugEvent(EVT_CONVERSATION_LOAD, "successful", Boolean.FALSE); return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java index 485b97112..d1c80d394 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Tester.java @@ -58,6 +58,12 @@ public static void dispatchDebugEvent(String name, String key1, Object value1, S } } + public static void dispatchDebugEvent(String name, String key1, Object value1, String key2, Object value2, String key3, Object value3) { + if (isListeningForDebugEvents()) { + notifyEvent(name, ObjectUtils.toMap(key1, value1, key2, value2, key3, value3)); + } + } + public static void dispatchException(Throwable e) { if (isListeningForDebugEvents() && e != null) { StringBuilder stackTrace = new StringBuilder(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java index 8fedd3439..36f064602 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java @@ -2,7 +2,7 @@ public class TesterEvent { - public static final String EVT_CONVERSATION_LOAD_ACTIVE = "conversation_load_active"; // { successful:boolean } + public static final String EVT_CONVERSATION_LOAD = "conversation_load"; // { successful:boolean, conversation_state:String, conversation_identifier:String } public static final String EVT_CONVERSATION_CREATE = "conversation_create"; // { successful:boolean, state:String } public static final String EVT_CONVERSATION_METADATA_LOAD = "conversation_metadata_load"; // { successful:boolean } public static final String EVT_INTERACTION_FETCH = "interaction_fetch"; // { successful:boolean } From 1882027a491cb9a05f1fb91db89026f58a0b9637 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 31 Mar 2017 14:47:04 -0700 Subject: [PATCH 186/465] Fixed conversation related test-events --- .../sdk/conversation/ConversationManager.java | 40 ++++++------------- .../android/sdk/debug/TesterEvent.java | 3 +- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 87f9ce76b..249539868 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -30,27 +30,19 @@ import java.io.IOException; import java.lang.ref.WeakReference; -import static com.apptentive.android.sdk.ApptentiveLogTag.CONVERSATION; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_STARTED; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; -import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS; -import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS_PENDING; -import static com.apptentive.android.sdk.conversation.ConversationState.LOGGED_IN; -import static com.apptentive.android.sdk.conversation.ConversationState.LOGGED_OUT; -import static com.apptentive.android.sdk.conversation.ConversationState.UNDEFINED; import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; -import static com.apptentive.android.sdk.debug.TesterEvent.EVT_CONVERSATION_CREATE; -import static com.apptentive.android.sdk.debug.TesterEvent.EVT_CONVERSATION_LOAD; -import static com.apptentive.android.sdk.debug.TesterEvent.EVT_CONVERSATION_METADATA_LOAD; + +import static com.apptentive.android.sdk.ApptentiveLogTag.*; +import static com.apptentive.android.sdk.ApptentiveNotifications.*; +import static com.apptentive.android.sdk.conversation.ConversationState.*; +import static com.apptentive.android.sdk.debug.TesterEvent.*; /** * Class responsible for managing conversations. *

  *   - Saving/Loading conversations from/to files.
  *   - Switching conversations when users login/logout.
- *   - Creating default conversation.
- *   - Migrating legacy conversation data.
+ *   - Creating anonymous conversation.
  * 
*/ public class ConversationManager { @@ -233,13 +225,13 @@ public void onFinish(HttpJsonRequest request) { if (StringUtils.isNullOrEmpty(conversationToken)) { ApptentiveLog.e(CONVERSATION, "Can't fetch conversation: missing 'token'"); - dispatchDebugEvent(EVT_CONVERSATION_CREATE, false); + dispatchDebugEvent(EVT_CONVERSATION_FETCH_TOKEN, false); return; } if (StringUtils.isNullOrEmpty(conversationId)) { ApptentiveLog.e(CONVERSATION, "Can't fetch conversation: missing 'id'"); - dispatchDebugEvent(EVT_CONVERSATION_CREATE, false); + dispatchDebugEvent(EVT_CONVERSATION_FETCH_TOKEN, false); return; } @@ -258,36 +250,28 @@ public void onFinish(HttpJsonRequest request) { // write conversation to the disk (sync operation) conversation.saveConversationData(); - dispatchDebugEvent(EVT_CONVERSATION_CREATE, true); + dispatchDebugEvent(EVT_CONVERSATION_FETCH_TOKEN, true); handleConversationStateChange(conversation); } catch (Exception e) { ApptentiveLog.e(e, "Exception while handling conversation token"); - dispatchDebugEvent(EVT_CONVERSATION_CREATE, false); + dispatchDebugEvent(EVT_CONVERSATION_FETCH_TOKEN, false); } } @Override public void onCancel(HttpJsonRequest request) { - dispatchDebugEvent(EVT_CONVERSATION_CREATE, false); + dispatchDebugEvent(EVT_CONVERSATION_FETCH_TOKEN, false); } @Override public void onFail(HttpJsonRequest request, String reason) { ApptentiveLog.w("Failed to fetch conversation token: %s", reason); - dispatchDebugEvent(EVT_CONVERSATION_CREATE, false); + dispatchDebugEvent(EVT_CONVERSATION_FETCH_TOKEN, false); } }); } - private File getConversationDataFile(Conversation conversation) { - return new File(storageDir, conversation.getConversationId() + "-conversation.bin"); - } - - private File getConversationMessagesFile(Conversation conversation) { - return new File(storageDir, conversation.getConversationId() + "-messages.bin"); - } - //endregion //region Conversation fetching diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java index 36f064602..a4f3e837a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java @@ -3,10 +3,9 @@ public class TesterEvent { public static final String EVT_CONVERSATION_LOAD = "conversation_load"; // { successful:boolean, conversation_state:String, conversation_identifier:String } - public static final String EVT_CONVERSATION_CREATE = "conversation_create"; // { successful:boolean, state:String } public static final String EVT_CONVERSATION_METADATA_LOAD = "conversation_metadata_load"; // { successful:boolean } + public static final String EVT_CONVERSATION_FETCH_TOKEN = "conversation_fetch_token"; // { successful:boolean } public static final String EVT_INTERACTION_FETCH = "interaction_fetch"; // { successful:boolean } - public static final String EVT_INSTANCE_CREATED = "instance_created"; // { successful:boolean } public static final String EVT_EXCEPTION = "exception"; // { class:String, message:String, stackTrace:String } public static final String EVT_APPTENTIVE_EVENT = "apptentive_event"; // { eventLabel:String } From d74915358a6594c6ad86134b3ec1d877922a1b15 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 31 Mar 2017 15:23:28 -0700 Subject: [PATCH 187/465] Fixed conversation related test-events --- .../apptentive/android/sdk/conversation/Conversation.java | 2 +- .../android/sdk/conversation/ConversationManager.java | 6 ++++++ .../java/com/apptentive/android/sdk/debug/TesterEvent.java | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 707cde0a5..de68cd28f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -73,6 +73,7 @@ public class Conversation implements DataChangedListener, Destroyable { @Override protected void execute() { final boolean updateSuccessful = fetchInteractionsSync(); + dispatchDebugEvent(EVT_CONVERSATION_FETCH_INTERACTIONS, updateSuccessful); // Update pending state on UI thread after finishing the task DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { @@ -80,7 +81,6 @@ protected void execute() { protected void execute() { if (hasActiveState()) { ApptentiveInternal.getInstance().notifyInteractionUpdated(updateSuccessful); - dispatchDebugEvent(EVT_INTERACTION_FETCH, updateSuccessful); } } }); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 249539868..38398b75b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -279,6 +279,12 @@ public void onFail(HttpJsonRequest request, String reason) { private void handleConversationStateChange(Conversation conversation) { Assert.assertTrue(conversation != null && !conversation.hasState(UNDEFINED)); // sanity check + if (conversation != null) { + dispatchDebugEvent(EVT_CONVERSATION_STATE_CHANGE, + "conversation_state", conversation.getState().toString(), + "conversation_identifier", conversation.getConversationId()); + } + ApptentiveNotificationCenter.defaultCenter() .postNotificationSync(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, conversation)); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java index a4f3e837a..da8205d5f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java @@ -3,9 +3,10 @@ public class TesterEvent { public static final String EVT_CONVERSATION_LOAD = "conversation_load"; // { successful:boolean, conversation_state:String, conversation_identifier:String } + public static final String EVT_CONVERSATION_STATE_CHANGE = "conversation_state_change"; // { conversation_state:String, conversation_identifier:String } public static final String EVT_CONVERSATION_METADATA_LOAD = "conversation_metadata_load"; // { successful:boolean } public static final String EVT_CONVERSATION_FETCH_TOKEN = "conversation_fetch_token"; // { successful:boolean } - public static final String EVT_INTERACTION_FETCH = "interaction_fetch"; // { successful:boolean } + public static final String EVT_CONVERSATION_FETCH_INTERACTIONS = "conversation_fetch_interactions"; // { successful:boolean } public static final String EVT_EXCEPTION = "exception"; // { class:String, message:String, stackTrace:String } public static final String EVT_APPTENTIVE_EVENT = "apptentive_event"; // { eventLabel:String } From ef21dd1852d6357b2b15e7565865c18bafb167ec Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 31 Mar 2017 16:21:33 -0700 Subject: [PATCH 188/465] Testing progress and refactoring --- .../sdk/conversation/ConversationManager.java | 13 +++++++------ .../android/sdk/debug/TesterEvent.java | 19 +++++++++++++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 38398b75b..1614a11d6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -193,6 +193,7 @@ public synchronized boolean endActiveConversation() { private void fetchConversationToken(final Conversation conversation) { ApptentiveLog.i(CONVERSATION, "Fetching Configuration token task started."); + dispatchDebugEvent(EVT_CONVERSATION_WILL_FETCH_TOKEN); final Context context = getContext(); if (context == null) { @@ -225,13 +226,13 @@ public void onFinish(HttpJsonRequest request) { if (StringUtils.isNullOrEmpty(conversationToken)) { ApptentiveLog.e(CONVERSATION, "Can't fetch conversation: missing 'token'"); - dispatchDebugEvent(EVT_CONVERSATION_FETCH_TOKEN, false); + dispatchDebugEvent(EVT_CONVERSATION_DID_FETCH_TOKEN, false); return; } if (StringUtils.isNullOrEmpty(conversationId)) { ApptentiveLog.e(CONVERSATION, "Can't fetch conversation: missing 'id'"); - dispatchDebugEvent(EVT_CONVERSATION_FETCH_TOKEN, false); + dispatchDebugEvent(EVT_CONVERSATION_DID_FETCH_TOKEN, false); return; } @@ -250,24 +251,24 @@ public void onFinish(HttpJsonRequest request) { // write conversation to the disk (sync operation) conversation.saveConversationData(); - dispatchDebugEvent(EVT_CONVERSATION_FETCH_TOKEN, true); + dispatchDebugEvent(EVT_CONVERSATION_DID_FETCH_TOKEN, true); handleConversationStateChange(conversation); } catch (Exception e) { ApptentiveLog.e(e, "Exception while handling conversation token"); - dispatchDebugEvent(EVT_CONVERSATION_FETCH_TOKEN, false); + dispatchDebugEvent(EVT_CONVERSATION_DID_FETCH_TOKEN, false); } } @Override public void onCancel(HttpJsonRequest request) { - dispatchDebugEvent(EVT_CONVERSATION_FETCH_TOKEN, false); + dispatchDebugEvent(EVT_CONVERSATION_DID_FETCH_TOKEN, false); } @Override public void onFail(HttpJsonRequest request, String reason) { ApptentiveLog.w("Failed to fetch conversation token: %s", reason); - dispatchDebugEvent(EVT_CONVERSATION_FETCH_TOKEN, false); + dispatchDebugEvent(EVT_CONVERSATION_DID_FETCH_TOKEN, false); } }); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java index da8205d5f..cf3178943 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java @@ -2,16 +2,31 @@ public class TesterEvent { + /** An active conversation loading finished (might be either successful or failed) */ public static final String EVT_CONVERSATION_LOAD = "conversation_load"; // { successful:boolean, conversation_state:String, conversation_identifier:String } + + /** An active conversation state changed */ public static final String EVT_CONVERSATION_STATE_CHANGE = "conversation_state_change"; // { conversation_state:String, conversation_identifier:String } + + /** Conversation metadata loading finished (might be either successful or failed) */ public static final String EVT_CONVERSATION_METADATA_LOAD = "conversation_metadata_load"; // { successful:boolean } - public static final String EVT_CONVERSATION_FETCH_TOKEN = "conversation_fetch_token"; // { successful:boolean } + + /** Conversation token fetch request started */ + public static final String EVT_CONVERSATION_WILL_FETCH_TOKEN = "conversation_will_fetch_token"; + + /** Conversation token fetch request finished (might be either successful or failed) */ + public static final String EVT_CONVERSATION_DID_FETCH_TOKEN = "conversation_did_fetch_token"; // { successful:boolean } + + /** Conversation interactions fetch request finished (might be either successful or failed) */ public static final String EVT_CONVERSATION_FETCH_INTERACTIONS = "conversation_fetch_interactions"; // { successful:boolean } + + /** There was an unexpected runtime exception */ public static final String EVT_EXCEPTION = "exception"; // { class:String, message:String, stackTrace:String } + /** Apptentive event was sent */ public static final String EVT_APPTENTIVE_EVENT = "apptentive_event"; // { eventLabel:String } public static final String EVT_APPTENTIVE_EVENT_KEY_EVENT_LABEL = "eventLabel"; + // Common event keys public static final String EVT_KEY_SUCCESSFUL = "successful"; - public static final String EVT_KEY_STATE = "state"; } From 528c1083e87b88b45b3f7f2c0da14bd021507ca0 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 4 Apr 2017 13:03:43 -0700 Subject: [PATCH 189/465] Added PayloadSender class --- .../sdk/storage/PayloadDataSource.java | 10 +++ .../android/sdk/storage/PayloadSender.java | 61 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadDataSource.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadDataSource.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadDataSource.java new file mode 100644 index 000000000..d4bd58739 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadDataSource.java @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +public interface PayloadDataSource { +} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java new file mode 100644 index 000000000..527635f13 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import com.apptentive.android.sdk.notifications.ApptentiveNotification; +import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; +import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; +import com.apptentive.android.sdk.util.Destroyable; + +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_BACKGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; + +public class PayloadSender implements ApptentiveNotificationObserver, Destroyable { + private final PayloadDataSource dataSource; + + public PayloadSender(PayloadDataSource dataSource) { + if (dataSource == null) { + throw new IllegalArgumentException("Data source is null"); + } + this.dataSource = dataSource; + + ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_APP_ENTER_BACKGROUND, this); + ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_APP_ENTER_FOREGROUND, this); + } + + //region Background/Foreground + + private void onAppEnterBackground() { + } + + private void onAppEnterForeground() { + } + + //endregion + + //region Destroyable + + @Override + public void destroy() { + ApptentiveNotificationCenter.defaultCenter().removeObserver(this); + } + + //endregion + + //region ApptentiveNotificationObserver + + @Override + public void onReceiveNotification(ApptentiveNotification notification) { + if (notification.hasName(NOTIFICATION_APP_ENTER_BACKGROUND)) { + onAppEnterBackground(); + } else if (notification.hasName(NOTIFICATION_APP_ENTER_FOREGROUND)) { + onAppEnterForeground(); + } + } + + //endregion +} From 66b5f91aca4754c7eb6c27c0cbc4d08aa661df37 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 4 Apr 2017 14:40:18 -0700 Subject: [PATCH 190/465] Refactoring Renamed `com.apptentive.android.sdk.model.Device` to `com.apptentive.android.sdk.model.DevicePayload` --- .../com/apptentive/android/sdk/comm/ApptentiveClient.java | 2 +- .../android/sdk/model/ConversationTokenRequest.java | 6 +++--- .../com/apptentive/android/sdk/model/DeviceFactory.java | 4 ++-- .../android/sdk/model/{Device.java => DevicePayload.java} | 6 +++--- .../com/apptentive/android/sdk/storage/DeviceManager.java | 5 +++-- .../apptentive/android/sdk/storage/PayloadSendWorker.java | 2 +- 6 files changed, 13 insertions(+), 12 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/model/{Device.java => DevicePayload.java} (98%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index af8b84cce..b2ea2142b 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -82,7 +82,7 @@ public static ApptentiveHttpResponse postEvent(Event event) { return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_EVENTS, Method.POST, event.marshallForSending()); } - public static ApptentiveHttpResponse putDevice(Device device) { + public static ApptentiveHttpResponse putDevice(DevicePayload device) { return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_DEVICES, Method.PUT, device.marshallForSending()); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java index 4674f82c8..c6745b363 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java @@ -17,11 +17,11 @@ public class ConversationTokenRequest extends JSONObject { public ConversationTokenRequest() { } - public void setDevice(Device device) { + public void setDevice(DevicePayload device) { try { - put(Device.KEY, device); + put(DevicePayload.KEY, device); } catch (JSONException e) { - ApptentiveLog.e("Error adding %s to ConversationTokenRequest", Device.KEY); + ApptentiveLog.e("Error adding %s to ConversationTokenRequest", DevicePayload.KEY); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DeviceFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DeviceFactory.java index 5bec3ca79..3f7d8c966 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DeviceFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DeviceFactory.java @@ -13,9 +13,9 @@ * @author Sky Kelsey */ public class DeviceFactory { - public static Device fromJson(String json) { + public static DevicePayload fromJson(String json) { try { - return new Device(json); + return new DevicePayload(json); } catch (JSONException e) { ApptentiveLog.v("Error parsing json as Device: %s", e, json); } catch (IllegalArgumentException e) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Device.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java similarity index 98% rename from apptentive/src/main/java/com/apptentive/android/sdk/model/Device.java rename to apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index 8e2af6154..2977839f1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Device.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -13,7 +13,7 @@ /** * @author Sky Kelsey */ -public class Device extends Payload { +public class DevicePayload extends Payload { public static final String KEY = "device"; @@ -44,11 +44,11 @@ public class Device extends Payload { private static final String KEY_INTEGRATION_CONFIG = "integration_config"; - public Device() { + public DevicePayload() { super(); } - public Device(String json) throws JSONException { + public DevicePayload(String json) throws JSONException { super(json); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java index a9d77fd79..82f876b56 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java @@ -11,6 +11,7 @@ import android.telephony.TelephonyManager; import com.apptentive.android.sdk.ApptentiveInternal; +import com.apptentive.android.sdk.model.DevicePayload; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -66,12 +67,12 @@ public static Device generateNewDevice(Context context) { public static void onSentDeviceInfo() { } - public static com.apptentive.android.sdk.model.Device getDiffPayload(com.apptentive.android.sdk.storage.Device oldDevice, com.apptentive.android.sdk.storage.Device newDevice) { + public static DevicePayload getDiffPayload(com.apptentive.android.sdk.storage.Device oldDevice, com.apptentive.android.sdk.storage.Device newDevice) { if (newDevice == null) { return null; } - com.apptentive.android.sdk.model.Device ret = new com.apptentive.android.sdk.model.Device(); + DevicePayload ret = new DevicePayload(); if (oldDevice == null || !oldDevice.getUuid().equals(newDevice.getUuid())) { ret.setUuid(newDevice.getUuid()); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java index a0d30756c..e8db332c6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java @@ -162,7 +162,7 @@ public void run() { response = ApptentiveClient.postEvent((Event) payload); break; case device: - response = ApptentiveClient.putDevice((com.apptentive.android.sdk.model.Device) payload); + response = ApptentiveClient.putDevice((DevicePayload) payload); DeviceManager.onSentDeviceInfo(); break; case sdk: From 7d0352d14294332a2f577455a12efd3c02263296 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 4 Apr 2017 14:42:38 -0700 Subject: [PATCH 191/465] Refactoring Renamed `com.apptentive.android.sdk.model.Sdk` to `com.apptentive.android.sdk.model.SdkPayload` --- .../apptentive/android/sdk/comm/ApptentiveClient.java | 2 +- .../android/sdk/model/ConversationTokenRequest.java | 6 +++--- .../android/sdk/model/SdkAndAppReleasePayload.java | 10 +++++----- .../com/apptentive/android/sdk/model/SdkFactory.java | 4 ++-- .../android/sdk/model/{Sdk.java => SdkPayload.java} | 6 +++--- .../android/sdk/storage/PayloadSendWorker.java | 2 +- .../com/apptentive/android/sdk/storage/SdkManager.java | 5 +++-- 7 files changed, 18 insertions(+), 17 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/model/{Sdk.java => SdkPayload.java} (96%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index b2ea2142b..be2590165 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -86,7 +86,7 @@ public static ApptentiveHttpResponse putDevice(DevicePayload device) { return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_DEVICES, Method.PUT, device.marshallForSending()); } - public static ApptentiveHttpResponse putSdk(Sdk sdk) { + public static ApptentiveHttpResponse putSdk(SdkPayload sdk) { return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, sdk.marshallForSending()); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java index c6745b363..510e57e86 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java @@ -25,11 +25,11 @@ public void setDevice(DevicePayload device) { } } - public void setSdk(Sdk sdk) { + public void setSdk(SdkPayload sdk) { try { - put(Sdk.KEY, sdk); + put(SdkPayload.KEY, sdk); } catch (JSONException e) { - ApptentiveLog.e("Error adding %s to ConversationTokenRequest", Sdk.KEY); + ApptentiveLog.e("Error adding %s to ConversationTokenRequest", SdkPayload.KEY); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index 3a1e25e81..ead162001 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -19,15 +19,15 @@ import org.json.JSONException; /** - * A combined payload of {@link Sdk} and {@link AppRelease} payloads. + * A combined payload of {@link SdkPayload} and {@link AppRelease} payloads. * - * This class effectively contains the source code from both {@link Sdk} + * This class effectively contains the source code from both {@link SdkPayload} * and {@link AppRelease} payloads (which still kept for backward compatibility * purposes). */ public class SdkAndAppReleasePayload extends Payload { - private final com.apptentive.android.sdk.model.Sdk sdk; + private final SdkPayload sdk; private final com.apptentive.android.sdk.model.AppRelease appRelease; public static SdkAndAppReleasePayload fromJson(String json) { @@ -44,13 +44,13 @@ public static SdkAndAppReleasePayload fromJson(String json) { private SdkAndAppReleasePayload(String json) throws JSONException { super(json); - sdk = new com.apptentive.android.sdk.model.Sdk(getJSONObject("sdk").toString()); + sdk = new SdkPayload(getJSONObject("sdk").toString()); appRelease = new com.apptentive.android.sdk.model.AppRelease(getJSONObject("app_release").toString()); } public SdkAndAppReleasePayload() { super(); - sdk = new com.apptentive.android.sdk.model.Sdk(); + sdk = new SdkPayload(); appRelease = new com.apptentive.android.sdk.model.AppRelease(); try { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkFactory.java index f2aa02f7f..7058635e5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkFactory.java @@ -13,9 +13,9 @@ * @author Sky Kelsey */ public class SdkFactory { - public static Sdk fromJson(String json) { + public static SdkPayload fromJson(String json) { try { - return new Sdk(json); + return new SdkPayload(json); } catch (JSONException e) { ApptentiveLog.v("Error parsing json as Sdk: %s", e, json); } catch (IllegalArgumentException e) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Sdk.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java similarity index 96% rename from apptentive/src/main/java/com/apptentive/android/sdk/model/Sdk.java rename to apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java index 929fe5f46..533bbb0e7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Sdk.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java @@ -12,7 +12,7 @@ /** * @author Sky Kelsey */ -public class Sdk extends Payload { +public class SdkPayload extends Payload { public static final String KEY = "sdk"; @@ -24,11 +24,11 @@ public class Sdk extends Payload { private static final String KEY_DISTRIBUTION = "distribution"; private static final String KEY_DISTRIBUTION_VERSION = "distribution_version"; - public Sdk(String json) throws JSONException { + public SdkPayload(String json) throws JSONException { super(json); } - public Sdk() { + public SdkPayload() { super(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java index e8db332c6..41e7ad1fc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java @@ -166,7 +166,7 @@ public void run() { DeviceManager.onSentDeviceInfo(); break; case sdk: - response = ApptentiveClient.putSdk((com.apptentive.android.sdk.model.Sdk) payload); + response = ApptentiveClient.putSdk((SdkPayload) payload); break; case app_release: response = ApptentiveClient.putAppRelease((com.apptentive.android.sdk.model.AppRelease) payload); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SdkManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SdkManager.java index 3f135ccf4..91ff9fa05 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SdkManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SdkManager.java @@ -7,6 +7,7 @@ package com.apptentive.android.sdk.storage; import com.apptentive.android.sdk.ApptentiveInternal; +import com.apptentive.android.sdk.model.SdkPayload; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -31,8 +32,8 @@ public static Sdk generateCurrentSdk() { return sdk; } - public static com.apptentive.android.sdk.model.Sdk getPayload(Sdk sdk) { - com.apptentive.android.sdk.model.Sdk ret = new com.apptentive.android.sdk.model.Sdk(); + public static SdkPayload getPayload(Sdk sdk) { + SdkPayload ret = new SdkPayload(); if (sdk == null) { return ret; } From 2a424eaf72bcd7ad4fe85a459b54fcb5d5508171 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 4 Apr 2017 14:44:09 -0700 Subject: [PATCH 192/465] Refactoring Renamed `com.apptentive.android.sdk.model.AppRelease` to `com.apptentive.android.sdk.model.AppReleasePayload` --- .../android/sdk/comm/ApptentiveClient.java | 2 +- .../android/sdk/model/AppReleaseFactory.java | 4 ++-- .../{AppRelease.java => AppReleasePayload.java} | 10 +++++----- .../sdk/model/ConversationTokenRequest.java | 2 +- .../android/sdk/model/SdkAndAppReleasePayload.java | 14 +++++++------- .../android/sdk/storage/AppReleaseManager.java | 4 ++-- .../android/sdk/storage/PayloadSendWorker.java | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/model/{AppRelease.java => AppReleasePayload.java} (95%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index be2590165..82667e581 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -90,7 +90,7 @@ public static ApptentiveHttpResponse putSdk(SdkPayload sdk) { return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, sdk.marshallForSending()); } - public static ApptentiveHttpResponse putAppRelease(AppRelease appRelease) { + public static ApptentiveHttpResponse putAppRelease(AppReleasePayload appRelease) { return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, appRelease.marshallForSending()); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleaseFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleaseFactory.java index aa4236bfe..f4958ff07 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleaseFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleaseFactory.java @@ -11,9 +11,9 @@ import org.json.JSONException; public class AppReleaseFactory { - public static AppRelease fromJson(String json) { + public static AppReleasePayload fromJson(String json) { try { - return new AppRelease(json); + return new AppReleasePayload(json); } catch (JSONException e) { ApptentiveLog.v("Error parsing json as AppRelease: %s", e, json); } catch (IllegalArgumentException e) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppRelease.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java similarity index 95% rename from apptentive/src/main/java/com/apptentive/android/sdk/model/AppRelease.java rename to apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java index 48fbd5c44..956cd8b57 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppRelease.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java @@ -18,7 +18,7 @@ import org.json.JSONException; -public class AppRelease extends Payload { +public class AppReleasePayload extends Payload { private static final String KEY_TYPE = "type"; private static final String KEY_VERSION_NAME = "version_name"; @@ -30,11 +30,11 @@ public class AppRelease extends Payload { private static final String KEY_STYLE_OVERRIDE = "overriding_styles"; private static final String KEY_DEBUG = "debug"; - public AppRelease(String json) throws JSONException { + public AppReleasePayload(String json) throws JSONException { super(json); } - public AppRelease() { + public AppReleasePayload() { super(); } @@ -170,9 +170,9 @@ public void setDebug(boolean debug) { } } - public static AppRelease generateCurrentAppRelease(Context context) { + public static AppReleasePayload generateCurrentAppRelease(Context context) { - AppRelease appRelease = new AppRelease(); + AppReleasePayload appRelease = new AppReleasePayload(); String appPackageName = context.getPackageName(); PackageManager packageManager = context.getPackageManager(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java index 510e57e86..d99dd2012 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java @@ -41,7 +41,7 @@ public void setPerson(Person person) { } } - public void setAppRelease(AppRelease appRelease) { + public void setAppRelease(AppReleasePayload appRelease) { try { put(appRelease.getBaseType().name(), appRelease); } catch (JSONException e) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index ead162001..158649a30 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -19,16 +19,16 @@ import org.json.JSONException; /** - * A combined payload of {@link SdkPayload} and {@link AppRelease} payloads. + * A combined payload of {@link SdkPayload} and {@link AppReleasePayload} payloads. * * This class effectively contains the source code from both {@link SdkPayload} - * and {@link AppRelease} payloads (which still kept for backward compatibility + * and {@link AppReleasePayload} payloads (which still kept for backward compatibility * purposes). */ public class SdkAndAppReleasePayload extends Payload { private final SdkPayload sdk; - private final com.apptentive.android.sdk.model.AppRelease appRelease; + private final AppReleasePayload appRelease; public static SdkAndAppReleasePayload fromJson(String json) { try { @@ -45,13 +45,13 @@ private SdkAndAppReleasePayload(String json) throws JSONException { super(json); sdk = new SdkPayload(getJSONObject("sdk").toString()); - appRelease = new com.apptentive.android.sdk.model.AppRelease(getJSONObject("app_release").toString()); + appRelease = new AppReleasePayload(getJSONObject("app_release").toString()); } public SdkAndAppReleasePayload() { super(); sdk = new SdkPayload(); - appRelease = new com.apptentive.android.sdk.model.AppRelease(); + appRelease = new AppReleasePayload(); try { put("sdk", sdk); @@ -200,9 +200,9 @@ public void setDebug(boolean debug) { appRelease.setDebug(debug); } - public static AppRelease generateCurrentAppRelease(Context context) { + public static AppReleasePayload generateCurrentAppRelease(Context context) { - AppRelease appRelease = new AppRelease(); + AppReleasePayload appRelease = new AppReleasePayload(); String appPackageName = context.getPackageName(); PackageManager packageManager = context.getPackageManager(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java index aab2f776e..7964a4e07 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java @@ -59,8 +59,8 @@ public static AppRelease generateCurrentAppRelease(Context context, ApptentiveIn return appRelease; } - public static com.apptentive.android.sdk.model.AppRelease getPayload(AppRelease appRelease) { - com.apptentive.android.sdk.model.AppRelease ret = new com.apptentive.android.sdk.model.AppRelease(); + public static AppReleasePayload getPayload(AppRelease appRelease) { + AppReleasePayload ret = new AppReleasePayload(); if (appRelease == null) { return ret; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java index 41e7ad1fc..55bb061bd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java @@ -169,7 +169,7 @@ public void run() { response = ApptentiveClient.putSdk((SdkPayload) payload); break; case app_release: - response = ApptentiveClient.putAppRelease((com.apptentive.android.sdk.model.AppRelease) payload); + response = ApptentiveClient.putAppRelease((AppReleasePayload) payload); break; case sdk_and_app_release: response = ApptentiveClient.putSdkAndAppRelease((com.apptentive.android.sdk.model.SdkAndAppReleasePayload) payload); From 552371d8b8df82a9cafa9ca2dc3f9e8704e5ae2d Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 4 Apr 2017 14:45:57 -0700 Subject: [PATCH 193/465] Refactoring Renamed `com.apptentive.android.sdk.model.Person` to `com.apptentive.android.sdk.model.PersonPayload` --- .../com/apptentive/android/sdk/comm/ApptentiveClient.java | 2 +- .../android/sdk/model/ConversationTokenRequest.java | 6 +++--- .../com/apptentive/android/sdk/model/PersonFactory.java | 4 ++-- .../android/sdk/model/{Person.java => PersonPayload.java} | 6 +++--- .../apptentive/android/sdk/storage/PayloadSendWorker.java | 4 ++-- .../com/apptentive/android/sdk/storage/PersonManager.java | 6 ++++-- 6 files changed, 15 insertions(+), 13 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/model/{Person.java => PersonPayload.java} (97%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 82667e581..6a996adcf 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -98,7 +98,7 @@ public static ApptentiveHttpResponse putSdkAndAppRelease(SdkAndAppReleasePayload return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, payload.marshallForSending()); } - public static ApptentiveHttpResponse putPerson(Person person) { + public static ApptentiveHttpResponse putPerson(PersonPayload person) { return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_PEOPLE, Method.PUT, person.marshallForSending()); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java index d99dd2012..3dcd2a5a3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java @@ -33,11 +33,11 @@ public void setSdk(SdkPayload sdk) { } } - public void setPerson(Person person) { + public void setPerson(PersonPayload person) { try { - put(Person.KEY, person); + put(PersonPayload.KEY, person); } catch (JSONException e) { - ApptentiveLog.e("Error adding %s to ConversationTokenRequest", Person.KEY); + ApptentiveLog.e("Error adding %s to ConversationTokenRequest", PersonPayload.KEY); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonFactory.java index fb40f0029..b68f221bb 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonFactory.java @@ -14,9 +14,9 @@ * @author Sky Kelsey */ public class PersonFactory { - public static Person fromJson(String json) { + public static PersonPayload fromJson(String json) { try { - return new Person(json); + return new PersonPayload(json); } catch (JSONException e) { ApptentiveLog.v("Error parsing json as Person: %s", e, json); } catch (IllegalArgumentException e) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Person.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java similarity index 97% rename from apptentive/src/main/java/com/apptentive/android/sdk/model/Person.java rename to apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index b9a314f5f..6c98f8d2d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Person.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -13,7 +13,7 @@ /** * @author Sky Kelsey */ -public class Person extends Payload { +public class PersonPayload extends Payload { public static final String KEY = "person"; @@ -29,11 +29,11 @@ public class Person extends Payload { private static final String KEY_BIRTHDAY = "birthday"; public static final String KEY_CUSTOM_DATA = "custom_data"; - public Person() { + public PersonPayload() { super(); } - public Person(String json) throws JSONException { + public PersonPayload(String json) throws JSONException { super(json); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java index 55bb061bd..c4bc2ea13 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java @@ -172,10 +172,10 @@ public void run() { response = ApptentiveClient.putAppRelease((AppReleasePayload) payload); break; case sdk_and_app_release: - response = ApptentiveClient.putSdkAndAppRelease((com.apptentive.android.sdk.model.SdkAndAppReleasePayload) payload); + response = ApptentiveClient.putSdkAndAppRelease((SdkAndAppReleasePayload) payload); break; case person: - response = ApptentiveClient.putPerson((com.apptentive.android.sdk.model.Person) payload); + response = ApptentiveClient.putPerson((PersonPayload) payload); break; case survey: response = ApptentiveClient.postSurvey((SurveyResponse) payload); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java index 82ed5f801..12813c437 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java @@ -6,14 +6,16 @@ package com.apptentive.android.sdk.storage; +import com.apptentive.android.sdk.model.PersonPayload; + public class PersonManager { - public static com.apptentive.android.sdk.model.Person getDiffPayload(com.apptentive.android.sdk.storage.Person oldPerson, com.apptentive.android.sdk.storage.Person newPerson) { + public static PersonPayload getDiffPayload(com.apptentive.android.sdk.storage.Person oldPerson, com.apptentive.android.sdk.storage.Person newPerson) { if (newPerson == null) { return null; } - com.apptentive.android.sdk.model.Person ret = new com.apptentive.android.sdk.model.Person(); + PersonPayload ret = new PersonPayload(); if (oldPerson == null || !oldPerson.getId().equals(newPerson.getId())) { ret.setId(newPerson.getId()); From 7fa2c0fa64ed6b090d51d705253efb96848f9b19 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 4 Apr 2017 14:47:14 -0700 Subject: [PATCH 194/465] Refactoring Renamed `com.apptentive.android.sdk.model.SurveyResponse` to `com.apptentive.android.sdk.model.SurveyResponsePayload` --- .../com/apptentive/android/sdk/comm/ApptentiveClient.java | 2 +- .../com/apptentive/android/sdk/model/PayloadFactory.java | 2 +- .../{SurveyResponse.java => SurveyResponsePayload.java} | 6 +++--- .../engagement/interaction/fragment/SurveyFragment.java | 4 ++-- .../apptentive/android/sdk/storage/PayloadSendWorker.java | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/model/{SurveyResponse.java => SurveyResponsePayload.java} (83%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 6a996adcf..e9fd9eaff 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -102,7 +102,7 @@ public static ApptentiveHttpResponse putPerson(PersonPayload person) { return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_PEOPLE, Method.PUT, person.marshallForSending()); } - public static ApptentiveHttpResponse postSurvey(SurveyResponse survey) { + public static ApptentiveHttpResponse postSurvey(SurveyResponsePayload survey) { String endpoint = String.format(ENDPOINT_SURVEYS_POST, survey.getId()); return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), endpoint, Method.POST, survey.marshallForSending()); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java index e55a230fc..033623b6a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java @@ -34,7 +34,7 @@ public static Payload fromJson(String json, Payload.BaseType baseType) { return PersonFactory.fromJson(json); case survey: try { - return new SurveyResponse(json); + return new SurveyResponsePayload(json); } catch (JSONException e) { // Ignore } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponse.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java similarity index 83% rename from apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponse.java rename to apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index 8506d9141..c1f7861d5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponse.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -14,17 +14,17 @@ import java.util.Map; -public class SurveyResponse extends ConversationItem { +public class SurveyResponsePayload extends ConversationItem { private static final String KEY_SURVEY_ID = "id"; private static final String KEY_SURVEY_ANSWERS = "answers"; - public SurveyResponse(String json) throws JSONException { + public SurveyResponsePayload(String json) throws JSONException { super(json); } - public SurveyResponse(SurveyInteraction definition, Map answers) { + public SurveyResponsePayload(SurveyInteraction definition, Map answers) { super(); try { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java index 70ba16b8f..462d8d7ce 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java @@ -30,7 +30,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.model.SurveyResponse; +import com.apptentive.android.sdk.model.SurveyResponsePayload; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.SurveyInteraction; import com.apptentive.android.sdk.module.engagement.interaction.model.survey.MultichoiceQuestion; @@ -122,7 +122,7 @@ public void onClick(View view) { EngagementModule.engageInternal(getActivity(), interaction, EVENT_SUBMIT); - ApptentiveInternal.getInstance().getApptentiveTaskManager().addPayload(new SurveyResponse(interaction, answers)); + ApptentiveInternal.getInstance().getApptentiveTaskManager().addPayload(new SurveyResponsePayload(interaction, answers)); ApptentiveLog.d("Survey Submitted."); callListener(true); } else { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java index c4bc2ea13..52882e81d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java @@ -178,7 +178,7 @@ public void run() { response = ApptentiveClient.putPerson((PersonPayload) payload); break; case survey: - response = ApptentiveClient.postSurvey((SurveyResponse) payload); + response = ApptentiveClient.postSurvey((SurveyResponsePayload) payload); break; default: ApptentiveLog.e("Didn't send unknown Payload BaseType: " + payload.getBaseType()); From dc7c410f7296b110123f50cda6e46afc0c6260f6 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 4 Apr 2017 14:49:07 -0700 Subject: [PATCH 195/465] Refactoring Renamed `com.apptentive.android.sdk.model.Event` to `com.apptentive.android.sdk.model.EventPayload` --- .../apptentive/android/sdk/ApptentiveInternal.java | 6 +++--- .../android/sdk/comm/ApptentiveClient.java | 2 +- .../apptentive/android/sdk/model/EventFactory.java | 4 ++-- .../apptentive/android/sdk/model/EventManager.java | 2 +- .../sdk/model/{Event.java => EventPayload.java} | 12 ++++++------ .../sdk/module/engagement/EngagementModule.java | 4 ++-- .../android/sdk/module/metric/MetricModule.java | 14 +++++++------- .../android/sdk/storage/PayloadSendWorker.java | 2 +- 8 files changed, 23 insertions(+), 23 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/model/{Event.java => EventPayload.java} (89%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index e802a82f7..129753c3c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -31,7 +31,7 @@ import com.apptentive.android.sdk.conversation.ConversationManager; import com.apptentive.android.sdk.lifecycle.ApptentiveActivityLifecycleCallbacks; import com.apptentive.android.sdk.model.Configuration; -import com.apptentive.android.sdk.model.Event; +import com.apptentive.android.sdk.model.EventPayload; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; @@ -406,11 +406,11 @@ public void runOnWorkerThread(Runnable r) { } public void onAppLaunch(final Context appContext) { - EngagementModule.engageInternal(appContext, Event.EventLabel.app__launch.getLabelName()); + EngagementModule.engageInternal(appContext, EventPayload.EventLabel.app__launch.getLabelName()); } public void onAppExit(final Context appContext) { - EngagementModule.engageInternal(appContext, Event.EventLabel.app__exit.getLabelName()); + EngagementModule.engageInternal(appContext, EventPayload.EventLabel.app__exit.getLabelName()); } public void onActivityStarted(Activity activity) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index e9fd9eaff..e35bb70bc 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -78,7 +78,7 @@ public static ApptentiveHttpResponse postMessage(ApptentiveMessage apptentiveMes return new ApptentiveHttpResponse(); } - public static ApptentiveHttpResponse postEvent(Event event) { + public static ApptentiveHttpResponse postEvent(EventPayload event) { return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_EVENTS, Method.POST, event.marshallForSending()); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventFactory.java index 4502e0f67..ec0a83c7b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventFactory.java @@ -14,9 +14,9 @@ * @author Sky Kelsey */ public class EventFactory { - public static Event fromJson(String json) { + public static EventPayload fromJson(String json) { try { - return new Event(json); + return new EventPayload(json); } catch (JSONException e) { ApptentiveLog.v("Error parsing json as Event: %s", e, json); } catch (IllegalArgumentException e) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java index 3a859d541..a114298d7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java @@ -18,7 +18,7 @@ private static EventStore getEventStore() { return ApptentiveInternal.getInstance().getApptentiveTaskManager(); } - public static void sendEvent(Event event) { + public static void sendEvent(EventPayload event) { dispatchDebugEvent(EVT_APPTENTIVE_EVENT, EVT_APPTENTIVE_EVENT_KEY_EVENT_LABEL, event.getEventLabel()); getEventStore().addPayload(event); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Event.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java similarity index 89% rename from apptentive/src/main/java/com/apptentive/android/sdk/model/Event.java rename to apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index 3f116b3d3..ccfe10a09 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Event.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -17,7 +17,7 @@ /** * @author Sky Kelsey */ -public class Event extends ConversationItem { +public class EventPayload extends ConversationItem { private static final String KEY_LABEL = "label"; private static final String KEY_INTERACTION_ID = "interaction_id"; @@ -25,11 +25,11 @@ public class Event extends ConversationItem { private static final String KEY_TRIGGER = "trigger"; private static final String KEY_CUSTOM_DATA = "custom_data"; - public Event(String json) throws JSONException { + public EventPayload(String json) throws JSONException { super(json); } - public Event(String label, JSONObject data) { + public EventPayload(String label, JSONObject data) { super(); try { put(KEY_LABEL, label); @@ -41,7 +41,7 @@ public Event(String label, JSONObject data) { } } - public Event(String label, Map data) { + public EventPayload(String label, Map data) { super(); try { put(KEY_LABEL, label); @@ -57,7 +57,7 @@ public Event(String label, Map data) { } } - public Event(String label, String interactionId, String data, Map customData, ExtendedData... extendedData) { + public EventPayload(String label, String interactionId, String data, Map customData, ExtendedData... extendedData) { super(); try { put(KEY_LABEL, label); @@ -104,7 +104,7 @@ private JSONObject generateCustomDataJson(Map customData) { return ret; } - public Event(String label, String trigger) { + public EventPayload(String label, String trigger) { this(label, (Map) null); Map data = new HashMap(); data.put(KEY_TRIGGER, trigger); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java index bf63bd852..2891f71cd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java @@ -13,7 +13,7 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.ApptentiveViewActivity; -import com.apptentive.android.sdk.model.Event; +import com.apptentive.android.sdk.model.EventPayload; import com.apptentive.android.sdk.model.EventManager; import com.apptentive.android.sdk.model.ExtendedData; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; @@ -59,7 +59,7 @@ public static synchronized boolean engage(Context context, String vendor, String String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); conversation.getEventData().storeEventForCurrentAppVersion(Util.currentTimeSeconds(), versionCode, versionName, eventLabel); - EventManager.sendEvent(new Event(eventLabel, interactionId, data, customData, extendedData)); + EventManager.sendEvent(new EventPayload(eventLabel, interactionId, data, customData, extendedData)); return doEngage(context, eventLabel); } } catch (Exception e) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java index 0fa3d2f1a..8664bdbbd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java @@ -8,7 +8,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.model.Configuration; -import com.apptentive.android.sdk.model.Event; +import com.apptentive.android.sdk.model.EventPayload; import com.apptentive.android.sdk.model.EventManager; import com.apptentive.android.sdk.util.Util; import org.json.JSONObject; @@ -22,19 +22,19 @@ public class MetricModule { private static final String KEY_EXCEPTION = "exception"; - public static void sendMetric(Event.EventLabel type) { + public static void sendMetric(EventPayload.EventLabel type) { sendMetric(type, null); } - public static void sendMetric(Event.EventLabel type, String trigger) { + public static void sendMetric(EventPayload.EventLabel type, String trigger) { sendMetric(type, trigger, null); } - public static void sendMetric(Event.EventLabel type, String trigger, Map data) { + public static void sendMetric(EventPayload.EventLabel type, String trigger, Map data) { Configuration config = Configuration.load(); if (config.isMetricsEnabled()) { ApptentiveLog.v("Sending Metric: %s, trigger: %s, data: %s", type.getLabelName(), trigger, data != null ? data.toString() : "null"); - Event event = new Event(type.getLabelName(), trigger); + EventPayload event = new EventPayload(type.getLabelName(), trigger); event.putData(data); EventManager.sendEvent(event); } @@ -48,7 +48,7 @@ public static void sendMetric(Event.EventLabel type, String trigger, Map Date: Tue, 4 Apr 2017 15:51:42 -0700 Subject: [PATCH 196/465] Payload sender progress --- .../android/sdk/ApptentiveLogTag.java | 1 + .../sdk/storage/ApptentiveTaskManager.java | 202 +++++++++++++++--- .../sdk/storage/PayloadSendWorker.java | 4 - .../android/sdk/storage/PayloadSender.java | 183 +++++++++++++++- ...DataSource.java => PayloadTypeSender.java} | 8 +- 5 files changed, 358 insertions(+), 40 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/storage/{PayloadDataSource.java => PayloadTypeSender.java} (53%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java index 2fc888887..69a54a7fa 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLogTag.java @@ -6,6 +6,7 @@ public enum ApptentiveLogTag { NOTIFICATIONS(true), MESSAGES(true), DATABASE(true), + PAYLOADS(true), TESTER_COMMANDS(true); ApptentiveLogTag(boolean enabled) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 064b41143..222944e18 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -8,12 +8,27 @@ import android.content.Context; +import com.apptentive.android.sdk.ApptentiveInternal; +import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.ApptentiveLogTag; +import com.apptentive.android.sdk.comm.ApptentiveClient; +import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.conversation.Conversation; +import com.apptentive.android.sdk.model.AppReleasePayload; +import com.apptentive.android.sdk.model.DevicePayload; +import com.apptentive.android.sdk.model.EventPayload; import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PersonPayload; +import com.apptentive.android.sdk.model.SdkAndAppReleasePayload; +import com.apptentive.android.sdk.model.SdkPayload; import com.apptentive.android.sdk.model.StoredFile; +import com.apptentive.android.sdk.model.SurveyResponsePayload; +import com.apptentive.android.sdk.module.messagecenter.MessageManager; +import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.notifications.ApptentiveNotification; import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; +import com.apptentive.android.sdk.util.StringUtils; import java.util.List; import java.util.concurrent.Callable; @@ -22,18 +37,32 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import static com.apptentive.android.sdk.ApptentiveNotifications.*; -import static com.apptentive.android.sdk.conversation.ConversationState.*; -import static com.apptentive.android.sdk.debug.Assert.*; +import static com.apptentive.android.sdk.ApptentiveLogTag.*; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; +import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS; +import static com.apptentive.android.sdk.conversation.ConversationState.UNDEFINED; +import static com.apptentive.android.sdk.debug.Assert.assertNotNull; +import static com.apptentive.android.sdk.debug.Assert.assertTrue; +import static com.apptentive.android.sdk.model.Payload.BaseType.app_release; +import static com.apptentive.android.sdk.model.Payload.BaseType.device; +import static com.apptentive.android.sdk.model.Payload.BaseType.event; +import static com.apptentive.android.sdk.model.Payload.BaseType.message; +import static com.apptentive.android.sdk.model.Payload.BaseType.person; +import static com.apptentive.android.sdk.model.Payload.BaseType.sdk; +import static com.apptentive.android.sdk.model.Payload.BaseType.sdk_and_app_release; +import static com.apptentive.android.sdk.model.Payload.BaseType.survey; -public class ApptentiveTaskManager implements PayloadStore, EventStore, ApptentiveNotificationObserver { +public class ApptentiveTaskManager implements PayloadStore, EventStore, ApptentiveNotificationObserver, PayloadSender.Listener { private ApptentiveDatabaseHelper dbHelper; - private ThreadPoolExecutor singleThreadExecutor; + private ThreadPoolExecutor singleThreadExecutor; // TODO: replace with a private concurrent dispatch queue // Set when receiving an ApptentiveNotification private String currentConversationId; + private final PayloadSender payloadSender; + /* * Creates an asynchronous task manager with one worker thread. This constructor must be invoked on the UI thread. */ @@ -46,31 +75,18 @@ public ApptentiveTaskManager(Context context) { * */ singleThreadExecutor = new ThreadPoolExecutor(1, 1, - 30L, TimeUnit.SECONDS, - new LinkedBlockingQueue(), - new ThreadPoolExecutor.CallerRunsPolicy()); + 30L, TimeUnit.SECONDS, + new LinkedBlockingQueue(), + new ThreadPoolExecutor.CallerRunsPolicy()); // If no new task arrives in 30 seconds, the worker thread terminates; otherwise it will be reused singleThreadExecutor.allowCoreThreadTimeOut(true); - ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, this); - } - + payloadSender = new PayloadSender(); + payloadSender.setListener(this); + registerPayloadTypeSenders(payloadSender); - /* Wrapper class that can be used to return worker thread result to caller through message - * Usage: Message message = callerThreadHandler.obtainMessage(MESSAGE_FINISH, - * new AsyncTaskExResult>(ApptentiveTaskManager.this, result)); - * message.sendToTarget(); - */ - @SuppressWarnings({"RawUseOfParameterizedType"}) - private static class ApptentiveTaskResult { - final ApptentiveTaskManager mTask; - final Data[] mData; - - ApptentiveTaskResult(ApptentiveTaskManager task, Data... data) { - mTask = task; - mData = data; - } + ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, this); } /** @@ -85,16 +101,18 @@ public void addPayload(final Payload... payloads) { @Override public void run() { dbHelper.addPayload(payloads); + sendNextPayload(); } }); } - public void deletePayload(final Payload payload){ + public void deletePayload(final Payload payload) { if (payload != null) { singleThreadExecutor.execute(new Runnable() { @Override public void run() { dbHelper.deletePayload(payload); + sendNextPayload(); } }); } @@ -136,7 +154,7 @@ public List call() throws Exception { }); } - public Future addCompoundMessageFiles(final List associatedFiles) throws Exception{ + public Future addCompoundMessageFiles(final List associatedFiles) throws Exception { return singleThreadExecutor.submit(new Callable() { @Override public Boolean call() throws Exception { @@ -149,6 +167,136 @@ public void reset(Context context) { dbHelper.reset(context); } + //region PayloadSender.Listener + + @Override + public void onFinishSending(PayloadSender sender, Payload payload) { + deletePayload(payload); + } + + @Override + public void onFailSending(PayloadSender sender, Payload payload, String errorMessage) { + ApptentiveLog.e(PAYLOADS, "Unable to send payload: %s", errorMessage); + deletePayload(payload); + } + + //endregion + + //region Payload Sending + + private void sendNextPayload() { + final Payload payload; + try { + payload = getOldestUnsentPayload().get(); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while peeking the next payload for sending"); + return; + } + + if (payload == null) { + ApptentiveLog.v(PAYLOADS, "Can't send the next payload: no unsent payloads found"); + return; + } + + if (StringUtils.isNullOrEmpty(payload.getConversationId())) { + ApptentiveLog.v(PAYLOADS, "Can't send the next payload: no conversation id"); + return; + } + + if (payloadSender.isBusy()) { + ApptentiveLog.v(PAYLOADS, "Can't send the next payload: payload sender is busy"); + return; + } + + payloadSender.sendPayload(payload); + } + + private void registerPayloadTypeSenders(PayloadSender sender) { + /* + * Each payload type requires its own way of being sent to the server. + * Instead of baking all the types into the {@link PayloadSender} we provide a + * lookup table. + */ + + // message + sender.registerTypeSender(message, new PayloadTypeSender() { + @Override + public ApptentiveHttpResponse sendPayload(ApptentiveMessage payload) { + // TODO: figure out a better solution (probably using notifications) + MessageManager mgr = ApptentiveInternal.getInstance().getMessageManager(); + if (mgr != null) { + mgr.resumeSending(); + } + final ApptentiveHttpResponse response = ApptentiveClient.postMessage(payload); + if (mgr != null) { + // if message is rejected temporarily, onSentMessage() will pause sending + mgr.onSentMessage(payload, response); + } + return response; + } + }); + + //event + sender.registerTypeSender(event, new PayloadTypeSender() { + @Override + public ApptentiveHttpResponse sendPayload(EventPayload event) { + return ApptentiveClient.postEvent(event); + } + }); + + // device + sender.registerTypeSender(device, new PayloadTypeSender() { + @Override + public ApptentiveHttpResponse sendPayload(DevicePayload payload) { + final ApptentiveHttpResponse response = ApptentiveClient.putDevice(payload); + DeviceManager.onSentDeviceInfo(); // TODO: find a better solution + return response; + } + }); + + // sdk + sender.registerTypeSender(sdk, new PayloadTypeSender() { + @Override + public ApptentiveHttpResponse sendPayload(SdkPayload payload) { + return ApptentiveClient.putSdk(payload); + } + }); + + // app_release + sender.registerTypeSender(app_release, new PayloadTypeSender() { + @Override + public ApptentiveHttpResponse sendPayload(AppReleasePayload payload) { + return ApptentiveClient.putAppRelease(payload); + } + }); + + // sdk_and_app_release + sender.registerTypeSender(sdk_and_app_release, new PayloadTypeSender() { + @Override + public ApptentiveHttpResponse sendPayload(SdkAndAppReleasePayload payload) { + return ApptentiveClient.putSdkAndAppRelease(payload); + } + }); + + // person + sender.registerTypeSender(person, new PayloadTypeSender() { + @Override + public ApptentiveHttpResponse sendPayload(PersonPayload payload) { + return ApptentiveClient.putPerson(payload); + } + }); + + // survey + sender.registerTypeSender(survey, new PayloadTypeSender() { + @Override + public ApptentiveHttpResponse sendPayload(SurveyResponsePayload payload) { + return ApptentiveClient.postSurvey(payload); + } + }); + } + + //endregion + @Override public void onReceiveNotification(ApptentiveNotification notification) { if (notification.hasName(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE)) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java index 2b596bc45..1856f3f59 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java @@ -86,10 +86,6 @@ public void handleMessage(android.os.Message msg) { } } - private PayloadStore getPayloadStore() { - return ApptentiveInternal.getInstance().getApptentiveTaskManager(); - } - private class PayloadSendRunnable implements Runnable { public PayloadSendRunnable() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 527635f13..ec4f94887 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -6,27 +6,170 @@ package com.apptentive.android.sdk.storage; +import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; +import com.apptentive.android.sdk.debug.Assert; +import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.notifications.ApptentiveNotification; import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; import com.apptentive.android.sdk.util.Destroyable; +import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchTask; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_BACKGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; +import static com.apptentive.android.sdk.debug.Assert.assertNotNull; +import static com.apptentive.android.sdk.debug.Assert.assertNull; -public class PayloadSender implements ApptentiveNotificationObserver, Destroyable { - private final PayloadDataSource dataSource; +class PayloadSender implements ApptentiveNotificationObserver, Destroyable { + private static final long RETRY_TIMEOUT_NO_CONNECTION = 5000; + private static final long RETRY_TIMEOUT_SERVER_ERROR = 5000; - public PayloadSender(PayloadDataSource dataSource) { - if (dataSource == null) { - throw new IllegalArgumentException("Data source is null"); - } - this.dataSource = dataSource; + private final Map senderLookup; + private final AtomicBoolean busy; + + private Listener listener; + + PayloadSender() { + senderLookup = new HashMap<>(); + busy = new AtomicBoolean(); ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_APP_ENTER_BACKGROUND, this); ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_APP_ENTER_FOREGROUND, this); } + //region Payloads + + public boolean sendPayload(final Payload payload) { + if (payload == null) { + throw new IllegalArgumentException("Payload is null"); + } + + // we don't allow concurrent payload sending + if (isBusy()) { + return false; + } + + setBusy(true); + + DispatchQueue.backgroundQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + sendPayloadSync(payload); + } + }); + + return true; + } + + private void sendPayloadSync(Payload payload) { + try { + sendPayloadSyncGuarded(payload); + } catch (Exception e) { + handleFailSendingPayload(payload, e.getMessage()); + } + } + + private void sendPayloadSyncGuarded(Payload payload) { + ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getBaseType(), payload.getDatabaseId(), payload.getConversationId()); + + PayloadTypeSender typeSender = senderLookup.get(payload.getBaseType()); + if (typeSender == null) { + handleFailSendingPayload(payload, "Unknown payload type: %s", payload.getBaseType()); + return; + } + + ApptentiveHttpResponse response = typeSender.sendPayload(payload); + assertNotNull(response); + + // that should probably not happen but we still need to handle this case + if (response == null) { + handleFailSendingPayload(payload, "Null-response for payload: %s", payload.getBaseType()); + return; + } + + if (response.isSuccessful()) { + ApptentiveLog.v(PAYLOADS, "Payload submission successful: %s", payload); + handleFinishSendingPayload(payload); + } + else if (response.isRejectedPermanently() || response.isBadPayload()) { + ApptentiveLog.v(PAYLOADS, "Payload response was rejected or invalid: %s", payload); + handleFinishSendingPayload(payload); + } + else if (response.isRejectedTemporarily()) { + ApptentiveLog.v(PAYLOADS, "Payload was temporary rejected: %s"); + if (response.isException()) { + retryPayloadSending(payload, RETRY_TIMEOUT_NO_CONNECTION); + } else { + retryPayloadSending(payload, RETRY_TIMEOUT_SERVER_ERROR); + } + } else { + handleFailSendingPayload(payload, "Payload submission failed due to an unknown cause: %s"); + } + } + + //endregion + + //region Sending Retry + + private void retryPayloadSending(final Payload payload, long delayMillis) { + ApptentiveLog.v(PAYLOADS, "Retrying sending payload: %s", payload); + setBusy(true); + + DispatchQueue.backgroundQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + sendPayload(payload); + } + }, delayMillis); + } + + //endregion + + //region Listener notification + + private void handleFinishSendingPayload(Payload payload) { + setBusy(false); + + try { + if (listener != null) { + listener.onFinishSending(this, payload); + } + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while notifying payload listener"); + } + } + + private void handleFailSendingPayload(Payload payload, String format, Object... args) { + setBusy(false); + + try { + if (listener != null) { + listener.onFailSending(this, payload, StringUtils.format(format, args)); + } + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while notifying payload listener"); + } + } + + //endregion + + //region Type Senders + + public void registerTypeSender(Payload.BaseType type, PayloadTypeSender sender) { + PayloadTypeSender existing = senderLookup.put(type, sender); + assertNull(existing, "Payload type sender already registered"); + } + + //endregion + //region Background/Foreground private void onAppEnterBackground() { @@ -58,4 +201,30 @@ public void onReceiveNotification(ApptentiveNotification notification) { } //endregion + + //region Getters/Setters + + public boolean isBusy() { + return busy.get(); + } + + private void setBusy(boolean value) { + busy.set(value); + } + + public void setListener(Listener listener) { + this.listener = listener; + } + + //endregion + + //region Listener + + public interface Listener { + void onFinishSending(PayloadSender sender, Payload payload); + + void onFailSending(PayloadSender sender, Payload payload, String errorMessage); + } + + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadDataSource.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadTypeSender.java similarity index 53% rename from apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadDataSource.java rename to apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadTypeSender.java index d4bd58739..8ef5cccfc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadDataSource.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadTypeSender.java @@ -6,5 +6,9 @@ package com.apptentive.android.sdk.storage; -public interface PayloadDataSource { -} \ No newline at end of file +import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; +import com.apptentive.android.sdk.model.Payload; + +interface PayloadTypeSender { + ApptentiveHttpResponse sendPayload(T payload); +} From ba3b8545bb618f5d9e6bb106bcb9b5fbb8ec1815 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 4 Apr 2017 16:55:51 -0700 Subject: [PATCH 197/465] Added more methods to ApptentiveHttpClient --- .../sdk/comm/ApptentiveHttpClient.java | 46 +++++++++++++++++-- .../android/sdk/storage/PayloadSender.java | 1 - 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index af5387fe9..c6001d9e6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -1,6 +1,13 @@ package com.apptentive.android.sdk.comm; +import com.apptentive.android.sdk.model.AppReleasePayload; import com.apptentive.android.sdk.model.ConversationTokenRequest; +import com.apptentive.android.sdk.model.DevicePayload; +import com.apptentive.android.sdk.model.EventPayload; +import com.apptentive.android.sdk.model.PersonPayload; +import com.apptentive.android.sdk.model.SdkAndAppReleasePayload; +import com.apptentive.android.sdk.model.SdkPayload; +import com.apptentive.android.sdk.model.SurveyResponsePayload; import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestManager; @@ -24,6 +31,10 @@ public class ApptentiveHttpClient { // Active API private static final String ENDPOINT_CONVERSATION = "/conversation"; + private static final String ENDPOINT_EVENTS = "/events"; + private static final String ENDPOINT_DEVICES = "/devices"; + private static final String ENDPOINT_PEOPLE = "/people"; + private static final String ENDPOINT_SURVEYS_POST = "/surveys/%s/respond"; private final String oauthToken; private final String serverURL; @@ -48,18 +59,47 @@ public ApptentiveHttpClient(String oauthToken, String serverURL) { //region API Requests public HttpJsonRequest getConversationToken(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { - return startJsonRequest(ENDPOINT_CONVERSATION, conversationTokenRequest, listener); + return startJsonRequest(ENDPOINT_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST, listener); + } + + public HttpJsonRequest sendEvent(EventPayload event, HttpRequest.Listener listener) { + return startJsonRequest(ENDPOINT_EVENTS, event, HttpRequestMethod.POST, listener); + } + + public HttpJsonRequest sendDevice(DevicePayload device, HttpRequest.Listener listener) { + return startJsonRequest(ENDPOINT_DEVICES, device, HttpRequestMethod.PUT, listener); + } + + public HttpJsonRequest sendSdk(SdkPayload sdk, HttpRequest.Listener listener) { + return startJsonRequest(ENDPOINT_CONVERSATION, sdk, HttpRequestMethod.PUT, listener); + } + + public HttpJsonRequest sendAppRelease(AppReleasePayload appRelease, HttpRequest.Listener listener) { + return startJsonRequest(ENDPOINT_CONVERSATION, appRelease, HttpRequestMethod.PUT, listener); + } + + public HttpJsonRequest sendSdkAndAppRelease(SdkAndAppReleasePayload payload, HttpRequest.Listener listener) { + return startJsonRequest(ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT, listener); + } + + public HttpJsonRequest sendPerson(PersonPayload person, HttpRequest.Listener listener) { + return startJsonRequest(ENDPOINT_PEOPLE, person, HttpRequestMethod.PUT, listener); + } + + public HttpJsonRequest sendSurvey(SurveyResponsePayload survey, HttpRequest.Listener listener) { + String endpoint = String.format(ENDPOINT_SURVEYS_POST, survey.getId()); + return startJsonRequest(endpoint, survey, HttpRequestMethod.POST, listener); } //endregion //region Helpers - private HttpJsonRequest startJsonRequest(String endpoint, JSONObject jsonObject, HttpRequest.Listener listener) { + private HttpJsonRequest startJsonRequest(String endpoint, JSONObject jsonObject, HttpRequestMethod method, HttpRequest.Listener listener) { String url = createEndpointURL(endpoint); HttpJsonRequest request = new HttpJsonRequest(url, jsonObject); setupRequestDefaults(request); - request.setMethod(HttpRequestMethod.POST); + request.setMethod(method); request.setRequestProperty("Content-Type", "application/json"); request.setListener(listener); httpRequestManager.startRequest(request); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index ec4f94887..4735f8efe 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -8,7 +8,6 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; -import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.notifications.ApptentiveNotification; import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; From fcd8e41c5962b26285949753ae4f8288e55b81fa Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 5 Apr 2017 12:45:48 -0700 Subject: [PATCH 198/465] Modified payload sender to use new HttpRequest class + added retry policy --- .../android/sdk/ApptentiveInternal.java | 2 +- .../sdk/comm/ApptentiveHttpClient.java | 118 +++++++++++--- .../sdk/comm/PayloadRequestFactory.java | 18 ++ .../sdk/storage/ApptentiveTaskManager.java | 128 ++------------- .../sdk/storage/PayloadRequestSender.java | 15 ++ .../android/sdk/storage/PayloadSender.java | 154 ++++++------------ 6 files changed, 194 insertions(+), 241 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/comm/PayloadRequestFactory.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 129753c3c..71ff7b912 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -168,7 +168,7 @@ private ApptentiveInternal(Application application, String apiKey, String server appRelease = AppReleaseManager.generateCurrentAppRelease(application, this); payloadWorker = new PayloadSendWorker(); - taskManager = new ApptentiveTaskManager(appContext); + taskManager = new ApptentiveTaskManager(appContext, apptentiveHttpClient); cachedExecutor = Executors.newCachedThreadPool(); lifecycleCallbacks = new ApptentiveActivityLifecycleCallbacks(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index c6001d9e6..0549757f7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -4,6 +4,7 @@ import com.apptentive.android.sdk.model.ConversationTokenRequest; import com.apptentive.android.sdk.model.DevicePayload; import com.apptentive.android.sdk.model.EventPayload; +import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.PersonPayload; import com.apptentive.android.sdk.model.SdkAndAppReleasePayload; import com.apptentive.android.sdk.model.SdkPayload; @@ -12,16 +13,20 @@ import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestManager; import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.storage.PayloadRequestSender; import com.apptentive.android.sdk.util.Constants; import org.json.JSONObject; +import java.util.HashMap; +import java.util.Map; + import static android.text.TextUtils.isEmpty; /** * Class responsible for all client-server network communications using asynchronous HTTP requests */ -public class ApptentiveHttpClient { +public class ApptentiveHttpClient implements PayloadRequestSender { public static final String API_VERSION = "7"; private static final String USER_AGENT_STRING = "Apptentive/%s (Android)"; // Format with SDK version string. @@ -41,6 +46,8 @@ public class ApptentiveHttpClient { private final String userAgentString; private final HttpRequestManager httpRequestManager; + private final Map, PayloadRequestFactory> payloadRequestFactoryLookup; + public ApptentiveHttpClient(String oauthToken, String serverURL) { if (isEmpty(oauthToken)) { throw new IllegalArgumentException("Illegal OAuth Token: '" + oauthToken + "'"); @@ -54,6 +61,7 @@ public ApptentiveHttpClient(String oauthToken, String serverURL) { this.oauthToken = oauthToken; this.serverURL = serverURL; this.userAgentString = String.format(USER_AGENT_STRING, Constants.APPTENTIVE_SDK_VERSION); + this.payloadRequestFactoryLookup = createPayloadRequestFactoryLookup(); } //region API Requests @@ -62,33 +70,92 @@ public HttpJsonRequest getConversationToken(ConversationTokenRequest conversatio return startJsonRequest(ENDPOINT_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST, listener); } - public HttpJsonRequest sendEvent(EventPayload event, HttpRequest.Listener listener) { - return startJsonRequest(ENDPOINT_EVENTS, event, HttpRequestMethod.POST, listener); - } + //endregion - public HttpJsonRequest sendDevice(DevicePayload device, HttpRequest.Listener listener) { - return startJsonRequest(ENDPOINT_DEVICES, device, HttpRequestMethod.PUT, listener); - } + //region PayloadRequestSender - public HttpJsonRequest sendSdk(SdkPayload sdk, HttpRequest.Listener listener) { - return startJsonRequest(ENDPOINT_CONVERSATION, sdk, HttpRequestMethod.PUT, listener); - } + @Override + public HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener) { + if (payload == null) { + throw new IllegalArgumentException("Payload is null"); + } - public HttpJsonRequest sendAppRelease(AppReleasePayload appRelease, HttpRequest.Listener listener) { - return startJsonRequest(ENDPOINT_CONVERSATION, appRelease, HttpRequestMethod.PUT, listener); - } + final PayloadRequestFactory requestFactory = payloadRequestFactoryLookup.get(payload.getClass()); + if (requestFactory == null) { + throw new IllegalArgumentException("Unexpected payload type: " + payload.getClass()); + } - public HttpJsonRequest sendSdkAndAppRelease(SdkAndAppReleasePayload payload, HttpRequest.Listener listener) { - return startJsonRequest(ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT, listener); + HttpRequest request = requestFactory.createRequest(payload); + request.setListener(listener); + httpRequestManager.startRequest(request); + return request; } - public HttpJsonRequest sendPerson(PersonPayload person, HttpRequest.Listener listener) { - return startJsonRequest(ENDPOINT_PEOPLE, person, HttpRequestMethod.PUT, listener); - } + //endregion - public HttpJsonRequest sendSurvey(SurveyResponsePayload survey, HttpRequest.Listener listener) { - String endpoint = String.format(ENDPOINT_SURVEYS_POST, survey.getId()); - return startJsonRequest(endpoint, survey, HttpRequestMethod.POST, listener); + //region Payload Request Factory + + private Map, PayloadRequestFactory> createPayloadRequestFactoryLookup() { + Map, PayloadRequestFactory> lookup = new HashMap<>(); + + // Event Payload + lookup.put(EventPayload.class, new PayloadRequestFactory() { + @Override + public HttpRequest createRequest(EventPayload payload) { + return createJsonRequest(ENDPOINT_EVENTS, payload, HttpRequestMethod.POST); + } + }); + + // Device Payload + lookup.put(DevicePayload.class, new PayloadRequestFactory() { + @Override + public HttpRequest createRequest(DevicePayload payload) { + return createJsonRequest(ENDPOINT_DEVICES, payload, HttpRequestMethod.PUT); + } + }); + + // SDK Payload + lookup.put(SdkPayload.class, new PayloadRequestFactory() { + @Override + public HttpRequest createRequest(SdkPayload payload) { + return createJsonRequest(ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); + } + }); + + // App Release Payload + lookup.put(AppReleasePayload.class, new PayloadRequestFactory() { + @Override + public HttpRequest createRequest(AppReleasePayload payload) { + return createJsonRequest(ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); + } + }); + + // SDK and App Release Payload + lookup.put(SdkAndAppReleasePayload.class, new PayloadRequestFactory() { + @Override + public HttpRequest createRequest(SdkAndAppReleasePayload payload) { + return createJsonRequest(ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); + } + }); + + // Person Payload + lookup.put(PersonPayload.class, new PayloadRequestFactory() { + @Override + public HttpRequest createRequest(PersonPayload payload) { + return createJsonRequest(ENDPOINT_PEOPLE, payload, HttpRequestMethod.PUT); + } + }); + + // Survey Payload + lookup.put(SurveyResponsePayload.class, new PayloadRequestFactory() { + @Override + public HttpRequest createRequest(SurveyResponsePayload survey) { + String endpoint = String.format(ENDPOINT_SURVEYS_POST, survey.getId()); + return createJsonRequest(endpoint, survey, HttpRequestMethod.POST); + } + }); + + return lookup; } //endregion @@ -96,13 +163,18 @@ public HttpJsonRequest sendSurvey(SurveyResponsePayload survey, HttpRequest.List //region Helpers private HttpJsonRequest startJsonRequest(String endpoint, JSONObject jsonObject, HttpRequestMethod method, HttpRequest.Listener listener) { + HttpJsonRequest request = createJsonRequest(endpoint, jsonObject, method); + request.setListener(listener); + httpRequestManager.startRequest(request); + return request; + } + + private HttpJsonRequest createJsonRequest(String endpoint, JSONObject jsonObject, HttpRequestMethod method) { String url = createEndpointURL(endpoint); HttpJsonRequest request = new HttpJsonRequest(url, jsonObject); setupRequestDefaults(request); request.setMethod(method); request.setRequestProperty("Content-Type", "application/json"); - request.setListener(listener); - httpRequestManager.startRequest(request); return request; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/PayloadRequestFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/PayloadRequestFactory.java new file mode 100644 index 000000000..badb58d3f --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/PayloadRequestFactory.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.comm; + +import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.network.HttpRequest; + +/** + * Created by alementuev on 4/4/17. + */ + +interface PayloadRequestFactory { + HttpRequest createRequest(T payload); +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 222944e18..96dd77bf5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -8,23 +8,11 @@ import android.content.Context; -import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.ApptentiveLogTag; -import com.apptentive.android.sdk.comm.ApptentiveClient; -import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; +import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.conversation.Conversation; -import com.apptentive.android.sdk.model.AppReleasePayload; -import com.apptentive.android.sdk.model.DevicePayload; -import com.apptentive.android.sdk.model.EventPayload; import com.apptentive.android.sdk.model.Payload; -import com.apptentive.android.sdk.model.PersonPayload; -import com.apptentive.android.sdk.model.SdkAndAppReleasePayload; -import com.apptentive.android.sdk.model.SdkPayload; import com.apptentive.android.sdk.model.StoredFile; -import com.apptentive.android.sdk.model.SurveyResponsePayload; -import com.apptentive.android.sdk.module.messagecenter.MessageManager; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.notifications.ApptentiveNotification; import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; @@ -37,21 +25,13 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import static com.apptentive.android.sdk.ApptentiveLogTag.*; +import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS; import static com.apptentive.android.sdk.conversation.ConversationState.UNDEFINED; import static com.apptentive.android.sdk.debug.Assert.assertNotNull; import static com.apptentive.android.sdk.debug.Assert.assertTrue; -import static com.apptentive.android.sdk.model.Payload.BaseType.app_release; -import static com.apptentive.android.sdk.model.Payload.BaseType.device; -import static com.apptentive.android.sdk.model.Payload.BaseType.event; -import static com.apptentive.android.sdk.model.Payload.BaseType.message; -import static com.apptentive.android.sdk.model.Payload.BaseType.person; -import static com.apptentive.android.sdk.model.Payload.BaseType.sdk; -import static com.apptentive.android.sdk.model.Payload.BaseType.sdk_and_app_release; -import static com.apptentive.android.sdk.model.Payload.BaseType.survey; public class ApptentiveTaskManager implements PayloadStore, EventStore, ApptentiveNotificationObserver, PayloadSender.Listener { @@ -66,7 +46,7 @@ public class ApptentiveTaskManager implements PayloadStore, EventStore, Apptenti /* * Creates an asynchronous task manager with one worker thread. This constructor must be invoked on the UI thread. */ - public ApptentiveTaskManager(Context context) { + public ApptentiveTaskManager(Context context, ApptentiveHttpClient apptentiveHttpClient) { dbHelper = new ApptentiveDatabaseHelper(context); /* When a new database task is submitted, the executor has the following behaviors: * 1. If the thread pool has no thread yet, it creates a single worker thread. @@ -82,9 +62,8 @@ public ApptentiveTaskManager(Context context) { // If no new task arrives in 30 seconds, the worker thread terminates; otherwise it will be reused singleThreadExecutor.allowCoreThreadTimeOut(true); - payloadSender = new PayloadSender(); + payloadSender = new PayloadSender(apptentiveHttpClient); payloadSender.setListener(this); - registerPayloadTypeSenders(payloadSender); ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, this); } @@ -169,14 +148,17 @@ public void reset(Context context) { //region PayloadSender.Listener - @Override - public void onFinishSending(PayloadSender sender, Payload payload) { - deletePayload(payload); - } @Override - public void onFailSending(PayloadSender sender, Payload payload, String errorMessage) { - ApptentiveLog.e(PAYLOADS, "Unable to send payload: %s", errorMessage); + public void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage) { + if (cancelled) { + ApptentiveLog.v(PAYLOADS, "Payload sending was cancelled: %s", payload); + } else if (errorMessage != null) { + ApptentiveLog.v(PAYLOADS, "Payload sending failed: %s", payload); + } else { + ApptentiveLog.v(PAYLOADS, "Payload was successfully sent: %s", payload); + } + deletePayload(payload); } @@ -211,90 +193,6 @@ private void sendNextPayload() { payloadSender.sendPayload(payload); } - private void registerPayloadTypeSenders(PayloadSender sender) { - /* - * Each payload type requires its own way of being sent to the server. - * Instead of baking all the types into the {@link PayloadSender} we provide a - * lookup table. - */ - - // message - sender.registerTypeSender(message, new PayloadTypeSender() { - @Override - public ApptentiveHttpResponse sendPayload(ApptentiveMessage payload) { - // TODO: figure out a better solution (probably using notifications) - MessageManager mgr = ApptentiveInternal.getInstance().getMessageManager(); - if (mgr != null) { - mgr.resumeSending(); - } - final ApptentiveHttpResponse response = ApptentiveClient.postMessage(payload); - if (mgr != null) { - // if message is rejected temporarily, onSentMessage() will pause sending - mgr.onSentMessage(payload, response); - } - return response; - } - }); - - //event - sender.registerTypeSender(event, new PayloadTypeSender() { - @Override - public ApptentiveHttpResponse sendPayload(EventPayload event) { - return ApptentiveClient.postEvent(event); - } - }); - - // device - sender.registerTypeSender(device, new PayloadTypeSender() { - @Override - public ApptentiveHttpResponse sendPayload(DevicePayload payload) { - final ApptentiveHttpResponse response = ApptentiveClient.putDevice(payload); - DeviceManager.onSentDeviceInfo(); // TODO: find a better solution - return response; - } - }); - - // sdk - sender.registerTypeSender(sdk, new PayloadTypeSender() { - @Override - public ApptentiveHttpResponse sendPayload(SdkPayload payload) { - return ApptentiveClient.putSdk(payload); - } - }); - - // app_release - sender.registerTypeSender(app_release, new PayloadTypeSender() { - @Override - public ApptentiveHttpResponse sendPayload(AppReleasePayload payload) { - return ApptentiveClient.putAppRelease(payload); - } - }); - - // sdk_and_app_release - sender.registerTypeSender(sdk_and_app_release, new PayloadTypeSender() { - @Override - public ApptentiveHttpResponse sendPayload(SdkAndAppReleasePayload payload) { - return ApptentiveClient.putSdkAndAppRelease(payload); - } - }); - - // person - sender.registerTypeSender(person, new PayloadTypeSender() { - @Override - public ApptentiveHttpResponse sendPayload(PersonPayload payload) { - return ApptentiveClient.putPerson(payload); - } - }); - - // survey - sender.registerTypeSender(survey, new PayloadTypeSender() { - @Override - public ApptentiveHttpResponse sendPayload(SurveyResponsePayload payload) { - return ApptentiveClient.postSurvey(payload); - } - }); - } - //endregion @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java new file mode 100644 index 000000000..cc07c5722 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.network.HttpJsonRequest; +import com.apptentive.android.sdk.network.HttpRequest; + +public interface PayloadRequestSender { + HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener); +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 4735f8efe..90d860a94 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -7,37 +7,49 @@ package com.apptentive.android.sdk.storage; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.network.HttpJsonRequest; +import com.apptentive.android.sdk.network.HttpRequest; +import com.apptentive.android.sdk.network.HttpRequestRetryPolicy; import com.apptentive.android.sdk.notifications.ApptentiveNotification; import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; import com.apptentive.android.sdk.util.Destroyable; -import com.apptentive.android.sdk.util.StringUtils; -import com.apptentive.android.sdk.util.threading.DispatchQueue; -import com.apptentive.android.sdk.util.threading.DispatchTask; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_BACKGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; -import static com.apptentive.android.sdk.debug.Assert.assertNotNull; -import static com.apptentive.android.sdk.debug.Assert.assertNull; class PayloadSender implements ApptentiveNotificationObserver, Destroyable { - private static final long RETRY_TIMEOUT_NO_CONNECTION = 5000; - private static final long RETRY_TIMEOUT_SERVER_ERROR = 5000; + private static final long RETRY_TIMEOUT = 5000; + private static final int RETRY_MAX_COUNT = 5; - private final Map senderLookup; + private final PayloadRequestSender requestSender; + private final HttpRequestRetryPolicy requestRetryPolicy; private final AtomicBoolean busy; private Listener listener; + private boolean appInBackground; + + PayloadSender(PayloadRequestSender requestSender) { + if (requestSender == null) { + throw new IllegalArgumentException("Payload request sender is null"); + } + + this.requestSender = requestSender; + + requestRetryPolicy = new HttpRequestRetryPolicy() { + @Override + protected boolean shouldRetryRequest(int responseCode) { + return !(appInBackground || responseCode >= 400 && responseCode < 500); + + } + }; + requestRetryPolicy.setRetryTimeoutMillis(RETRY_TIMEOUT); + requestRetryPolicy.setMaxRetryCount(RETRY_MAX_COUNT); - PayloadSender() { - senderLookup = new HashMap<>(); busy = new AtomicBoolean(); ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_APP_ENTER_BACKGROUND, this); @@ -46,7 +58,7 @@ class PayloadSender implements ApptentiveNotificationObserver, Destroyable { //region Payloads - public boolean sendPayload(final Payload payload) { + boolean sendPayload(final Payload payload) { if (payload == null) { throw new IllegalArgumentException("Payload is null"); } @@ -58,100 +70,47 @@ public boolean sendPayload(final Payload payload) { setBusy(true); - DispatchQueue.backgroundQueue().dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - sendPayloadSync(payload); - } - }); - - return true; - } - - private void sendPayloadSync(Payload payload) { try { - sendPayloadSyncGuarded(payload); + sendPayloadRequest(payload); } catch (Exception e) { - handleFailSendingPayload(payload, e.getMessage()); - } - } - - private void sendPayloadSyncGuarded(Payload payload) { - ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getBaseType(), payload.getDatabaseId(), payload.getConversationId()); - - PayloadTypeSender typeSender = senderLookup.get(payload.getBaseType()); - if (typeSender == null) { - handleFailSendingPayload(payload, "Unknown payload type: %s", payload.getBaseType()); - return; + handleFinishSendingPayload(payload, false, e.getMessage()); } - ApptentiveHttpResponse response = typeSender.sendPayload(payload); - assertNotNull(response); - - // that should probably not happen but we still need to handle this case - if (response == null) { - handleFailSendingPayload(payload, "Null-response for payload: %s", payload.getBaseType()); - return; - } - - if (response.isSuccessful()) { - ApptentiveLog.v(PAYLOADS, "Payload submission successful: %s", payload); - handleFinishSendingPayload(payload); - } - else if (response.isRejectedPermanently() || response.isBadPayload()) { - ApptentiveLog.v(PAYLOADS, "Payload response was rejected or invalid: %s", payload); - handleFinishSendingPayload(payload); - } - else if (response.isRejectedTemporarily()) { - ApptentiveLog.v(PAYLOADS, "Payload was temporary rejected: %s"); - if (response.isException()) { - retryPayloadSending(payload, RETRY_TIMEOUT_NO_CONNECTION); - } else { - retryPayloadSending(payload, RETRY_TIMEOUT_SERVER_ERROR); - } - } else { - handleFailSendingPayload(payload, "Payload submission failed due to an unknown cause: %s"); - } + return true; } - //endregion + private void sendPayloadRequest(final Payload payload) { + ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getBaseType(), payload.getDatabaseId(), payload.getConversationId()); - //region Sending Retry + final HttpRequest payloadRequest = requestSender.sendPayload(payload, new HttpRequest.Listener() { + @Override + public void onFinish(HttpJsonRequest request) { + handleFinishSendingPayload(payload, false, null); + } - private void retryPayloadSending(final Payload payload, long delayMillis) { - ApptentiveLog.v(PAYLOADS, "Retrying sending payload: %s", payload); - setBusy(true); + @Override + public void onCancel(HttpJsonRequest request) { + handleFinishSendingPayload(payload, true, null); + } - DispatchQueue.backgroundQueue().dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - sendPayload(payload); - } - }, delayMillis); + @Override + public void onFail(HttpJsonRequest request, String reason) { + handleFinishSendingPayload(payload, false, reason); + } + }); + payloadRequest.setRetryPolicy(requestRetryPolicy); } //endregion //region Listener notification - private void handleFinishSendingPayload(Payload payload) { + private void handleFinishSendingPayload(Payload payload, boolean cancelled, String errorMessage) { setBusy(false); try { if (listener != null) { - listener.onFinishSending(this, payload); - } - } catch (Exception e) { - ApptentiveLog.e(e, "Exception while notifying payload listener"); - } - } - - private void handleFailSendingPayload(Payload payload, String format, Object... args) { - setBusy(false); - - try { - if (listener != null) { - listener.onFailSending(this, payload, StringUtils.format(format, args)); + listener.onFinishSending(this, payload, cancelled, errorMessage); } } catch (Exception e) { ApptentiveLog.e(e, "Exception while notifying payload listener"); @@ -160,21 +119,14 @@ private void handleFailSendingPayload(Payload payload, String format, Object... //endregion - //region Type Senders - - public void registerTypeSender(Payload.BaseType type, PayloadTypeSender sender) { - PayloadTypeSender existing = senderLookup.put(type, sender); - assertNull(existing, "Payload type sender already registered"); - } - - //endregion - //region Background/Foreground private void onAppEnterBackground() { + appInBackground = true; } private void onAppEnterForeground() { + appInBackground = false; } //endregion @@ -203,7 +155,7 @@ public void onReceiveNotification(ApptentiveNotification notification) { //region Getters/Setters - public boolean isBusy() { + boolean isBusy() { return busy.get(); } @@ -220,9 +172,7 @@ public void setListener(Listener listener) { //region Listener public interface Listener { - void onFinishSending(PayloadSender sender, Payload payload); - - void onFailSending(PayloadSender sender, Payload payload, String errorMessage); + void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage); } //endregion From f1cb21392e7bf51d15c3db791943f056c7e9a30a Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 5 Apr 2017 13:05:48 -0700 Subject: [PATCH 199/465] Removed old PayloadSendWorker class --- .../android/sdk/ApptentiveInternal.java | 12 +- .../android/sdk/ApptentiveNotifications.java | 10 + .../sdk/storage/ApptentiveDatabaseHelper.java | 5 - .../sdk/storage/PayloadSendWorker.java | 238 ------------------ 4 files changed, 11 insertions(+), 254 deletions(-) delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 71ff7b912..3093260fc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -44,7 +44,6 @@ import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.AppReleaseManager; import com.apptentive.android.sdk.storage.ApptentiveTaskManager; -import com.apptentive.android.sdk.storage.PayloadSendWorker; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.storage.VersionHistoryItem; @@ -78,7 +77,6 @@ */ public class ApptentiveInternal { - private final PayloadSendWorker payloadWorker; private final ApptentiveTaskManager taskManager; private final ApptentiveActivityLifecycleCallbacks lifecycleCallbacks; @@ -144,7 +142,6 @@ public static PushAction parse(String name) { // for unit testing protected ApptentiveInternal() { - payloadWorker = null; taskManager = null; globalSharedPrefs = null; apiKey = null; @@ -167,7 +164,6 @@ private ApptentiveInternal(Application application, String apiKey, String server conversationManager = new ConversationManager(appContext, Util.getInternalDir(appContext, "conversations", true)); appRelease = AppReleaseManager.generateCurrentAppRelease(application, this); - payloadWorker = new PayloadSendWorker(); taskManager = new ApptentiveTaskManager(appContext, apptentiveHttpClient); cachedExecutor = Executors.newCachedThreadPool(); @@ -341,10 +337,6 @@ public MessageManager getMessageManager() { return conversation != null ? conversation.getMessageManager() : null; } - public PayloadSendWorker getPayloadWorker() { - return payloadWorker; - } - public ApptentiveTaskManager getApptentiveTaskManager() { return taskManager; } @@ -437,7 +429,6 @@ public void onActivityResumed(Activity activity) { public void onAppEnterForeground() { appIsInForeground = true; - payloadWorker.appWentToForeground(); // Post a notification ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_APP_ENTER_FOREGROUND); @@ -446,7 +437,6 @@ public void onAppEnterForeground() { public void onAppEnterBackground() { appIsInForeground = false; currentTaskStackTopActivity = null; - payloadWorker.appWentToBackground(); // Post a notification ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_APP_ENTER_BACKGROUND); @@ -677,7 +667,7 @@ private void invalidateCaches() { /** * Fetches the global app configuration from the server and stores the keys into our SharedPreferences. */ - private void fetchAppConfiguration() { + private void fetchAppConfiguration() { // FIXME: remove unused method ApptentiveLog.i("Fetching new Configuration task started."); ApptentiveHttpResponse response = ApptentiveClient.getAppConfiguration(); try { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java index c09e3a9dc..835db765a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -33,6 +33,16 @@ public class ApptentiveNotifications { */ public static final String NOTIFICATION_APP_ENTER_BACKGROUND = "NOTIFICATION_APP_ENTER_BACKGROUND"; + /** + * Sent before payload request is sent to the server + */ + public static final String NOTIFICATION_PAYLOAD_WILL_SEND = "NOTIFICATION_PAYLOAD_WILL_SEND"; // { payload: Payload } + + /** + * Sent after payload sending if finished (might be successful or not) + */ + public static final String NOTIFICATION_PAYLOAD_DID_SEND = "NOTIFICATION_PAYLOAD_DID_SEND"; // { successful : boolean, payload: Payload } + /** * Sent if user requested to close all interactions. */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index c26d9be58..7a4b83656 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -347,11 +347,6 @@ public void addPayload(Payload... payloads) { } db.setTransactionSuccessful(); db.endTransaction(); - - PayloadSendWorker worker = ApptentiveInternal.getInstance().getPayloadWorker(); - if (worker != null) { - worker.setCanRunPayloadThread(true); - } } catch (SQLException sqe) { ApptentiveLog.e("addPayload EXCEPTION: " + sqe.getMessage()); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java deleted file mode 100644 index 1856f3f59..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.storage; - -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.text.TextUtils; - -import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.comm.ApptentiveClient; -import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; -import com.apptentive.android.sdk.model.*; -import com.apptentive.android.sdk.module.messagecenter.MessageManager; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; -import com.apptentive.android.sdk.module.metric.MetricModule; -import com.apptentive.android.sdk.util.Util; - -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * @author Sky Kelsey - */ -public class PayloadSendWorker { - - private static final int NO_TOKEN_SLEEP = 5000; - private static final int NO_CONNECTION_SLEEP_TIME = 5000; - private static final int SERVER_ERROR_SLEEP_TIME = 5000; - - private static final int UI_THREAD_MESSAGE_RETRY_CHECK = 1; - - private PayloadSendRunnable payloadSendRunnable; - private Handler uiHandler; - - private AtomicBoolean appInForeground = new AtomicBoolean(false); - private AtomicBoolean threadRunning = new AtomicBoolean(false); - private AtomicBoolean threadCanRun = new AtomicBoolean(false); - - - public PayloadSendWorker() { - } - - /* expect: true, createNew: true Check if payloadSendRunnable can be run and create a new one if not exist - * expect: true, createNew: false Check if payloadSendRunnable can be run only if one already exists - * expect: false Nullify payloadSendRunnable and cancel pending check as well - */ - public synchronized void checkIfStartSendPayload(boolean expect, boolean createNew) { - if (expect && createNew && payloadSendRunnable == null) { - payloadSendRunnable = new PayloadSendRunnable(); - } else if (!expect) { - uiHandler.removeMessages(UI_THREAD_MESSAGE_RETRY_CHECK); - payloadSendRunnable = null; - uiHandler = null; - } - - if (payloadSendRunnable != null) { - if (uiHandler == null) { - uiHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(android.os.Message msg) { - switch (msg.what) { - case UI_THREAD_MESSAGE_RETRY_CHECK: - checkIfStartSendPayload(true, true); - break; - default: - super.handleMessage(msg); - } - } - }; - } else { - uiHandler.removeMessages(UI_THREAD_MESSAGE_RETRY_CHECK); - } - - if (threadCanRun.get() && !threadRunning.get()) { - // Check passed - threadRunning.set(true); - // Start payload send runnable now - ApptentiveInternal.getInstance().runOnWorkerThread(payloadSendRunnable); - } - } - } - - private class PayloadSendRunnable implements Runnable { - - public PayloadSendRunnable() { - } - - public void run() { - try { - ApptentiveLog.v("Started %s", toString()); - - while (appInForeground.get()) { - MessageManager mgr = ApptentiveInternal.getInstance().getMessageManager(); - - if (ApptentiveInternal.getInstance().getConversation() == null){ - ApptentiveLog.i("Conversation is null."); - if (mgr != null) { - mgr.pauseSending(MessageManager.SEND_PAUSE_REASON_SERVER); - } - retryLater(NO_TOKEN_SLEEP); - break; - } - - if (TextUtils.isEmpty(ApptentiveInternal.getInstance().getConversation().getConversationToken())){ - ApptentiveLog.i("No conversation token yet."); - if (mgr != null) { - mgr.pauseSending(MessageManager.SEND_PAUSE_REASON_SERVER); - } - retryLater(NO_TOKEN_SLEEP); - break; - } - if (!Util.isNetworkConnectionPresent()) { - ApptentiveLog.d("Can't send payloads. No network connection."); - if (mgr != null) { - mgr.pauseSending(MessageManager.SEND_PAUSE_REASON_NETWORK); - } - retryLater(NO_CONNECTION_SLEEP_TIME); - break; - } - ApptentiveLog.v("Checking for payloads to send."); - - Payload payload = null; - try { - Future future = ApptentiveInternal.getInstance().getApptentiveTaskManager().getOldestUnsentPayload(); - payload = future.get(); - } catch (Exception e) { - ApptentiveLog.e("Error getting oldest unsent payload in worker thread"); - } - if (payload == null) { - // There is no payload in the db. Terminate the thread - threadCanRun.set(false); - break; - } - ApptentiveLog.d("Got a payload to send: %s:%d", payload.getBaseType(), payload.getDatabaseId()); - ApptentiveLog.v("Payload Conversation ID: %s", payload.getConversationId()); - - ApptentiveHttpResponse response = null; - - - switch (payload.getBaseType()) { - case message: - if (mgr != null) { - mgr.resumeSending(); - } - response = ApptentiveClient.postMessage((ApptentiveMessage) payload); - if (mgr != null) { - // if message is rejected temporarily, onSentMessage() will pause sending - mgr.onSentMessage((ApptentiveMessage) payload, response); - } - break; - case event: - response = ApptentiveClient.postEvent((EventPayload) payload); - break; - case device: - response = ApptentiveClient.putDevice((DevicePayload) payload); - DeviceManager.onSentDeviceInfo(); - break; - case sdk: - response = ApptentiveClient.putSdk((SdkPayload) payload); - break; - case app_release: - response = ApptentiveClient.putAppRelease((AppReleasePayload) payload); - break; - case sdk_and_app_release: - response = ApptentiveClient.putSdkAndAppRelease((SdkAndAppReleasePayload) payload); - break; - case person: - response = ApptentiveClient.putPerson((PersonPayload) payload); - break; - case survey: - response = ApptentiveClient.postSurvey((SurveyResponsePayload) payload); - break; - default: - ApptentiveLog.e("Didn't send unknown Payload BaseType: " + payload.getBaseType()); - ApptentiveInternal.getInstance().getApptentiveTaskManager().deletePayload(payload); - break; - } - - // Each Payload type is handled by the appropriate handler, but if sent correctly, or failed permanently to send, it should be removed from the queue. - if (response != null) { - if (response.isSuccessful()) { - ApptentiveLog.d("Payload submission successful. Removing from send queue."); - ApptentiveInternal.getInstance().getApptentiveTaskManager().deletePayload(payload); - } else if (response.isRejectedPermanently() || response.isBadPayload()) { - ApptentiveLog.d("Payload rejected. Removing from send queue."); - ApptentiveLog.v("Rejected json:", payload.toString()); - ApptentiveInternal.getInstance().getApptentiveTaskManager().deletePayload(payload); - } else if (response.isRejectedTemporarily()) { - ApptentiveLog.d("Unable to send JSON. Leaving in queue."); - if (response.isException()) { - retryLater(NO_CONNECTION_SLEEP_TIME); - break; - } else { - retryLater(SERVER_ERROR_SLEEP_TIME); - break; - } - } - } - } - } catch (Throwable throwable) { - MetricModule.sendError(throwable, null, null); - } finally - { - ApptentiveLog.v("Stopping PayloadSendThread."); - threadRunning.set(false); - } - } - - private void retryLater(int millis) { - Message msg = uiHandler.obtainMessage(UI_THREAD_MESSAGE_RETRY_CHECK); - uiHandler.removeMessages(UI_THREAD_MESSAGE_RETRY_CHECK); - uiHandler.sendMessageDelayed(msg, millis); - } - } - - public void appWentToForeground() { - appInForeground.set(true); - checkIfStartSendPayload(true, true); - } - - public void appWentToBackground() { - appInForeground.set(false); - checkIfStartSendPayload(true, false); - } - - public void setCanRunPayloadThread(boolean b) { - threadCanRun.set(b); - if (uiHandler == null || !uiHandler.hasMessages(UI_THREAD_MESSAGE_RETRY_CHECK)) { - checkIfStartSendPayload(true, true); - } - } -} From e3ee0fad5b8baa9b8d2ce395979812f0f3d7b2c3 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 5 Apr 2017 13:19:25 -0700 Subject: [PATCH 200/465] Added payload sending notifications + fixed cancelled payloads on the queue --- .../android/sdk/ApptentiveInternal.java | 5 +-- .../android/sdk/ApptentiveNotifications.java | 2 + .../ApptentiveNotificationCenter.java | 8 ++++ .../sdk/storage/ApptentiveTaskManager.java | 7 ++- .../android/sdk/storage/PayloadSender.java | 44 ++++++++++++------- 5 files changed, 46 insertions(+), 20 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 3093260fc..86e82cef3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -48,7 +48,6 @@ import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.storage.VersionHistoryItem; import com.apptentive.android.sdk.util.Constants; -import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; @@ -412,7 +411,7 @@ public void onActivityStarted(Activity activity) { // Post a notification ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_ACTIVITY_STARTED, - ObjectUtils.toMap(NOTIFICATION_KEY_ACTIVITY, activity)); + NOTIFICATION_KEY_ACTIVITY, activity); } } @@ -423,7 +422,7 @@ public void onActivityResumed(Activity activity) { // Post a notification ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_ACTIVITY_RESUMED, - ObjectUtils.toMap(NOTIFICATION_KEY_ACTIVITY, activity)); + NOTIFICATION_KEY_ACTIVITY, activity); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java index 835db765a..9b3b043e0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -49,6 +49,8 @@ public class ApptentiveNotifications { public static final String NOTIFICATION_INTERACTIONS_SHOULD_DISMISS = "NOTIFICATION_INTERACTIONS_SHOULD_DISMISS"; // keys + public static final String NOTIFICATION_KEY_SUCCESSFUL = "successful"; public static final String NOTIFICATION_KEY_ACTIVITY = "activity"; public static final String NOTIFICATION_KEY_CONVERSATION = "conversation"; + public static final String NOTIFICATION_KEY_PAYLOAD = "payload"; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java index 9ca275df2..1be921106 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java @@ -7,6 +7,7 @@ package com.apptentive.android.sdk.notifications; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -96,6 +97,13 @@ public synchronized void postNotification(String name) { postNotification(name, EMPTY_USER_INFO); } + /** + * Creates a notification with a given name and user info and posts it to the receiver. + */ + public synchronized void postNotification(final String name, Object... args) { + postNotification(name, ObjectUtils.toMap(args)); + } + /** * Creates a notification with a given name and user info and posts it to the receiver. */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 96dd77bf5..e87b87510 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -153,8 +153,11 @@ public void reset(Context context) { public void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage) { if (cancelled) { ApptentiveLog.v(PAYLOADS, "Payload sending was cancelled: %s", payload); - } else if (errorMessage != null) { - ApptentiveLog.v(PAYLOADS, "Payload sending failed: %s", payload); + return; // don't remove cancelled payloads from the queue + } + + if (errorMessage != null) { + ApptentiveLog.v(PAYLOADS, "Payload sending failed: %s\n%s", payload, errorMessage); } else { ApptentiveLog.v(PAYLOADS, "Payload was successfully sent: %s", payload); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 90d860a94..117ef7a01 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -21,6 +21,12 @@ import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_BACKGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_PAYLOAD; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_SUCCESSFUL; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_DID_SEND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_WILL_SEND; +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; class PayloadSender implements ApptentiveNotificationObserver, Destroyable { private static final long RETRY_TIMEOUT = 5000; @@ -82,22 +88,25 @@ boolean sendPayload(final Payload payload) { private void sendPayloadRequest(final Payload payload) { ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getBaseType(), payload.getDatabaseId(), payload.getConversationId()); + ApptentiveNotificationCenter.defaultCenter() + .postNotification(NOTIFICATION_PAYLOAD_WILL_SEND, NOTIFICATION_KEY_PAYLOAD, payload); + final HttpRequest payloadRequest = requestSender.sendPayload(payload, new HttpRequest.Listener() { - @Override - public void onFinish(HttpJsonRequest request) { - handleFinishSendingPayload(payload, false, null); - } - - @Override - public void onCancel(HttpJsonRequest request) { - handleFinishSendingPayload(payload, true, null); - } - - @Override - public void onFail(HttpJsonRequest request, String reason) { - handleFinishSendingPayload(payload, false, reason); - } - }); + @Override + public void onFinish(HttpJsonRequest request) { + handleFinishSendingPayload(payload, false, null); + } + + @Override + public void onCancel(HttpJsonRequest request) { + handleFinishSendingPayload(payload, true, null); + } + + @Override + public void onFail(HttpJsonRequest request, String reason) { + handleFinishSendingPayload(payload, false, reason); + } + }); payloadRequest.setRetryPolicy(requestRetryPolicy); } @@ -108,6 +117,11 @@ public void onFail(HttpJsonRequest request, String reason) { private void handleFinishSendingPayload(Payload payload, boolean cancelled, String errorMessage) { setBusy(false); + ApptentiveNotificationCenter.defaultCenter() + .postNotification(NOTIFICATION_PAYLOAD_DID_SEND, + NOTIFICATION_KEY_PAYLOAD, payload, + NOTIFICATION_KEY_SUCCESSFUL, errorMessage == null ? TRUE : FALSE); + try { if (listener != null) { listener.onFinishSending(this, payload, cancelled, errorMessage); From 4050a8ab7ea94b7269a3de290ccea65120ee1e48 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 5 Apr 2017 13:29:26 -0700 Subject: [PATCH 201/465] Fixed shared variables access --- .../sdk/storage/ApptentiveTaskManager.java | 2 +- .../android/sdk/storage/PayloadSender.java | 26 +++++++------------ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index e87b87510..eb9f492c6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -188,7 +188,7 @@ private void sendNextPayload() { return; } - if (payloadSender.isBusy()) { + if (payloadSender.isSendingPayload()) { ApptentiveLog.v(PAYLOADS, "Can't send the next payload: payload sender is busy"); return; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 117ef7a01..4fd1c55cd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -16,8 +16,6 @@ import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; import com.apptentive.android.sdk.util.Destroyable; -import java.util.concurrent.atomic.AtomicBoolean; - import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_BACKGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; @@ -34,10 +32,10 @@ class PayloadSender implements ApptentiveNotificationObserver, Destroyable { private final PayloadRequestSender requestSender; private final HttpRequestRetryPolicy requestRetryPolicy; - private final AtomicBoolean busy; private Listener listener; private boolean appInBackground; + private boolean sendingFlag; // this variable is only accessed in a synchronized context PayloadSender(PayloadRequestSender requestSender) { if (requestSender == null) { @@ -56,25 +54,23 @@ protected boolean shouldRetryRequest(int responseCode) { requestRetryPolicy.setRetryTimeoutMillis(RETRY_TIMEOUT); requestRetryPolicy.setMaxRetryCount(RETRY_MAX_COUNT); - busy = new AtomicBoolean(); - ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_APP_ENTER_BACKGROUND, this); ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_APP_ENTER_FOREGROUND, this); } //region Payloads - boolean sendPayload(final Payload payload) { + synchronized boolean sendPayload(final Payload payload) { if (payload == null) { throw new IllegalArgumentException("Payload is null"); } // we don't allow concurrent payload sending - if (isBusy()) { + if (isSendingPayload()) { return false; } - setBusy(true); + sendingFlag = true; try { sendPayloadRequest(payload); @@ -85,7 +81,7 @@ boolean sendPayload(final Payload payload) { return true; } - private void sendPayloadRequest(final Payload payload) { + private synchronized void sendPayloadRequest(final Payload payload) { ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getBaseType(), payload.getDatabaseId(), payload.getConversationId()); ApptentiveNotificationCenter.defaultCenter() @@ -114,8 +110,8 @@ public void onFail(HttpJsonRequest request, String reason) { //region Listener notification - private void handleFinishSendingPayload(Payload payload, boolean cancelled, String errorMessage) { - setBusy(false); + private synchronized void handleFinishSendingPayload(Payload payload, boolean cancelled, String errorMessage) { + sendingFlag = false; ApptentiveNotificationCenter.defaultCenter() .postNotification(NOTIFICATION_PAYLOAD_DID_SEND, @@ -169,12 +165,8 @@ public void onReceiveNotification(ApptentiveNotification notification) { //region Getters/Setters - boolean isBusy() { - return busy.get(); - } - - private void setBusy(boolean value) { - busy.set(value); + synchronized boolean isSendingPayload() { + return sendingFlag; } public void setListener(Listener listener) { From 2c8ee58734b849adf30658645cfcccf2d13e4dde Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 5 Apr 2017 14:39:37 -0700 Subject: [PATCH 202/465] Fixed MessageManager notifications --- .../module/messagecenter/MessageManager.java | 33 +++++++++++++++++-- .../ApptentiveNotificationCenter.java | 3 +- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index e6ee64200..8c8ef2c8c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -16,6 +16,7 @@ import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; +import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveToastNotification; import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; @@ -46,6 +47,9 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_BACKGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_ACTIVITY; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_PAYLOAD; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_DID_SEND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_WILL_SEND; public class MessageManager implements Destroyable, ApptentiveNotificationObserver { @@ -94,6 +98,8 @@ public MessageManager(MessageStore messageStore) { this.messageStore = messageStore; this.pollingWorker = new MessagePollingWorker(this); // conversation.setMessageCenterFeatureUsed(true); FIXME: figure out what to do with this call + + registerNotifications(); } /* @@ -320,13 +326,26 @@ public int getUnreadMessageCount() { return msgCount; } + //region Notifications + + private void registerNotifications() { + ApptentiveNotificationCenter.defaultCenter() + .addObserver(NOTIFICATION_ACTIVITY_STARTED, this) + .addObserver(NOTIFICATION_ACTIVITY_RESUMED, this) + .addObserver(NOTIFICATION_APP_ENTER_FOREGROUND, this) + .addObserver(NOTIFICATION_APP_ENTER_BACKGROUND, this) + .addObserver(NOTIFICATION_PAYLOAD_WILL_SEND, this) + .addObserver(NOTIFICATION_PAYLOAD_DID_SEND, this); + } + + //endregion + //region Notification Observer @Override public void onReceiveNotification(ApptentiveNotification notification) { if (notification.hasName(NOTIFICATION_ACTIVITY_STARTED) || - notification.hasName(NOTIFICATION_ACTIVITY_RESUMED)) { - + notification.hasName(NOTIFICATION_ACTIVITY_RESUMED)) { final Activity activity = notification.getRequiredUserInfo(NOTIFICATION_KEY_ACTIVITY, Activity.class); setCurrentForegroundActivity(activity); } else if (notification.hasName(NOTIFICATION_APP_ENTER_FOREGROUND)) { @@ -334,6 +353,16 @@ public void onReceiveNotification(ApptentiveNotification notification) { } else if (notification.hasName(NOTIFICATION_APP_ENTER_BACKGROUND)) { setCurrentForegroundActivity(null); appWentToBackground(); + } else if (notification.hasName(NOTIFICATION_PAYLOAD_WILL_SEND)) { + final Payload payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, Payload.class); + if (payload instanceof ApptentiveMessage) { + resumeSending(); + } + } else if (notification.hasName(NOTIFICATION_PAYLOAD_DID_SEND)) { + final Payload payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, Payload.class); + if (payload instanceof ApptentiveMessage) { + // onSentMessage((ApptentiveMessage) payload, ???); + } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java index 1be921106..09cf2026d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java @@ -53,8 +53,9 @@ public class ApptentiveNotificationCenter { /** * Adds an entry to the receiver’s dispatch table with an observer using strong reference. */ - public synchronized void addObserver(String notification, ApptentiveNotificationObserver observer) { + public synchronized ApptentiveNotificationCenter addObserver(String notification, ApptentiveNotificationObserver observer) { addObserver(notification, observer, false); + return this; } /** From 321bdccf4f82167a4cb5a211a3f7d355ad067a79 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 5 Apr 2017 14:40:20 -0700 Subject: [PATCH 203/465] Refactoring Modified ApptentiveNotificationCenter chaining calls --- .../com/apptentive/android/sdk/ApptentiveBaseActivity.java | 5 +++-- .../android/sdk/conversation/ConversationManager.java | 4 ++-- .../com/apptentive/android/sdk/storage/PayloadSender.java | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java index 5efe1d9e6..5a84645fe 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveBaseActivity.java @@ -32,8 +32,9 @@ protected void onDestroy() { //region Notifications protected void registerNotifications() { - ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_INTERACTIONS_SHOULD_DISMISS, this); - ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, this); + ApptentiveNotificationCenter.defaultCenter() + .addObserver(NOTIFICATION_INTERACTIONS_SHOULD_DISMISS, this) + .addObserver(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, this); } protected void unregisterNotification() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 1614a11d6..ba5a74d6a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -71,8 +71,8 @@ public ConversationManager(Context context, File storageDir) { this.contextRef = new WeakReference<>(context.getApplicationContext()); this.storageDir = storageDir; - ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_ACTIVITY_STARTED, - new ApptentiveNotificationObserver() { + ApptentiveNotificationCenter.defaultCenter() + .addObserver(NOTIFICATION_ACTIVITY_STARTED, new ApptentiveNotificationObserver() { @Override public void onReceiveNotification(ApptentiveNotification notification) { if (activeConversation != null && activeConversation.hasActiveState()) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 4fd1c55cd..c05bf561e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -48,14 +48,14 @@ class PayloadSender implements ApptentiveNotificationObserver, Destroyable { @Override protected boolean shouldRetryRequest(int responseCode) { return !(appInBackground || responseCode >= 400 && responseCode < 500); - } }; requestRetryPolicy.setRetryTimeoutMillis(RETRY_TIMEOUT); requestRetryPolicy.setMaxRetryCount(RETRY_MAX_COUNT); - ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_APP_ENTER_BACKGROUND, this); - ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_APP_ENTER_FOREGROUND, this); + ApptentiveNotificationCenter.defaultCenter() + .addObserver(NOTIFICATION_APP_ENTER_BACKGROUND, this) + .addObserver(NOTIFICATION_APP_ENTER_FOREGROUND, this); } //region Payloads From 41583cb7018c1dff494dcc8c0621fae87d0fa51c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 6 Apr 2017 13:17:55 -0700 Subject: [PATCH 204/465] Fixed retry policy --- .../sdk/network/HttpRequestManagerTest.java | 14 +---- .../android/sdk/network/HttpRequest.java | 16 ++--- .../sdk/network/HttpRequestRetryPolicy.java | 46 +------------- .../HttpRequestRetryPolicyDefault.java | 60 ++++++++++++++++++ .../sdk/storage/ApptentiveTaskManager.java | 24 ++++++- .../android/sdk/storage/PayloadSender.java | 62 +++---------------- 6 files changed, 99 insertions(+), 123 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicyDefault.java diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java index 25b916ce0..05309640e 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java @@ -208,12 +208,7 @@ public void onRequestsCancel(HttpRequestManager manager) { @Test public void testFailedRetry() { - HttpRequestRetryPolicy retryPolicy = new HttpRequestRetryPolicy() { - @Override - protected boolean shouldRetryRequest(int responseCode) { - return responseCode == 500; - } - }; + HttpRequestRetryPolicyDefault retryPolicy = new HttpRequestRetryPolicyDefault(); retryPolicy.setMaxRetryCount(2); retryPolicy.setRetryTimeoutMillis(0); @@ -237,12 +232,7 @@ protected boolean shouldRetryRequest(int responseCode) { @Test public void testSuccessfulRetry() { - HttpRequestRetryPolicy retryPolicy = new HttpRequestRetryPolicy() { - @Override - protected boolean shouldRetryRequest(int responseCode) { - return responseCode == 500; - } - }; + HttpRequestRetryPolicyDefault retryPolicy = new HttpRequestRetryPolicyDefault(); retryPolicy.setMaxRetryCount(3); retryPolicy.setRetryTimeoutMillis(0); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 7c9099df0..a74161299 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -39,7 +39,7 @@ public class HttpRequest { /** * Default retry policy (used if a custom one is not specified) */ - private static final HttpRequestRetryPolicy DEFAULT_RETRY_POLICY = new HttpRequestRetryPolicy(); + private static final HttpRequestRetryPolicy DEFAULT_RETRY_POLICY = new HttpRequestRetryPolicyDefault(); /** * Id-number of the next request @@ -124,7 +124,7 @@ public class HttpRequest { /** * How many times request was retried already */ - private int retryAttemptCount; + private int retryAttempt; /** * Flag indicating if the request is currently scheduled for a retry @@ -303,21 +303,15 @@ protected void execute() { private boolean retryRequest(DispatchQueue networkQueue, int responseCode) { assertFalse(retryDispatchTask.isScheduled()); - final int maxRetryCount = retryPolicy.getMaxRetryCount(); - if (maxRetryCount != HttpRequestRetryPolicy.RETRY_INDEFINITELY && retryAttemptCount >= maxRetryCount) { - ApptentiveLog.v(NETWORK, "Request maximum retry limit reached (%d)", maxRetryCount); - return false; - } + ++retryAttempt; - if (!retryPolicy.shouldRetryRequest(responseCode)) { + if (!retryPolicy.shouldRetryRequest(responseCode, retryAttempt)) { ApptentiveLog.v(NETWORK, "Retry policy declined request retry"); return false; } - ++retryAttemptCount; - retrying = true; - networkQueue.dispatchAsyncOnce(retryDispatchTask, retryPolicy.getRetryTimeoutMillis(retryAttemptCount)); + networkQueue.dispatchAsyncOnce(retryDispatchTask, retryPolicy.getRetryTimeoutMillis(retryAttempt)); return true; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicy.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicy.java index f13fbc4d9..c447ab884 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicy.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicy.java @@ -6,47 +6,7 @@ package com.apptentive.android.sdk.network; -public class HttpRequestRetryPolicy { - public static final int RETRY_INDEFINITELY = -1; - public static final long DEFAULT_RETRY_TIMEOUT_MILLIS = 5 * 1000; - - /** - * How many times should request retry before giving up - */ - private int maxRetryCount = RETRY_INDEFINITELY; - - /** - * How long should we wait before retrying again - */ - private long retryTimeoutMillis = DEFAULT_RETRY_TIMEOUT_MILLIS; - - /** - * Returns true is request should be retried. - * - * @param responseCode - HTTP response code for the request - */ - protected boolean shouldRetryRequest(int responseCode) { - return false; // TODO: decide based on response code - } - - /** - * Returns the delay in millis for the next retry - * - * @param retryCount - number of retries attempted already - */ - protected long getRetryTimeoutMillis(int retryCount) { - return retryTimeoutMillis; - } - - public int getMaxRetryCount() { - return maxRetryCount; - } - - public void setMaxRetryCount(int maxRetryCount) { - this.maxRetryCount = maxRetryCount; - } - - public void setRetryTimeoutMillis(long retryTimeoutMillis) { - this.retryTimeoutMillis = retryTimeoutMillis; - } +public interface HttpRequestRetryPolicy { + boolean shouldRetryRequest(int responseCode, int retryAttempt); + long getRetryTimeoutMillis(int retryAttempt); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicyDefault.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicyDefault.java new file mode 100644 index 000000000..05fdad295 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestRetryPolicyDefault.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.network; + +public class HttpRequestRetryPolicyDefault implements HttpRequestRetryPolicy { + public static final int RETRY_COUNT_INFINITE = -1; + + public static final long DEFAULT_RETRY_TIMEOUT_MILLIS = 5 * 1000; + public static final int DEFAULT_RETRY_COUNT = 5; + + /** + * How many times should request retry before giving up + */ + private int maxRetryCount = DEFAULT_RETRY_COUNT; + + /** + * How long should we wait before retrying again + */ + private long retryTimeoutMillis = DEFAULT_RETRY_TIMEOUT_MILLIS; + + /** + * Returns true is request should be retried. + * + * @param responseCode - HTTP response code for the request + */ + @Override + public boolean shouldRetryRequest(int responseCode, int retryAttempt) { + if (responseCode >= 400 && responseCode < 500) { + return false; // don't retry if request was rejected permanently + } + + if (maxRetryCount == RETRY_COUNT_INFINITE) { + return true; // keep retrying indefinitely + } + + return retryAttempt <= maxRetryCount; // retry if we still can + } + + /** + * Returns the delay in millis for the next retry + * + * @param retryAttempt - number of retries attempted already + */ + @Override + public long getRetryTimeoutMillis(int retryAttempt) { + return retryTimeoutMillis; + } + + public void setMaxRetryCount(int maxRetryCount) { + this.maxRetryCount = maxRetryCount; + } + + public void setRetryTimeoutMillis(long retryTimeoutMillis) { + this.retryTimeoutMillis = retryTimeoutMillis; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index eb9f492c6..0d2cf8e33 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -13,6 +13,8 @@ import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.StoredFile; +import com.apptentive.android.sdk.network.HttpRequestRetryPolicy; +import com.apptentive.android.sdk.network.HttpRequestRetryPolicyDefault; import com.apptentive.android.sdk.notifications.ApptentiveNotification; import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; @@ -26,6 +28,8 @@ import java.util.concurrent.TimeUnit; import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_BACKGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS; @@ -42,6 +46,7 @@ public class ApptentiveTaskManager implements PayloadStore, EventStore, Apptenti private String currentConversationId; private final PayloadSender payloadSender; + private boolean appInBackground; /* * Creates an asynchronous task manager with one worker thread. This constructor must be invoked on the UI thread. @@ -62,10 +67,21 @@ public ApptentiveTaskManager(Context context, ApptentiveHttpClient apptentiveHtt // If no new task arrives in 30 seconds, the worker thread terminates; otherwise it will be reused singleThreadExecutor.allowCoreThreadTimeOut(true); - payloadSender = new PayloadSender(apptentiveHttpClient); + payloadSender = new PayloadSender(apptentiveHttpClient, new HttpRequestRetryPolicyDefault() { + @Override + public boolean shouldRetryRequest(int responseCode, int retryAttempt) { + if (appInBackground) { + return false; // don't retry if the app went background + } + return super.shouldRetryRequest(responseCode, retryAttempt); + } + }); payloadSender.setListener(this); - ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, this); + ApptentiveNotificationCenter.defaultCenter() + .addObserver(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, this) + .addObserver(NOTIFICATION_APP_ENTER_BACKGROUND, this) + .addObserver(NOTIFICATION_APP_ENTER_FOREGROUND, this); } /** @@ -222,6 +238,10 @@ public void run() { } else { currentConversationId = null; } + } else if (notification.hasName(NOTIFICATION_APP_ENTER_FOREGROUND)) { + appInBackground = false; + } else if (notification.hasName(NOTIFICATION_APP_ENTER_BACKGROUND)) { + appInBackground = true; } } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index c05bf561e..24a08ead2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -11,14 +11,9 @@ import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestRetryPolicy; -import com.apptentive.android.sdk.notifications.ApptentiveNotification; import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; -import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; -import com.apptentive.android.sdk.util.Destroyable; import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_BACKGROUND; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_PAYLOAD; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_SUCCESSFUL; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_DID_SEND; @@ -26,7 +21,7 @@ import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; -class PayloadSender implements ApptentiveNotificationObserver, Destroyable { +class PayloadSender { private static final long RETRY_TIMEOUT = 5000; private static final int RETRY_MAX_COUNT = 5; @@ -34,28 +29,19 @@ class PayloadSender implements ApptentiveNotificationObserver, Destroyable { private final HttpRequestRetryPolicy requestRetryPolicy; private Listener listener; - private boolean appInBackground; private boolean sendingFlag; // this variable is only accessed in a synchronized context - PayloadSender(PayloadRequestSender requestSender) { + PayloadSender(PayloadRequestSender requestSender, HttpRequestRetryPolicy retryPolicy) { if (requestSender == null) { throw new IllegalArgumentException("Payload request sender is null"); } - this.requestSender = requestSender; - - requestRetryPolicy = new HttpRequestRetryPolicy() { - @Override - protected boolean shouldRetryRequest(int responseCode) { - return !(appInBackground || responseCode >= 400 && responseCode < 500); - } - }; - requestRetryPolicy.setRetryTimeoutMillis(RETRY_TIMEOUT); - requestRetryPolicy.setMaxRetryCount(RETRY_MAX_COUNT); + if (retryPolicy == null) { + throw new IllegalArgumentException("Retry policy is null"); + } - ApptentiveNotificationCenter.defaultCenter() - .addObserver(NOTIFICATION_APP_ENTER_BACKGROUND, this) - .addObserver(NOTIFICATION_APP_ENTER_FOREGROUND, this); + this.requestSender = requestSender; + this.requestRetryPolicy = retryPolicy; } //region Payloads @@ -129,40 +115,6 @@ private synchronized void handleFinishSendingPayload(Payload payload, boolean ca //endregion - //region Background/Foreground - - private void onAppEnterBackground() { - appInBackground = true; - } - - private void onAppEnterForeground() { - appInBackground = false; - } - - //endregion - - //region Destroyable - - @Override - public void destroy() { - ApptentiveNotificationCenter.defaultCenter().removeObserver(this); - } - - //endregion - - //region ApptentiveNotificationObserver - - @Override - public void onReceiveNotification(ApptentiveNotification notification) { - if (notification.hasName(NOTIFICATION_APP_ENTER_BACKGROUND)) { - onAppEnterBackground(); - } else if (notification.hasName(NOTIFICATION_APP_ENTER_FOREGROUND)) { - onAppEnterForeground(); - } - } - - //endregion - //region Getters/Setters synchronized boolean isSendingPayload() { From 79c44ed6c1b40f2608a243e9f23d80ca8fd75471 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 6 Apr 2017 13:48:31 -0700 Subject: [PATCH 205/465] Fixed unit tests --- .../sdk/network/HttpRequestManagerTest.java | 17 +++++++++++++++-- .../sdk/network/MockHttpJsonRequest.java | 0 .../android/sdk/network/MockHttpRequest.java | 12 ++++++++++++ .../sdk/network/MockHttpURLConnection.java | 0 4 files changed, 27 insertions(+), 2 deletions(-) rename apptentive/src/{androidTest => testCommon}/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java (100%) rename apptentive/src/{androidTest => testCommon}/java/com/apptentive/android/sdk/network/MockHttpRequest.java (77%) rename apptentive/src/{androidTest => testCommon}/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java (100%) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java index 05309640e..316b18f0b 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java @@ -158,6 +158,8 @@ public void onRequestsCancel(HttpRequestManager manager) { } }); + + // start requests and let them finish requestManager.startRequest(new MockHttpRequest("1")); requestManager.startRequest(new MockHttpRequest("2").setMockResponseCode(500)); @@ -208,7 +210,12 @@ public void onRequestsCancel(HttpRequestManager manager) { @Test public void testFailedRetry() { - HttpRequestRetryPolicyDefault retryPolicy = new HttpRequestRetryPolicyDefault(); + HttpRequestRetryPolicyDefault retryPolicy = new HttpRequestRetryPolicyDefault() { + @Override + public boolean shouldRetryRequest(int responseCode, int retryAttempt) { + return responseCode != -1 && super.shouldRetryRequest(responseCode, retryAttempt); // don't retry on an exception + } + }; retryPolicy.setMaxRetryCount(2); retryPolicy.setRetryTimeoutMillis(0); @@ -232,7 +239,12 @@ public void testFailedRetry() { @Test public void testSuccessfulRetry() { - HttpRequestRetryPolicyDefault retryPolicy = new HttpRequestRetryPolicyDefault(); + HttpRequestRetryPolicyDefault retryPolicy = new HttpRequestRetryPolicyDefault() { + @Override + public boolean shouldRetryRequest(int responseCode, int retryAttempt) { + return responseCode != -1 && super.shouldRetryRequest(responseCode, retryAttempt); // don't retry on an exception + } + }; retryPolicy.setMaxRetryCount(3); retryPolicy.setRetryTimeoutMillis(0); @@ -282,6 +294,7 @@ public void onFail(MockHttpRequest request, String reason) { addResult("failed: " + request + " " + reason); } }); + requestManager.startRequest(request); } diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java similarity index 100% rename from apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java rename to apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpRequest.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpRequest.java similarity index 77% rename from apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpRequest.java rename to apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpRequest.java index e3ab9303b..21df62ec8 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpRequest.java +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpRequest.java @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + package com.apptentive.android.sdk.network; import com.apptentive.android.sdk.network.MockHttpURLConnection.ResponseHandler; @@ -15,6 +21,12 @@ class MockHttpRequest extends HttpRequest { connection = new MockHttpURLConnection(); connection.setMockResponseCode(200); setName(name); + setRetryPolicy(new HttpRequestRetryPolicyDefault() { + @Override + public boolean shouldRetryRequest(int responseCode, int retryAttempt) { + return false; // do not retry by default + } + }); } @Override diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java similarity index 100% rename from apptentive/src/androidTest/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java rename to apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java From f3c29e019e4b3e067c05cdb92e88c07c45ebf56a Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 6 Apr 2017 14:00:59 -0700 Subject: [PATCH 206/465] Payload sender is only responsible for sending payloads and nothing else --- .../sdk/storage/ApptentiveTaskManager.java | 18 ++++++++++++++++-- .../android/sdk/storage/PayloadSender.java | 18 ------------------ 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 0d2cf8e33..17b1ee94b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -32,10 +32,16 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_PAYLOAD; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_SUCCESSFUL; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_DID_SEND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_WILL_SEND; import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS; import static com.apptentive.android.sdk.conversation.ConversationState.UNDEFINED; import static com.apptentive.android.sdk.debug.Assert.assertNotNull; import static com.apptentive.android.sdk.debug.Assert.assertTrue; +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; public class ApptentiveTaskManager implements PayloadStore, EventStore, ApptentiveNotificationObserver, PayloadSender.Listener { @@ -164,9 +170,13 @@ public void reset(Context context) { //region PayloadSender.Listener - @Override public void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage) { + ApptentiveNotificationCenter.defaultCenter() + .postNotification(NOTIFICATION_PAYLOAD_DID_SEND, + NOTIFICATION_KEY_PAYLOAD, payload, + NOTIFICATION_KEY_SUCCESSFUL, errorMessage == null && !cancelled ? TRUE : FALSE); + if (cancelled) { ApptentiveLog.v(PAYLOADS, "Payload sending was cancelled: %s", payload); return; // don't remove cancelled payloads from the queue @@ -209,7 +219,11 @@ private void sendNextPayload() { return; } - payloadSender.sendPayload(payload); + boolean scheduled = payloadSender.sendPayload(payload); + if (scheduled) { + ApptentiveNotificationCenter.defaultCenter() + .postNotification(NOTIFICATION_PAYLOAD_WILL_SEND, NOTIFICATION_KEY_PAYLOAD, payload); + } } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 24a08ead2..ed1efd38d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -11,20 +11,10 @@ import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestRetryPolicy; -import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_PAYLOAD; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_SUCCESSFUL; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_DID_SEND; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_WILL_SEND; -import static java.lang.Boolean.FALSE; -import static java.lang.Boolean.TRUE; class PayloadSender { - private static final long RETRY_TIMEOUT = 5000; - private static final int RETRY_MAX_COUNT = 5; - private final PayloadRequestSender requestSender; private final HttpRequestRetryPolicy requestRetryPolicy; @@ -70,9 +60,6 @@ synchronized boolean sendPayload(final Payload payload) { private synchronized void sendPayloadRequest(final Payload payload) { ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getBaseType(), payload.getDatabaseId(), payload.getConversationId()); - ApptentiveNotificationCenter.defaultCenter() - .postNotification(NOTIFICATION_PAYLOAD_WILL_SEND, NOTIFICATION_KEY_PAYLOAD, payload); - final HttpRequest payloadRequest = requestSender.sendPayload(payload, new HttpRequest.Listener() { @Override public void onFinish(HttpJsonRequest request) { @@ -99,11 +86,6 @@ public void onFail(HttpJsonRequest request, String reason) { private synchronized void handleFinishSendingPayload(Payload payload, boolean cancelled, String errorMessage) { sendingFlag = false; - ApptentiveNotificationCenter.defaultCenter() - .postNotification(NOTIFICATION_PAYLOAD_DID_SEND, - NOTIFICATION_KEY_PAYLOAD, payload, - NOTIFICATION_KEY_SUCCESSFUL, errorMessage == null ? TRUE : FALSE); - try { if (listener != null) { listener.onFinishSending(this, payload, cancelled, errorMessage); From ef1f7761fcfe4c42e0dd13f16f9f1259f41e80d1 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 6 Apr 2017 15:14:41 -0700 Subject: [PATCH 207/465] Added tests for PayloadSender --- .../sdk/network/HttpRequestManagerTest.java | 5 +- .../sdk/comm/ApptentiveHttpClient.java | 2 +- .../apptentive/android/sdk/model/Payload.java | 2 +- .../sdk/storage/PayloadRequestSender.java | 2 +- .../android/sdk/storage/PayloadSender.java | 18 ++- .../sdk/storage/PayloadSenderTest.java | 143 ++++++++++++++++++ .../apptentive/android/sdk/TestCaseBase.java | 4 + .../sdk/network/MockHttpJsonRequest.java | 10 +- .../android/sdk/network/MockHttpRequest.java | 4 +- .../sdk/network/MockHttpURLConnection.java | 31 ++-- 10 files changed, 187 insertions(+), 34 deletions(-) create mode 100644 apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java index 316b18f0b..fc862c103 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java @@ -1,7 +1,7 @@ package com.apptentive.android.sdk.network; import com.apptentive.android.sdk.TestCaseBase; -import com.apptentive.android.sdk.network.MockHttpURLConnection.AbstractResponseHandler; +import com.apptentive.android.sdk.network.MockHttpURLConnection.DefaultResponseHandler; import com.apptentive.android.sdk.util.threading.MockDispatchQueue; import junit.framework.Assert; @@ -159,7 +159,6 @@ public void onRequestsCancel(HttpRequestManager manager) { }); - // start requests and let them finish requestManager.startRequest(new MockHttpRequest("1")); requestManager.startRequest(new MockHttpRequest("2").setMockResponseCode(500)); @@ -249,7 +248,7 @@ public boolean shouldRetryRequest(int responseCode, int retryAttempt) { retryPolicy.setRetryTimeoutMillis(0); // fail this request twice and then finish successfully - startRequest(new MockHttpRequest("1").setMockResponseHandler(new AbstractResponseHandler() { + startRequest(new MockHttpRequest("1").setMockResponseHandler(new DefaultResponseHandler() { int requestAttempts = 0; @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 0549757f7..ecc20da34 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -75,7 +75,7 @@ public HttpJsonRequest getConversationToken(ConversationTokenRequest conversatio //region PayloadRequestSender @Override - public HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener) { + public HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener) { if (payload == null) { throw new IllegalArgumentException("Payload is null"); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index 72d4eb802..72b05e828 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -14,7 +14,7 @@ public abstract class Payload extends JSONObject { // These three are not stored in the JSON, only the DB. - private Long databaseId; + private Long databaseId; // FIXME: use 'long' instead private BaseType baseType; private String conversationId; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java index cc07c5722..054db3388 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java @@ -11,5 +11,5 @@ import com.apptentive.android.sdk.network.HttpRequest; public interface PayloadRequestSender { - HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener); + HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index ed1efd38d..8e460923f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -11,6 +11,7 @@ import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestRetryPolicy; +import com.apptentive.android.sdk.util.StringUtils; import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; @@ -51,7 +52,14 @@ synchronized boolean sendPayload(final Payload payload) { try { sendPayloadRequest(payload); } catch (Exception e) { - handleFinishSendingPayload(payload, false, e.getMessage()); + ApptentiveLog.e(PAYLOADS, "Exception while sending payload: %s", payload); + + String message = e.getMessage(); + if (message == null) { + message = StringUtils.format("%s is thrown", e.getClass().getSimpleName()); + } + + handleFinishSendingPayload(payload, false, message); } return true; @@ -60,19 +68,19 @@ synchronized boolean sendPayload(final Payload payload) { private synchronized void sendPayloadRequest(final Payload payload) { ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getBaseType(), payload.getDatabaseId(), payload.getConversationId()); - final HttpRequest payloadRequest = requestSender.sendPayload(payload, new HttpRequest.Listener() { + final HttpRequest payloadRequest = requestSender.sendPayload(payload, new HttpRequest.Listener() { @Override - public void onFinish(HttpJsonRequest request) { + public void onFinish(HttpRequest request) { handleFinishSendingPayload(payload, false, null); } @Override - public void onCancel(HttpJsonRequest request) { + public void onCancel(HttpRequest request) { handleFinishSendingPayload(payload, true, null); } @Override - public void onFail(HttpJsonRequest request, String reason) { + public void onFail(HttpRequest request, String reason) { handleFinishSendingPayload(payload, false, reason); } }); diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java new file mode 100644 index 000000000..0c0d493d5 --- /dev/null +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import com.apptentive.android.sdk.TestCaseBase; +import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.network.HttpRequest; +import com.apptentive.android.sdk.network.HttpRequestManager; +import com.apptentive.android.sdk.network.HttpRequestRetryPolicyDefault; +import com.apptentive.android.sdk.network.MockHttpRequest; +import com.apptentive.android.sdk.network.MockHttpURLConnection; +import com.apptentive.android.sdk.network.MockHttpURLConnection.DefaultResponseHandler; +import com.apptentive.android.sdk.network.MockHttpURLConnection.ResponseHandler; +import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.util.threading.MockDispatchQueue; + +import org.junit.Before; +import org.junit.Test; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.TestCase.assertTrue; + +public class PayloadSenderTest extends TestCaseBase { + private MockDispatchQueue networkQueue; + + @Before + public void setUp() { + super.setUp(); + networkQueue = new MockDispatchQueue(false); + } + + @Test + public void testSendPayload() throws Exception { + + final MockPayloadRequestSender requestSender = new MockPayloadRequestSender(); + + PayloadSender sender = new PayloadSender(requestSender, new HttpRequestRetryPolicyDefault()); + sender.setListener(new PayloadSender.Listener() { + @Override + public void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage) { + if (cancelled) { + addResult("cancelled: " + payload); + } else if (errorMessage != null) { + addResult("failed: " + payload + " " + errorMessage); + } else { + addResult("succeed: " + payload); + } + } + }); + + final MockPayload payload1 = new MockPayload("key1", "value1"); + final MockPayload payload2 = new MockPayload("key2", "value2").setResponseHandler(new DefaultResponseHandler() { + int retryAttempt = 0; + + @Override + public int getResponseCode() { + return ++retryAttempt > 1 ? 200 : 500; + } + }); + final MockPayload payload3 = new MockPayload("key3", "value3").setResponseCode(400); + + assertTrue(sender.sendPayload(payload1)); + assertFalse(sender.sendPayload(payload2)); // would not start sending until the first one is complete + assertFalse(sender.sendPayload(payload3)); // would not start sending until the first one is complete + + networkQueue.dispatchTasks(); + assertResult( + "succeed: {'key1':'value1'}" + ); + + assertTrue(sender.sendPayload(payload2)); + assertFalse(sender.sendPayload(payload3)); // would not start sending until the first one is complete + + networkQueue.dispatchTasks(); + assertResult( + "succeed: {'key2':'value2'}" // NOTE: this request would succeed on the second attempt + ); + + assertTrue(sender.sendPayload(payload3)); + + networkQueue.dispatchTasks(); + assertResult( + "failed: {'key3':'value3'} Unexpected response code: 400 (Bad Request)" + ); + } + + class MockPayload extends Payload { + private final String json; + private ResponseHandler responseHandler; + + public MockPayload(String key, Object value) { + json = StringUtils.format("{'%s':'%s'}", key, value); + responseHandler = new DefaultResponseHandler(); + setDatabaseId(0L); + } + + @Override + protected void initBaseType() { + setBaseType(BaseType.event); + } + + public MockPayload setResponseCode(int responseCode) { + ((DefaultResponseHandler)responseHandler).setResponseCode(responseCode); + return this; + } + + public MockPayload setResponseHandler(ResponseHandler responseHandler) { + this.responseHandler = responseHandler; + return this; + } + + public ResponseHandler getResponseHandler() { + return responseHandler; + } + + @Override + public String toString() { + return json; + } + } + + class MockPayloadRequestSender implements PayloadRequestSender { + private final HttpRequestManager requestManager; + + public MockPayloadRequestSender() { + requestManager = new HttpRequestManager(networkQueue); + } + + @Override + public HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener) { + + MockHttpRequest request = new MockHttpRequest("http://apptentive.com"); + request.setMockResponseHandler(((MockPayload) payload).getResponseHandler()); + request.setListener(listener); + requestManager.startRequest(request); + return request; + } + } +} \ No newline at end of file diff --git a/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java index f9babe56f..e8b712b1c 100644 --- a/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/TestCaseBase.java @@ -46,6 +46,10 @@ protected void addResult(String str) { result.add(str); } + protected void addResult(String format, Object... params) { + result.add(StringUtils.format(format, params)); + } + protected void assertResult(String... expected) { // Make sure the expected and result sets contain the same number of items if (expected.length != result.size()) { diff --git a/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java index 46ad309e9..5869d0eb0 100644 --- a/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + package com.apptentive.android.sdk.network; import org.json.JSONObject; @@ -6,11 +12,11 @@ import java.net.HttpURLConnection; import java.net.URL; -class MockHttpJsonRequest extends HttpJsonRequest { +public class MockHttpJsonRequest extends HttpJsonRequest { private final MockHttpURLConnection connection; - MockHttpJsonRequest(String name, JSONObject requestObject) { + public MockHttpJsonRequest(String name, JSONObject requestObject) { super("https://abc.com", requestObject); connection = new MockHttpURLConnection(); connection.setMockResponseCode(200); diff --git a/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpRequest.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpRequest.java index 21df62ec8..d0d03b89c 100644 --- a/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpRequest.java +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpRequest.java @@ -12,11 +12,11 @@ import java.net.HttpURLConnection; import java.net.URL; -class MockHttpRequest extends HttpRequest { +public class MockHttpRequest extends HttpRequest { private final MockHttpURLConnection connection; - MockHttpRequest(String name) { + public MockHttpRequest(String name) { super("https://abc.com"); connection = new MockHttpURLConnection(); connection.setMockResponseCode(200); diff --git a/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java index 928ceca04..054c70dd1 100644 --- a/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpURLConnection.java @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + package com.apptentive.android.sdk.network; import java.io.ByteArrayInputStream; @@ -11,7 +17,7 @@ import java.util.HashMap; import java.util.Map; -class MockHttpURLConnection extends HttpURLConnection { +public class MockHttpURLConnection extends HttpURLConnection { private static final Map statusLookup; static { @@ -105,28 +111,15 @@ public interface ResponseHandler { String getErrorData(); } - public static class AbstractResponseHandler implements ResponseHandler { - @Override - public int getResponseCode() { - return 200; - } - - @Override - public String getResponseData() { - return ""; - } - - @Override - public String getErrorData() { - return ""; - } - } - - private static class DefaultResponseHandler implements ResponseHandler { + public static class DefaultResponseHandler implements ResponseHandler { private int responseCode; private String responseData; private String errorData; + public DefaultResponseHandler() { + this(200, "", ""); + } + public DefaultResponseHandler(int responseCode, String responseData, String errorData) { this.responseCode = responseCode; this.responseData = responseData; From cbc83e95183243632c2999e7da3e6eea58618560 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 6 Apr 2017 16:02:50 -0700 Subject: [PATCH 208/465] Refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed: ‘NOTIFICATION_PAYLOAD_WILL_SEND’ to ‘NOTIFICATION_PAYLOAD_WILL_START_SEND’ ‘NOTIFICATION_PAYLOAD_DID_SEND’ to ‘NOTIFICATION_PAYLOAD_DID_FINISH_SEND’ --- .../android/sdk/ApptentiveNotifications.java | 4 ++-- .../sdk/module/messagecenter/MessageManager.java | 12 ++++++------ .../android/sdk/storage/ApptentiveTaskManager.java | 9 ++++----- .../android/sdk/storage/PayloadSender.java | 1 - 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java index 9b3b043e0..60193cbcd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -36,12 +36,12 @@ public class ApptentiveNotifications { /** * Sent before payload request is sent to the server */ - public static final String NOTIFICATION_PAYLOAD_WILL_SEND = "NOTIFICATION_PAYLOAD_WILL_SEND"; // { payload: Payload } + public static final String NOTIFICATION_PAYLOAD_WILL_START_SEND = "NOTIFICATION_PAYLOAD_WILL_START_SEND"; // { payload: Payload } /** * Sent after payload sending if finished (might be successful or not) */ - public static final String NOTIFICATION_PAYLOAD_DID_SEND = "NOTIFICATION_PAYLOAD_DID_SEND"; // { successful : boolean, payload: Payload } + public static final String NOTIFICATION_PAYLOAD_DID_FINISH_SEND = "NOTIFICATION_PAYLOAD_DID_FINISH_SEND"; // { successful : boolean, payload: Payload } /** * Sent if user requested to close all interactions. diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 8c8ef2c8c..b10cf46d7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -48,8 +48,8 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_ACTIVITY; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_PAYLOAD; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_DID_SEND; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_WILL_SEND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_DID_FINISH_SEND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_WILL_START_SEND; public class MessageManager implements Destroyable, ApptentiveNotificationObserver { @@ -334,8 +334,8 @@ private void registerNotifications() { .addObserver(NOTIFICATION_ACTIVITY_RESUMED, this) .addObserver(NOTIFICATION_APP_ENTER_FOREGROUND, this) .addObserver(NOTIFICATION_APP_ENTER_BACKGROUND, this) - .addObserver(NOTIFICATION_PAYLOAD_WILL_SEND, this) - .addObserver(NOTIFICATION_PAYLOAD_DID_SEND, this); + .addObserver(NOTIFICATION_PAYLOAD_WILL_START_SEND, this) + .addObserver(NOTIFICATION_PAYLOAD_DID_FINISH_SEND, this); } //endregion @@ -353,12 +353,12 @@ public void onReceiveNotification(ApptentiveNotification notification) { } else if (notification.hasName(NOTIFICATION_APP_ENTER_BACKGROUND)) { setCurrentForegroundActivity(null); appWentToBackground(); - } else if (notification.hasName(NOTIFICATION_PAYLOAD_WILL_SEND)) { + } else if (notification.hasName(NOTIFICATION_PAYLOAD_WILL_START_SEND)) { final Payload payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, Payload.class); if (payload instanceof ApptentiveMessage) { resumeSending(); } - } else if (notification.hasName(NOTIFICATION_PAYLOAD_DID_SEND)) { + } else if (notification.hasName(NOTIFICATION_PAYLOAD_DID_FINISH_SEND)) { final Payload payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, Payload.class); if (payload instanceof ApptentiveMessage) { // onSentMessage((ApptentiveMessage) payload, ???); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 17b1ee94b..bb42906c6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -13,7 +13,6 @@ import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.StoredFile; -import com.apptentive.android.sdk.network.HttpRequestRetryPolicy; import com.apptentive.android.sdk.network.HttpRequestRetryPolicyDefault; import com.apptentive.android.sdk.notifications.ApptentiveNotification; import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; @@ -34,8 +33,8 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_PAYLOAD; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_SUCCESSFUL; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_DID_SEND; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_WILL_SEND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_DID_FINISH_SEND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_WILL_START_SEND; import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS; import static com.apptentive.android.sdk.conversation.ConversationState.UNDEFINED; import static com.apptentive.android.sdk.debug.Assert.assertNotNull; @@ -173,7 +172,7 @@ public void reset(Context context) { @Override public void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage) { ApptentiveNotificationCenter.defaultCenter() - .postNotification(NOTIFICATION_PAYLOAD_DID_SEND, + .postNotification(NOTIFICATION_PAYLOAD_DID_FINISH_SEND, NOTIFICATION_KEY_PAYLOAD, payload, NOTIFICATION_KEY_SUCCESSFUL, errorMessage == null && !cancelled ? TRUE : FALSE); @@ -222,7 +221,7 @@ private void sendNextPayload() { boolean scheduled = payloadSender.sendPayload(payload); if (scheduled) { ApptentiveNotificationCenter.defaultCenter() - .postNotification(NOTIFICATION_PAYLOAD_WILL_SEND, NOTIFICATION_KEY_PAYLOAD, payload); + .postNotification(NOTIFICATION_PAYLOAD_WILL_START_SEND, NOTIFICATION_KEY_PAYLOAD, payload); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 8e460923f..61817c590 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -8,7 +8,6 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.model.Payload; -import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestRetryPolicy; import com.apptentive.android.sdk.util.StringUtils; From fb96a4307c81de0e4aed529f75fad6a2903a8a8f Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 6 Apr 2017 16:03:13 -0700 Subject: [PATCH 209/465] Fixed payload background sending behavior --- .../android/sdk/storage/ApptentiveTaskManager.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index bb42906c6..d3eed22b6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -183,6 +183,10 @@ public void onFinishSending(PayloadSender sender, Payload payload, boolean cance if (errorMessage != null) { ApptentiveLog.v(PAYLOADS, "Payload sending failed: %s\n%s", payload, errorMessage); + if (appInBackground) { + ApptentiveLog.v(PAYLOADS, "The app went to the background so we won't remove the payload from the queue"); + return; + } } else { ApptentiveLog.v(PAYLOADS, "Payload was successfully sent: %s", payload); } @@ -195,6 +199,11 @@ public void onFinishSending(PayloadSender sender, Payload payload, boolean cance //region Payload Sending private void sendNextPayload() { + if (appInBackground) { + ApptentiveLog.v(PAYLOADS, "Can't send the next payload: the app is in the background"); + return; + } + final Payload payload; try { payload = getOldestUnsentPayload().get(); From b52165fa8d01a07899072f446fbb5f23bd876719 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 6 Apr 2017 16:15:53 -0700 Subject: [PATCH 210/465] Fixed sending payloads --- .../sdk/storage/ApptentiveTaskManager.java | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index d3eed22b6..6ac1cb580 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -18,6 +18,8 @@ import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchTask; import java.util.List; import java.util.concurrent.Callable; @@ -101,7 +103,7 @@ public void addPayload(final Payload... payloads) { @Override public void run() { dbHelper.addPayload(payloads); - sendNextPayload(); + sendNextPayloadSync(); } }); } @@ -112,7 +114,7 @@ public void deletePayload(final Payload payload) { @Override public void run() { dbHelper.deletePayload(payload); - sendNextPayload(); + sendNextPayloadSync(); } }); } @@ -131,11 +133,15 @@ public synchronized Future getOldestUnsentPayload() throws Exception { return singleThreadExecutor.submit(new Callable() { @Override public Payload call() throws Exception { - return dbHelper.getOldestUnsentPayload(); + return getOldestUnsentPayloadSync(); } }); } + private Payload getOldestUnsentPayloadSync() { + return dbHelper.getOldestUnsentPayload(); + } + public void deleteAssociatedFiles(final String messageNonce) { singleThreadExecutor.execute(new Runnable() { @Override @@ -197,16 +203,29 @@ public void onFinishSending(PayloadSender sender, Payload payload, boolean cance //endregion //region Payload Sending - private void sendNextPayload() { + DispatchQueue.backgroundQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + sendNextPayloadSync(); + } + }); + } + + private void sendNextPayloadSync() { if (appInBackground) { ApptentiveLog.v(PAYLOADS, "Can't send the next payload: the app is in the background"); return; } + if (payloadSender.isSendingPayload()) { + ApptentiveLog.v(PAYLOADS, "Can't send the next payload: payload sender is busy"); + return; + } + final Payload payload; try { - payload = getOldestUnsentPayload().get(); + payload = getOldestUnsentPayloadSync(); } catch (Exception e) { ApptentiveLog.e(e, "Exception while peeking the next payload for sending"); return; @@ -222,11 +241,6 @@ private void sendNextPayload() { return; } - if (payloadSender.isSendingPayload()) { - ApptentiveLog.v(PAYLOADS, "Can't send the next payload: payload sender is busy"); - return; - } - boolean scheduled = payloadSender.sendPayload(payload); if (scheduled) { ApptentiveNotificationCenter.defaultCenter() @@ -253,6 +267,7 @@ public void onReceiveNotification(ApptentiveNotification notification) { @Override public void run() { dbHelper.updateMissingConversationIds(currentConversationId); + sendNextPayloadSync(); // after we've updated payloads - we need to send them } }); } @@ -262,6 +277,7 @@ public void run() { } } else if (notification.hasName(NOTIFICATION_APP_ENTER_FOREGROUND)) { appInBackground = false; + sendNextPayload(); } else if (notification.hasName(NOTIFICATION_APP_ENTER_BACKGROUND)) { appInBackground = true; } From a35862a3c79998846a258e090137cdfa2ba852da Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 6 Apr 2017 16:31:12 -0700 Subject: [PATCH 211/465] Added missing code comments --- .../sdk/storage/ApptentiveTaskManager.java | 5 ++- .../sdk/storage/PayloadRequestSender.java | 11 ++++- .../android/sdk/storage/PayloadSender.java | 42 ++++++++++++++++++- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 6ac1cb580..fe695d150 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -74,6 +74,7 @@ public ApptentiveTaskManager(Context context, ApptentiveHttpClient apptentiveHtt // If no new task arrives in 30 seconds, the worker thread terminates; otherwise it will be reused singleThreadExecutor.allowCoreThreadTimeOut(true); + // Create payload sender object with a custom 'retry' policy payloadSender = new PayloadSender(apptentiveHttpClient, new HttpRequestRetryPolicyDefault() { @Override public boolean shouldRetryRequest(int responseCode, int retryAttempt) { @@ -242,6 +243,8 @@ private void sendNextPayloadSync() { } boolean scheduled = payloadSender.sendPayload(payload); + + // if payload sending was scheduled - notify the rest of the SDK if (scheduled) { ApptentiveNotificationCenter.defaultCenter() .postNotification(NOTIFICATION_PAYLOAD_WILL_START_SEND, NOTIFICATION_KEY_PAYLOAD, payload); @@ -277,7 +280,7 @@ public void run() { } } else if (notification.hasName(NOTIFICATION_APP_ENTER_FOREGROUND)) { appInBackground = false; - sendNextPayload(); + sendNextPayload(); // when the app comes back from the background - we need to resume sending payloads } else if (notification.hasName(NOTIFICATION_APP_ENTER_BACKGROUND)) { appInBackground = true; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java index 054db3388..7282a1cb2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java @@ -7,9 +7,18 @@ package com.apptentive.android.sdk.storage; import com.apptentive.android.sdk.model.Payload; -import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; +/** + * Class responsible for creating and sending an {@link HttpRequest} with a given payload + * FIXME: this is a legacy workaround and would be removed soon + */ public interface PayloadRequestSender { + /** + * Creates and sends an {@link HttpRequest} for a given payload + * + * @param payload to be sent + * @param listener Http-request listener for the payload request + */ HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 61817c590..a6becc302 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -14,11 +14,25 @@ import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; +/** + * Class responsible for a serial payload sending (one at a time) + */ class PayloadSender { + /** + * Object which creates and send Http-request for payloads + */ private final PayloadRequestSender requestSender; + + /** + * Payload Http-request retry policy + */ private final HttpRequestRetryPolicy requestRetryPolicy; private Listener listener; + + /** + * Indicates whenever the sender is busy sending a payload + */ private boolean sendingFlag; // this variable is only accessed in a synchronized context PayloadSender(PayloadRequestSender requestSender, HttpRequestRetryPolicy retryPolicy) { @@ -36,6 +50,12 @@ class PayloadSender { //region Payloads + /** + * Sends payload asynchronously. Returns boolean flag immediately indicating if payload send was + * scheduled + * + * @throws IllegalArgumentException is payload is null + */ synchronized boolean sendPayload(final Payload payload) { if (payload == null) { throw new IllegalArgumentException("Payload is null"); @@ -46,6 +66,7 @@ synchronized boolean sendPayload(final Payload payload) { return false; } + // we mark the sender as "busy" so no other payloads would be sent until we're done sendingFlag = true; try { @@ -53,20 +74,27 @@ synchronized boolean sendPayload(final Payload payload) { } catch (Exception e) { ApptentiveLog.e(PAYLOADS, "Exception while sending payload: %s", payload); + // for NullPointerException, the message object would be null, we should handle it separately + // TODO: add a helper class for handling that String message = e.getMessage(); if (message == null) { message = StringUtils.format("%s is thrown", e.getClass().getSimpleName()); } + // if an exception was thrown - mark payload as failed handleFinishSendingPayload(payload, false, message); } return true; } + /** + * Creates and sends payload Http-request asynchronously (returns immediately) + */ private synchronized void sendPayloadRequest(final Payload payload) { ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getBaseType(), payload.getDatabaseId(), payload.getConversationId()); + // create request object final HttpRequest payloadRequest = requestSender.sendPayload(payload, new HttpRequest.Listener() { @Override public void onFinish(HttpRequest request) { @@ -83,6 +111,8 @@ public void onFail(HttpRequest request, String reason) { handleFinishSendingPayload(payload, false, reason); } }); + + // set 'retry' policy payloadRequest.setRetryPolicy(requestRetryPolicy); } @@ -90,8 +120,15 @@ public void onFail(HttpRequest request, String reason) { //region Listener notification + /** + * Executed when we're done with the current payload + * + * @param payload - current payload + * @param cancelled - flag indicating if payload Http-request was cancelled + * @param errorMessage - if not null - payload request failed + */ private synchronized void handleFinishSendingPayload(Payload payload, boolean cancelled, String errorMessage) { - sendingFlag = false; + sendingFlag = false; // mark sender as 'not busy' try { if (listener != null) { @@ -106,6 +143,9 @@ private synchronized void handleFinishSendingPayload(Payload payload, boolean ca //region Getters/Setters + /** + * Returns true if sender is currently busy with a payload + */ synchronized boolean isSendingPayload() { return sendingFlag; } From 4256f23e48289e44c0fec9e9f21c0757c63c52d2 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 7 Apr 2017 13:05:07 -0700 Subject: [PATCH 212/465] Fixed sending payloads --- .../sdk/comm/ApptentiveHttpClient.java | 44 +++++++++---------- .../apptentive/android/sdk/model/Payload.java | 9 ++++ .../sdk/storage/ApptentiveTaskManager.java | 14 +++++- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index ecc20da34..9723e0354 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -1,5 +1,6 @@ package com.apptentive.android.sdk.comm; +import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.model.AppReleasePayload; import com.apptentive.android.sdk.model.ConversationTokenRequest; import com.apptentive.android.sdk.model.DevicePayload; @@ -41,16 +42,16 @@ public class ApptentiveHttpClient implements PayloadRequestSender { private static final String ENDPOINT_PEOPLE = "/people"; private static final String ENDPOINT_SURVEYS_POST = "/surveys/%s/respond"; - private final String oauthToken; + private final String apiKey; private final String serverURL; private final String userAgentString; private final HttpRequestManager httpRequestManager; private final Map, PayloadRequestFactory> payloadRequestFactoryLookup; - public ApptentiveHttpClient(String oauthToken, String serverURL) { - if (isEmpty(oauthToken)) { - throw new IllegalArgumentException("Illegal OAuth Token: '" + oauthToken + "'"); + public ApptentiveHttpClient(String apiKey, String serverURL) { + if (isEmpty(apiKey)) { + throw new IllegalArgumentException("Illegal API key: '" + apiKey + "'"); } if (isEmpty(serverURL)) { @@ -58,7 +59,7 @@ public ApptentiveHttpClient(String oauthToken, String serverURL) { } this.httpRequestManager = new HttpRequestManager(); - this.oauthToken = oauthToken; + this.apiKey = apiKey; this.serverURL = serverURL; this.userAgentString = String.format(USER_AGENT_STRING, Constants.APPTENTIVE_SDK_VERSION); this.payloadRequestFactoryLookup = createPayloadRequestFactoryLookup(); @@ -67,7 +68,10 @@ public ApptentiveHttpClient(String oauthToken, String serverURL) { //region API Requests public HttpJsonRequest getConversationToken(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { - return startJsonRequest(ENDPOINT_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST, listener); + HttpJsonRequest request = createJsonRequest(apiKey, ENDPOINT_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST); + request.setListener(listener); + httpRequestManager.startRequest(request); + return request; } //endregion @@ -102,7 +106,7 @@ private Map, PayloadRequestFactory> createPayloadReques lookup.put(EventPayload.class, new PayloadRequestFactory() { @Override public HttpRequest createRequest(EventPayload payload) { - return createJsonRequest(ENDPOINT_EVENTS, payload, HttpRequestMethod.POST); + return createJsonRequest(payload.getConversationToken(), ENDPOINT_EVENTS, payload, HttpRequestMethod.POST); } }); @@ -110,7 +114,7 @@ public HttpRequest createRequest(EventPayload payload) { lookup.put(DevicePayload.class, new PayloadRequestFactory() { @Override public HttpRequest createRequest(DevicePayload payload) { - return createJsonRequest(ENDPOINT_DEVICES, payload, HttpRequestMethod.PUT); + return createJsonRequest(payload.getConversationToken(), ENDPOINT_DEVICES, payload, HttpRequestMethod.PUT); } }); @@ -118,7 +122,7 @@ public HttpRequest createRequest(DevicePayload payload) { lookup.put(SdkPayload.class, new PayloadRequestFactory() { @Override public HttpRequest createRequest(SdkPayload payload) { - return createJsonRequest(ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); + return createJsonRequest(payload.getConversationToken(), ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); } }); @@ -126,7 +130,7 @@ public HttpRequest createRequest(SdkPayload payload) { lookup.put(AppReleasePayload.class, new PayloadRequestFactory() { @Override public HttpRequest createRequest(AppReleasePayload payload) { - return createJsonRequest(ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); + return createJsonRequest(payload.getConversationToken(), ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); } }); @@ -134,7 +138,7 @@ public HttpRequest createRequest(AppReleasePayload payload) { lookup.put(SdkAndAppReleasePayload.class, new PayloadRequestFactory() { @Override public HttpRequest createRequest(SdkAndAppReleasePayload payload) { - return createJsonRequest(ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); + return createJsonRequest(payload.getConversationToken(), ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); } }); @@ -142,7 +146,7 @@ public HttpRequest createRequest(SdkAndAppReleasePayload payload) { lookup.put(PersonPayload.class, new PayloadRequestFactory() { @Override public HttpRequest createRequest(PersonPayload payload) { - return createJsonRequest(ENDPOINT_PEOPLE, payload, HttpRequestMethod.PUT); + return createJsonRequest(payload.getConversationToken(), ENDPOINT_PEOPLE, payload, HttpRequestMethod.PUT); } }); @@ -151,7 +155,7 @@ public HttpRequest createRequest(PersonPayload payload) { @Override public HttpRequest createRequest(SurveyResponsePayload survey) { String endpoint = String.format(ENDPOINT_SURVEYS_POST, survey.getId()); - return createJsonRequest(endpoint, survey, HttpRequestMethod.POST); + return createJsonRequest(survey.getConversationToken(), endpoint, survey, HttpRequestMethod.POST); } }); @@ -162,23 +166,19 @@ public HttpRequest createRequest(SurveyResponsePayload survey) { //region Helpers - private HttpJsonRequest startJsonRequest(String endpoint, JSONObject jsonObject, HttpRequestMethod method, HttpRequest.Listener listener) { - HttpJsonRequest request = createJsonRequest(endpoint, jsonObject, method); - request.setListener(listener); - httpRequestManager.startRequest(request); - return request; - } + private HttpJsonRequest createJsonRequest(String oauthToken, String endpoint, JSONObject jsonObject, HttpRequestMethod method) { + Assert.assertNotNull(oauthToken); + Assert.assertNotNull(endpoint); - private HttpJsonRequest createJsonRequest(String endpoint, JSONObject jsonObject, HttpRequestMethod method) { String url = createEndpointURL(endpoint); HttpJsonRequest request = new HttpJsonRequest(url, jsonObject); - setupRequestDefaults(request); + setupRequestDefaults(request, oauthToken); request.setMethod(method); request.setRequestProperty("Content-Type", "application/json"); return request; } - private void setupRequestDefaults(HttpRequest request) { + private void setupRequestDefaults(HttpRequest request, String oauthToken) { request.setRequestProperty("User-Agent", userAgentString); request.setRequestProperty("Connection", "Keep-Alive"); request.setRequestProperty("Authorization", "OAuth " + oauthToken); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index 72b05e828..9c554bf29 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -17,6 +17,7 @@ public abstract class Payload extends JSONObject { private Long databaseId; // FIXME: use 'long' instead private BaseType baseType; private String conversationId; + private String conversationToken; public Payload() { initBaseType(); @@ -77,6 +78,14 @@ public void setConversationId(String conversationId) { this.conversationId = conversationId; } + public void setConversationToken(String conversationToken) { + this.conversationToken = conversationToken; + } + + public String getConversationToken() { + return conversationToken; + } + public enum BaseType { message, event, diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index fe695d150..42064a141 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -11,6 +11,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.conversation.Conversation; +import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.network.HttpRequestRetryPolicyDefault; @@ -51,6 +52,7 @@ public class ApptentiveTaskManager implements PayloadStore, EventStore, Apptenti // Set when receiving an ApptentiveNotification private String currentConversationId; + private String currentConversationToken; private final PayloadSender payloadSender; private boolean appInBackground; @@ -99,6 +101,8 @@ public boolean shouldRetryRequest(int responseCode, int retryAttempt) { public void addPayload(final Payload... payloads) { for (Payload payload : payloads) { payload.setConversationId(currentConversationId); + payload.setConversationToken(currentConversationToken); + } singleThreadExecutor.execute(new Runnable() { @Override @@ -242,6 +246,10 @@ private void sendNextPayloadSync() { return; } + // FIXME: store conversation data in the database + payload.setConversationId(currentConversationId); + payload.setConversationToken(currentConversationToken); + boolean scheduled = payloadSender.sendPayload(payload); // if payload sending was scheduled - notify the rest of the SDK @@ -261,6 +269,10 @@ public void onReceiveNotification(ApptentiveNotification notification) { if (conversation.hasActiveState()) { assertNotNull(conversation.getConversationId()); currentConversationId = conversation.getConversationId(); + Assert.assertNotNull(currentConversationId); + + currentConversationToken = conversation.getConversationToken(); + Assert.assertNotNull(currentConversationToken); // when the Conversation ID comes back from the server, we need to update // the payloads that may have already been enqueued so @@ -269,7 +281,7 @@ public void onReceiveNotification(ApptentiveNotification notification) { singleThreadExecutor.execute(new Runnable() { @Override public void run() { - dbHelper.updateMissingConversationIds(currentConversationId); + dbHelper.updateMissingConversationIds(currentConversationId); // TODO: update missing conversation token sendNextPayloadSync(); // after we've updated payloads - we need to send them } }); From eb3ba4675d8664e169c77aedb61ca49eda76efa9 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 10 Apr 2017 17:40:00 -0700 Subject: [PATCH 213/465] Don't attempt to parse empry HTTP response. --- .../com/apptentive/android/sdk/network/HttpJsonRequest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonRequest.java index f93bb41ed..2e7175236 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonRequest.java @@ -1,5 +1,7 @@ package com.apptentive.android.sdk.network; +import com.apptentive.android.sdk.util.StringUtils; + import org.json.JSONException; import org.json.JSONObject; @@ -30,7 +32,9 @@ protected byte[] createRequestData() throws IOException { @Override protected void handleResponse(String response) throws IOException { try { - responseObject = new JSONObject(response); + if (!StringUtils.isNullOrEmpty(response)) { + responseObject = new JSONObject(response); + } } catch (JSONException e) { throw new IOException(e); } From 04f3c9c5bad04ba26157aaaa204ef130375861a6 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 10 Apr 2017 17:43:21 -0700 Subject: [PATCH 214/465] Better logging in `HttpRequest` --- .../java/com/apptentive/android/sdk/network/HttpRequest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index a74161299..5dced3309 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -259,6 +259,7 @@ protected void sendRequestSync() throws IOException { // send request responseCode = connection.getResponseCode(); + ApptentiveLog.d(NETWORK, "Response: %d %s", responseCode, connection.getResponseMessage()); if (isCancelled()) { return; @@ -271,11 +272,11 @@ protected void sendRequestSync() throws IOException { boolean gzipped = isGzipContentEncoding(responseHeaders); if (responseCode >= HttpURLConnection.HTTP_OK && responseCode < HttpURLConnection.HTTP_MULT_CHOICE) { responseData = readResponse(connection.getInputStream(), gzipped); - ApptentiveLog.v(NETWORK, "Response: %s", responseData); + ApptentiveLog.v(NETWORK, "Response data: %s", responseData); } else { errorMessage = StringUtils.format("Unexpected response code: %d (%s)", responseCode, connection.getResponseMessage()); responseData = readResponse(connection.getErrorStream(), gzipped); - ApptentiveLog.w(NETWORK, "Response: %s", responseData); + ApptentiveLog.w(NETWORK, "Response data: %s", responseData); } if (isCancelled()) { From 36f175c229ec137b254c1958de43863f02e3df84 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 10 Apr 2017 17:44:47 -0700 Subject: [PATCH 215/465] ANDROID-932 Store auth token in each payload Also rename `conversationToken` to just `token`, since we will ultimately use either the conversation or app level token. --- .../sdk/comm/ApptentiveHttpClient.java | 14 +++++------ .../apptentive/android/sdk/model/Payload.java | 13 +++++----- .../sdk/storage/ApptentiveDatabaseHelper.java | 24 ++++++++++++------- .../sdk/storage/ApptentiveTaskManager.java | 11 +++------ 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 9723e0354..1dcb25f99 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -106,7 +106,7 @@ private Map, PayloadRequestFactory> createPayloadReques lookup.put(EventPayload.class, new PayloadRequestFactory() { @Override public HttpRequest createRequest(EventPayload payload) { - return createJsonRequest(payload.getConversationToken(), ENDPOINT_EVENTS, payload, HttpRequestMethod.POST); + return createJsonRequest(payload.getToken(), ENDPOINT_EVENTS, payload, HttpRequestMethod.POST); } }); @@ -114,7 +114,7 @@ public HttpRequest createRequest(EventPayload payload) { lookup.put(DevicePayload.class, new PayloadRequestFactory() { @Override public HttpRequest createRequest(DevicePayload payload) { - return createJsonRequest(payload.getConversationToken(), ENDPOINT_DEVICES, payload, HttpRequestMethod.PUT); + return createJsonRequest(payload.getToken(), ENDPOINT_DEVICES, payload, HttpRequestMethod.PUT); } }); @@ -122,7 +122,7 @@ public HttpRequest createRequest(DevicePayload payload) { lookup.put(SdkPayload.class, new PayloadRequestFactory() { @Override public HttpRequest createRequest(SdkPayload payload) { - return createJsonRequest(payload.getConversationToken(), ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); + return createJsonRequest(payload.getToken(), ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); } }); @@ -130,7 +130,7 @@ public HttpRequest createRequest(SdkPayload payload) { lookup.put(AppReleasePayload.class, new PayloadRequestFactory() { @Override public HttpRequest createRequest(AppReleasePayload payload) { - return createJsonRequest(payload.getConversationToken(), ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); + return createJsonRequest(payload.getToken(), ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); } }); @@ -138,7 +138,7 @@ public HttpRequest createRequest(AppReleasePayload payload) { lookup.put(SdkAndAppReleasePayload.class, new PayloadRequestFactory() { @Override public HttpRequest createRequest(SdkAndAppReleasePayload payload) { - return createJsonRequest(payload.getConversationToken(), ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); + return createJsonRequest(payload.getToken(), ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); } }); @@ -146,7 +146,7 @@ public HttpRequest createRequest(SdkAndAppReleasePayload payload) { lookup.put(PersonPayload.class, new PayloadRequestFactory() { @Override public HttpRequest createRequest(PersonPayload payload) { - return createJsonRequest(payload.getConversationToken(), ENDPOINT_PEOPLE, payload, HttpRequestMethod.PUT); + return createJsonRequest(payload.getToken(), ENDPOINT_PEOPLE, payload, HttpRequestMethod.PUT); } }); @@ -155,7 +155,7 @@ public HttpRequest createRequest(PersonPayload payload) { @Override public HttpRequest createRequest(SurveyResponsePayload survey) { String endpoint = String.format(ENDPOINT_SURVEYS_POST, survey.getId()); - return createJsonRequest(survey.getConversationToken(), endpoint, survey, HttpRequestMethod.POST); + return createJsonRequest(survey.getToken(), endpoint, survey, HttpRequestMethod.POST); } }); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index 9c554bf29..8b7fdf1ea 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -17,7 +17,7 @@ public abstract class Payload extends JSONObject { private Long databaseId; // FIXME: use 'long' instead private BaseType baseType; private String conversationId; - private String conversationToken; + private String token; public Payload() { initBaseType(); @@ -28,9 +28,10 @@ public Payload(String json) throws JSONException { initBaseType(); } - public Payload(String json, String conversationId) throws JSONException { + public Payload(String json, String conversationId, String token) throws JSONException { this(json); this.conversationId = conversationId; + this.token = token; } /** @@ -78,12 +79,12 @@ public void setConversationId(String conversationId) { this.conversationId = conversationId; } - public void setConversationToken(String conversationToken) { - this.conversationToken = conversationToken; + public void setToken(String token) { + this.token = token; } - public String getConversationToken() { - return conversationToken; + public String getToken() { + return token; } public enum BaseType { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 7a4b83656..1544483a4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -14,7 +14,6 @@ import android.database.sqlite.SQLiteOpenHelper; import android.text.TextUtils; -import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.PayloadFactory; @@ -50,6 +49,7 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { public static final String PAYLOAD_KEY_BASE_TYPE = "base_type"; // 1 public static final String PAYLOAD_KEY_JSON = "json"; // 2 private static final String PAYLOAD_KEY_CONVERSATION_ID = "conversation_id"; // 3 + private static final String PAYLOAD_KEY_TOKEN = "token"; // 4 private static final String TABLE_CREATE_PAYLOAD = "CREATE TABLE " + TABLE_PAYLOAD + @@ -57,14 +57,16 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { PAYLOAD_KEY_DB_ID + " INTEGER PRIMARY KEY, " + PAYLOAD_KEY_BASE_TYPE + " TEXT, " + PAYLOAD_KEY_JSON + " TEXT," + - PAYLOAD_KEY_CONVERSATION_ID + " TEXT" + + PAYLOAD_KEY_CONVERSATION_ID + " TEXT," + + PAYLOAD_KEY_TOKEN + " TEXT" + ");"; public static final String QUERY_PAYLOAD_GET_NEXT_TO_SEND = "SELECT * FROM " + TABLE_PAYLOAD + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC LIMIT 1"; - public static final String QUERY_UPDATE_MISSING_CONVERSATION_IDS = StringUtils.format("UPDATE %s SET %s = ? WHERE %s IS NULL", TABLE_PAYLOAD, PAYLOAD_KEY_CONVERSATION_ID, PAYLOAD_KEY_CONVERSATION_ID); + private static final String QUERY_UPDATE_INCOMPLETE_PAYLOADS = StringUtils.format("UPDATE %s SET %s = ?, %s = ? WHERE %s IS NULL OR %s IS NULL", TABLE_PAYLOAD, PAYLOAD_KEY_CONVERSATION_ID, PAYLOAD_KEY_TOKEN, PAYLOAD_KEY_CONVERSATION_ID, PAYLOAD_KEY_TOKEN); private static final String QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER = "SELECT * FROM " + TABLE_PAYLOAD + " WHERE " + PAYLOAD_KEY_BASE_TYPE + " = ?" + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC"; - private static final String UPGRADE_V2_to_v3_ALTER_PAYLOAD = "ALTER TABLE " + TABLE_PAYLOAD + " ADD COLUMN " + PAYLOAD_KEY_CONVERSATION_ID + " TEXT"; + private static final String UPGRADE_V2_to_v3_ALTER_PAYLOAD_ADD_CONVERSATION_ID = "ALTER TABLE " + TABLE_PAYLOAD + " ADD COLUMN " + PAYLOAD_KEY_CONVERSATION_ID + " TEXT"; + private static final String UPGRADE_V2_to_v3_ALTER_PAYLOAD_ADD_TOKEN = "ALTER TABLE " + TABLE_PAYLOAD + " ADD COLUMN " + PAYLOAD_KEY_TOKEN + " TEXT"; //endregion @@ -322,7 +324,8 @@ private void upgradeVersion1to2(SQLiteDatabase db) { private void upgradeVersion2to3(SQLiteDatabase db) { ApptentiveLog.i(DATABASE, "Upgrading Database from v2 to v3"); - db.execSQL(UPGRADE_V2_to_v3_ALTER_PAYLOAD); + db.execSQL(UPGRADE_V2_to_v3_ALTER_PAYLOAD_ADD_CONVERSATION_ID); + db.execSQL(UPGRADE_V2_to_v3_ALTER_PAYLOAD_ADD_TOKEN); } //endregion @@ -343,6 +346,7 @@ public void addPayload(Payload... payloads) { values.put(PAYLOAD_KEY_BASE_TYPE, payload.getBaseType().name()); values.put(PAYLOAD_KEY_JSON, payload.toString()); values.put(PAYLOAD_KEY_CONVERSATION_ID, payload.getConversationId()); + values.put(PAYLOAD_KEY_TOKEN, payload.getToken()); db.insert(TABLE_PAYLOAD, null, values); } db.setTransactionSuccessful(); @@ -387,10 +391,12 @@ public Payload getOldestUnsentPayload() { Payload.BaseType baseType = Payload.BaseType.parse(cursor.getString(1)); String json = cursor.getString(2); String conversationId = cursor.getString(3); + String token = cursor.getString(4); payload = PayloadFactory.fromJson(json, baseType); if (payload != null) { payload.setDatabaseId(databaseId); payload.setConversationId(conversationId); + payload.setToken(token); } } return payload; @@ -402,15 +408,17 @@ public Payload getOldestUnsentPayload() { } } - public void updateMissingConversationIds(String conversationId) { + public void updateIncompletePayloads(String conversationId, String token) { if (StringUtils.isNullOrEmpty(conversationId)) { throw new IllegalArgumentException("Conversation id is null or empty"); } - + if (StringUtils.isNullOrEmpty(token)) { + throw new IllegalArgumentException("Token is null or empty"); + } Cursor cursor = null; try { SQLiteDatabase db = getWritableDatabase(); - cursor = db.rawQuery(QUERY_UPDATE_MISSING_CONVERSATION_IDS, new String[] { conversationId }); + cursor = db.rawQuery(QUERY_UPDATE_INCOMPLETE_PAYLOADS, new String[] { conversationId , token}); cursor.moveToFirst(); // we need to move a cursor in order to update database ApptentiveLog.v(DATABASE, "Updated missing conversation ids"); } catch (SQLException e) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 42064a141..c0fbeb9ee 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -101,8 +101,7 @@ public boolean shouldRetryRequest(int responseCode, int retryAttempt) { public void addPayload(final Payload... payloads) { for (Payload payload : payloads) { payload.setConversationId(currentConversationId); - payload.setConversationToken(currentConversationToken); - + payload.setToken(currentConversationToken); } singleThreadExecutor.execute(new Runnable() { @Override @@ -241,15 +240,11 @@ private void sendNextPayloadSync() { return; } - if (StringUtils.isNullOrEmpty(payload.getConversationId())) { + if (StringUtils.isNullOrEmpty(payload.getConversationId()) || StringUtils.isNullOrEmpty(payload.getToken())) { ApptentiveLog.v(PAYLOADS, "Can't send the next payload: no conversation id"); return; } - // FIXME: store conversation data in the database - payload.setConversationId(currentConversationId); - payload.setConversationToken(currentConversationToken); - boolean scheduled = payloadSender.sendPayload(payload); // if payload sending was scheduled - notify the rest of the SDK @@ -281,7 +276,7 @@ public void onReceiveNotification(ApptentiveNotification notification) { singleThreadExecutor.execute(new Runnable() { @Override public void run() { - dbHelper.updateMissingConversationIds(currentConversationId); // TODO: update missing conversation token + dbHelper.updateIncompletePayloads(currentConversationId, currentConversationToken); sendNextPayloadSync(); // after we've updated payloads - we need to send them } }); From 1a1e1b2469ccb7269bc8637c8ced1d1c4a5165c7 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 10 Apr 2017 17:48:11 -0700 Subject: [PATCH 216/465] Add `DATABASE` tag to each database log. --- .../sdk/storage/ApptentiveDatabaseHelper.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 1544483a4..6a22ccd2b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -166,7 +166,7 @@ public ApptentiveDatabaseHelper(Context context) { */ @Override public void onCreate(SQLiteDatabase db) { - ApptentiveLog.d("ApptentiveDatabase.onCreate(db)"); + ApptentiveLog.d(DATABASE, "ApptentiveDatabase.onCreate(db)"); db.execSQL(TABLE_CREATE_PAYLOAD); db.execSQL(TABLE_CREATE_MESSAGE); db.execSQL(TABLE_CREATE_FILESTORE); @@ -179,7 +179,7 @@ public void onCreate(SQLiteDatabase db) { */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - ApptentiveLog.d("ApptentiveDatabase.onUpgrade(db, %d, %d)", oldVersion, newVersion); + ApptentiveLog.d(DATABASE, "ApptentiveDatabase.onUpgrade(db, %d, %d)", oldVersion, newVersion); switch (oldVersion) { case 1: upgradeVersion1to2(db); @@ -220,7 +220,7 @@ private void upgradeVersion1to2(SQLiteDatabase db) { } while (cursor.moveToNext()); } } catch (SQLException sqe) { - ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); + ApptentiveLog.e(DATABASE, "migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); } finally { ensureClosed(cursor); } @@ -262,12 +262,12 @@ private void upgradeVersion1to2(SQLiteDatabase db) { db.update(TABLE_MESSAGE, messageValues, MESSAGE_KEY_DB_ID + " = ?", new String[]{databaseId}); } } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as Message: %s", e, json); + ApptentiveLog.v(DATABASE, "Error parsing json as Message: %s", e, json); } } while (cursor.moveToNext()); } } catch (SQLException sqe) { - ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); + ApptentiveLog.e(DATABASE, "migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); } finally { ensureClosed(cursor); } @@ -311,12 +311,12 @@ private void upgradeVersion1to2(SQLiteDatabase db) { db.update(TABLE_PAYLOAD, messageValues, PAYLOAD_KEY_DB_ID + " = ?", new String[]{databaseId}); } } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as Message: %s", e, json); + ApptentiveLog.v(DATABASE, "Error parsing json as Message: %s", e, json); } } while (cursor.moveToNext()); } } catch (SQLException sqe) { - ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); + ApptentiveLog.e(DATABASE, "migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); } finally { ensureClosed(cursor); } @@ -352,7 +352,7 @@ public void addPayload(Payload... payloads) { db.setTransactionSuccessful(); db.endTransaction(); } catch (SQLException sqe) { - ApptentiveLog.e("addPayload EXCEPTION: " + sqe.getMessage()); + ApptentiveLog.e(DATABASE, "addPayload EXCEPTION: " + sqe.getMessage()); } } @@ -363,7 +363,7 @@ public void deletePayload(Payload payload) { db = getWritableDatabase(); db.delete(TABLE_PAYLOAD, PAYLOAD_KEY_DB_ID + " = ?", new String[]{Long.toString(payload.getDatabaseId())}); } catch (SQLException sqe) { - ApptentiveLog.e("deletePayload EXCEPTION: " + sqe.getMessage()); + ApptentiveLog.e(DATABASE, "deletePayload EXCEPTION: " + sqe.getMessage()); } } } @@ -374,7 +374,7 @@ public void deleteAllPayloads() { db = getWritableDatabase(); db.delete(TABLE_PAYLOAD, "", null); } catch (SQLException sqe) { - ApptentiveLog.e("deleteAllPayloads EXCEPTION: " + sqe.getMessage()); + ApptentiveLog.e(DATABASE, "deleteAllPayloads EXCEPTION: " + sqe.getMessage()); } } @@ -401,7 +401,7 @@ public Payload getOldestUnsentPayload() { } return payload; } catch (SQLException sqe) { - ApptentiveLog.e("getOldestUnsentPayload EXCEPTION: " + sqe.getMessage()); + ApptentiveLog.e(DATABASE, "getOldestUnsentPayload EXCEPTION: " + sqe.getMessage()); return null; } finally { ensureClosed(cursor); @@ -440,7 +440,7 @@ public synchronized int getUnreadMessageCount() { cursor = db.rawQuery(QUERY_MESSAGE_UNREAD, null); return cursor.getCount(); } catch (SQLException sqe) { - ApptentiveLog.e("getUnreadMessageCount EXCEPTION: " + sqe.getMessage()); + ApptentiveLog.e(DATABASE, "getUnreadMessageCount EXCEPTION: " + sqe.getMessage()); return 0; } finally { ensureClosed(cursor); @@ -453,7 +453,7 @@ public synchronized void deleteAllMessages() { db = getWritableDatabase(); db.delete(TABLE_MESSAGE, "", null); } catch (SQLException sqe) { - ApptentiveLog.e("deleteAllMessages EXCEPTION: " + sqe.getMessage()); + ApptentiveLog.e(DATABASE, "deleteAllMessages EXCEPTION: " + sqe.getMessage()); } } @@ -462,9 +462,9 @@ public synchronized void deleteMessage(String nonce) { try { db = getWritableDatabase(); int deleted = db.delete(TABLE_MESSAGE, MESSAGE_KEY_NONCE + " = ?", new String[]{nonce}); - ApptentiveLog.d("Deleted %d messages.", deleted); + ApptentiveLog.d(DATABASE, "Deleted %d messages.", deleted); } catch (SQLException sqe) { - ApptentiveLog.e("deleteMessage EXCEPTION: " + sqe.getMessage()); + ApptentiveLog.e(DATABASE, "deleteMessage EXCEPTION: " + sqe.getMessage()); } } @@ -477,9 +477,9 @@ public void deleteAssociatedFiles(String messageNonce) { try { db = getWritableDatabase(); int deleted = db.delete(TABLE_COMPOUND_MESSAGE_FILESTORE, COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?", new String[]{messageNonce}); - ApptentiveLog.d("Deleted %d stored files.", deleted); + ApptentiveLog.d(DATABASE, "Deleted %d stored files.", deleted); } catch (SQLException sqe) { - ApptentiveLog.e("deleteAssociatedFiles EXCEPTION: " + sqe.getMessage()); + ApptentiveLog.e(DATABASE, "deleteAssociatedFiles EXCEPTION: " + sqe.getMessage()); } } @@ -504,7 +504,7 @@ public List getAssociatedFiles(String nonce) { } while (cursor.moveToNext()); } } catch (SQLException sqe) { - ApptentiveLog.e("getAssociatedFiles EXCEPTION: " + sqe.getMessage()); + ApptentiveLog.e(DATABASE, "getAssociatedFiles EXCEPTION: " + sqe.getMessage()); } finally { ensureClosed(cursor); } @@ -541,7 +541,7 @@ public boolean addCompoundMessageFiles(List associatedFiles) { db.setTransactionSuccessful(); db.endTransaction(); } catch (SQLException sqe) { - ApptentiveLog.e("addCompoundMessageFiles EXCEPTION: " + sqe.getMessage()); + ApptentiveLog.e(DATABASE, "addCompoundMessageFiles EXCEPTION: " + sqe.getMessage()); } finally { return ret != -1; } @@ -557,7 +557,7 @@ private void ensureClosed(Cursor cursor) { cursor.close(); } } catch (Exception e) { - ApptentiveLog.w("Error closing SQLite cursor.", e); + ApptentiveLog.w(DATABASE, "Error closing SQLite cursor.", e); } } From f39e5f93976f0c8c938e7b0281f64e78c7e7240c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 11 Apr 2017 12:36:37 -0700 Subject: [PATCH 217/465] Added region comments --- .../apptentive/android/sdk/Apptentive.java | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 874b104a2..a1e7425e0 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -46,7 +46,6 @@ */ public class Apptentive { - /** * Must be called from the {@link Application#onCreate()} method in the {@link Application} object defined in your app's manifest. * @@ -65,9 +64,7 @@ private static void register(Application application, String apptentiveApiKey, S ApptentiveInternal.createInstance(application, apptentiveApiKey, serverUrl); } - // **************************************************************************************** - // GLOBAL DATA METHODS - // **************************************************************************************** + //region Global Data Methods /** * Sets the user's email address. This email address will be sent to the Apptentive server to allow out of app @@ -311,10 +308,9 @@ public static void removeCustomPersonData(String key) { } } + //endregion - // **************************************************************************************** - // THIRD PARTY INTEGRATIONS - // **************************************************************************************** + //region Third Party Integrations private static final String INTEGRATION_PUSH_TOKEN = "token"; @@ -403,9 +399,9 @@ public static void setPushNotificationIntegration(int pushProvider, String token } } - // **************************************************************************************** - // PUSH NOTIFICATIONS - // **************************************************************************************** + //endregion + + //region Push Notifications /** * Determines whether this Intent is a push notification sent from Apptentive. @@ -674,9 +670,9 @@ public static String getBodyFromApptentivePush(Map data) { return data.get(ApptentiveInternal.BODY_DEFAULT); } - // **************************************************************************************** - // RATINGS - // **************************************************************************************** + //endregion + + //region Rating /** * Use this to choose where to send the user when they are prompted to rate the app. This should be the same place @@ -704,10 +700,9 @@ public static void putRatingProviderArg(String key, String value) { } } - // **************************************************************************************** - // MESSAGE CENTER - // **************************************************************************************** + //endregion + //region Message Center /** * Opens the Apptentive Message Center UI Activity @@ -954,6 +949,8 @@ public static void sendAttachmentFile(InputStream is, String mimeType) { } } + //endregion + /** * This method takes a unique event string, stores a record of that event having been visited, determines * if there is an interaction that is able to run for this event, and then runs it. If more than one interaction From f27fc095cb25780e18b100c40059bd2a3b0a14a5 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 11 Apr 2017 13:46:56 -0700 Subject: [PATCH 218/465] Added support for multiple listeners in HttpRequest --- .../sdk/network/HttpRequestManagerTest.java | 8 +-- .../sdk/comm/ApptentiveHttpClient.java | 4 +- .../android/sdk/network/HttpRequest.java | 59 +++++++++++++++---- .../sdk/network/HttpRequestManager.java | 37 ++++++++---- .../sdk/storage/PayloadSenderTest.java | 2 +- 5 files changed, 80 insertions(+), 30 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java index fc862c103..86fdd6115 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java @@ -58,7 +58,7 @@ public void testRequestData() { final AtomicBoolean finished = new AtomicBoolean(false); HttpRequest request = new MockHttpRequest("request").setResponseData(expected); - request.setListener(new HttpRequest.Adapter() { + request.addListener(new HttpRequest.Adapter() { @Override public void onFinish(HttpRequest request) { Assert.assertEquals(expected, request.getResponseData()); @@ -91,7 +91,7 @@ public void testJsonRequestData() throws JSONException { final AtomicBoolean finished = new AtomicBoolean(false); HttpJsonRequest request = new MockHttpJsonRequest("request", requestObject).setMockResponseData(expected); - request.setListener(new HttpRequest.Adapter() { + request.addListener(new HttpRequest.Adapter() { @Override public void onFinish(HttpJsonRequest request) { Assert.assertEquals(expected.toString(), request.getResponseObject().toString()); @@ -121,7 +121,7 @@ public void testJsonRequestCorruptedData() throws JSONException { final AtomicBoolean finished = new AtomicBoolean(false); HttpJsonRequest request = new MockHttpJsonRequest("request", requestObject).setMockResponseData(invalidJson); - request.setListener(new HttpRequest.Adapter() { + request.addListener(new HttpRequest.Adapter() { @Override public void onFail(HttpJsonRequest request, String reason) { finished.set(true); @@ -277,7 +277,7 @@ public int getResponseCode() { //region Helpers private void startRequest(HttpRequest request) { - request.setListener(new HttpRequest.Listener() { + request.addListener(new HttpRequest.Listener() { @Override public void onFinish(MockHttpRequest request) { addResult("finished: " + request); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 1dcb25f99..9527c2fb1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -69,7 +69,7 @@ public ApptentiveHttpClient(String apiKey, String serverURL) { public HttpJsonRequest getConversationToken(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { HttpJsonRequest request = createJsonRequest(apiKey, ENDPOINT_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST); - request.setListener(listener); + request.addListener(listener); httpRequestManager.startRequest(request); return request; } @@ -90,7 +90,7 @@ public HttpRequest sendPayload(Payload payload, HttpRequest.Listener listeners; /** * Optional dispatch queue for listener callbacks @@ -144,6 +150,7 @@ public HttpRequest(String urlString) { throw new IllegalArgumentException("Invalid URL string '" + urlString + "'"); } + this.listeners = new ArrayList<>(1); this.id = nextRequestId++; this.urlString = urlString; } @@ -153,18 +160,30 @@ public HttpRequest(String urlString) { @SuppressWarnings("unchecked") private void finishRequest() { - try { - if (listener != null) { - if (isSuccessful()) { + if (isSuccessful()) { + for (Listener listener : listeners) { + try { listener.onFinish(this); - } else if (isCancelled()) { + } catch (Exception e) { + ApptentiveLog.e(e, "Exception in request finish listener"); + } + } + } else if (isCancelled()) { + for (Listener listener : listeners) { + try { listener.onCancel(this); - } else { + } catch (Exception e) { + ApptentiveLog.e(e, "Exception in request finish listener"); + } + } + } else { + for (Listener listener : listeners) { + try { listener.onFail(this, errorMessage); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception in request finish listener"); } } - } catch (Exception e) { - ApptentiveLog.e(e, "Exception in request finish listener"); } } @@ -481,8 +500,24 @@ public String getName() { return name; } - public void setListener(Listener listener) { - this.listener = listener; + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public void addListener(Listener listener) { + if (listener == null) { + throw new IllegalArgumentException("Listener is null"); + } + + boolean contains = listeners.contains(listener); + assertFalse(contains, "Already contains listener: %s", listener); + if (!contains) { + listeners.add(listener); + } } public void setCallbackQueue(DispatchQueue callbackQueue) { @@ -510,7 +545,7 @@ protected void setResponseCode(int code) { //region Listener - public interface Listener { + public interface Listener { void onFinish(T request); void onCancel(T request); @@ -518,7 +553,7 @@ public interface Listener { void onFail(T request, String reason); } - public static class Adapter implements Listener { + public static class Adapter implements Listener { @Override public void onFinish(T request) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java index 5fc9bc40c..2ed1e21fe 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java @@ -1,5 +1,6 @@ package com.apptentive.android.sdk.network; +import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchQueueType; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -35,7 +36,7 @@ public HttpRequestManager() { /** * Creates a request manager with custom network dispatch queue * - * @param networkQueue - dispatch queue for blocking network operations + * @param networkQueue - dispatch queue for blocking network operations * @throws IllegalArgumentException if queue is null */ public HttpRequestManager(DispatchQueue networkQueue) { @@ -63,7 +64,9 @@ public synchronized HttpRequest startRequest(final HttpRequest request) { return request; } - /** Handles request synchronously */ + /** + * Handles request synchronously + */ void dispatchRequest(final HttpRequest request) { networkQueue.dispatchAsync(new DispatchTask() { @Override @@ -114,26 +117,38 @@ synchronized void unregisterRequest(HttpRequest request) { } } + /** + * Returns a request with a specified tag or null is not found + */ + public synchronized HttpRequest findRequest(String tag) { + for (HttpRequest request : activeRequests) { + if (StringUtils.equal(request.getTag(), tag)) { + return request; + } + } + return null; + } + //endregion //region Listener callbacks private void notifyRequestStarted(final HttpRequest request) { - if (listener != null) { - listener.onRequestStart(HttpRequestManager.this, request); - } + if (listener != null) { + listener.onRequestStart(HttpRequestManager.this, request); + } } private void notifyRequestFinished(final HttpRequest request) { - if (listener != null) { - listener.onRequestFinish(HttpRequestManager.this, request); - } + if (listener != null) { + listener.onRequestFinish(HttpRequestManager.this, request); + } } private void notifyCancelledAllRequests() { - if (listener != null) { - listener.onRequestsCancel(HttpRequestManager.this); - } + if (listener != null) { + listener.onRequestsCancel(HttpRequestManager.this); + } } //endregion diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java index 0c0d493d5..79c6ac532 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java @@ -135,7 +135,7 @@ public HttpRequest sendPayload(Payload payload, HttpRequest.Listener Date: Tue, 11 Apr 2017 15:09:42 -0700 Subject: [PATCH 219/465] Added login functionality --- .../apptentive/android/sdk/Apptentive.java | 41 +++++ .../android/sdk/ApptentiveInternal.java | 13 +- .../sdk/comm/ApptentiveHttpClient.java | 7 + .../sdk/conversation/ConversationManager.java | 142 ++++++++++++++++-- 4 files changed, 184 insertions(+), 19 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index a1e7425e0..8965bb8ac 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -1060,6 +1060,47 @@ public static void setOnSurveyFinishedListener(OnSurveyFinishedListener listener } } + //region Login/Logout + + /** + * Starts login process asynchronously. This call returns immediately. + * @throws IllegalArgumentException if token is null. + */ + public void login(String token, LoginCallback callback) { + if (token == null) { + throw new IllegalArgumentException("Token is null"); + } + + final ApptentiveInternal instance = ApptentiveInternal.getInstance(); + if (instance == null) { + ApptentiveLog.e("Unable to login: Apptentive instance is not properly initialized"); + if (callback != null) { + callback.onLoginFail("Apptentive instance is not properly initialized"); + } + } else { + instance.login(token, callback); + } + } + + /** + * Callback interface for an async login process. + */ + public interface LoginCallback { + /** + * Called when a login attempt has completed successfully. + */ + void onLoginFinish(); + + /** + * Called when a login attempt has failed. + * + * @param errorMessage failure cause message + */ + void onLoginFail(String errorMessage); + } + + //endregion + /** *

This type represents a semantic version. It can be initialized * with a string or a long, and there is no limit to the number of parts your semantic version can diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 86e82cef3..c2c6a5777 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -24,6 +24,7 @@ import android.support.v4.content.ContextCompat; import android.text.TextUtils; +import com.apptentive.android.sdk.Apptentive.LoginCallback; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; @@ -392,10 +393,6 @@ public ApptentiveHttpClient getApptentiveHttpClient() { return apptentiveHttpClient; } - public void runOnWorkerThread(Runnable r) { - cachedExecutor.execute(r); - } - public void onAppLaunch(final Context appContext) { EngagementModule.engageInternal(appContext, EventPayload.EventLabel.app__launch.getLabelName()); } @@ -964,6 +961,14 @@ private String getEndpointBase(SharedPreferences prefs) { //endregion + //region Login/Logout + + void login(String token, LoginCallback callback) { + conversationManager.login(token, callback); + } + + //endregion + /** * Dismisses any currently-visible interactions. This method is for internal use and is subject to change. */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 9527c2fb1..b8b1cfa0f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -74,6 +74,13 @@ public HttpJsonRequest getConversationToken(ConversationTokenRequest conversatio return request; } + /** + * Returns the first request with a given tag or null is not found + */ + public HttpRequest findRequest(String tag) { + return httpRequestManager.findRequest(tag); + } + //endregion //region PayloadRequestSender diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index ba5a74d6a..0f3862722 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -2,10 +2,11 @@ import android.content.Context; +import com.apptentive.android.sdk.Apptentive.LoginCallback; import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.conversation.ConversationMetadata.Filter; -import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.model.ConversationTokenRequest; import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; @@ -23,6 +24,8 @@ import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; +import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchTask; import org.json.JSONObject; @@ -30,11 +33,11 @@ import java.io.IOException; import java.lang.ref.WeakReference; -import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; - import static com.apptentive.android.sdk.ApptentiveLogTag.*; import static com.apptentive.android.sdk.ApptentiveNotifications.*; import static com.apptentive.android.sdk.conversation.ConversationState.*; +import static com.apptentive.android.sdk.debug.Assert.*; +import static com.apptentive.android.sdk.debug.Tester.*; import static com.apptentive.android.sdk.debug.TesterEvent.*; /** @@ -48,6 +51,7 @@ public class ConversationManager { protected static final String CONVERSATION_METADATA_PATH = "conversation-v1.meta"; + private static final String TAG_FETCH_CONVERSATION_TOKEN_REQUEST = "fetch_conversation_token"; private final WeakReference contextRef; @@ -191,30 +195,44 @@ public synchronized boolean endActiveConversation() { //region Conversation Token Fetching - private void fetchConversationToken(final Conversation conversation) { - ApptentiveLog.i(CONVERSATION, "Fetching Configuration token task started."); - dispatchDebugEvent(EVT_CONVERSATION_WILL_FETCH_TOKEN); - + /** + * Starts fetching conversation token. Returns immediately if conversation is already fetching. + * + * @return a new http-request object if conversation is not currently fetched or an instance of + * the existing request + */ + private HttpRequest fetchConversationToken(final Conversation conversation) { + // check if context is lost final Context context = getContext(); if (context == null) { ApptentiveLog.w(CONVERSATION, "Unable to fetch conversation token: context reference is lost"); - return; + return null; + } + + // check for an existing request + HttpRequest existingRequest = getHttpClient().findRequest(TAG_FETCH_CONVERSATION_TOKEN_REQUEST); + if (existingRequest != null) { + ApptentiveLog.d(CONVERSATION, "Conversation already fetching"); + return existingRequest; } + ApptentiveLog.i(CONVERSATION, "Fetching Configuration token task started."); + dispatchDebugEvent(EVT_CONVERSATION_WILL_FETCH_TOKEN); + // Try to fetch a new one from the server. - ConversationTokenRequest request = new ConversationTokenRequest(); + ConversationTokenRequest payload = new ConversationTokenRequest(); // Send the Device and Sdk now, so they are available on the server from the start. final Device device = DeviceManager.generateNewDevice(context); final Sdk sdk = SdkManager.generateCurrentSdk(); final AppRelease appRelease = ApptentiveInternal.getInstance().getAppRelease(); - request.setDevice(DeviceManager.getDiffPayload(null, device)); - request.setSdk(SdkManager.getPayload(sdk)); - request.setAppRelease(AppReleaseManager.getPayload(appRelease)); + payload.setDevice(DeviceManager.getDiffPayload(null, device)); + payload.setSdk(SdkManager.getPayload(sdk)); + payload.setAppRelease(AppReleaseManager.getPayload(appRelease)); - ApptentiveInternal.getInstance().getApptentiveHttpClient() - .getConversationToken(request, new HttpRequest.Listener() { + HttpRequest request = getHttpClient() + .getConversationToken(payload, new HttpRequest.Listener() { @Override public void onFinish(HttpJsonRequest request) { try { @@ -271,6 +289,9 @@ public void onFail(HttpJsonRequest request, String reason) { dispatchDebugEvent(EVT_CONVERSATION_DID_FETCH_TOKEN, false); } }); + + request.setTag(TAG_FETCH_CONVERSATION_TOKEN_REQUEST); + return request; } //endregion @@ -278,7 +299,7 @@ public void onFail(HttpJsonRequest request, String reason) { //region Conversation fetching private void handleConversationStateChange(Conversation conversation) { - Assert.assertTrue(conversation != null && !conversation.hasState(UNDEFINED)); // sanity check + assertTrue(conversation != null && !conversation.hasState(UNDEFINED)); // sanity check if (conversation != null) { dispatchDebugEvent(EVT_CONVERSATION_STATE_CHANGE, @@ -385,6 +406,93 @@ private void saveMetadata() { //endregion + //region Login/Logout + + private static final LoginCallback NULL_LOGIN_CALLBACK = new LoginCallback() { + @Override + public void onLoginFinish() { + } + + @Override + public void onLoginFail(String errorMessage) { + } + }; + + public void login(final String token, final LoginCallback callback) { + // we only deal with an active conversation on the main thread + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + requestLoggedInConversation(token, callback != null ? callback : NULL_LOGIN_CALLBACK); // avoid constant null-pointer checking + } + }); + } + + private void requestLoggedInConversation(final String token, final LoginCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("Callback is null"); + } + + // check if active conversation exists + if (activeConversation == null) { + ApptentiveLog.e(CONVERSATION, "Unable to login: no active conversation"); + callback.onLoginFail("No active conversation"); + return; + } + + switch (activeConversation.getState()) { + case ANONYMOUS_PENDING: + // start fetching conversation token (if not yet fetched) + final HttpRequest fetchRequest = fetchConversationToken(activeConversation); + if (fetchRequest == null) { + ApptentiveLog.e(CONVERSATION, "Unable to login: fetch request failed to send"); + callback.onLoginFail("fetch request failed to send"); + return; + } + + // attach a listener to an active request + fetchRequest.addListener(new HttpRequest.Listener() { + @Override + public void onFinish(HttpRequest request) { + assertTrue(activeConversation != null && activeConversation.hasState(ANONYMOUS), "Active conversation is missing or in a wrong state: %s", activeConversation); + + if (activeConversation != null && activeConversation.hasState(ANONYMOUS)) { + ApptentiveLog.d(CONVERSATION, "Conversation fetching complete. Performing login..."); + sendLoginRequest(token, callback); + } else { + callback.onLoginFail("Conversation fetching completed abnormally"); + } + } + + @Override + public void onCancel(HttpRequest request) { + ApptentiveLog.d(CONVERSATION, "Unable to login: conversation fetching cancelled."); + callback.onLoginFail("Conversation fetching was cancelled"); + } + + @Override + public void onFail(HttpRequest request, String reason) { + ApptentiveLog.d(CONVERSATION, "Unable to login: conversation fetching failed."); + callback.onLoginFail("Conversation fetching failed: " + reason); + } + }); + break; + case ANONYMOUS: + case LOGGED_OUT: + sendLoginRequest(token, callback); + break; + case LOGGED_IN: + callback.onLoginFail("already logged in"); // TODO: force logout? + break; + } + } + + private void sendLoginRequest(String token, LoginCallback callback) { + callback.onLoginFail("login not yet implemented"); // FIXME: kick off login request + } + + //endregion + //region Getters/Setters public Conversation getActiveConversation() { @@ -395,6 +503,10 @@ public ConversationMetadata getConversationMetadata() { return conversationMetadata; } + private ApptentiveHttpClient getHttpClient() { + return ApptentiveInternal.getInstance().getApptentiveHttpClient(); // TODO: remove coupling + } + private Context getContext() { return contextRef.get(); } From e49f248fa8f2664f4c0777b3220a00dcc7364741 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 12 Apr 2017 10:07:44 -0700 Subject: [PATCH 220/465] Check in Travis file. --- .travis.yml | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..704cee48a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,43 @@ +language: android + +jdk: + - oraclejdk8 + +before_cache: + - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock + - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ + +cache: + directories: + - $HOME/.gradle/caches/ + - $HOME/.gradle/wrapper/ + - $HOME/.android/build-cache + +android: + + components: + - tools + - tools + - platform-tools + - build-tools-25.0.2 + - android-24 + - android-25 + + - extra-google-google_play_services + - extra-google-m2repository + - extra-android-m2repository + - addon-google_apis-google-25 + + - sys-img-armeabi-v7a-android-19 + +install: true + +# Emulator Management: Create, Start and Wait +before_script: + - echo no | android create avd --force -n test -t android-19 --abi armeabi-v7a + - emulator -avd test -no-audio -no-window & + - android-wait-for-emulator + - adb shell input keyevent 82 & + +script: + - ./gradlew :apptentive:test -i \ No newline at end of file From a26ef74d81827f3d3e99332a7797ab8479fff3cc Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 12 Apr 2017 11:06:44 -0700 Subject: [PATCH 221/465] Fixed race conditions + fixed conversation data change listeners --- .../apptentive/android/sdk/conversation/Conversation.java | 5 +++++ .../android/sdk/conversation/ConversationManager.java | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index de68cd28f..0eb7d232e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -111,6 +111,8 @@ public Conversation(File conversationDataFile, File conversationMessagesFile) { this.conversationMessagesFile = conversationMessagesFile; conversationData = new ConversationData(); + conversationData.setDataChangedListener(this); + FileMessageStore messageStore = new FileMessageStore(conversationMessagesFile); messageManager = new MessageManager(messageStore); // it's important to initialize message manager in a constructor since other SDK parts depend on it via Apptentive singleton } @@ -213,14 +215,17 @@ synchronized void saveConversationData() throws SerializerException { ApptentiveLog.d(CONVERSATION, "Saving Conversation"); ApptentiveLog.v(CONVERSATION, "EventData: %s", getEventData().toString()); // TODO: remove + long start = System.currentTimeMillis(); FileSerializer serializer = new FileSerializer(conversationDataFile); serializer.serialize(conversationData); + ApptentiveLog.v(CONVERSATION, "Conversation data saved (took %d ms)", System.currentTimeMillis() - start); } synchronized void loadConversationData() throws SerializerException { ApptentiveLog.d(CONVERSATION, "Loading conversation data"); FileSerializer serializer = new FileSerializer(conversationDataFile); conversationData = (ConversationData) serializer.deserialize(); + conversationData.setDataChangedListener(this); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 0f3862722..5a4058f4f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -266,9 +266,6 @@ public void onFinish(HttpJsonRequest request) { ApptentiveLog.d(CONVERSATION, "PersonId: " + personId); conversation.setPersonId(personId); - // write conversation to the disk (sync operation) - conversation.saveConversationData(); - dispatchDebugEvent(EVT_CONVERSATION_DID_FETCH_TOKEN, true); handleConversationStateChange(conversation); @@ -290,6 +287,7 @@ public void onFail(HttpJsonRequest request, String reason) { } }); + request.setCallbackQueue(DispatchQueue.mainQueue()); // we only deal with conversation on the main queue request.setTag(TAG_FETCH_CONVERSATION_TOKEN_REQUEST); return request; } From 8636b206b621e7128a1ec5267ab7911df48de3c3 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 12 Apr 2017 11:09:03 -0700 Subject: [PATCH 222/465] Do not throw an IllegalArgumentException if login token is null --- .../main/java/com/apptentive/android/sdk/Apptentive.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 8965bb8ac..d7f121934 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -1064,11 +1064,14 @@ public static void setOnSurveyFinishedListener(OnSurveyFinishedListener listener /** * Starts login process asynchronously. This call returns immediately. - * @throws IllegalArgumentException if token is null. */ public void login(String token, LoginCallback callback) { if (token == null) { - throw new IllegalArgumentException("Token is null"); + if (callback != null) { + callback.onLoginFail("token is null"); + } + + return; } final ApptentiveInternal instance = ApptentiveInternal.getInstance(); From b460585760b76ef05cc4b021d7c8448cb44b5f9f Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 12 Apr 2017 11:09:33 -0700 Subject: [PATCH 223/465] Refactoring --- .../android/sdk/conversation/ConversationManager.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 5a4058f4f..0dd561047 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -220,19 +220,19 @@ private HttpRequest fetchConversationToken(final Conversation conversation) { dispatchDebugEvent(EVT_CONVERSATION_WILL_FETCH_TOKEN); // Try to fetch a new one from the server. - ConversationTokenRequest payload = new ConversationTokenRequest(); + ConversationTokenRequest conversationTokenRequest = new ConversationTokenRequest(); // Send the Device and Sdk now, so they are available on the server from the start. final Device device = DeviceManager.generateNewDevice(context); final Sdk sdk = SdkManager.generateCurrentSdk(); final AppRelease appRelease = ApptentiveInternal.getInstance().getAppRelease(); - payload.setDevice(DeviceManager.getDiffPayload(null, device)); - payload.setSdk(SdkManager.getPayload(sdk)); - payload.setAppRelease(AppReleaseManager.getPayload(appRelease)); + conversationTokenRequest.setDevice(DeviceManager.getDiffPayload(null, device)); + conversationTokenRequest.setSdk(SdkManager.getPayload(sdk)); + conversationTokenRequest.setAppRelease(AppReleaseManager.getPayload(appRelease)); HttpRequest request = getHttpClient() - .getConversationToken(payload, new HttpRequest.Listener() { + .getConversationToken(conversationTokenRequest, new HttpRequest.Listener() { @Override public void onFinish(HttpJsonRequest request) { try { From a52fcc6f386c71a48b12243f238ed7949151eff6 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 12 Apr 2017 11:40:39 -0700 Subject: [PATCH 224/465] Changed the way http-request uses callback queue Each request will be unregistered on the callback queue --- .../android/sdk/network/HttpRequest.java | 50 ++++++++++--------- .../sdk/network/HttpRequestManager.java | 8 +-- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 5da13266d..2010a0086 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -160,30 +160,34 @@ public HttpRequest(String urlString) { @SuppressWarnings("unchecked") private void finishRequest() { - if (isSuccessful()) { - for (Listener listener : listeners) { - try { - listener.onFinish(this); - } catch (Exception e) { - ApptentiveLog.e(e, "Exception in request finish listener"); + try { + if (isSuccessful()) { + for (Listener listener : listeners) { + try { + listener.onFinish(this); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception in request finish listener"); + } } - } - } else if (isCancelled()) { - for (Listener listener : listeners) { - try { - listener.onCancel(this); - } catch (Exception e) { - ApptentiveLog.e(e, "Exception in request finish listener"); + } else if (isCancelled()) { + for (Listener listener : listeners) { + try { + listener.onCancel(this); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception in request finish listener"); + } } - } - } else { - for (Listener listener : listeners) { - try { - listener.onFail(this, errorMessage); - } catch (Exception e) { - ApptentiveLog.e(e, "Exception in request finish listener"); + } else { + for (Listener listener : listeners) { + try { + listener.onFail(this, errorMessage); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception in request finish listener"); + } } } + } finally { + requestManager.unregisterRequest(HttpRequest.this); } } @@ -239,7 +243,7 @@ protected void execute() { } } - protected void sendRequestSync() throws IOException { + private void sendRequestSync() throws IOException { try { URL url = new URL(urlString); ApptentiveLog.d(NETWORK, "Performing request: %s", url); @@ -403,7 +407,7 @@ private static String readResponse(InputStream is, boolean gzipped) throws IOExc /** * Returns true if request is cancelled */ - public synchronized boolean isCancelled() { + synchronized boolean isCancelled() { return cancelled; } @@ -524,7 +528,7 @@ public void setCallbackQueue(DispatchQueue callbackQueue) { this.callbackQueue = callbackQueue; } - public String getResponseData() { + String getResponseData() { return responseData; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java index 2ed1e21fe..bc563c3b9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java @@ -71,13 +71,7 @@ void dispatchRequest(final HttpRequest request) { networkQueue.dispatchAsync(new DispatchTask() { @Override protected void execute() { - try { - request.dispatchSync(networkQueue); - } finally { - if (!request.retrying) { - unregisterRequest(request); - } - } + request.dispatchSync(networkQueue); } }); } From e1f6365dfe8555f352de92137a17842c0d02702b Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 12 Apr 2017 11:41:06 -0700 Subject: [PATCH 225/465] Fixed possible null-pointer exception while handling conversation state change --- .../sdk/conversation/ConversationManager.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 0dd561047..cc4fcd473 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -297,23 +297,22 @@ public void onFail(HttpJsonRequest request, String reason) { //region Conversation fetching private void handleConversationStateChange(Conversation conversation) { - assertTrue(conversation != null && !conversation.hasState(UNDEFINED)); // sanity check - - if (conversation != null) { + assertTrue(conversation != null && !conversation.hasState(UNDEFINED)); + if (conversation != null && !conversation.hasState(UNDEFINED)) { dispatchDebugEvent(EVT_CONVERSATION_STATE_CHANGE, "conversation_state", conversation.getState().toString(), "conversation_identifier", conversation.getConversationId()); - } - ApptentiveNotificationCenter.defaultCenter() - .postNotificationSync(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, - ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, conversation)); + ApptentiveNotificationCenter.defaultCenter() + .postNotificationSync(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, + ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, conversation)); - if (conversation != null && conversation.hasActiveState()) { - conversation.fetchInteractions(getContext()); - } + if (conversation.hasActiveState()) { + conversation.fetchInteractions(getContext()); + } - updateMetadataItems(conversation); + updateMetadataItems(conversation); + } } /* For testing purposes */ From f674bdafb6ed4babe59896ab5d6e15ba94a66e92 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 12 Apr 2017 11:50:31 -0700 Subject: [PATCH 226/465] Added threading asserts --- .../sdk/conversation/ConversationManager.java | 6 +++++ .../apptentive/android/sdk/debug/Assert.java | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index cc4fcd473..fc0493394 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -235,6 +235,8 @@ private HttpRequest fetchConversationToken(final Conversation conversation) { .getConversationToken(conversationTokenRequest, new HttpRequest.Listener() { @Override public void onFinish(HttpJsonRequest request) { + assertMainThread(); + try { JSONObject root = request.getResponseObject(); String conversationToken = root.getString("token"); @@ -297,7 +299,9 @@ public void onFail(HttpJsonRequest request, String reason) { //region Conversation fetching private void handleConversationStateChange(Conversation conversation) { + assertMainThread(); assertTrue(conversation != null && !conversation.hasState(UNDEFINED)); + if (conversation != null && !conversation.hasState(UNDEFINED)) { dispatchDebugEvent(EVT_CONVERSATION_STATE_CHANGE, "conversation_state", conversation.getState().toString(), @@ -426,6 +430,8 @@ protected void execute() { } private void requestLoggedInConversation(final String token, final LoginCallback callback) { + assertMainThread(); + if (callback == null) { throw new IllegalArgumentException("Callback is null"); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java index 879ac10da..653092d25 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -1,5 +1,7 @@ package com.apptentive.android.sdk.debug; +import android.os.Looper; + import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; @@ -142,6 +144,28 @@ public static void assertEquals(Object expected, Object actual) { //endregion + //region Threading + + /** + * Asserts that code executes on the main thread. + */ + public static void assertMainThread() { + if (imp != null && Looper.myLooper() != Looper.getMainLooper()) { + imp.assertFailed(StringUtils.format("Expected 'main' thread but was '%s'", Thread.currentThread().getName())); + } + } + + /** + * Asserts that code executes on the main thread. + */ + public static void assertBackgroundThread() { + if (imp != null && Looper.myLooper() == Looper.getMainLooper()) { + imp.assertFailed("Expected background thread but was 'main'"); + } + } + + //endregion + public static void setImp(AssertImp imp) { Assert.imp = imp; } From a636e9dfbff1be74b034181cfb964604b68f7774 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 13 Apr 2017 11:22:31 -0700 Subject: [PATCH 227/465] Fixed login request while not having an active conversation --- .../sdk/conversation/ConversationManager.java | 9 ++++++--- .../com/apptentive/android/sdk/debug/Assert.java | 13 +++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index fc0493394..ecfbd0bcb 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -438,8 +438,8 @@ private void requestLoggedInConversation(final String token, final LoginCallback // check if active conversation exists if (activeConversation == null) { - ApptentiveLog.e(CONVERSATION, "Unable to login: no active conversation"); - callback.onLoginFail("No active conversation"); + ApptentiveLog.d(CONVERSATION, "No active conversation. Performing login..."); + sendLoginRequest(token, callback); return; } @@ -481,12 +481,15 @@ public void onFail(HttpRequest request, String reason) { }); break; case ANONYMOUS: - case LOGGED_OUT: sendLoginRequest(token, callback); break; case LOGGED_IN: callback.onLoginFail("already logged in"); // TODO: force logout? break; + default: + assertFail("Unexpected conversation state: " + activeConversation.getState()); + callback.onLoginFail("internal error"); + break; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java index 653092d25..aad241163 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -166,6 +166,19 @@ public static void assertBackgroundThread() { //endregion + //region Failure + + /** + * General failure with a message + */ + public static void assertFail(String message) { + if (imp != null) { + imp.assertFailed(message); + } + } + + //endregion + public static void setImp(AssertImp imp) { Assert.imp = imp; } From 3a794b0d253016ab66c977cfd3a01096036eb9c0 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 13 Apr 2017 13:45:59 -0700 Subject: [PATCH 228/465] Replaced PayloadRequestFactory with additional fields in Payload class --- .../sdk/comm/ApptentiveHttpClient.java | 81 +++---------------- .../sdk/comm/PayloadRequestFactory.java | 18 ----- .../android/sdk/model/AppReleasePayload.java | 20 +++++ .../android/sdk/model/DevicePayload.java | 21 +++++ .../android/sdk/model/EventPayload.java | 21 +++++ .../apptentive/android/sdk/model/Payload.java | 22 ++++- .../android/sdk/model/PersonPayload.java | 21 +++++ .../sdk/model/SdkAndAppReleasePayload.java | 21 +++++ .../android/sdk/model/SdkPayload.java | 22 +++++ .../sdk/model/SurveyResponsePayload.java | 21 +++++ .../messagecenter/model/CompoundMessage.java | 20 +++++ .../sdk/storage/PayloadSenderTest.java | 16 ++++ 12 files changed, 214 insertions(+), 90 deletions(-) delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/comm/PayloadRequestFactory.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index b8b1cfa0f..8a2ee1c8d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -47,8 +47,6 @@ public class ApptentiveHttpClient implements PayloadRequestSender { private final String userAgentString; private final HttpRequestManager httpRequestManager; - private final Map, PayloadRequestFactory> payloadRequestFactoryLookup; - public ApptentiveHttpClient(String apiKey, String serverURL) { if (isEmpty(apiKey)) { throw new IllegalArgumentException("Illegal API key: '" + apiKey + "'"); @@ -62,7 +60,6 @@ public ApptentiveHttpClient(String apiKey, String serverURL) { this.apiKey = apiKey; this.serverURL = serverURL; this.userAgentString = String.format(USER_AGENT_STRING, Constants.APPTENTIVE_SDK_VERSION); - this.payloadRequestFactoryLookup = createPayloadRequestFactoryLookup(); } //region API Requests @@ -91,82 +88,24 @@ public HttpRequest sendPayload(Payload payload, HttpRequest.Listener, PayloadRequestFactory> createPayloadRequestFactoryLookup() { - Map, PayloadRequestFactory> lookup = new HashMap<>(); - - // Event Payload - lookup.put(EventPayload.class, new PayloadRequestFactory() { - @Override - public HttpRequest createRequest(EventPayload payload) { - return createJsonRequest(payload.getToken(), ENDPOINT_EVENTS, payload, HttpRequestMethod.POST); - } - }); - - // Device Payload - lookup.put(DevicePayload.class, new PayloadRequestFactory() { - @Override - public HttpRequest createRequest(DevicePayload payload) { - return createJsonRequest(payload.getToken(), ENDPOINT_DEVICES, payload, HttpRequestMethod.PUT); - } - }); - - // SDK Payload - lookup.put(SdkPayload.class, new PayloadRequestFactory() { - @Override - public HttpRequest createRequest(SdkPayload payload) { - return createJsonRequest(payload.getToken(), ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); - } - }); - - // App Release Payload - lookup.put(AppReleasePayload.class, new PayloadRequestFactory() { - @Override - public HttpRequest createRequest(AppReleasePayload payload) { - return createJsonRequest(payload.getToken(), ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); - } - }); + private HttpRequest createRequest(Payload payload) { + final String token = payload.getToken(); + final String endPoint = payload.getHttpEndPoint(); + final HttpRequestMethod requestMethod = payload.getHttpRequestMethod(); - // SDK and App Release Payload - lookup.put(SdkAndAppReleasePayload.class, new PayloadRequestFactory() { - @Override - public HttpRequest createRequest(SdkAndAppReleasePayload payload) { - return createJsonRequest(payload.getToken(), ENDPOINT_CONVERSATION, payload, HttpRequestMethod.PUT); + switch (payload.getHttpRequestContentType()) { + case "application/json": { + return createJsonRequest(token, endPoint, payload, requestMethod); } - }); - - // Person Payload - lookup.put(PersonPayload.class, new PayloadRequestFactory() { - @Override - public HttpRequest createRequest(PersonPayload payload) { - return createJsonRequest(payload.getToken(), ENDPOINT_PEOPLE, payload, HttpRequestMethod.PUT); - } - }); - - // Survey Payload - lookup.put(SurveyResponsePayload.class, new PayloadRequestFactory() { - @Override - public HttpRequest createRequest(SurveyResponsePayload survey) { - String endpoint = String.format(ENDPOINT_SURVEYS_POST, survey.getId()); - return createJsonRequest(survey.getToken(), endpoint, survey, HttpRequestMethod.POST); - } - }); + } - return lookup; + throw new IllegalArgumentException("Unexpected content type: " + payload.getHttpRequestContentType()); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/PayloadRequestFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/PayloadRequestFactory.java deleted file mode 100644 index badb58d3f..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/PayloadRequestFactory.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.comm; - -import com.apptentive.android.sdk.model.Payload; -import com.apptentive.android.sdk.network.HttpRequest; - -/** - * Created by alementuev on 4/4/17. - */ - -interface PayloadRequestFactory { - HttpRequest createRequest(T payload); -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java index 956cd8b57..01ddbeb0e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java @@ -14,6 +14,7 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.util.Util; import org.json.JSONException; @@ -38,6 +39,25 @@ public AppReleasePayload() { super(); } + //region Http-request + + @Override + public String getHttpEndPoint() { + throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach + } + + @Override + public HttpRequestMethod getHttpRequestMethod() { + throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach + } + + @Override + public String getHttpRequestContentType() { + throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach + } + + //endregion + public void initBaseType() { setBaseType(BaseType.app_release); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index 2977839f1..fbaa02ff0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -7,6 +7,8 @@ package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; @@ -52,6 +54,25 @@ public DevicePayload(String json) throws JSONException { super(json); } + //region Http-request + + @Override + public String getHttpEndPoint() { + return StringUtils.format("conversations/%s/devices", getConversationId()); + } + + @Override + public HttpRequestMethod getHttpRequestMethod() { + return HttpRequestMethod.PUT; + } + + @Override + public String getHttpRequestContentType() { + return "application/json"; + } + + //endregion + public void initBaseType() { setBaseType(BaseType.device); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index ccfe10a09..4e29f3a5c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -7,6 +7,8 @@ package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; import org.json.JSONObject; @@ -111,6 +113,25 @@ public EventPayload(String label, String trigger) { putData(data); } + //region Http-request + + @Override + public String getHttpEndPoint() { + return StringUtils.format("conversations/%s/event", getConversationId()); + } + + @Override + public HttpRequestMethod getHttpRequestMethod() { + return HttpRequestMethod.POST; + } + + @Override + public String getHttpRequestContentType() { + return "application/json"; + } + + //endregion + @Override protected void initBaseType() { setBaseType(BaseType.event); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index 8b7fdf1ea..c88c439d3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -7,6 +7,7 @@ package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.network.HttpRequestMethod; import org.json.JSONException; import org.json.JSONObject; @@ -112,10 +113,29 @@ public static BaseType parse(String type) { /** * @deprecated Do not use this method to check for key existence. Instead us !isNull(KEY_NAME), as this works better - * with keys with null values. + * with keys with null values. */ @Override public boolean has(String key) { return super.has(key); } + + //region Http-request + + /** + * Http endpoint for sending this payload + */ + public abstract String getHttpEndPoint(); + + /** + * Http request method for sending this payload + */ + public abstract HttpRequestMethod getHttpRequestMethod(); + + /** + * Http content type for sending this payload + */ + public abstract String getHttpRequestContentType(); + + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index 6c98f8d2d..73215afea 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -7,6 +7,8 @@ package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; @@ -37,6 +39,25 @@ public PersonPayload(String json) throws JSONException { super(json); } + //region Http-request + + @Override + public String getHttpEndPoint() { + return StringUtils.format("converations/%s/people", getConversationId()); + } + + @Override + public HttpRequestMethod getHttpRequestMethod() { + return HttpRequestMethod.PUT; + } + + @Override + public String getHttpRequestContentType() { + return "application/json"; + } + + //endregion + public void initBaseType() { setBaseType(BaseType.person); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index 158649a30..3792437ae 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -14,6 +14,8 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; import org.json.JSONException; @@ -61,6 +63,25 @@ public SdkAndAppReleasePayload() { } } + //region Http-request + + @Override + public String getHttpEndPoint() { + return StringUtils.format("conversations/%s/sdkapprelease", getConversationId()); + } + + @Override + public HttpRequestMethod getHttpRequestMethod() { + return HttpRequestMethod.PUT; + } + + @Override + public String getHttpRequestContentType() { + return "application/json"; + } + + //endregion + //region Inheritance public void initBaseType() { setBaseType(BaseType.sdk_and_app_release); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java index 533bbb0e7..79244c855 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java @@ -7,6 +7,9 @@ package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.util.StringUtils; + import org.json.JSONException; /** @@ -32,6 +35,25 @@ public SdkPayload() { super(); } + //region Http-request + + @Override + public String getHttpEndPoint() { + throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach + } + + @Override + public HttpRequestMethod getHttpRequestMethod() { + throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach + } + + @Override + public String getHttpRequestContentType() { + throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach + } + + //endregion + public void initBaseType() { setBaseType(BaseType.sdk); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index c1f7861d5..d7f5aeadc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -8,6 +8,8 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.module.engagement.interaction.model.SurveyInteraction; +import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; import org.json.JSONObject; @@ -40,6 +42,25 @@ public SurveyResponsePayload(SurveyInteraction definition, Map a } } + //region Http-request + + @Override + public String getHttpEndPoint() { + return StringUtils.format("conversations/%s/surveys/%s/respond", getConversationId(), getId()); + } + + @Override + public HttpRequestMethod getHttpRequestMethod() { + return HttpRequestMethod.POST; + } + + @Override + public String getHttpRequestContentType() { + return "application/json"; + } + + //endregion + public String getId() { return optString(KEY_SURVEY_ID, ""); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java index 672587b8d..c6b7fc22a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java @@ -9,6 +9,8 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.model.StoredFile; +import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.image.ImageItem; import org.json.JSONArray; @@ -61,6 +63,24 @@ public CompoundMessage(String json, boolean bOutgoing) throws JSONException { isOutgoing = bOutgoing; } + //region Http-request + + @Override + public String getHttpEndPoint() { + return StringUtils.format("conversations/%s/messages", getConversationId()); + } + + @Override + public HttpRequestMethod getHttpRequestMethod() { + return HttpRequestMethod.POST; + } + + @Override + public String getHttpRequestContentType() { + return "multipart/mixed;boundary=xxx"; + } + + //endregion @Override protected void initType() { diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java index 79c6ac532..4fac5df0a 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java @@ -10,6 +10,7 @@ import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestManager; +import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.network.HttpRequestRetryPolicyDefault; import com.apptentive.android.sdk.network.MockHttpRequest; import com.apptentive.android.sdk.network.MockHttpURLConnection; @@ -117,6 +118,21 @@ public ResponseHandler getResponseHandler() { return responseHandler; } + @Override + public String getHttpEndPoint() { + return null; + } + + @Override + public HttpRequestMethod getHttpRequestMethod() { + return null; + } + + @Override + public String getHttpRequestContentType() { + return null; + } + @Override public String toString() { return json; From f011c6ebed3d98a5df46a5fc272d6fdf02637f0d Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 13 Apr 2017 14:56:21 -0700 Subject: [PATCH 229/465] Update to Android Gradle plugin 2.3.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d4a9d338e..4d72013e5 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,6 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0' + classpath 'com.android.tools.build:gradle:2.3.1' } } \ No newline at end of file From 1222557567ac2c1d3b06649a61a349d7964f3fb5 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 13 Apr 2017 15:00:38 -0700 Subject: [PATCH 230/465] Fixed payload end points --- .../java/com/apptentive/android/sdk/model/DevicePayload.java | 2 +- .../java/com/apptentive/android/sdk/model/EventPayload.java | 2 +- .../java/com/apptentive/android/sdk/model/PersonPayload.java | 2 +- .../apptentive/android/sdk/model/SdkAndAppReleasePayload.java | 2 +- .../com/apptentive/android/sdk/model/SurveyResponsePayload.java | 2 +- .../android/sdk/module/messagecenter/model/CompoundMessage.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index fbaa02ff0..2b568d733 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -58,7 +58,7 @@ public DevicePayload(String json) throws JSONException { @Override public String getHttpEndPoint() { - return StringUtils.format("conversations/%s/devices", getConversationId()); + return StringUtils.format("/conversations/%s/devices", getConversationId()); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index 4e29f3a5c..df80dcedd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -117,7 +117,7 @@ public EventPayload(String label, String trigger) { @Override public String getHttpEndPoint() { - return StringUtils.format("conversations/%s/event", getConversationId()); + return StringUtils.format("/conversations/%s/event", getConversationId()); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index 73215afea..470619b33 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -43,7 +43,7 @@ public PersonPayload(String json) throws JSONException { @Override public String getHttpEndPoint() { - return StringUtils.format("converations/%s/people", getConversationId()); + return StringUtils.format("/converations/%s/people", getConversationId()); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index 3792437ae..6469cb821 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -67,7 +67,7 @@ public SdkAndAppReleasePayload() { @Override public String getHttpEndPoint() { - return StringUtils.format("conversations/%s/sdkapprelease", getConversationId()); + return StringUtils.format("/conversations/%s/sdkapprelease", getConversationId()); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index d7f5aeadc..a5f252dd4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -46,7 +46,7 @@ public SurveyResponsePayload(SurveyInteraction definition, Map a @Override public String getHttpEndPoint() { - return StringUtils.format("conversations/%s/surveys/%s/respond", getConversationId(), getId()); + return StringUtils.format("/conversations/%s/surveys/%s/respond", getConversationId(), getId()); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java index c6b7fc22a..f38d1caa6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java @@ -67,7 +67,7 @@ public CompoundMessage(String json, boolean bOutgoing) throws JSONException { @Override public String getHttpEndPoint() { - return StringUtils.format("conversations/%s/messages", getConversationId()); + return StringUtils.format("/conversations/%s/messages", getConversationId()); } @Override From 5c983a009ac0271ac36ac81c8de289c65e1b97a4 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 14 Apr 2017 09:32:43 -0700 Subject: [PATCH 231/465] Add `Apptentive.logout()` public API method. 1. Add the API method. 2. Add logic for determining if it should actually run. 3. Run it on the main thread. 4. End an active LOGGED_IN conversation, else warn. 5. Add Tester commands and events for exposing to cucumber tests. --- .../apptentive/android/sdk/Apptentive.java | 11 ++++++- .../android/sdk/ApptentiveInternal.java | 4 +++ .../sdk/conversation/ConversationManager.java | 32 +++++++++++++++++++ .../android/sdk/debug/TesterEvent.java | 7 ++++ .../android/sdk/model/LogoutPayload.java | 14 ++++++++ .../apptentive/android/sdk/model/Payload.java | 1 + 6 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index d7f121934..18312b4d2 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -1065,7 +1065,7 @@ public static void setOnSurveyFinishedListener(OnSurveyFinishedListener listener /** * Starts login process asynchronously. This call returns immediately. */ - public void login(String token, LoginCallback callback) { + public static void login(String token, LoginCallback callback) { if (token == null) { if (callback != null) { callback.onLoginFail("token is null"); @@ -1102,6 +1102,15 @@ public interface LoginCallback { void onLoginFail(String errorMessage); } + public static void logout() { + final ApptentiveInternal instance = ApptentiveInternal.getInstance(); + if (instance == null) { + ApptentiveLog.e("Unable to logout: Apptentive instance is not properly initialized"); + } else { + instance.logout(); + } + } + //endregion /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index c2c6a5777..82773f032 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -967,6 +967,10 @@ void login(String token, LoginCallback callback) { conversationManager.login(token, callback); } + void logout() { + conversationManager.logout(); + } + //endregion /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index ecfbd0bcb..ebc3ed059 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -1,6 +1,7 @@ package com.apptentive.android.sdk.conversation; import android.content.Context; +import android.os.Looper; import com.apptentive.android.sdk.Apptentive.LoginCallback; import com.apptentive.android.sdk.ApptentiveInternal; @@ -497,6 +498,37 @@ private void sendLoginRequest(String token, LoginCallback callback) { callback.onLoginFail("login not yet implemented"); // FIXME: kick off login request } + public void logout() { + // we only deal with an active conversation on the main thread + if (Looper.myLooper() != Looper.getMainLooper()) { + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + doLogout(); + } + }); + } else { + doLogout(); + } + } + + private void doLogout() { + // 1. Check to make sure we need to log out. + if (activeConversation != null) { + switch (activeConversation.getState()) { + case LOGGED_IN: + endActiveConversation(); + break; + default: + ApptentiveLog.w(CONVERSATION, "Attempted to logout() from Conversation, but the Active Conversation was not in LOGGED_IN state."); + break; + } + } else { + ApptentiveLog.w(CONVERSATION, "Attempted to logout(), but there was no Active Conversation."); + } + dispatchDebugEvent(EVT_LOGOUT); + } + //endregion //region Getters/Setters diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java index cf3178943..fcf0f0431 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/TesterEvent.java @@ -27,6 +27,13 @@ public class TesterEvent { public static final String EVT_APPTENTIVE_EVENT = "apptentive_event"; // { eventLabel:String } public static final String EVT_APPTENTIVE_EVENT_KEY_EVENT_LABEL = "eventLabel"; + /** The login attempt finished (might be either successful or failed) */ + public static final String EVT_LOGIN_FINISHED = "login_finished"; + public static final String EVT_LOGIN_FINISHED_ERROR_MESSAGE = "error_message"; + + /** The logout call finished */ + public static final String EVT_LOGOUT = "logout"; + // Common event keys public static final String EVT_KEY_SUCCESSFUL = "successful"; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java new file mode 100644 index 000000000..5ef26623c --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.model; + +public class LogoutPayload extends Payload { + @Override + protected void initBaseType() { + setBaseType(BaseType.logout); + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index 8b7fdf1ea..db28daf17 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -95,6 +95,7 @@ public enum BaseType { app_release, sdk_and_app_release, person, + logout, unknown, // Legacy survey; From 0b2b09957a73e1d56319414582ca625cb8f7072d Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 14 Apr 2017 11:14:18 -0700 Subject: [PATCH 232/465] Don't allow `logout()` to throw an Exception --- .../com/apptentive/android/sdk/Apptentive.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 18312b4d2..99888aa78 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -1103,11 +1103,15 @@ public interface LoginCallback { } public static void logout() { - final ApptentiveInternal instance = ApptentiveInternal.getInstance(); - if (instance == null) { - ApptentiveLog.e("Unable to logout: Apptentive instance is not properly initialized"); - } else { - instance.logout(); + try { + final ApptentiveInternal instance = ApptentiveInternal.getInstance(); + if (instance == null) { + ApptentiveLog.e("Unable to logout: Apptentive instance is not properly initialized"); + } else { + instance.logout(); + } + } catch (Exception e) { + ApptentiveLog.e("Exception while logging out of conversation.", e); } } From b4e0325529559f6191d879bab51603aad9dbc452 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 14 Apr 2017 11:32:05 -0700 Subject: [PATCH 233/465] Added LogoutPayload to PayloadFactory and fixed some build errors --- .../android/sdk/model/LogoutPayload.java | 32 +++++++++++++++++++ .../sdk/model/LogoutPayloadFactory.java | 27 ++++++++++++++++ .../android/sdk/model/PayloadFactory.java | 2 ++ 3 files changed, 61 insertions(+) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayloadFactory.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java index 5ef26623c..243725de1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java @@ -6,9 +6,41 @@ package com.apptentive.android.sdk.model; +import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.util.StringUtils; + +import org.json.JSONException; + public class LogoutPayload extends Payload { + public LogoutPayload() { + super(); + } + + public LogoutPayload(String json) throws JSONException { + super(json); + } + @Override protected void initBaseType() { setBaseType(BaseType.logout); } + + //region Http-request + + @Override + public String getHttpEndPoint() { + return StringUtils.format("/conversations/%s/logout", getConversationId()); + } + + @Override + public HttpRequestMethod getHttpRequestMethod() { + return HttpRequestMethod.POST; + } + + @Override + public String getHttpRequestContentType() { + return "application/json"; // TODO: application/octet-stream + } + + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayloadFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayloadFactory.java new file mode 100644 index 000000000..5f27de624 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayloadFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2013, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.model; + +import com.apptentive.android.sdk.ApptentiveLog; + +import org.json.JSONException; + +/** + * @author Sky Kelsey + */ +public class LogoutPayloadFactory { + public static LogoutPayload fromJson(String json) { + try { + return new LogoutPayload(json); + } catch (JSONException e) { + ApptentiveLog.v("Error parsing json as LogoutPayload: %s", e, json); + } catch (IllegalArgumentException e) { + // Unknown unknown #rumsfeld + } + return null; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java index 033623b6a..0d6d352b6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java @@ -32,6 +32,8 @@ public static Payload fromJson(String json, Payload.BaseType baseType) { return SdkAndAppReleasePayload.fromJson(json); case person: return PersonFactory.fromJson(json); + case logout: + return LogoutPayloadFactory.fromJson(json); case survey: try { return new SurveyResponsePayload(json); From 76be2f5ab55e1379f8d3a8d552f1ccf08a43399f Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 14 Apr 2017 13:24:40 -0700 Subject: [PATCH 234/465] Removed some dead code from `ApptentiveClient` --- .../android/sdk/comm/ApptentiveClient.java | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index e35bb70bc..372044556 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -78,35 +78,6 @@ public static ApptentiveHttpResponse postMessage(ApptentiveMessage apptentiveMes return new ApptentiveHttpResponse(); } - public static ApptentiveHttpResponse postEvent(EventPayload event) { - return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_EVENTS, Method.POST, event.marshallForSending()); - } - - public static ApptentiveHttpResponse putDevice(DevicePayload device) { - return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_DEVICES, Method.PUT, device.marshallForSending()); - } - - public static ApptentiveHttpResponse putSdk(SdkPayload sdk) { - return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, sdk.marshallForSending()); - } - - public static ApptentiveHttpResponse putAppRelease(AppReleasePayload appRelease) { - return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, appRelease.marshallForSending()); - } - - public static ApptentiveHttpResponse putSdkAndAppRelease(SdkAndAppReleasePayload payload) { - return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_CONVERSATION, Method.PUT, payload.marshallForSending()); - } - - public static ApptentiveHttpResponse putPerson(PersonPayload person) { - return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_PEOPLE, Method.PUT, person.marshallForSending()); - } - - public static ApptentiveHttpResponse postSurvey(SurveyResponsePayload survey) { - String endpoint = String.format(ENDPOINT_SURVEYS_POST, survey.getId()); - return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), endpoint, Method.POST, survey.marshallForSending()); - } - public static ApptentiveHttpResponse getInteractions() { return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_INTERACTIONS, Method.GET, null); } From 3dd59267da8dcc0b1cb8c10fae8f11d1a44a6c97 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 14 Apr 2017 14:11:03 -0700 Subject: [PATCH 235/465] Added support for http-multipart requests --- .../sdk/comm/ApptentiveHttpClient.java | 63 ++++++-- .../android/sdk/model/MultipartPayload.java | 16 ++ .../messagecenter/model/CompoundMessage.java | 5 +- .../sdk/network/HttpJsonMultipartRequest.java | 146 ++++++++++++++++++ 4 files changed, 212 insertions(+), 18 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/MultipartPayload.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 8a2ee1c8d..7df1a92e9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -1,15 +1,10 @@ package com.apptentive.android.sdk.comm; -import com.apptentive.android.sdk.debug.Assert; -import com.apptentive.android.sdk.model.AppReleasePayload; import com.apptentive.android.sdk.model.ConversationTokenRequest; -import com.apptentive.android.sdk.model.DevicePayload; -import com.apptentive.android.sdk.model.EventPayload; +import com.apptentive.android.sdk.model.MultipartPayload; import com.apptentive.android.sdk.model.Payload; -import com.apptentive.android.sdk.model.PersonPayload; -import com.apptentive.android.sdk.model.SdkAndAppReleasePayload; -import com.apptentive.android.sdk.model.SdkPayload; -import com.apptentive.android.sdk.model.SurveyResponsePayload; +import com.apptentive.android.sdk.model.StoredFile; +import com.apptentive.android.sdk.network.HttpJsonMultipartRequest; import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestManager; @@ -19,8 +14,7 @@ import org.json.JSONObject; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import static android.text.TextUtils.isEmpty; @@ -88,17 +82,23 @@ public HttpRequest sendPayload(Payload payload, HttpRequest.Listener associatedFiles = ((MultipartPayload) payload).getAssociatedFiles(); + return createMultipartRequest(token, endPoint, payload, associatedFiles, requestMethod); + } + switch (payload.getHttpRequestContentType()) { case "application/json": { return createJsonRequest(token, endPoint, payload, requestMethod); @@ -112,18 +112,49 @@ private HttpRequest createRequest(Payload payload) { //region Helpers - private HttpJsonRequest createJsonRequest(String oauthToken, String endpoint, JSONObject jsonObject, HttpRequestMethod method) { - Assert.assertNotNull(oauthToken); - Assert.assertNotNull(endpoint); + private HttpJsonRequest createJsonRequest(String oauthToken, String endpoint, JSONObject payload, HttpRequestMethod method) { + if (oauthToken == null) { + throw new IllegalArgumentException("OAuth token is null"); + } + if (endpoint == null) { + throw new IllegalArgumentException("Endpoint is null"); + } + if (payload == null) { + throw new IllegalArgumentException("Payload is null"); + } + if (method == null) { + throw new IllegalArgumentException("Method is null"); + } String url = createEndpointURL(endpoint); - HttpJsonRequest request = new HttpJsonRequest(url, jsonObject); + HttpJsonRequest request = new HttpJsonRequest(url, payload); setupRequestDefaults(request, oauthToken); request.setMethod(method); request.setRequestProperty("Content-Type", "application/json"); return request; } + private HttpJsonMultipartRequest createMultipartRequest(String oauthToken, String endpoint, JSONObject payload, List files, HttpRequestMethod method) { + if (oauthToken == null) { + throw new IllegalArgumentException("OAuth token is null"); + } + if (endpoint == null) { + throw new IllegalArgumentException("Endpoint is null"); + } + if (payload == null) { + throw new IllegalArgumentException("Payload is null"); + } + if (method == null) { + throw new IllegalArgumentException("Method is null"); + } + + String url = createEndpointURL(endpoint); + HttpJsonMultipartRequest request = new HttpJsonMultipartRequest(url, payload, files); + setupRequestDefaults(request, oauthToken); + request.setMethod(method); + return request; + } + private void setupRequestDefaults(HttpRequest request, String oauthToken) { request.setRequestProperty("User-Agent", userAgentString); request.setRequestProperty("Connection", "Keep-Alive"); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/MultipartPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/MultipartPayload.java new file mode 100644 index 000000000..bd82c936c --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/MultipartPayload.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.model; + +import java.util.List; + +/** + * Interface for payloads which should be send with an http-multipart request + */ +public interface MultipartPayload { + List getAssociatedFiles(); +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java index f38d1caa6..cb9067282 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java @@ -8,6 +8,7 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.model.MultipartPayload; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.util.StringUtils; @@ -23,7 +24,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -public class CompoundMessage extends ApptentiveMessage implements MessageCenterUtil.CompoundMessageCommonInterface { +public class CompoundMessage extends ApptentiveMessage implements MultipartPayload, MessageCenterUtil.CompoundMessageCommonInterface { private static final String KEY_BODY = "body"; @@ -203,7 +204,7 @@ public boolean setAssociatedFiles(List attachedFiles) { } } - + @Override public List getAssociatedFiles() { if (hasNoAttachments) { return null; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java new file mode 100644 index 000000000..7f3814580 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.network; + +import com.apptentive.android.sdk.model.StoredFile; +import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.util.Util; +import com.apptentive.android.sdk.util.image.ImageUtil; + +import org.json.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +/** + * Class representing HTTP multipart request with Json body + */ +public class HttpJsonMultipartRequest extends HttpJsonRequest { + private static final String lineEnd = "\r\n"; + private static final String twoHyphens = "--"; + + private final List files; + private final String boundary; + + public HttpJsonMultipartRequest(String urlString, JSONObject requestObject, List files) { + super(urlString, requestObject); + + if (files == null) { + throw new IllegalArgumentException("Files reference is null"); + } + this.files = files; + + boundary = UUID.randomUUID().toString(); + setRequestProperty("Content-Type", "multipart/mixed;boundary=" + boundary); + } + + @Override + protected byte[] createRequestData() throws IOException { + ByteArrayOutputStream stream = null; + try { + // get payload bytes + final byte[] jsonData = super.createRequestData(); + + // write data + stream = new ByteArrayOutputStream(); + writeRequestData(new DataOutputStream(stream), jsonData); + return stream.toByteArray(); + } finally { + Util.ensureClosed(stream); + } + } + + private void writeRequestData(DataOutputStream out, byte[] jsonData) throws IOException { + out.writeBytes(twoHyphens + boundary + lineEnd); + + // Write text message + out.writeBytes("Content-Disposition: form-data; name=\"message\"" + lineEnd); + // Indicate the character encoding is UTF-8 + out.writeBytes("Content-Type: text/plain;charset=UTF-8" + lineEnd); + + out.writeBytes(lineEnd); + + // Explicitly encode message json + out.write(jsonData); + out.writeBytes(lineEnd); + + // Send associated files + if (files != null) { + writeFiles(out, files); + } + out.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); + } + + private void writeFiles(DataOutputStream out, List files) throws IOException { + for (StoredFile file : files) { + writeFile(out, file); + } + } + + private void writeFile(DataOutputStream out, StoredFile file) throws IOException { + String cachedImagePathString = file.getLocalFilePath(); + String originalFilePath = file.getSourceUriOrPath(); + File cachedImageFile = new File(cachedImagePathString); + + // No local cache found + if (!cachedImageFile.exists()) { + boolean cachedCreated = false; + if (Util.isMimeTypeImage(file.getMimeType())) { + // Create a scaled down version of original image + cachedCreated = ImageUtil.createScaledDownImageCacheFile(originalFilePath, cachedImagePathString); + } else { + // For non-image file, just copy to a cache file + if (Util.createLocalStoredFile(originalFilePath, cachedImagePathString, null) != null) { + cachedCreated = true; + } + } + + if (!cachedCreated) { + return; + } + } + out.writeBytes(twoHyphens + boundary + lineEnd); + StringBuilder requestText = new StringBuilder(); + String fileFullPathName = originalFilePath; + if (StringUtils.isNullOrEmpty(fileFullPathName)) { + fileFullPathName = cachedImagePathString; + } + requestText.append(String.format("Content-Disposition: form-data; name=\"file[]\"; filename=\"%s\"", fileFullPathName)).append(lineEnd); + requestText.append("Content-Type: ").append(file.getMimeType()).append(lineEnd); + + // Write file attributes + out.writeBytes(requestText.toString()); + out.writeBytes(lineEnd); + + FileInputStream fis = null; + try { + fis = new FileInputStream(cachedImageFile); + + int bytesAvailable = fis.available(); + int maxBufferSize = 512 * 512; + int bufferSize = Math.min(bytesAvailable, maxBufferSize); + byte[] buffer = new byte[bufferSize]; + + // read image data 0.5MB at a time and write it into buffer + int bytesRead = fis.read(buffer, 0, bufferSize); + while (bytesRead > 0) { + out.write(buffer, 0, bufferSize); + bytesAvailable = fis.available(); + bufferSize = Math.min(bytesAvailable, maxBufferSize); + bytesRead = fis.read(buffer, 0, bufferSize); + } + } finally { + Util.ensureClosed(fis); + } + out.writeBytes(lineEnd); + } +} From f660d4c8aae27ebf035b564b7b99cc54fc0c53b3 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 14 Apr 2017 14:14:36 -0700 Subject: [PATCH 236/465] Removed dead code from ApptentiveClient --- .../android/sdk/comm/ApptentiveClient.java | 181 ------------------ 1 file changed, 181 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 372044556..026bc24fe 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -11,12 +11,8 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.model.*; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; -import com.apptentive.android.sdk.util.image.ImageUtil; import java.io.*; import java.net.HttpURLConnection; @@ -38,12 +34,7 @@ public class ApptentiveClient { // Active API private static final String ENDPOINT_CONVERSATION = "/conversation"; private static final String ENDPOINT_CONVERSATION_FETCH = ENDPOINT_CONVERSATION + "?count=%s&after_id=%s&before_id=%s"; - private static final String ENDPOINT_MESSAGES = "/messages"; - private static final String ENDPOINT_EVENTS = "/events"; - private static final String ENDPOINT_DEVICES = "/devices"; - private static final String ENDPOINT_PEOPLE = "/people"; private static final String ENDPOINT_CONFIGURATION = ENDPOINT_CONVERSATION + "/configuration"; - private static final String ENDPOINT_SURVEYS_POST = "/surveys/%s/respond"; private static final String ENDPOINT_INTERACTIONS = "/interactions"; @@ -65,19 +56,6 @@ public static ApptentiveHttpResponse getMessages(Integer count, String afterId, return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), uri, Method.GET, null); } - public static ApptentiveHttpResponse postMessage(ApptentiveMessage apptentiveMessage) { - switch (apptentiveMessage.getType()) { - case CompoundMessage: { - CompoundMessage compoundMessage = (CompoundMessage) apptentiveMessage; - List associatedFiles = compoundMessage.getAssociatedFiles(); - return performMultipartFilePost(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_MESSAGES, apptentiveMessage.marshallForSending(), associatedFiles); - } - case unknown: - break; - } - return new ApptentiveHttpResponse(); - } - public static ApptentiveHttpResponse getInteractions() { return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_INTERACTIONS, Method.GET, null); } @@ -196,165 +174,6 @@ private static void sendPostPutRequest(final HttpURLConnection connection, final } } - private static ApptentiveHttpResponse performMultipartFilePost(String oauthToken, String uri, String postBody, List associatedFiles) { - uri = getEndpointBase() + uri; - ApptentiveLog.d("Performing multipart POST to %s", uri); - ApptentiveLog.d("Multipart POST body: %s", postBody); - - ApptentiveHttpResponse ret = new ApptentiveHttpResponse(); - if (!Util.isNetworkConnectionPresent()) { - ApptentiveLog.d("Network unavailable."); - return ret; - } - - String lineEnd = "\r\n"; - String twoHyphens = "--"; - String boundary = UUID.randomUUID().toString(); - - HttpURLConnection connection = null; - DataOutputStream os = null; - - try { - - // Set up the request. - URL url = new URL(uri); - connection = (HttpURLConnection) url.openConnection(); - connection.setDoInput(true); - connection.setDoOutput(true); - connection.setUseCaches(false); - connection.setConnectTimeout(DEFAULT_HTTP_CONNECT_TIMEOUT); - connection.setReadTimeout(DEFAULT_HTTP_SOCKET_TIMEOUT); - connection.setRequestMethod("POST"); - - connection.setRequestProperty("Content-Type", "multipart/mixed;boundary=" + boundary); - connection.setRequestProperty("Authorization", "OAuth " + oauthToken); - connection.setRequestProperty("Accept", "application/json"); - connection.setRequestProperty("X-API-Version", String.valueOf(API_VERSION)); - connection.setRequestProperty("User-Agent", getUserAgentString()); - - // Open an output stream. - os = new DataOutputStream(connection.getOutputStream()); - os.writeBytes(twoHyphens + boundary + lineEnd); - - // Write text message - os.writeBytes("Content-Disposition: form-data; name=\"message\"" + lineEnd); - // Indicate the character encoding is UTF-8 - os.writeBytes("Content-Type: text/plain;charset=UTF-8" + lineEnd); - - os.writeBytes(lineEnd); - // Explicitly encode message json in utf-8 - os.write(postBody.getBytes("UTF-8")); - os.writeBytes(lineEnd); - - - // Send associated files - if (associatedFiles != null) { - for (StoredFile storedFile : associatedFiles) { - FileInputStream fis = null; - try { - String cachedImagePathString = storedFile.getLocalFilePath(); - String originalFilePath = storedFile.getSourceUriOrPath(); - File cachedImageFile = new File(cachedImagePathString); - // No local cache found - if (!cachedImageFile.exists()) { - boolean bCachedCreated = false; - if (Util.isMimeTypeImage(storedFile.getMimeType())) { - // Create a scaled down version of original image - bCachedCreated = ImageUtil.createScaledDownImageCacheFile(originalFilePath, cachedImagePathString); - } else { - // For non-image file, just copy to a cache file - if (Util.createLocalStoredFile(originalFilePath, cachedImagePathString, null) != null) { - bCachedCreated = true; - } - } - - if (!bCachedCreated) { - continue; - } - } - os.writeBytes(twoHyphens + boundary + lineEnd); - StringBuilder requestText = new StringBuilder(); - String fileFullPathName = originalFilePath; - if (TextUtils.isEmpty(fileFullPathName)) { - fileFullPathName = cachedImagePathString; - } - requestText.append(String.format("Content-Disposition: form-data; name=\"file[]\"; filename=\"%s\"", fileFullPathName)).append(lineEnd); - requestText.append("Content-Type: ").append(storedFile.getMimeType()).append(lineEnd); - // Write file attributes - os.writeBytes(requestText.toString()); - os.writeBytes(lineEnd); - - fis = new FileInputStream(cachedImageFile); - - int bytesAvailable = fis.available(); - int maxBufferSize = 512 * 512; - int bufferSize = Math.min(bytesAvailable, maxBufferSize); - byte[] buffer = new byte[bufferSize]; - - // read image data 0.5MB at a time and write it into buffer - int bytesRead = fis.read(buffer, 0, bufferSize); - while (bytesRead > 0) { - os.write(buffer, 0, bufferSize); - bytesAvailable = fis.available(); - bufferSize = Math.min(bytesAvailable, maxBufferSize); - bytesRead = fis.read(buffer, 0, bufferSize); - } - } catch (IOException e) { - ApptentiveLog.d("Error writing file bytes to HTTP connection.", e); - ret.setBadPayload(true); - throw e; - } finally { - Util.ensureClosed(fis); - } - os.writeBytes(lineEnd); - } - } - os.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); - - os.flush(); - os.close(); - - ret.setCode(connection.getResponseCode()); - ret.setReason(connection.getResponseMessage()); - - // Read the normal response. - InputStream responseInputStream = null; - ByteArrayOutputStream byteArrayOutputStream = null; - try { - responseInputStream = connection.getInputStream(); - byteArrayOutputStream = new ByteArrayOutputStream(); - byte[] eBuf = new byte[1024]; - int eRead; - while (responseInputStream != null && (eRead = responseInputStream.read(eBuf, 0, 1024)) > 0) { - byteArrayOutputStream.write(eBuf, 0, eRead); - } - ret.setContent(byteArrayOutputStream.toString()); - } finally { - Util.ensureClosed(responseInputStream); - Util.ensureClosed(byteArrayOutputStream); - } - - ApptentiveLog.d("HTTP %d: %s", connection.getResponseCode(), connection.getResponseMessage()); - ApptentiveLog.v("Response: %s", ret.getContent()); - } catch (FileNotFoundException e) { - ApptentiveLog.e("Error getting file to upload.", e); - } catch (MalformedURLException e) { - ApptentiveLog.e("Error constructing url for file upload.", e); - } catch (SocketTimeoutException e) { - ApptentiveLog.w("Timeout communicating with server."); - } catch (IOException e) { - ApptentiveLog.e("Error executing file upload.", e); - try { - ret.setContent(getErrorResponse(connection, ret.isZipped())); - } catch (IOException ex) { - ApptentiveLog.w("Can't read error stream.", ex); - } - } finally { - Util.ensureClosed(os); - } - return ret; - } - private enum Method { GET, PUT, From f503d7d242ba6e1234b8b35b187f3d2b53c5610a Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 14 Apr 2017 14:15:30 -0700 Subject: [PATCH 237/465] Removed dead code from ApptentiveHttpClient --- .../android/sdk/comm/ApptentiveHttpClient.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 7df1a92e9..b918929fa 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -22,19 +22,15 @@ * Class responsible for all client-server network communications using asynchronous HTTP requests */ public class ApptentiveHttpClient implements PayloadRequestSender { - public static final String API_VERSION = "7"; + private static final String API_VERSION = "7"; private static final String USER_AGENT_STRING = "Apptentive/%s (Android)"; // Format with SDK version string. - public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 45000; - public static final int DEFAULT_HTTP_SOCKET_TIMEOUT = 45000; + private static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 45000; + private static final int DEFAULT_HTTP_SOCKET_TIMEOUT = 45000; // Active API private static final String ENDPOINT_CONVERSATION = "/conversation"; - private static final String ENDPOINT_EVENTS = "/events"; - private static final String ENDPOINT_DEVICES = "/devices"; - private static final String ENDPOINT_PEOPLE = "/people"; - private static final String ENDPOINT_SURVEYS_POST = "/surveys/%s/respond"; private final String apiKey; private final String serverURL; From 3439be615d8fc990e4cf2d79a1f6d2ff1d548bee Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 17 Apr 2017 13:47:11 -0700 Subject: [PATCH 238/465] Refactoring Added JsonPayload class and a base Payload class --- .../sdk/comm/ApptentiveHttpClient.java | 6 +- .../android/sdk/model/AppReleasePayload.java | 2 +- .../android/sdk/model/ConversationItem.java | 2 +- .../android/sdk/model/DevicePayload.java | 2 +- .../android/sdk/model/JsonPayload.java | 142 +++++++++++++++++ .../android/sdk/model/LogoutPayload.java | 2 +- .../apptentive/android/sdk/model/Payload.java | 150 ++++++------------ .../android/sdk/model/PayloadFactory.java | 2 +- .../android/sdk/model/PersonPayload.java | 2 +- .../sdk/model/SdkAndAppReleasePayload.java | 2 +- .../android/sdk/model/SdkPayload.java | 3 +- .../module/messagecenter/MessageManager.java | 6 +- .../sdk/storage/ApptentiveDatabaseHelper.java | 16 +- .../sdk/storage/ApptentiveTaskManager.java | 20 +-- .../sdk/storage/PayloadRequestSender.java | 4 +- .../android/sdk/storage/PayloadSender.java | 10 +- .../android/sdk/storage/PayloadStore.java | 8 +- .../sdk/storage/PayloadTypeSender.java | 4 +- ...erTest.java => JsonPayloadSenderTest.java} | 11 +- 19 files changed, 239 insertions(+), 155 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java rename apptentive/src/test/java/com/apptentive/android/sdk/storage/{PayloadSenderTest.java => JsonPayloadSenderTest.java} (91%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index b918929fa..f2ab793ee 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -1,8 +1,8 @@ package com.apptentive.android.sdk.comm; import com.apptentive.android.sdk.model.ConversationTokenRequest; +import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.model.MultipartPayload; -import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.network.HttpJsonMultipartRequest; import com.apptentive.android.sdk.network.HttpJsonRequest; @@ -73,7 +73,7 @@ public HttpRequest findRequest(String tag) { //region PayloadRequestSender @Override - public HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener) { + public HttpRequest sendPayload(JsonPayload payload, HttpRequest.Listener listener) { if (payload == null) { throw new IllegalArgumentException("Payload is null"); } @@ -84,7 +84,7 @@ public HttpRequest sendPayload(Payload payload, HttpRequest.Listener attachments; // TODO: Figure out attachment handling + + public String getType() { + return type; } - public Payload(String json) throws JSONException { - super(json); - initBaseType(); + public void setType(String type) { + this.type = type; } - public Payload(String json, String conversationId, String token) throws JSONException { - this(json); - this.conversationId = conversationId; - this.token = token; + public String getNonce() { + return nonce; } - /** - * Each subclass must set its type in this method. - */ - protected abstract void initBaseType(); - - /** - * Subclasses should override this method if there is any peculiarity in how they present or wrap json before sending. - * - * @return A wrapper object containing the name of the object type, the value of which is the contents of this Object. - */ - public String marshallForSending() { - JSONObject wrapper = new JSONObject(); - try { - wrapper.put(getBaseType().name(), this); - } catch (JSONException e) { - ApptentiveLog.w("Error wrapping Payload in JSONObject.", e); - return null; - } - return wrapper.toString(); + public void setNonce(String nonce) { + this.nonce = nonce; } - public long getDatabaseId() { - return databaseId; + public int getApiVersion() { + return apiVersion; } - public void setDatabaseId(long databaseId) { - this.databaseId = databaseId; + public void setApiVersion(int apiVersion) { + this.apiVersion = apiVersion; } - public BaseType getBaseType() { - return baseType; + public String getContentType() { + return contentType; } - protected void setBaseType(BaseType baseType) { - this.baseType = baseType; + public void setContentType(String contentType) { + this.contentType = contentType; } - public String getConversationId() { - return conversationId; + public String getAuthToken() { + return authToken; } - public void setConversationId(String conversationId) { - this.conversationId = conversationId; + public void setAuthToken(String authToken) { + this.authToken = authToken; } - public void setToken(String token) { - this.token = token; + public String getMethod() { + return method; } - public String getToken() { - return token; + public void setMethod(String method) { + this.method = method; } - public enum BaseType { - message, - event, - device, - sdk, - app_release, - sdk_and_app_release, - person, - logout, - unknown, - // Legacy - survey; - - public static BaseType parse(String type) { - try { - return BaseType.valueOf(type); - } catch (IllegalArgumentException e) { - ApptentiveLog.v("Error parsing unknown Payload.BaseType: " + type); - } - return unknown; - } - + public String getPath() { + return path; } - /** - * @deprecated Do not use this method to check for key existence. Instead us !isNull(KEY_NAME), as this works better - * with keys with null values. - */ - @Override - public boolean has(String key) { - return super.has(key); + public void setPath(String path) { + this.path = path; } - //region Http-request - - /** - * Http endpoint for sending this payload - */ - public abstract String getHttpEndPoint(); - - /** - * Http request method for sending this payload - */ - public abstract HttpRequestMethod getHttpRequestMethod(); + public List getAttachments() { + return attachments; + } - /** - * Http content type for sending this payload - */ - public abstract String getHttpRequestContentType(); + public void setAttachments(List attachments) { + this.attachments = attachments; + } - //endregion + public abstract byte[] getData(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java index 0d6d352b6..5f253a8ab 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java @@ -16,7 +16,7 @@ */ public class PayloadFactory { - public static Payload fromJson(String json, Payload.BaseType baseType) { + public static JsonPayload fromJson(String json, JsonPayload.BaseType baseType) { switch (baseType) { case message: return MessageFactory.fromJson(json); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index 470619b33..9e707fa25 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -15,7 +15,7 @@ /** * @author Sky Kelsey */ -public class PersonPayload extends Payload { +public class PersonPayload extends JsonPayload { public static final String KEY = "person"; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index 6469cb821..1c60d53d7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -27,7 +27,7 @@ * and {@link AppReleasePayload} payloads (which still kept for backward compatibility * purposes). */ -public class SdkAndAppReleasePayload extends Payload { +public class SdkAndAppReleasePayload extends JsonPayload { private final SdkPayload sdk; private final AppReleasePayload appRelease; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java index 79244c855..f46dc4b5d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java @@ -8,14 +8,13 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.network.HttpRequestMethod; -import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; /** * @author Sky Kelsey */ -public class SdkPayload extends Payload { +public class SdkPayload extends JsonPayload { public static final String KEY = "sdk"; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index b10cf46d7..73cd9793a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -16,7 +16,7 @@ import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; -import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveToastNotification; import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; @@ -354,12 +354,12 @@ public void onReceiveNotification(ApptentiveNotification notification) { setCurrentForegroundActivity(null); appWentToBackground(); } else if (notification.hasName(NOTIFICATION_PAYLOAD_WILL_START_SEND)) { - final Payload payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, Payload.class); + final JsonPayload payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, JsonPayload.class); if (payload instanceof ApptentiveMessage) { resumeSending(); } } else if (notification.hasName(NOTIFICATION_PAYLOAD_DID_FINISH_SEND)) { - final Payload payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, Payload.class); + final JsonPayload payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, JsonPayload.class); if (payload instanceof ApptentiveMessage) { // onSentMessage((ApptentiveMessage) payload, ???); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 6a22ccd2b..c1b01be3e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -15,7 +15,7 @@ import android.text.TextUtils; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.model.PayloadFactory; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; @@ -275,7 +275,7 @@ private void upgradeVersion1to2(SQLiteDatabase db) { // Migrate all pending payload messages // Migrate legacy message types to CompoundMessage Type try { - cursor = db.rawQuery(QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER, new String[]{Payload.BaseType.message.name()}); + cursor = db.rawQuery(QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER, new String[]{JsonPayload.BaseType.message.name()}); if (cursor.moveToFirst()) { do { String json = cursor.getString(2); @@ -336,12 +336,12 @@ private void upgradeVersion2to3(SQLiteDatabase db) { * If an item with the same nonce as an item passed in already exists, it is overwritten by the item. Otherwise * a new message is added. */ - public void addPayload(Payload... payloads) { + public void addPayload(JsonPayload... payloads) { SQLiteDatabase db = null; try { db = getWritableDatabase(); db.beginTransaction(); - for (Payload payload : payloads) { + for (JsonPayload payload : payloads) { ContentValues values = new ContentValues(); values.put(PAYLOAD_KEY_BASE_TYPE, payload.getBaseType().name()); values.put(PAYLOAD_KEY_JSON, payload.toString()); @@ -356,7 +356,7 @@ public void addPayload(Payload... payloads) { } } - public void deletePayload(Payload payload) { + public void deletePayload(JsonPayload payload) { if (payload != null) { SQLiteDatabase db = null; try { @@ -378,17 +378,17 @@ public void deleteAllPayloads() { } } - public Payload getOldestUnsentPayload() { + public JsonPayload getOldestUnsentPayload() { SQLiteDatabase db = null; Cursor cursor = null; try { db = getWritableDatabase(); cursor = db.rawQuery(QUERY_PAYLOAD_GET_NEXT_TO_SEND, null); - Payload payload = null; + JsonPayload payload = null; if (cursor.moveToFirst()) { long databaseId = Long.parseLong(cursor.getString(0)); - Payload.BaseType baseType = Payload.BaseType.parse(cursor.getString(1)); + JsonPayload.BaseType baseType = JsonPayload.BaseType.parse(cursor.getString(1)); String json = cursor.getString(2); String conversationId = cursor.getString(3); String token = cursor.getString(4); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index c0fbeb9ee..3b222a11d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -12,7 +12,7 @@ import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.debug.Assert; -import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.network.HttpRequestRetryPolicyDefault; import com.apptentive.android.sdk.notifications.ApptentiveNotification; @@ -98,8 +98,8 @@ public boolean shouldRetryRequest(int responseCode, int retryAttempt) { * If an item with the same nonce as an item passed in already exists, it is overwritten by the item. Otherwise * a new message is added. */ - public void addPayload(final Payload... payloads) { - for (Payload payload : payloads) { + public void addPayload(final JsonPayload... payloads) { + for (JsonPayload payload : payloads) { payload.setConversationId(currentConversationId); payload.setToken(currentConversationToken); } @@ -112,7 +112,7 @@ public void run() { }); } - public void deletePayload(final Payload payload) { + public void deletePayload(final JsonPayload payload) { if (payload != null) { singleThreadExecutor.execute(new Runnable() { @Override @@ -133,16 +133,16 @@ public void run() { }); } - public synchronized Future getOldestUnsentPayload() throws Exception { - return singleThreadExecutor.submit(new Callable() { + public synchronized Future getOldestUnsentPayload() throws Exception { + return singleThreadExecutor.submit(new Callable() { @Override - public Payload call() throws Exception { + public JsonPayload call() throws Exception { return getOldestUnsentPayloadSync(); } }); } - private Payload getOldestUnsentPayloadSync() { + private JsonPayload getOldestUnsentPayloadSync() { return dbHelper.getOldestUnsentPayload(); } @@ -180,7 +180,7 @@ public void reset(Context context) { //region PayloadSender.Listener @Override - public void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage) { + public void onFinishSending(PayloadSender sender, JsonPayload payload, boolean cancelled, String errorMessage) { ApptentiveNotificationCenter.defaultCenter() .postNotification(NOTIFICATION_PAYLOAD_DID_FINISH_SEND, NOTIFICATION_KEY_PAYLOAD, payload, @@ -227,7 +227,7 @@ private void sendNextPayloadSync() { return; } - final Payload payload; + final JsonPayload payload; try { payload = getOldestUnsentPayloadSync(); } catch (Exception e) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java index 7282a1cb2..492b9ea83 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java @@ -6,7 +6,7 @@ package com.apptentive.android.sdk.storage; -import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.network.HttpRequest; /** @@ -20,5 +20,5 @@ public interface PayloadRequestSender { * @param payload to be sent * @param listener Http-request listener for the payload request */ - HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener); + HttpRequest sendPayload(JsonPayload payload, HttpRequest.Listener listener); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index a6becc302..b6506cc4d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -7,7 +7,7 @@ package com.apptentive.android.sdk.storage; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestRetryPolicy; import com.apptentive.android.sdk.util.StringUtils; @@ -56,7 +56,7 @@ class PayloadSender { * * @throws IllegalArgumentException is payload is null */ - synchronized boolean sendPayload(final Payload payload) { + synchronized boolean sendPayload(final JsonPayload payload) { if (payload == null) { throw new IllegalArgumentException("Payload is null"); } @@ -91,7 +91,7 @@ synchronized boolean sendPayload(final Payload payload) { /** * Creates and sends payload Http-request asynchronously (returns immediately) */ - private synchronized void sendPayloadRequest(final Payload payload) { + private synchronized void sendPayloadRequest(final JsonPayload payload) { ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getBaseType(), payload.getDatabaseId(), payload.getConversationId()); // create request object @@ -127,7 +127,7 @@ public void onFail(HttpRequest request, String reason) { * @param cancelled - flag indicating if payload Http-request was cancelled * @param errorMessage - if not null - payload request failed */ - private synchronized void handleFinishSendingPayload(Payload payload, boolean cancelled, String errorMessage) { + private synchronized void handleFinishSendingPayload(JsonPayload payload, boolean cancelled, String errorMessage) { sendingFlag = false; // mark sender as 'not busy' try { @@ -159,7 +159,7 @@ public void setListener(Listener listener) { //region Listener public interface Listener { - void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage); + void onFinishSending(PayloadSender sender, JsonPayload payload, boolean cancelled, String errorMessage); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java index 527762d33..100f3037f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java @@ -1,6 +1,6 @@ package com.apptentive.android.sdk.storage; -import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.JsonPayload; import java.util.concurrent.Future; @@ -9,15 +9,15 @@ */ public interface PayloadStore { - public void addPayload(Payload... payloads); + public void addPayload(JsonPayload... payloads); - public void deletePayload(Payload payload); + public void deletePayload(JsonPayload payload); public void deleteAllPayloads(); /* Asynchronous call to retrieve the oldest unsent payload from the data storage. * Calling get() method on the returned Future object will block the caller until the Future has completed, */ - public Future getOldestUnsentPayload() throws Exception; + public Future getOldestUnsentPayload() throws Exception; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadTypeSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadTypeSender.java index 8ef5cccfc..2af1e5405 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadTypeSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadTypeSender.java @@ -7,8 +7,8 @@ package com.apptentive.android.sdk.storage; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; -import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.JsonPayload; -interface PayloadTypeSender { +interface PayloadTypeSender { ApptentiveHttpResponse sendPayload(T payload); } diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java similarity index 91% rename from apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java rename to apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java index 4fac5df0a..f96656204 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/PayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java @@ -7,13 +7,12 @@ package com.apptentive.android.sdk.storage; import com.apptentive.android.sdk.TestCaseBase; -import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestManager; import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.network.HttpRequestRetryPolicyDefault; import com.apptentive.android.sdk.network.MockHttpRequest; -import com.apptentive.android.sdk.network.MockHttpURLConnection; import com.apptentive.android.sdk.network.MockHttpURLConnection.DefaultResponseHandler; import com.apptentive.android.sdk.network.MockHttpURLConnection.ResponseHandler; import com.apptentive.android.sdk.util.StringUtils; @@ -25,7 +24,7 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.TestCase.assertTrue; -public class PayloadSenderTest extends TestCaseBase { +public class JsonPayloadSenderTest extends TestCaseBase { private MockDispatchQueue networkQueue; @Before @@ -42,7 +41,7 @@ public void testSendPayload() throws Exception { PayloadSender sender = new PayloadSender(requestSender, new HttpRequestRetryPolicyDefault()); sender.setListener(new PayloadSender.Listener() { @Override - public void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage) { + public void onFinishSending(PayloadSender sender, JsonPayload payload, boolean cancelled, String errorMessage) { if (cancelled) { addResult("cancelled: " + payload); } else if (errorMessage != null) { @@ -89,7 +88,7 @@ public int getResponseCode() { ); } - class MockPayload extends Payload { + class MockPayload extends JsonPayload { private final String json; private ResponseHandler responseHandler; @@ -147,7 +146,7 @@ public MockPayloadRequestSender() { } @Override - public HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener) { + public HttpRequest sendPayload(JsonPayload payload, HttpRequest.Listener listener) { MockHttpRequest request = new MockHttpRequest("http://apptentive.com"); request.setMockResponseHandler(((MockPayload) payload).getResponseHandler()); From 4d31ffec45425513e6bd42798a2238778f2937e3 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 17 Apr 2017 14:57:38 -0700 Subject: [PATCH 239/465] Refactored json object usage --- .../sdk/comm/ApptentiveHttpClient.java | 4 +- .../android/sdk/model/AppReleaseFactory.java | 24 - .../android/sdk/model/AppReleasePayload.java | 137 +----- .../android/sdk/model/ConversationItem.java | 46 +- .../android/sdk/model/DeviceFactory.java | 26 -- .../android/sdk/model/DevicePayload.java | 427 ++---------------- .../android/sdk/model/EventFactory.java | 27 -- .../android/sdk/model/EventPayload.java | 18 +- .../android/sdk/model/JsonPayload.java | 152 +++++-- .../android/sdk/model/LogoutPayload.java | 8 - .../sdk/model/LogoutPayloadFactory.java | 27 -- .../apptentive/android/sdk/model/Payload.java | 6 +- .../android/sdk/model/PayloadFactory.java | 51 --- .../android/sdk/model/PersonFactory.java | 27 -- .../android/sdk/model/PersonPayload.java | 183 +------- .../sdk/model/SdkAndAppReleasePayload.java | 27 +- .../android/sdk/model/SdkFactory.java | 26 -- .../android/sdk/model/SdkPayload.java | 119 +---- .../sdk/model/SurveyResponsePayload.java | 6 +- .../module/messagecenter/MessageManager.java | 2 +- .../model/ApptentiveMessage.java | 81 +--- .../messagecenter/model/CompoundMessage.java | 5 - .../MessageCenterRecyclerViewAdapter.java | 2 +- .../sdk/storage/ApptentiveDatabaseHelper.java | 14 +- 24 files changed, 239 insertions(+), 1206 deletions(-) delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleaseFactory.java delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/DeviceFactory.java delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/EventFactory.java delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayloadFactory.java delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/PersonFactory.java delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/SdkFactory.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index f2ab793ee..eabb71986 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -92,12 +92,12 @@ private HttpRequest createPayloadRequest(JsonPayload payload) { // TODO: figure out a better solution if (payload instanceof MultipartPayload) { final List associatedFiles = ((MultipartPayload) payload).getAssociatedFiles(); - return createMultipartRequest(token, endPoint, payload, associatedFiles, requestMethod); + return createMultipartRequest(token, endPoint, payload.getJsonObject(), associatedFiles, requestMethod); } switch (payload.getHttpRequestContentType()) { case "application/json": { - return createJsonRequest(token, endPoint, payload, requestMethod); + return createJsonRequest(token, endPoint, payload.getJsonObject(), requestMethod); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleaseFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleaseFactory.java deleted file mode 100644 index f4958ff07..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleaseFactory.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2013, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.model; - -import com.apptentive.android.sdk.ApptentiveLog; - -import org.json.JSONException; - -public class AppReleaseFactory { - public static AppReleasePayload fromJson(String json) { - try { - return new AppReleasePayload(json); - } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as AppRelease: %s", e, json); - } catch (IllegalArgumentException e) { - // Unknown unknown #rumsfeld - } - return null; - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java index b3c8e93dc..008bed3d2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java @@ -17,8 +17,6 @@ import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.util.Util; -import org.json.JSONException; - public class AppReleasePayload extends JsonPayload { private static final String KEY_TYPE = "type"; @@ -31,14 +29,6 @@ public class AppReleasePayload extends JsonPayload { private static final String KEY_STYLE_OVERRIDE = "overriding_styles"; private static final String KEY_DEBUG = "debug"; - public AppReleasePayload(String json) throws JSONException { - super(json); - } - - public AppReleasePayload() { - super(); - } - //region Http-request @Override @@ -63,170 +53,79 @@ public void initBaseType() { } public String getType() { - if (!isNull(KEY_TYPE)) { - return optString(KEY_TYPE, null); - } - return null; + return getString(KEY_TYPE); } public void setType(String type) { - try { - put(KEY_TYPE, type); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_TYPE); - } + put(KEY_TYPE, type); } public String getVersionName() { - if (!isNull(KEY_VERSION_NAME)) { - return optString(KEY_VERSION_NAME, null); - } - return null; + return getString(KEY_VERSION_NAME); } public void setVersionName(String versionName) { - try { - put(KEY_VERSION_NAME, versionName); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_VERSION_NAME); - } + put(KEY_VERSION_NAME, versionName); } public int getVersionCode() { if (!isNull(KEY_VERSION_CODE)) { - return optInt(KEY_VERSION_CODE, -1); + return getInt(KEY_VERSION_CODE); } return -1; } public void setVersionCode(int versionCode) { - try { - put(KEY_VERSION_CODE, versionCode); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_VERSION_CODE); - } + put(KEY_VERSION_CODE, versionCode); } public String getIdentifier() { - if (!isNull(KEY_IDENTIFIER)) { - return optString(KEY_IDENTIFIER, null); - } - return null; + return getString(KEY_IDENTIFIER); } public void setIdentifier(String identifier) { - try { - put(KEY_IDENTIFIER, identifier); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_IDENTIFIER); - } + put(KEY_IDENTIFIER, identifier); } public String getTargetSdkVersion() { - if (!isNull(KEY_TARGET_SDK_VERSION)) { - return optString(KEY_TARGET_SDK_VERSION); - } - return null; + return getString(KEY_TARGET_SDK_VERSION); } public void setTargetSdkVersion(String targetSdkVersion) { - try { - put(KEY_TARGET_SDK_VERSION, targetSdkVersion); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_TARGET_SDK_VERSION); - } + put(KEY_TARGET_SDK_VERSION, targetSdkVersion); } public String getAppStore() { - if (!isNull(KEY_APP_STORE)) { - return optString(KEY_APP_STORE, null); - } - return null; + return getString(KEY_APP_STORE); } public void setAppStore(String appStore) { - try { - put(KEY_APP_STORE, appStore); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_APP_STORE); - } + put(KEY_APP_STORE, appStore); } // Flag for whether the apptentive is inheriting styles from the host app public boolean getInheritStyle() { - return optBoolean(KEY_STYLE_INHERIT); + return getBoolean(KEY_STYLE_INHERIT); } public void setInheritStyle(boolean inheritStyle) { - try { - put(KEY_STYLE_INHERIT, inheritStyle); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_STYLE_INHERIT); - } + put(KEY_STYLE_INHERIT, inheritStyle); } // Flag for whether the app is overriding any Apptentive Styles public boolean getOverrideStyle() { - return optBoolean(KEY_STYLE_OVERRIDE); + return getBoolean(KEY_STYLE_OVERRIDE); } public void setOverrideStyle(boolean overrideStyle) { - try { - put(KEY_STYLE_OVERRIDE, overrideStyle); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_STYLE_OVERRIDE); - } + put(KEY_STYLE_OVERRIDE, overrideStyle); } public boolean getDebug() { - return optBoolean(KEY_DEBUG); + return getBoolean(KEY_DEBUG); } public void setDebug(boolean debug) { - try { - put(KEY_DEBUG, debug); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_DEBUG); - } - } - - public static AppReleasePayload generateCurrentAppRelease(Context context) { - - AppReleasePayload appRelease = new AppReleasePayload(); - - String appPackageName = context.getPackageName(); - PackageManager packageManager = context.getPackageManager(); - - int currentVersionCode = 0; - String currentVersionName = "0"; - int targetSdkVersion = 0; - boolean isAppDebuggable = false; - try { - PackageInfo packageInfo = packageManager.getPackageInfo(appPackageName, PackageManager.GET_META_DATA | PackageManager.GET_RECEIVERS); - ApplicationInfo ai = packageInfo.applicationInfo; - currentVersionCode = packageInfo.versionCode; - currentVersionName = packageInfo.versionName; - targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion; - Bundle metaData = ai.metaData; - if (metaData != null) { - isAppDebuggable = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; - } - } catch (PackageManager.NameNotFoundException e) { - ApptentiveLog.e("Failed to read app's PackageInfo."); - } - - int themeOverrideResId = context.getResources().getIdentifier("ApptentiveThemeOverride", "style", appPackageName); - - appRelease.setType("android"); - appRelease.setVersionName(currentVersionName); - appRelease.setIdentifier(appPackageName); - appRelease.setVersionCode(currentVersionCode); - appRelease.setTargetSdkVersion(String.valueOf(targetSdkVersion)); - appRelease.setAppStore(Util.getInstallerPackageName(context)); - appRelease.setInheritStyle(ApptentiveInternal.getInstance().isAppUsingAppCompatTheme()); - appRelease.setOverrideStyle(themeOverrideResId != 0); - appRelease.setDebug(isAppDebuggable); - - return appRelease; + put(KEY_DEBUG, debug); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java index 77b003f55..c6424b630 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java @@ -6,9 +6,7 @@ package com.apptentive.android.sdk.model; -import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.util.Util; -import org.json.JSONException; import java.util.UUID; @@ -33,55 +31,25 @@ protected ConversationItem() { } - protected ConversationItem(String json) throws JSONException { - super(json); - } - + @Override public void setNonce(String nonce) { - try { - put(KEY_NONCE, nonce); - } catch (JSONException e) { - ApptentiveLog.e("Exception setting ConversationItem's %s field.", e, KEY_NONCE); - } - } - - public String getNonce() { - try { - if (!isNull((KEY_NONCE))) { - return getString(KEY_NONCE); - } - } catch (JSONException e) { - // Ignore - } - return null; + super.setNonce(nonce); + put(KEY_NONCE, nonce); } public Double getClientCreatedAt() { - try { - return getDouble(KEY_CLIENT_CREATED_AT); - } catch (JSONException e) { - // Ignore - } - return null; + return getDouble(KEY_CLIENT_CREATED_AT); } - public void setClientCreatedAt(Double clientCreatedAt) { - try { - put(KEY_CLIENT_CREATED_AT, clientCreatedAt); - } catch (JSONException e) { - ApptentiveLog.e("Exception setting ConversationItem's %s field.", e, KEY_CLIENT_CREATED_AT); - } + public void setClientCreatedAt(double clientCreatedAt) { + put(KEY_CLIENT_CREATED_AT, clientCreatedAt); } /** * This is made public primarily so that unit tests can be made to run successfully no matter what the time zone. */ public void setClientCreatedAtUtcOffset(int clientCreatedAtUtcOffset) { - try { - put(KEY_CLIENT_CREATED_AT_UTC_OFFSET, clientCreatedAtUtcOffset); - } catch (JSONException e) { - ApptentiveLog.e("Exception setting ConversationItem's %s field.", e, KEY_CLIENT_CREATED_AT_UTC_OFFSET); - } + put(KEY_CLIENT_CREATED_AT_UTC_OFFSET, clientCreatedAtUtcOffset); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DeviceFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DeviceFactory.java deleted file mode 100644 index 3f7d8c966..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DeviceFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2013, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.model; - -import com.apptentive.android.sdk.ApptentiveLog; -import org.json.JSONException; - -/** - * @author Sky Kelsey - */ -public class DeviceFactory { - public static DevicePayload fromJson(String json) { - try { - return new DevicePayload(json); - } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as Device: %s", e, json); - } catch (IllegalArgumentException e) { - // Unknown unknown #rumsfeld - } - return null; - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index 9764c4ae5..7fccdf4c9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -6,15 +6,11 @@ package com.apptentive.android.sdk.model; -import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; -/** - * @author Sky Kelsey - */ public class DevicePayload extends JsonPayload { public static final String KEY = "device"; @@ -38,22 +34,13 @@ public class DevicePayload extends JsonPayload { private static final String KEY_BUILD_ID = "build_id"; private static final String KEY_BOOTLOADER_VERSION = "bootloader_version"; private static final String KEY_RADIO_VERSION = "radio_version"; - public static final String KEY_CUSTOM_DATA = "custom_data"; + public static final String KEY_CUSTOM_DATA = "custom_data"; private static final String KEY_LOCALE_COUNTRY_CODE = "locale_country_code"; private static final String KEY_LOCALE_LANGUAGE_CODE = "locale_language_code"; private static final String KEY_LOCALE_RAW = "locale_raw"; private static final String KEY_UTC_OFFSET = "utc_offset"; private static final String KEY_INTEGRATION_CONFIG = "integration_config"; - - public DevicePayload() { - super(); - } - - public DevicePayload(String json) throws JSONException { - super(json); - } - //region Http-request @Override @@ -77,370 +64,96 @@ public void initBaseType() { setBaseType(BaseType.device); } - public String getUuid() { - try { - if(!isNull(KEY_UUID)) { - return getString(KEY_UUID); - } - } catch (JSONException e) { - // Ignore - } - return null; - } - public void setUuid(String uuid) { - try { - put(KEY_UUID, uuid); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_UUID); - } - } - - public String getOsName() { - try { - if(!isNull(KEY_OS_NAME)) { - return getString(KEY_OS_NAME); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_UUID, uuid); } public void setOsName(String osName) { - try { - put(KEY_OS_NAME, osName); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_OS_NAME); - } - } - - public String getOsVersion() { - try { - if(!isNull(KEY_OS_VERSION)) { - return getString(KEY_OS_VERSION); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_OS_NAME, osName); } public void setOsVersion(String osVersion) { - try { - put(KEY_OS_VERSION, osVersion); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_OS_VERSION); - } - } - - public String getOsBuild() { - try { - if(!isNull(KEY_OS_BUILD)) { - return getString(KEY_OS_BUILD); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_OS_VERSION, osVersion); } public void setOsBuild(String osBuild) { - try { - put(KEY_OS_BUILD, osBuild); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_OS_BUILD); - } - } - - public String getOsApiLevel() { - try { - if(!isNull(KEY_OS_API_LEVEL)) { - return getString(KEY_OS_API_LEVEL); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_OS_BUILD, osBuild); } public void setOsApiLevel(String osApiLevel) { - try { - put(KEY_OS_API_LEVEL, osApiLevel); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_OS_API_LEVEL); - } - } - - public String getManufacturer() { - try { - if(!isNull(KEY_MANUFACTURER)) { - return getString(KEY_MANUFACTURER); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_OS_API_LEVEL, osApiLevel); } public void setManufacturer(String manufacturer) { - try { - put(KEY_MANUFACTURER, manufacturer); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_MANUFACTURER); - } + put(KEY_MANUFACTURER, manufacturer); } public String getModel() { - try { - if(!isNull(KEY_MODEL)) { - return getString(KEY_MODEL); - } - } catch (JSONException e) { - // Ignore + if (!isNull(KEY_MODEL)) { + return getString(KEY_MODEL); } return null; } public void setModel(String model) { - try { - put(KEY_MODEL, model); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_MODEL); - } - } - - public String getBoard() { - try { - if(!isNull(KEY_BOARD)) { - return getString(KEY_BOARD); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_MODEL, model); } public void setBoard(String board) { - try { - put(KEY_BOARD, board); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_BOARD); - } - } - - public String getProduct() { - try { - if(!isNull(KEY_PRODUCT)) { - return getString(KEY_PRODUCT); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_BOARD, board); } public void setProduct(String product) { - try { - put(KEY_PRODUCT, product); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_PRODUCT); - } - } - - public String getBrand() { - try { - if(!isNull(KEY_BRAND)) { - return getString(KEY_BRAND); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_PRODUCT, product); } public void setBrand(String brand) { - try { - put(KEY_BRAND, brand); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_BRAND); - } - } - - public String getCpu() { - try { - if(!isNull(KEY_CPU)) { - return getString(KEY_CPU); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_BRAND, brand); } public void setCpu(String cpu) { - try { - put(KEY_CPU, cpu); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_CPU); - } + put(KEY_CPU, cpu); } public String getDevice() { - try { - if(!isNull(KEY_DEVICE)) { - return getString(KEY_DEVICE); - } - } catch (JSONException e) { - // Ignore - } - return null; + return getString(KEY_DEVICE); } public void setDevice(String device) { - try { - put(KEY_DEVICE, device); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_DEVICE); - } - } - - public String getCarrier() { - try { - if(!isNull(KEY_CARRIER)) { - return getString(KEY_CARRIER); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_DEVICE, device); } public void setCarrier(String carrier) { - try { - put(KEY_CARRIER, carrier); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_CARRIER); - } - } - - public String getCurrentCarrier() { - try { - if(!isNull(KEY_CURRENT_CARRIER)) { - return getString(KEY_CURRENT_CARRIER); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_CARRIER, carrier); } public void setCurrentCarrier(String currentCarrier) { - try { - put(KEY_CURRENT_CARRIER, currentCarrier); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_CURRENT_CARRIER); - } - } - - public String getNetworkType() { - try { - if(!isNull(KEY_NETWORK_TYPE)) { - return getString(KEY_NETWORK_TYPE); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_CURRENT_CARRIER, currentCarrier); } public void setNetworkType(String networkType) { - try { - put(KEY_NETWORK_TYPE, networkType); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_NETWORK_TYPE); - } - } - - public String getBuildType() { - try { - if(!isNull(KEY_BUILD_TYPE)) { - return getString(KEY_BUILD_TYPE); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_NETWORK_TYPE, networkType); } public void setBuildType(String buildType) { - try { - put(KEY_BUILD_TYPE, buildType); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_BUILD_TYPE); - } - } - - public String getBuildId() { - try { - if(!isNull(KEY_BUILD_ID)) { - return getString(KEY_BUILD_ID); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_BUILD_TYPE, buildType); } public void setBuildId(String buildId) { - try { - put(KEY_BUILD_ID, buildId); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_BUILD_ID); - } - } - - public String getBootloaderVersion() { - try { - if(!isNull(KEY_BOOTLOADER_VERSION)) { - return getString(KEY_BOOTLOADER_VERSION); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_BUILD_ID, buildId); } public void setBootloaderVersion(String bootloaderVersion) { - try { - put(KEY_BOOTLOADER_VERSION, bootloaderVersion); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_BOOTLOADER_VERSION); - } - } - - public String getRadioVersion() { - try { - if(!isNull(KEY_RADIO_VERSION)) { - return getString(KEY_RADIO_VERSION); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_BOOTLOADER_VERSION, bootloaderVersion); } public void setRadioVersion(String radioVersion) { - try { - put(KEY_RADIO_VERSION, radioVersion); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_RADIO_VERSION); - } + put(KEY_RADIO_VERSION, radioVersion); } @SuppressWarnings("unchecked") // We check it coming in. public CustomData getCustomData() { - if(!isNull(KEY_CUSTOM_DATA)) { + if (!isNull(KEY_CUSTOM_DATA)) { try { return new CustomData(getJSONObject(KEY_CUSTOM_DATA)); } catch (JSONException e) { @@ -451,107 +164,27 @@ public CustomData getCustomData() { } public void setCustomData(CustomData customData) { - try { - put(KEY_CUSTOM_DATA, customData); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_CUSTOM_DATA); - } - } - - @SuppressWarnings("unchecked") // We check it coming in. - public CustomData getIntegrationConfig() { - if(!isNull(KEY_INTEGRATION_CONFIG)) { - try { - return new CustomData(getJSONObject(KEY_INTEGRATION_CONFIG)); - } catch (JSONException e) { - // Ignore - } - } - return null; + put(KEY_CUSTOM_DATA, customData); } public void setIntegrationConfig(CustomData integrationConfig) { - try { - put(KEY_INTEGRATION_CONFIG, integrationConfig); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_INTEGRATION_CONFIG); - } - } - - public String getLocaleCountryCode() { - try { - if(!isNull(KEY_LOCALE_COUNTRY_CODE)) { - return getString(KEY_LOCALE_COUNTRY_CODE); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_INTEGRATION_CONFIG, integrationConfig); } public void setLocaleCountryCode(String localeCountryCode) { - try { - put(KEY_LOCALE_COUNTRY_CODE, localeCountryCode); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_LOCALE_COUNTRY_CODE); - } - } - - public String getLocaleLanguageCode() { - try { - if(!isNull(KEY_LOCALE_LANGUAGE_CODE)) { - return getString(KEY_LOCALE_LANGUAGE_CODE); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_LOCALE_COUNTRY_CODE, localeCountryCode); } public void setLocaleLanguageCode(String localeLanguageCode) { - try { - put(KEY_LOCALE_LANGUAGE_CODE, localeLanguageCode); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_LOCALE_LANGUAGE_CODE); - } - } - - public String getLocaleRaw() { - try { - if(!isNull(KEY_LOCALE_RAW)) { - return getString(KEY_LOCALE_RAW); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_LOCALE_LANGUAGE_CODE, localeLanguageCode); } public void setLocaleRaw(String localeRaw) { - try { - put(KEY_LOCALE_RAW, localeRaw); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_LOCALE_RAW); - } - } - - public String getUtcOffset() { - try { - if(!isNull(KEY_UTC_OFFSET)) { - return getString(KEY_UTC_OFFSET); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_LOCALE_RAW, localeRaw); } public void setUtcOffset(String utcOffset) { - try { - put(KEY_UTC_OFFSET, utcOffset); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_UTC_OFFSET); - } + put(KEY_UTC_OFFSET, utcOffset); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventFactory.java deleted file mode 100644 index ec0a83c7b..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventFactory.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2013, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.model; - -import com.apptentive.android.sdk.ApptentiveLog; - -import org.json.JSONException; - -/** - * @author Sky Kelsey - */ -public class EventFactory { - public static EventPayload fromJson(String json) { - try { - return new EventPayload(json); - } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as Event: %s", e, json); - } catch (IllegalArgumentException e) { - // Unknown unknown #rumsfeld - } - return null; - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index df80dcedd..36911b92a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -27,19 +27,11 @@ public class EventPayload extends ConversationItem { private static final String KEY_TRIGGER = "trigger"; private static final String KEY_CUSTOM_DATA = "custom_data"; - public EventPayload(String json) throws JSONException { - super(json); - } - public EventPayload(String label, JSONObject data) { super(); - try { - put(KEY_LABEL, label); - if (data != null) { - put(KEY_DATA, data); - } - } catch (JSONException e) { - ApptentiveLog.e("Unable to construct Event.", e); + put(KEY_LABEL, label); + if (data != null) { + put(KEY_DATA, data); } } @@ -88,7 +80,7 @@ public EventPayload(String label, String interactionId, String data, Map customData) { @@ -108,7 +100,7 @@ private JSONObject generateCustomDataJson(Map customData) { public EventPayload(String label, String trigger) { this(label, (Map) null); - Map data = new HashMap(); + Map data = new HashMap<>(); data.put(KEY_TRIGGER, trigger); putData(data); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index 59f2c9dd3..794d41d0d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -9,53 +9,29 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.network.HttpRequestMethod; -import org.json.JSONException; import org.json.JSONObject; +import java.io.UnsupportedEncodingException; + public abstract class JsonPayload extends Payload { + private final JSONObject jsonObject; + // These three are not stored in the JSON, only the DB. - private Long databaseId; // FIXME: use 'long' instead + private long databaseId; private BaseType baseType; private String conversationId; - private String token; public JsonPayload() { + jsonObject = new JSONObject(); initBaseType(); } - public JsonPayload(String json) throws JSONException { - super(json); - initBaseType(); - } - - public JsonPayload(String json, String conversationId, String token) throws JSONException { - this(json); - this.conversationId = conversationId; - this.token = token; - } - /** * Each subclass must set its type in this method. */ protected abstract void initBaseType(); - /** - * Subclasses should override this method if there is any peculiarity in how they present or wrap json before sending. - * - * @return A wrapper object containing the name of the object type, the value of which is the contents of this Object. - */ - public String marshallForSending() { - JSONObject wrapper = new JSONObject(); - try { - wrapper.put(getBaseType().name(), this); - } catch (JSONException e) { - ApptentiveLog.w("Error wrapping Payload in JSONObject.", e); - return null; - } - return wrapper.toString(); - } - public long getDatabaseId() { return databaseId; } @@ -80,14 +56,6 @@ public void setConversationId(String conversationId) { this.conversationId = conversationId; } - public void setToken(String token) { - this.token = token; - } - - public String getToken() { - return token; - } - public enum BaseType { message, event, @@ -112,15 +80,111 @@ public static BaseType parse(String type) { } - /** - * @deprecated Do not use this method to check for key existence. Instead us !isNull(KEY_NAME), as this works better - * with keys with null values. - */ + //region Data + @Override - public boolean has(String key) { - return super.has(key); + public byte[] getData() { + try { + return jsonObject.toString().getBytes("utf-8"); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + } + + //endregion + + //region Json + + protected void put(String key, String value) { + try { + jsonObject.put(key, value); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while putting json pair '%s'='%s'", key, value); + } } + protected void put(String key, boolean value) { + try { + jsonObject.put(key, value); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while putting json pair '%s'='%s'", key, value); + } + } + + protected void put(String key, int value) { + try { + jsonObject.put(key, value); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while putting json pair '%s'='%s'", key, value); + } + } + + protected void put(String key, double value) { + try { + jsonObject.put(key, value); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while putting json pair '%s'='%s'", key, value); + } + } + + protected void put(String key, JSONObject object) { + try { + jsonObject.put(key, object); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while putting json pair '%s'='%s'", key, object); + } + } + + protected void remove(String key) { // TODO: rename to removeKey + jsonObject.remove(key); + } + + protected String getString(String key) { + return jsonObject.optString(key); + } + + public int getInt(String key) { + return getInt(key, 0); + } + + public int getInt(String key, int defaultValue) { + return jsonObject.optInt(key, defaultValue); + } + + public boolean getBoolean(String key) { + return getBoolean(key, false); + } + + public boolean getBoolean(String key, boolean defaultValue) { + return jsonObject.optBoolean(key, defaultValue); + } + + protected double getDouble(String key) { + return getDouble(key, 0.0); + } + + protected double getDouble(String key, double defaultValue) { + return jsonObject.optDouble(key, defaultValue); + } + + protected JSONObject getJSONObject(String key) { + return jsonObject.optJSONObject(key); + } + + protected boolean isNull(String key) { // TODO: rename to containsKey + return jsonObject.isNull(key); + } + + //endregion + + //region Getters/Setters + + public JSONObject getJsonObject() { + return jsonObject; + } + + //endregion + //region Http-request /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java index ea65ff8f8..d39f69d7c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java @@ -12,14 +12,6 @@ import org.json.JSONException; public class LogoutPayload extends JsonPayload { - public LogoutPayload() { - super(); - } - - public LogoutPayload(String json) throws JSONException { - super(json); - } - @Override protected void initBaseType() { setBaseType(BaseType.logout); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayloadFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayloadFactory.java deleted file mode 100644 index 5f27de624..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayloadFactory.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2013, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.model; - -import com.apptentive.android.sdk.ApptentiveLog; - -import org.json.JSONException; - -/** - * @author Sky Kelsey - */ -public class LogoutPayloadFactory { - public static LogoutPayload fromJson(String json) { - try { - return new LogoutPayload(json); - } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as LogoutPayload: %s", e, json); - } catch (IllegalArgumentException e) { - // Unknown unknown #rumsfeld - } - return null; - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index 8c40caec4..cd02b170a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -50,11 +50,11 @@ public void setContentType(String contentType) { this.contentType = contentType; } - public String getAuthToken() { + public String getToken() { return authToken; - } + } // TODO: rename to getAuthToken - public void setAuthToken(String authToken) { + public void setToken(String authToken) { // TODO: rename to setAuthToken this.authToken = authToken; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java deleted file mode 100644 index 5f253a8ab..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadFactory.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.model; - -import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; - -import org.json.JSONException; - -/** - * @author Sky Kelsey - */ -public class PayloadFactory { - - public static JsonPayload fromJson(String json, JsonPayload.BaseType baseType) { - switch (baseType) { - case message: - return MessageFactory.fromJson(json); - case event: - return EventFactory.fromJson(json); - case device: - return DeviceFactory.fromJson(json); - case sdk: - return SdkFactory.fromJson(json); - case app_release: - return AppReleaseFactory.fromJson(json); - case sdk_and_app_release: - return SdkAndAppReleasePayload.fromJson(json); - case person: - return PersonFactory.fromJson(json); - case logout: - return LogoutPayloadFactory.fromJson(json); - case survey: - try { - return new SurveyResponsePayload(json); - } catch (JSONException e) { - // Ignore - } - case unknown: - ApptentiveLog.v("Ignoring unknown RecordType."); - break; - default: - break; - } - return null; - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonFactory.java deleted file mode 100644 index b68f221bb..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonFactory.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2013, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.model; - -import com.apptentive.android.sdk.ApptentiveLog; - -import org.json.JSONException; - -/** - * @author Sky Kelsey - */ -public class PersonFactory { - public static PersonPayload fromJson(String json) { - try { - return new PersonPayload(json); - } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as Person: %s", e, json); - } catch (IllegalArgumentException e) { - // Unknown unknown #rumsfeld - } - return null; - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index 9e707fa25..524a6d4e2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -6,15 +6,11 @@ package com.apptentive.android.sdk.model; -import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; -/** - * @author Sky Kelsey - */ public class PersonPayload extends JsonPayload { public static final String KEY = "person"; @@ -31,14 +27,6 @@ public class PersonPayload extends JsonPayload { private static final String KEY_BIRTHDAY = "birthday"; public static final String KEY_CUSTOM_DATA = "custom_data"; - public PersonPayload() { - super(); - } - - public PersonPayload(String json) throws JSONException { - super(json); - } - //region Http-request @Override @@ -63,198 +51,63 @@ public void initBaseType() { } public String getId() { - try { - if (!isNull(KEY_ID)) { - return getString(KEY_ID); - } - } catch (JSONException e) { - // Ignore + if (!isNull(KEY_ID)) { + return getString(KEY_ID); } return null; } public void setId(String id) { - try { - put(KEY_ID, id); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set field " + KEY_ID + " of " + KEY); - } + put(KEY_ID, id); } public String getEmail() { - try { - if (!isNull(KEY_EMAIL)) { - return getString(KEY_EMAIL); - } - } catch (JSONException e) { - // Ignore - } - return null; + return getString(KEY_EMAIL); } public void setEmail(String email) { - try { - put(KEY_EMAIL, email); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set field " + KEY_EMAIL + " of " + KEY); - } + put(KEY_EMAIL, email); } public String getName() { - try { - if (!isNull(KEY_NAME)) { - return getString(KEY_NAME); - } - } catch (JSONException e) { - // Ignore - } - return null; + return getString(KEY_NAME); } public void setName(String name) { - try { - put(KEY_NAME, name); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set field " + KEY_NAME + " of " + KEY); - } - } - - public String getFacebookId() { - try { - if (!isNull(KEY_FACEBOOK_ID)) { - return getString(KEY_FACEBOOK_ID); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_NAME, name); } public void setFacebookId(String facebookId) { - try { - put(KEY_FACEBOOK_ID, facebookId); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set field " + KEY_FACEBOOK_ID + " of " + KEY); - } - } - - public String getPhoneNumber() { - try { - if (!isNull(KEY_PHONE_NUMBER)) { - return getString(KEY_PHONE_NUMBER); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_FACEBOOK_ID, facebookId); } public void setPhoneNumber(String phoneNumber) { - try { - put(KEY_PHONE_NUMBER, phoneNumber); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set field " + KEY_PHONE_NUMBER + " of " + KEY); - } - } - - public String getStreet() { - try { - if (!isNull(KEY_STREET)) { - return getString(KEY_STREET); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_PHONE_NUMBER, phoneNumber); } public void setStreet(String street) { - try { - put(KEY_STREET, street); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set field " + KEY_STREET + " of " + KEY); - } - } - - public String getCity() { - try { - if (!isNull(KEY_CITY)) { - return getString(KEY_CITY); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_STREET, street); } public void setCity(String city) { - try { - put(KEY_CITY, city); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set field " + KEY_CITY + " of " + KEY); - } - } - - public String getZip() { - try { - if (!isNull(KEY_ZIP)) { - return getString(KEY_ZIP); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_CITY, city); } public void setZip(String zip) { - try { - put(KEY_ZIP, zip); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set field " + KEY_ZIP + " of " + KEY); - } - } - - public String getCountry() { - try { - if (!isNull(KEY_COUNTRY)) { - return getString(KEY_COUNTRY); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_ZIP, zip); } public void setCountry(String country) { - try { - put(KEY_COUNTRY, country); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set field " + KEY_COUNTRY + " of " + KEY); - } - } - - public String getBirthday() { - try { - if (!isNull(KEY_BIRTHDAY)) { - return getString(KEY_BIRTHDAY); - } - } catch (JSONException e) { - // Ignore - } - return null; + put(KEY_COUNTRY, country); } public void setBirthday(String birthday) { - try { - put(KEY_BIRTHDAY, birthday); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set field " + KEY_BIRTHDAY + " of " + KEY); - } + put(KEY_BIRTHDAY, birthday); } @SuppressWarnings("unchecked") // We check it coming in. public CustomData getCustomData() { - if(!isNull(KEY_CUSTOM_DATA)) { + if (!isNull(KEY_CUSTOM_DATA)) { try { return new CustomData(getJSONObject(KEY_CUSTOM_DATA)); } catch (JSONException e) { @@ -265,10 +118,6 @@ public CustomData getCustomData() { } public void setCustomData(CustomData customData) { - try { - put(KEY_CUSTOM_DATA, customData); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Device.", KEY_CUSTOM_DATA); - } + put(KEY_CUSTOM_DATA, customData); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index 1c60d53d7..9c83b173b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -32,35 +32,14 @@ public class SdkAndAppReleasePayload extends JsonPayload { private final SdkPayload sdk; private final AppReleasePayload appRelease; - public static SdkAndAppReleasePayload fromJson(String json) { - try { - return new SdkAndAppReleasePayload(json); - } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as SdkAndAppReleasePayload: %s", e, json); - } catch (IllegalArgumentException e) { - // Unknown unknown #rumsfeld - } - return null; - } - - private SdkAndAppReleasePayload(String json) throws JSONException { - super(json); - - sdk = new SdkPayload(getJSONObject("sdk").toString()); - appRelease = new AppReleasePayload(getJSONObject("app_release").toString()); - } - public SdkAndAppReleasePayload() { super(); sdk = new SdkPayload(); appRelease = new AppReleasePayload(); - try { - put("sdk", sdk); - put("app_release", appRelease); - } catch (JSONException e) { - throw new IllegalStateException(e); // that should not happen but we can't ignore that - } + // TODO: a better solution + put("sdk", sdk.getJsonObject()); + put("app_release", appRelease.getJsonObject()); } //region Http-request diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkFactory.java deleted file mode 100644 index 7058635e5..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2013, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.model; - -import com.apptentive.android.sdk.ApptentiveLog; -import org.json.JSONException; - -/** - * @author Sky Kelsey - */ -public class SdkFactory { - public static SdkPayload fromJson(String json) { - try { - return new SdkPayload(json); - } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as Sdk: %s", e, json); - } catch (IllegalArgumentException e) { - // Unknown unknown #rumsfeld - } - return null; - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java index f46dc4b5d..b74b854e4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java @@ -6,14 +6,8 @@ package com.apptentive.android.sdk.model; -import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.network.HttpRequestMethod; -import org.json.JSONException; - -/** - * @author Sky Kelsey - */ public class SdkPayload extends JsonPayload { public static final String KEY = "sdk"; @@ -26,29 +20,21 @@ public class SdkPayload extends JsonPayload { private static final String KEY_DISTRIBUTION = "distribution"; private static final String KEY_DISTRIBUTION_VERSION = "distribution_version"; - public SdkPayload(String json) throws JSONException { - super(json); - } - - public SdkPayload() { - super(); - } - //region Http-request @Override public String getHttpEndPoint() { - throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach + throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach } @Override public HttpRequestMethod getHttpRequestMethod() { - throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach + throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach } @Override public String getHttpRequestContentType() { - throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach + throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach } //endregion @@ -58,135 +44,76 @@ public void initBaseType() { } public String getVersion() { - try { - if(!isNull(KEY_VERSION)) { - return getString(KEY_VERSION); - } - } catch (JSONException e) { - // Ignore + if (!isNull(KEY_VERSION)) { + return getString(KEY_VERSION); } return null; } public void setVersion(String version) { - try { - put(KEY_VERSION, version); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_VERSION); - } + put(KEY_VERSION, version); } public String getProgrammingLanguage() { - try { - if(!isNull(KEY_PROGRAMMING_LANGUAGE)) { - return getString(KEY_PROGRAMMING_LANGUAGE); - } - } catch (JSONException e) { - // Ignore - } - return null; + return getString(KEY_PROGRAMMING_LANGUAGE); } public void setProgrammingLanguage(String programmingLanguage) { - try { - put(KEY_PROGRAMMING_LANGUAGE, programmingLanguage); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_PROGRAMMING_LANGUAGE); - } + put(KEY_PROGRAMMING_LANGUAGE, programmingLanguage); } public String getAuthorName() { - try { - if(!isNull(KEY_AUTHOR_NAME)) { - return getString(KEY_AUTHOR_NAME); - } - } catch (JSONException e) { - // Ignore + if (!isNull(KEY_AUTHOR_NAME)) { + return getString(KEY_AUTHOR_NAME); } return null; } public void setAuthorName(String authorName) { - try { - put(KEY_AUTHOR_NAME, authorName); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_AUTHOR_NAME); - } + put(KEY_AUTHOR_NAME, authorName); } public String getAuthorEmail() { - try { - if(!isNull(KEY_AUTHOR_EMAIL)) { - return getString(KEY_AUTHOR_EMAIL); - } - } catch (JSONException e) { - // Ignore + if (!isNull(KEY_AUTHOR_EMAIL)) { + return getString(KEY_AUTHOR_EMAIL); } return null; } public void setAuthorEmail(String authorEmail) { - try { - put(KEY_AUTHOR_EMAIL, authorEmail); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_AUTHOR_EMAIL); - } + put(KEY_AUTHOR_EMAIL, authorEmail); } public String getPlatform() { - try { - if(!isNull(KEY_PLATFORM)) { - return getString(KEY_PLATFORM); - } - } catch (JSONException e) { - // Ignore + if (!isNull(KEY_PLATFORM)) { + return getString(KEY_PLATFORM); } return null; } public void setPlatform(String platform) { - try { - put(KEY_PLATFORM, platform); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_PLATFORM); - } + put(KEY_PLATFORM, platform); } public String getDistribution() { - try { - if(!isNull(KEY_DISTRIBUTION)) { - return getString(KEY_DISTRIBUTION); - } - } catch (JSONException e) { - // Ignore + if (!isNull(KEY_DISTRIBUTION)) { + return getString(KEY_DISTRIBUTION); } return null; } public void setDistribution(String distribution) { - try { - put(KEY_DISTRIBUTION, distribution); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_DISTRIBUTION); - } + put(KEY_DISTRIBUTION, distribution); } public String getDistributionVersion() { - try { - if(!isNull(KEY_DISTRIBUTION_VERSION)) { - return getString(KEY_DISTRIBUTION_VERSION); - } - } catch (JSONException e) { - // Ignore + if (!isNull(KEY_DISTRIBUTION_VERSION)) { + return getString(KEY_DISTRIBUTION_VERSION); } return null; } public void setDistributionVersion(String distributionVersion) { - try { - put(KEY_DISTRIBUTION_VERSION, distributionVersion); - } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to Sdk.", KEY_DISTRIBUTION_VERSION); - } + put(KEY_DISTRIBUTION_VERSION, distributionVersion); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index a5f252dd4..890c58b42 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -22,10 +22,6 @@ public class SurveyResponsePayload extends ConversationItem { private static final String KEY_SURVEY_ANSWERS = "answers"; - public SurveyResponsePayload(String json) throws JSONException { - super(json); - } - public SurveyResponsePayload(SurveyInteraction definition, Map answers) { super(); @@ -62,7 +58,7 @@ public String getHttpRequestContentType() { //endregion public String getId() { - return optString(KEY_SURVEY_ID, ""); + return getString(KEY_SURVEY_ID); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 73cd9793a..26fdf1644 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -158,7 +158,7 @@ synchronized boolean fetchAndStoreMessages(boolean isMessageCenterForeground, bo apptentiveMessage.setRead(true); } else { if (messageOnToast == null) { - if (apptentiveMessage.getType() == ApptentiveMessage.Type.CompoundMessage) { + if (apptentiveMessage.getMessageType() == ApptentiveMessage.Type.CompoundMessage) { messageOnToast = (CompoundMessage) apptentiveMessage; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java index 4c8037694..837e49254 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java @@ -44,10 +44,6 @@ protected ApptentiveMessage() { initType(); } - protected ApptentiveMessage(String json) throws JSONException { - super(json); - } - protected void initBaseType() { setBaseType(BaseType.message); } @@ -55,76 +51,38 @@ protected void initBaseType() { protected abstract void initType(); public void setId(String id) { - try { - put(KEY_ID, id); - } catch (JSONException e) { - ApptentiveLog.e("Exception setting ApptentiveMessage's %s field.", e, KEY_ID); - } + put(KEY_ID, id); } public String getId() { - try { - if (!isNull((KEY_ID))) { - return getString(KEY_ID); - } - } catch (JSONException e) { - // Ignore - } - return null; + return getString(KEY_ID); } - public Double getCreatedAt() { - try { - return getDouble(KEY_CREATED_AT); - } catch (JSONException e) { - // Ignore - } - return null; + public Double getCreatedAt() { // // FIXME: figure out primitive vs Double + return getDouble(KEY_CREATED_AT); } public void setCreatedAt(Double createdAt) { - try { - put(KEY_CREATED_AT, createdAt); - } catch (JSONException e) { - ApptentiveLog.e("Exception setting ApptentiveMessage's %s field.", e, KEY_CREATED_AT); - } + put(KEY_CREATED_AT, createdAt); } - public Type getType() { - try { - if (isNull((KEY_TYPE))) { - return Type.CompoundMessage; - } - return Type.parse(getString(KEY_TYPE)); - } catch (JSONException e) { - // Ignore + public Type getMessageType() { + if (isNull(KEY_TYPE)) { + return Type.CompoundMessage; } - return Type.unknown; + return Type.parse(getString(KEY_TYPE)); } protected void setType(Type type) { - try { - put(KEY_TYPE, type.name()); - } catch (JSONException e) { - ApptentiveLog.e("Exception setting ApptentiveMessage's %s field.", e, KEY_TYPE); - } + put(KEY_TYPE, type.name()); } public boolean isHidden() { - try { - return getBoolean(KEY_HIDDEN); - } catch (JSONException e) { - // Ignore - } - return false; + return getBoolean(KEY_HIDDEN); } public void setHidden(boolean hidden) { - try { - put(KEY_HIDDEN, hidden); - } catch (JSONException e) { - ApptentiveLog.e("Exception setting ApptentiveMessage's %s field.", e, KEY_HIDDEN); - } + put(KEY_HIDDEN, hidden); } public void setCustomData(Map customData) { @@ -223,22 +181,11 @@ public String getSenderProfilePhoto() { } public boolean getAutomated() { - try { - if (!isNull((KEY_AUTOMATED))) { - return getBoolean(KEY_AUTOMATED); - } - } catch (JSONException e) { - // Ignore - } - return false; + return getBoolean(KEY_AUTOMATED); } public void setAutomated(boolean isAutomated) { - try { - put(KEY_AUTOMATED, isAutomated); - } catch (JSONException e) { - ApptentiveLog.e("Exception setting ApptentiveMessage's %s field.", e, KEY_AUTOMATED); - } + put(KEY_AUTOMATED, isAutomated); } public String getDatestamp() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java index cb9067282..125d6a17e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java @@ -88,11 +88,6 @@ protected void initType() { setType(Type.CompoundMessage); } - @Override - public String marshallForSending() { - return toString(); - } - // Get text message body, maybe empty @Override public String getBody() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java index e16cc4702..156a47c6c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java @@ -248,7 +248,7 @@ protected Void doInBackground(ApptentiveMessage... messages) { JSONObject data = new JSONObject(); try { data.put("message_id", messages[0].getId()); - data.put("message_type", messages[0].getType().name()); + data.put("message_type", messages[0].getMessageType().name()); } catch (JSONException e) { // } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index c1b01be3e..0439d2d29 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -16,7 +16,6 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.model.JsonPayload; -import com.apptentive.android.sdk.model.PayloadFactory; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; @@ -392,12 +391,13 @@ public JsonPayload getOldestUnsentPayload() { String json = cursor.getString(2); String conversationId = cursor.getString(3); String token = cursor.getString(4); - payload = PayloadFactory.fromJson(json, baseType); - if (payload != null) { - payload.setDatabaseId(databaseId); - payload.setConversationId(conversationId); - payload.setToken(token); - } +// payload = PayloadFactory.fromJson(json, baseType); +// if (payload != null) { +// payload.setDatabaseId(databaseId); +// payload.setConversationId(conversationId); +// payload.setToken(token); +// } + throw new RuntimeException("Implement me"); // FIXME: implement me } return payload; } catch (SQLException sqe) { From 48ef2076cfb187c1ceea1c30c7e8e1ff9ab28f5b Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 17 Apr 2017 15:07:26 -0700 Subject: [PATCH 240/465] Refactoring Removed dead code --- .../messagecenter/model/ApptentiveMessage.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java index 837e49254..b7c57ce6d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java @@ -122,20 +122,6 @@ public void setRead(boolean read) { this.read = read; } - public String getSenderId() { - try { - if (!isNull((KEY_SENDER))) { - JSONObject sender = getJSONObject(KEY_SENDER); - if (!sender.isNull((KEY_SENDER_ID))) { - return sender.getString(KEY_SENDER_ID); - } - } - } catch (JSONException e) { - // Ignore - } - return null; - } - // For debugging only. public void setSenderId(String senderId) { try { @@ -227,7 +213,6 @@ public boolean isAutomatedMessage() { return getAutomated(); } - public enum Type { TextMessage, FileMessage, From 84087ca40a3b27b4c4247815a68a08ea4a73c717 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 19 Apr 2017 09:18:06 -0700 Subject: [PATCH 241/465] Fix getters and setters on CompoundMessage to fit the new JSON backing object. --- .../conversation/FileMessageStoreTest.java | 4 +- .../android/sdk/model/ConversationItem.java | 4 ++ .../android/sdk/model/JsonPayload.java | 15 +++++++ .../model/ApptentiveMessage.java | 6 +++ .../messagecenter/model/CompoundMessage.java | 41 +++---------------- 5 files changed, 34 insertions(+), 36 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java index 4ef9b50f7..ca6a2ea8f 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java @@ -275,6 +275,8 @@ private void addResult(List messages) throws JSONException { private String toString(ApptentiveMessage message) throws JSONException { String result = "{"; +/* + // FIXME final Iterator keys = message.keys(); while (keys.hasNext()) { String key = keys.next(); @@ -287,7 +289,7 @@ private String toString(ApptentiveMessage message) throws JSONException { result += StringUtils.format("'state':'%s',", message.getState().name()); result += StringUtils.format("'read':'%s'", message.isRead()); result += "}"; - +*/ return result; } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java index c6424b630..f70337b92 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java @@ -31,6 +31,10 @@ protected ConversationItem() { } + protected ConversationItem(String json) { + super(json); + } + @Override public void setNonce(String nonce) { super.setNonce(nonce); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index 794d41d0d..8842c9eef 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -7,8 +7,10 @@ package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.ApptentiveLogTag; import com.apptentive.android.sdk.network.HttpRequestMethod; +import org.json.JSONException; import org.json.JSONObject; import java.io.UnsupportedEncodingException; @@ -23,10 +25,23 @@ public abstract class JsonPayload extends Payload { private String conversationId; public JsonPayload() { + super(); jsonObject = new JSONObject(); initBaseType(); } + public JsonPayload(String json) { + super(); + JSONObject temp = null; + try { + temp = new JSONObject(json); + } catch (JSONException e) { + ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error creating JsonPayload from json string.", e); + } + jsonObject = (temp == null ? null : temp); + initBaseType(); + } + /** * Each subclass must set its type in this method. */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java index b7c57ce6d..793d1f974 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java @@ -44,6 +44,12 @@ protected ApptentiveMessage() { initType(); } + protected ApptentiveMessage(String json) { + super(json); + state = State.unknown; + initType(); + } + protected void initBaseType() { setBaseType(BaseType.message); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java index 125d6a17e..74c91f71f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java @@ -91,58 +91,29 @@ protected void initType() { // Get text message body, maybe empty @Override public String getBody() { - try { - if (!isNull(KEY_BODY)) { - return getString(KEY_BODY); - } - } catch (JSONException e) { - // Ignore - } - return null; + return getString(KEY_BODY); } // Set text message body, maybe empty @Override public void setBody(String body) { - try { - put(KEY_BODY, body); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set message body."); - } + put(KEY_BODY, body); } public String getTitle() { - try { - return getString(KEY_TITLE); - } catch (JSONException e) { - // Ignore - } - return null; + return getString(KEY_TITLE); } public void setTitle(String title) { - try { - put(KEY_TITLE, title); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set title."); - } + put(KEY_TITLE, title); } public boolean getTextOnly() { - try { - return getBoolean(KEY_TEXT_ONLY); - } catch (JSONException e) { - // Ignore - } - return true; + return getBoolean(KEY_TEXT_ONLY); } public void setTextOnly(boolean bVal) { - try { - put(KEY_TEXT_ONLY, bVal); - } catch (JSONException e) { - ApptentiveLog.e("Unable to set file filePath."); - } + put(KEY_TEXT_ONLY, bVal); } From 38fc6a57842284b6a48f0faf1f52e6a562c42b6e Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 19 Apr 2017 09:23:28 -0700 Subject: [PATCH 242/465] Move `ApptentiveMessage` and `CompoundMessage` into `sdk.model` --- .../android/sdk/conversation/FileMessageStoreTest.java | 8 +++----- .../main/java/com/apptentive/android/sdk/Apptentive.java | 2 +- .../android/sdk/conversation/FileMessageStore.java | 2 +- .../messagecenter => }/model/ApptentiveMessage.java | 4 ++-- .../{module/messagecenter => }/model/CompoundMessage.java | 5 ++--- .../interaction/fragment/MessageCenterFragment.java | 4 ++-- .../android/sdk/module/messagecenter/MessageManager.java | 4 ++-- .../sdk/module/messagecenter/model/MessageFactory.java | 2 ++ .../view/MessageCenterRecyclerViewAdapter.java | 4 ++-- .../sdk/module/messagecenter/view/MessageView.java | 2 +- .../messagecenter/view/holder/AutomatedMessageHolder.java | 2 +- .../view/holder/IncomingCompoundMessageHolder.java | 2 +- .../module/messagecenter/view/holder/MessageHolder.java | 2 +- .../view/holder/OutgoingCompoundMessageHolder.java | 2 +- .../android/sdk/storage/ApptentiveDatabaseHelper.java | 4 ++-- .../com/apptentive/android/sdk/storage/MessageStore.java | 3 +-- 16 files changed, 25 insertions(+), 27 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/{module/messagecenter => }/model/ApptentiveMessage.java (97%) rename apptentive/src/main/java/com/apptentive/android/sdk/{module/messagecenter => }/model/CompoundMessage.java (97%) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java index ca6a2ea8f..c4136d8df 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java @@ -9,9 +9,8 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveInternalMock; import com.apptentive.android.sdk.TestCaseBase; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; -import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.CompoundMessage; import org.json.JSONException; import org.json.JSONObject; @@ -23,11 +22,10 @@ import java.io.File; import java.io.IOException; -import java.util.Iterator; import java.util.List; import java.util.UUID; -import static com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage.State; +import static com.apptentive.android.sdk.model.ApptentiveMessage.State; import static junit.framework.Assert.assertEquals; public class FileMessageStoreTest extends TestCaseBase { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 99888aa78..1502396b0 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -25,7 +25,7 @@ import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.messagecenter.MessageManager; import com.apptentive.android.sdk.module.messagecenter.UnreadMessagesListener; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; +import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.module.metric.MetricModule; import com.apptentive.android.sdk.module.rating.IRatingProvider; import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index 8cfac58ad..87f405768 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -8,7 +8,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.debug.Assert; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; import com.apptentive.android.sdk.serialization.SerializableObject; import com.apptentive.android.sdk.storage.MessageStore; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java similarity index 97% rename from apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java rename to apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java index 793d1f974..bb21f39d3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java @@ -4,10 +4,10 @@ * under which redistribution and use of this file is permitted. */ -package com.apptentive.android.sdk.module.messagecenter.model; +package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.model.ConversationItem; +import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem; import org.json.JSONException; import org.json.JSONObject; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java similarity index 97% rename from apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java rename to apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java index 74c91f71f..1591553f5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java @@ -4,12 +4,11 @@ * under which redistribution and use of this file is permitted. */ -package com.apptentive.android.sdk.module.messagecenter.model; +package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.model.MultipartPayload; -import com.apptentive.android.sdk.model.StoredFile; +import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterUtil; import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.image.ImageItem; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index 96a722cfb..c60795a00 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -48,8 +48,8 @@ import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; import com.apptentive.android.sdk.module.messagecenter.MessageManager; import com.apptentive.android.sdk.module.messagecenter.OnListviewItemActionListener; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; +import com.apptentive.android.sdk.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.module.messagecenter.model.ContextMessage; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterStatus; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 26fdf1644..2474d2e34 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -17,9 +17,9 @@ import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.model.JsonPayload; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveToastNotification; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; +import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem; import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; import com.apptentive.android.sdk.module.metric.MetricModule; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java index 0e160d268..1ee97066c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java @@ -10,6 +10,8 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.CompoundMessage; import org.json.JSONException; import org.json.JSONObject; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java index 156a47c6c..4a87fc888 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java @@ -21,9 +21,9 @@ import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; import com.apptentive.android.sdk.module.messagecenter.MessageManager; import com.apptentive.android.sdk.module.messagecenter.OnListviewItemActionListener; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.Composer; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; +import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.module.messagecenter.model.ContextMessage; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterGreeting; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageView.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageView.java index 764df2e68..84114ed14 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageView.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageView.java @@ -9,7 +9,7 @@ import android.content.Context; import android.widget.LinearLayout; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.ApptentiveMessage; /** * @author Sky Kelsey diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/AutomatedMessageHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/AutomatedMessageHolder.java index df6bdc28b..0bc999954 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/AutomatedMessageHolder.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/AutomatedMessageHolder.java @@ -11,7 +11,7 @@ import android.widget.TextView; import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; +import com.apptentive.android.sdk.model.CompoundMessage; public class AutomatedMessageHolder extends RecyclerView.ViewHolder { public TextView body; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/IncomingCompoundMessageHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/IncomingCompoundMessageHolder.java index 3302f5b1a..d22e1d365 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/IncomingCompoundMessageHolder.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/IncomingCompoundMessageHolder.java @@ -16,7 +16,7 @@ import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.module.engagement.interaction.fragment.MessageCenterFragment; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; +import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.module.messagecenter.view.ApptentiveAvatarView; import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerViewAdapter; import com.apptentive.android.sdk.util.Util; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageHolder.java index fef57d5a4..d912a8ac1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageHolder.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageHolder.java @@ -13,7 +13,7 @@ import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.module.engagement.interaction.fragment.MessageCenterFragment; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; +import com.apptentive.android.sdk.model.CompoundMessage; public abstract class MessageHolder extends RecyclerView.ViewHolder { public TextView datestamp; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/OutgoingCompoundMessageHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/OutgoingCompoundMessageHolder.java index fa8583d7f..10bfa23b8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/OutgoingCompoundMessageHolder.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/OutgoingCompoundMessageHolder.java @@ -16,7 +16,7 @@ import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.module.engagement.interaction.fragment.MessageCenterFragment; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; +import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerViewAdapter; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.image.ApptentiveImageGridView; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 0439d2d29..7171784a0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -17,8 +17,8 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.model.StoredFile; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; +import com.apptentive.android.sdk.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/MessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/MessageStore.java index 820dc9890..a6837b15d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/MessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/MessageStore.java @@ -7,10 +7,9 @@ package com.apptentive.android.sdk.storage; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.ApptentiveMessage; import java.util.List; -import java.util.concurrent.Future; /** * @author Sky Kelsey From a4ba6012cf4ed0d1676283f2ab4f4505b37a81dd Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 19 Apr 2017 22:51:33 -0700 Subject: [PATCH 243/465] Introduce `OutgoingPayload` for holding payloads pulled out of the databasein order to send them. Refactor Http code to take a `Payload` instead of a `JsonPayload`. Introduce `RawHttpRequest`, since we are abstracting the contents of the request away when we enqueue payloads. --- .../sdk/comm/ApptentiveHttpClient.java | 37 ++++---- .../android/sdk/model/AppReleasePayload.java | 13 +-- .../android/sdk/model/ApptentiveMessage.java | 6 +- .../sdk/model/ConversationTokenRequest.java | 4 +- .../android/sdk/model/DevicePayload.java | 4 +- .../android/sdk/model/EventPayload.java | 4 +- .../android/sdk/model/ExtendedData.java | 2 +- .../android/sdk/model/JsonPayload.java | 80 +---------------- .../android/sdk/model/LogoutPayload.java | 6 +- .../android/sdk/model/OutgoingPayload.java | 47 ++++++++++ .../apptentive/android/sdk/model/Payload.java | 85 ++++++++++++++++++- .../android/sdk/model/PersonPayload.java | 4 +- .../sdk/model/SdkAndAppReleasePayload.java | 6 +- .../android/sdk/model/SdkPayload.java | 4 +- .../sdk/model/SurveyResponsePayload.java | 4 +- .../sdk/network/HttpJsonMultipartRequest.java | 10 +-- .../android/sdk/network/RawHttpRequest.java | 38 +++++++++ .../sdk/storage/ApptentiveDatabaseHelper.java | 43 +++++----- .../sdk/storage/ApptentiveTaskManager.java | 24 +++--- .../sdk/storage/PayloadRequestSender.java | 4 +- .../android/sdk/storage/PayloadSender.java | 12 +-- .../android/sdk/storage/PayloadStore.java | 13 ++- .../sdk/storage/JsonPayloadSenderTest.java | 10 +-- 23 files changed, 263 insertions(+), 197 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/OutgoingPayload.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/network/RawHttpRequest.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index eabb71986..9ce0dfc88 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -1,14 +1,15 @@ package com.apptentive.android.sdk.comm; import com.apptentive.android.sdk.model.ConversationTokenRequest; -import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.model.MultipartPayload; +import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.network.HttpJsonMultipartRequest; import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestManager; import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.network.RawHttpRequest; import com.apptentive.android.sdk.storage.PayloadRequestSender; import com.apptentive.android.sdk.util.Constants; @@ -54,8 +55,8 @@ public ApptentiveHttpClient(String apiKey, String serverURL) { //region API Requests - public HttpJsonRequest getConversationToken(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { - HttpJsonRequest request = createJsonRequest(apiKey, ENDPOINT_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST); + public RawHttpRequest getConversationToken(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { + RawHttpRequest request = createRawRequest(apiKey, ENDPOINT_CONVERSATION, conversationTokenRequest.toString().getBytes(), HttpRequestMethod.POST); request.addListener(listener); httpRequestManager.startRequest(request); return request; @@ -73,7 +74,7 @@ public HttpRequest findRequest(String tag) { //region PayloadRequestSender @Override - public HttpRequest sendPayload(JsonPayload payload, HttpRequest.Listener listener) { + public HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener) { if (payload == null) { throw new IllegalArgumentException("Payload is null"); } @@ -84,38 +85,32 @@ public HttpRequest sendPayload(JsonPayload payload, HttpRequest.Listener associatedFiles = ((MultipartPayload) payload).getAssociatedFiles(); - return createMultipartRequest(token, endPoint, payload.getJsonObject(), associatedFiles, requestMethod); + return createMultipartRequest(token, endPoint, payload.getData(), associatedFiles, requestMethod); } - switch (payload.getHttpRequestContentType()) { - case "application/json": { - return createJsonRequest(token, endPoint, payload.getJsonObject(), requestMethod); - } - } - - throw new IllegalArgumentException("Unexpected content type: " + payload.getHttpRequestContentType()); + return createRawRequest(token, endPoint, payload.getData(), requestMethod); } //endregion //region Helpers - private HttpJsonRequest createJsonRequest(String oauthToken, String endpoint, JSONObject payload, HttpRequestMethod method) { + private RawHttpRequest createRawRequest(String oauthToken, String endpoint, byte[] data, HttpRequestMethod method) { if (oauthToken == null) { throw new IllegalArgumentException("OAuth token is null"); } if (endpoint == null) { throw new IllegalArgumentException("Endpoint is null"); } - if (payload == null) { + if (data == null) { throw new IllegalArgumentException("Payload is null"); } if (method == null) { @@ -123,29 +118,29 @@ private HttpJsonRequest createJsonRequest(String oauthToken, String endpoint, JS } String url = createEndpointURL(endpoint); - HttpJsonRequest request = new HttpJsonRequest(url, payload); + RawHttpRequest request = new RawHttpRequest(url, data); setupRequestDefaults(request, oauthToken); request.setMethod(method); request.setRequestProperty("Content-Type", "application/json"); return request; } - private HttpJsonMultipartRequest createMultipartRequest(String oauthToken, String endpoint, JSONObject payload, List files, HttpRequestMethod method) { + private HttpJsonMultipartRequest createMultipartRequest(String oauthToken, String endpoint, byte[] data, List files, HttpRequestMethod method) { if (oauthToken == null) { throw new IllegalArgumentException("OAuth token is null"); } if (endpoint == null) { throw new IllegalArgumentException("Endpoint is null"); } - if (payload == null) { - throw new IllegalArgumentException("Payload is null"); + if (data == null) { + throw new IllegalArgumentException("Data is null"); } if (method == null) { throw new IllegalArgumentException("Method is null"); } String url = createEndpointURL(endpoint); - HttpJsonMultipartRequest request = new HttpJsonMultipartRequest(url, payload, files); + HttpJsonMultipartRequest request = new HttpJsonMultipartRequest(url, data, files); setupRequestDefaults(request, oauthToken); request.setMethod(method); return request; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java index 008bed3d2..81111bc07 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java @@ -6,16 +6,7 @@ package com.apptentive.android.sdk.model; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Bundle; - -import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.network.HttpRequestMethod; -import com.apptentive.android.sdk.util.Util; public class AppReleasePayload extends JsonPayload { @@ -48,8 +39,8 @@ public String getHttpRequestContentType() { //endregion - public void initBaseType() { - setBaseType(BaseType.app_release); + public void initPayloadType() { + setPayloadType(PayloadType.app_release); } public String getType() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java index bb21f39d3..efcedaac6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java @@ -40,7 +40,7 @@ protected ApptentiveMessage() { super(); state = State.sending; read = true; // This message originated here. - setBaseType(BaseType.message); + setPayloadType(PayloadType.message); initType(); } @@ -50,8 +50,8 @@ protected ApptentiveMessage(String json) { initType(); } - protected void initBaseType() { - setBaseType(BaseType.message); + protected void initPayloadType() { + setPayloadType(PayloadType.message); } protected abstract void initType(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java index 3dcd2a5a3..d8c45c0c4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java @@ -43,9 +43,9 @@ public void setPerson(PersonPayload person) { public void setAppRelease(AppReleasePayload appRelease) { try { - put(appRelease.getBaseType().name(), appRelease); + put(appRelease.getPayloadType().name(), appRelease); } catch (JSONException e) { - ApptentiveLog.e("Error adding %s to ConversationTokenRequest", appRelease.getBaseType().name()); + ApptentiveLog.e("Error adding %s to ConversationTokenRequest", appRelease.getPayloadType().name()); } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index 7fccdf4c9..067bd9032 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -60,8 +60,8 @@ public String getHttpRequestContentType() { //endregion - public void initBaseType() { - setBaseType(BaseType.device); + public void initPayloadType() { + setPayloadType(PayloadType.device); } public void setUuid(String uuid) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index 36911b92a..d274978a6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -125,8 +125,8 @@ public String getHttpRequestContentType() { //endregion @Override - protected void initBaseType() { - setBaseType(BaseType.event); + protected void initPayloadType() { + setPayloadType(PayloadType.event); } public void putData(Map data) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ExtendedData.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ExtendedData.java index cd27c73e5..301e99821 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ExtendedData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ExtendedData.java @@ -58,7 +58,7 @@ public static Type parse(String type) { try { return Type.valueOf(type); } catch (IllegalArgumentException e) { - ApptentiveLog.v("Error parsing unknown ExtendedData.BaseType: " + type); + ApptentiveLog.v("Error parsing unknown ExtendedData.PayloadType: " + type); } return unknown; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index 8842c9eef..6364c60d6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -8,7 +8,6 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.ApptentiveLogTag; -import com.apptentive.android.sdk.network.HttpRequestMethod; import org.json.JSONException; import org.json.JSONObject; @@ -20,14 +19,11 @@ public abstract class JsonPayload extends Payload { private final JSONObject jsonObject; // These three are not stored in the JSON, only the DB. - private long databaseId; - private BaseType baseType; - private String conversationId; public JsonPayload() { super(); jsonObject = new JSONObject(); - initBaseType(); + initPayloadType(); } public JsonPayload(String json) { @@ -39,60 +35,7 @@ public JsonPayload(String json) { ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error creating JsonPayload from json string.", e); } jsonObject = (temp == null ? null : temp); - initBaseType(); - } - - /** - * Each subclass must set its type in this method. - */ - protected abstract void initBaseType(); - - public long getDatabaseId() { - return databaseId; - } - - public void setDatabaseId(long databaseId) { - this.databaseId = databaseId; - } - - public BaseType getBaseType() { - return baseType; - } - - protected void setBaseType(BaseType baseType) { - this.baseType = baseType; - } - - public String getConversationId() { - return conversationId; - } - - public void setConversationId(String conversationId) { - this.conversationId = conversationId; - } - - public enum BaseType { - message, - event, - device, - sdk, - app_release, - sdk_and_app_release, - person, - logout, - unknown, - // Legacy - survey; - - public static BaseType parse(String type) { - try { - return BaseType.valueOf(type); - } catch (IllegalArgumentException e) { - ApptentiveLog.v("Error parsing unknown Payload.BaseType: " + type); - } - return unknown; - } - + initPayloadType(); } //region Data @@ -199,23 +142,4 @@ public JSONObject getJsonObject() { } //endregion - - //region Http-request - - /** - * Http endpoint for sending this payload - */ - public abstract String getHttpEndPoint(); - - /** - * Http request method for sending this payload - */ - public abstract HttpRequestMethod getHttpRequestMethod(); - - /** - * Http content type for sending this payload - */ - public abstract String getHttpRequestContentType(); - - //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java index d39f69d7c..bd0f136db 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java @@ -9,12 +9,10 @@ import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.util.StringUtils; -import org.json.JSONException; - public class LogoutPayload extends JsonPayload { @Override - protected void initBaseType() { - setBaseType(BaseType.logout); + protected void initPayloadType() { + setPayloadType(PayloadType.logout); } //region Http-request diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/OutgoingPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/OutgoingPayload.java new file mode 100644 index 000000000..c6219287b --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/OutgoingPayload.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.model; + +import com.apptentive.android.sdk.network.HttpRequestMethod; + +public class OutgoingPayload extends Payload { + + private byte[] data; + + public OutgoingPayload(PayloadType payloadType) { + setPayloadType(payloadType); + } + + @Override + public byte[] getData() { + return data; + } + + public void setData(byte[] data) { + this.data = data; + } + + @Override + protected void initPayloadType() { + setPayloadType(PayloadType.outgoing); + } + + @Override + public String getHttpEndPoint() { + return null; + } + + @Override + public HttpRequestMethod getHttpRequestMethod() { + return null; + } + + @Override + public String getHttpRequestContentType() { + return null; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index cd02b170a..fa0fd74ad 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -6,9 +6,13 @@ package com.apptentive.android.sdk.model; +import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.network.HttpRequestMethod; + import java.util.List; public abstract class Payload { + private long databaseId; protected String type; protected String nonce; protected int apiVersion; @@ -16,8 +20,11 @@ public abstract class Payload { protected String authToken; protected String method; protected String path; + protected String conversationId; protected List attachments; // TODO: Figure out attachment handling + private PayloadType payloadType; + public String getType() { return type; } @@ -50,11 +57,11 @@ public void setContentType(String contentType) { this.contentType = contentType; } - public String getToken() { + public String getAuthToken() { return authToken; - } // TODO: rename to getAuthToken + } - public void setToken(String authToken) { // TODO: rename to setAuthToken + public void setAuthToken(String authToken) { this.authToken = authToken; } @@ -82,5 +89,77 @@ public void setAttachments(List attachments) { this.attachments = attachments; } + public String getConversationId() { + return conversationId; + } + + public void setConversationId(String conversationId) { + this.conversationId = conversationId; + } + + /** + * Each subclass must set its type in this method. + */ + protected abstract void initPayloadType(); + + public long getDatabaseId() { + return databaseId; + } + + public void setDatabaseId(long databaseId) { + this.databaseId = databaseId; + } + + public PayloadType getPayloadType() { + return payloadType; + } + + protected void setPayloadType(PayloadType payloadType) { + this.payloadType = payloadType; + } + + public enum PayloadType { + message, + event, + device, + sdk, + app_release, + sdk_and_app_release, + person, + logout, + unknown, + outgoing, + // Legacy + survey; + + public static PayloadType parse(String type) { + try { + return PayloadType.valueOf(type); + } catch (IllegalArgumentException e) { + ApptentiveLog.v("Error parsing unknown Payload.PayloadType: " + type); + } + return unknown; + } + } + public abstract byte[] getData(); + + //region Http-request + + /** + * Http endpoint for sending this payload + */ + public abstract String getHttpEndPoint(); + + /** + * Http request method for sending this payload + */ + public abstract HttpRequestMethod getHttpRequestMethod(); + + /** + * Http content type for sending this payload + */ + public abstract String getHttpRequestContentType(); + + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index 524a6d4e2..e1a95e7bc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -46,8 +46,8 @@ public String getHttpRequestContentType() { //endregion - public void initBaseType() { - setBaseType(BaseType.person); + public void initPayloadType() { + setPayloadType(PayloadType.person); } public String getId() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index 9c83b173b..6bc0d40ff 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -18,8 +18,6 @@ import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; -import org.json.JSONException; - /** * A combined payload of {@link SdkPayload} and {@link AppReleasePayload} payloads. * @@ -62,8 +60,8 @@ public String getHttpRequestContentType() { //endregion //region Inheritance - public void initBaseType() { - setBaseType(BaseType.sdk_and_app_release); + public void initPayloadType() { + setPayloadType(PayloadType.sdk_and_app_release); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java index b74b854e4..eebf89c2b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java @@ -39,8 +39,8 @@ public String getHttpRequestContentType() { //endregion - public void initBaseType() { - setBaseType(BaseType.sdk); + public void initPayloadType() { + setPayloadType(PayloadType.sdk); } public String getVersion() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index 890c58b42..f56454cc1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -62,7 +62,7 @@ public String getId() { } @Override - protected void initBaseType() { - setBaseType(BaseType.survey); + protected void initPayloadType() { + setPayloadType(PayloadType.survey); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java index 7f3814580..eaac36901 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java @@ -11,8 +11,6 @@ import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.image.ImageUtil; -import org.json.JSONObject; - import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; @@ -24,20 +22,22 @@ /** * Class representing HTTP multipart request with Json body */ -public class HttpJsonMultipartRequest extends HttpJsonRequest { +public class HttpJsonMultipartRequest extends HttpRequest { private static final String lineEnd = "\r\n"; private static final String twoHyphens = "--"; + private final byte[] requestData; private final List files; private final String boundary; - public HttpJsonMultipartRequest(String urlString, JSONObject requestObject, List files) { - super(urlString, requestObject); + public HttpJsonMultipartRequest(String urlString, byte[] requestData, List files) { + super(urlString); if (files == null) { throw new IllegalArgumentException("Files reference is null"); } this.files = files; + this.requestData = requestData; boundary = UUID.randomUUID().toString(); setRequestProperty("Content-Type", "multipart/mixed;boundary=" + boundary); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/RawHttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/RawHttpRequest.java new file mode 100644 index 000000000..c9be8a0ed --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/RawHttpRequest.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.network; + +import java.io.IOException; + +public class RawHttpRequest extends HttpRequest { + + private final byte[] data; + String response; + + public RawHttpRequest(String urlString, byte[] data) { + super(urlString); + + if (data == null) { + throw new IllegalArgumentException("data is null"); + } + this.data = data; + } + + @Override + protected byte[] createRequestData() throws IOException { + return data; + } + + @Override + protected void handleResponse(String response) throws IOException { + this.response = response; + } + + public String getResponseObject() { + return response; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 7171784a0..dcb753ca4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -15,7 +15,8 @@ import android.text.TextUtils; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.model.JsonPayload; +import com.apptentive.android.sdk.model.OutgoingPayload; +import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.model.CompoundMessage; @@ -48,7 +49,7 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { public static final String PAYLOAD_KEY_BASE_TYPE = "base_type"; // 1 public static final String PAYLOAD_KEY_JSON = "json"; // 2 private static final String PAYLOAD_KEY_CONVERSATION_ID = "conversation_id"; // 3 - private static final String PAYLOAD_KEY_TOKEN = "token"; // 4 + private static final String PAYLOAD_KEY_AUTH_TOKEN = "auth_token"; // 4 private static final String TABLE_CREATE_PAYLOAD = "CREATE TABLE " + TABLE_PAYLOAD + @@ -57,15 +58,15 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { PAYLOAD_KEY_BASE_TYPE + " TEXT, " + PAYLOAD_KEY_JSON + " TEXT," + PAYLOAD_KEY_CONVERSATION_ID + " TEXT," + - PAYLOAD_KEY_TOKEN + " TEXT" + + PAYLOAD_KEY_AUTH_TOKEN + " TEXT" + ");"; public static final String QUERY_PAYLOAD_GET_NEXT_TO_SEND = "SELECT * FROM " + TABLE_PAYLOAD + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC LIMIT 1"; - private static final String QUERY_UPDATE_INCOMPLETE_PAYLOADS = StringUtils.format("UPDATE %s SET %s = ?, %s = ? WHERE %s IS NULL OR %s IS NULL", TABLE_PAYLOAD, PAYLOAD_KEY_CONVERSATION_ID, PAYLOAD_KEY_TOKEN, PAYLOAD_KEY_CONVERSATION_ID, PAYLOAD_KEY_TOKEN); + private static final String QUERY_UPDATE_INCOMPLETE_PAYLOADS = StringUtils.format("UPDATE %s SET %s = ?, %s = ? WHERE %s IS NULL OR %s IS NULL", TABLE_PAYLOAD, PAYLOAD_KEY_CONVERSATION_ID, PAYLOAD_KEY_AUTH_TOKEN, PAYLOAD_KEY_CONVERSATION_ID, PAYLOAD_KEY_AUTH_TOKEN); private static final String QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER = "SELECT * FROM " + TABLE_PAYLOAD + " WHERE " + PAYLOAD_KEY_BASE_TYPE + " = ?" + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC"; private static final String UPGRADE_V2_to_v3_ALTER_PAYLOAD_ADD_CONVERSATION_ID = "ALTER TABLE " + TABLE_PAYLOAD + " ADD COLUMN " + PAYLOAD_KEY_CONVERSATION_ID + " TEXT"; - private static final String UPGRADE_V2_to_v3_ALTER_PAYLOAD_ADD_TOKEN = "ALTER TABLE " + TABLE_PAYLOAD + " ADD COLUMN " + PAYLOAD_KEY_TOKEN + " TEXT"; + private static final String UPGRADE_V2_to_v3_ALTER_PAYLOAD_ADD_TOKEN = "ALTER TABLE " + TABLE_PAYLOAD + " ADD COLUMN " + PAYLOAD_KEY_AUTH_TOKEN + " TEXT"; //endregion @@ -274,7 +275,7 @@ private void upgradeVersion1to2(SQLiteDatabase db) { // Migrate all pending payload messages // Migrate legacy message types to CompoundMessage Type try { - cursor = db.rawQuery(QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER, new String[]{JsonPayload.BaseType.message.name()}); + cursor = db.rawQuery(QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER, new String[]{Payload.PayloadType.message.name()}); if (cursor.moveToFirst()) { do { String json = cursor.getString(2); @@ -335,17 +336,17 @@ private void upgradeVersion2to3(SQLiteDatabase db) { * If an item with the same nonce as an item passed in already exists, it is overwritten by the item. Otherwise * a new message is added. */ - public void addPayload(JsonPayload... payloads) { + public void addPayload(Payload... payloads) { SQLiteDatabase db = null; try { db = getWritableDatabase(); db.beginTransaction(); - for (JsonPayload payload : payloads) { + for (Payload payload : payloads) { ContentValues values = new ContentValues(); - values.put(PAYLOAD_KEY_BASE_TYPE, payload.getBaseType().name()); + values.put(PAYLOAD_KEY_BASE_TYPE, payload.getPayloadType().name()); values.put(PAYLOAD_KEY_JSON, payload.toString()); values.put(PAYLOAD_KEY_CONVERSATION_ID, payload.getConversationId()); - values.put(PAYLOAD_KEY_TOKEN, payload.getToken()); + values.put(PAYLOAD_KEY_AUTH_TOKEN, payload.getAuthToken()); db.insert(TABLE_PAYLOAD, null, values); } db.setTransactionSuccessful(); @@ -355,7 +356,7 @@ public void addPayload(JsonPayload... payloads) { } } - public void deletePayload(JsonPayload payload) { + public void deletePayload(Payload payload) { if (payload != null) { SQLiteDatabase db = null; try { @@ -377,27 +378,25 @@ public void deleteAllPayloads() { } } - public JsonPayload getOldestUnsentPayload() { + public Payload getOldestUnsentPayload() { SQLiteDatabase db = null; Cursor cursor = null; try { db = getWritableDatabase(); cursor = db.rawQuery(QUERY_PAYLOAD_GET_NEXT_TO_SEND, null); - JsonPayload payload = null; + OutgoingPayload payload = null; if (cursor.moveToFirst()) { long databaseId = Long.parseLong(cursor.getString(0)); - JsonPayload.BaseType baseType = JsonPayload.BaseType.parse(cursor.getString(1)); + Payload.PayloadType payloadType = Payload.PayloadType.parse(cursor.getString(1)); String json = cursor.getString(2); String conversationId = cursor.getString(3); - String token = cursor.getString(4); -// payload = PayloadFactory.fromJson(json, baseType); -// if (payload != null) { -// payload.setDatabaseId(databaseId); -// payload.setConversationId(conversationId); -// payload.setToken(token); -// } - throw new RuntimeException("Implement me"); // FIXME: implement me + String authToken = cursor.getString(4); + payload = new OutgoingPayload(payloadType); + payload.setDatabaseId(databaseId); + payload.setConversationId(conversationId); + payload.setAuthToken(authToken); + payload.setData(json.getBytes()); } return payload; } catch (SQLException sqe) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 3b222a11d..e6bc5417e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -12,7 +12,7 @@ import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.debug.Assert; -import com.apptentive.android.sdk.model.JsonPayload; +import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.network.HttpRequestRetryPolicyDefault; import com.apptentive.android.sdk.notifications.ApptentiveNotification; @@ -98,10 +98,10 @@ public boolean shouldRetryRequest(int responseCode, int retryAttempt) { * If an item with the same nonce as an item passed in already exists, it is overwritten by the item. Otherwise * a new message is added. */ - public void addPayload(final JsonPayload... payloads) { - for (JsonPayload payload : payloads) { + public void addPayload(final Payload... payloads) { + for (Payload payload : payloads) { payload.setConversationId(currentConversationId); - payload.setToken(currentConversationToken); + payload.setAuthToken(currentConversationToken); } singleThreadExecutor.execute(new Runnable() { @Override @@ -112,7 +112,7 @@ public void run() { }); } - public void deletePayload(final JsonPayload payload) { + public void deletePayload(final Payload payload) { if (payload != null) { singleThreadExecutor.execute(new Runnable() { @Override @@ -133,16 +133,16 @@ public void run() { }); } - public synchronized Future getOldestUnsentPayload() throws Exception { - return singleThreadExecutor.submit(new Callable() { + public synchronized Future getOldestUnsentPayload() throws Exception { + return singleThreadExecutor.submit(new Callable() { @Override - public JsonPayload call() throws Exception { + public Payload call() throws Exception { return getOldestUnsentPayloadSync(); } }); } - private JsonPayload getOldestUnsentPayloadSync() { + private Payload getOldestUnsentPayloadSync() { return dbHelper.getOldestUnsentPayload(); } @@ -180,7 +180,7 @@ public void reset(Context context) { //region PayloadSender.Listener @Override - public void onFinishSending(PayloadSender sender, JsonPayload payload, boolean cancelled, String errorMessage) { + public void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage) { ApptentiveNotificationCenter.defaultCenter() .postNotification(NOTIFICATION_PAYLOAD_DID_FINISH_SEND, NOTIFICATION_KEY_PAYLOAD, payload, @@ -227,7 +227,7 @@ private void sendNextPayloadSync() { return; } - final JsonPayload payload; + final Payload payload; try { payload = getOldestUnsentPayloadSync(); } catch (Exception e) { @@ -240,7 +240,7 @@ private void sendNextPayloadSync() { return; } - if (StringUtils.isNullOrEmpty(payload.getConversationId()) || StringUtils.isNullOrEmpty(payload.getToken())) { + if (StringUtils.isNullOrEmpty(payload.getConversationId()) || StringUtils.isNullOrEmpty(payload.getAuthToken())) { ApptentiveLog.v(PAYLOADS, "Can't send the next payload: no conversation id"); return; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java index 492b9ea83..7282a1cb2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java @@ -6,7 +6,7 @@ package com.apptentive.android.sdk.storage; -import com.apptentive.android.sdk.model.JsonPayload; +import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.network.HttpRequest; /** @@ -20,5 +20,5 @@ public interface PayloadRequestSender { * @param payload to be sent * @param listener Http-request listener for the payload request */ - HttpRequest sendPayload(JsonPayload payload, HttpRequest.Listener listener); + HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index b6506cc4d..325ebb869 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -7,7 +7,7 @@ package com.apptentive.android.sdk.storage; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.model.JsonPayload; +import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestRetryPolicy; import com.apptentive.android.sdk.util.StringUtils; @@ -56,7 +56,7 @@ class PayloadSender { * * @throws IllegalArgumentException is payload is null */ - synchronized boolean sendPayload(final JsonPayload payload) { + synchronized boolean sendPayload(final Payload payload) { if (payload == null) { throw new IllegalArgumentException("Payload is null"); } @@ -91,8 +91,8 @@ synchronized boolean sendPayload(final JsonPayload payload) { /** * Creates and sends payload Http-request asynchronously (returns immediately) */ - private synchronized void sendPayloadRequest(final JsonPayload payload) { - ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getBaseType(), payload.getDatabaseId(), payload.getConversationId()); + private synchronized void sendPayloadRequest(final Payload payload) { + ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getPayloadType(), payload.getDatabaseId(), payload.getConversationId()); // create request object final HttpRequest payloadRequest = requestSender.sendPayload(payload, new HttpRequest.Listener() { @@ -127,7 +127,7 @@ public void onFail(HttpRequest request, String reason) { * @param cancelled - flag indicating if payload Http-request was cancelled * @param errorMessage - if not null - payload request failed */ - private synchronized void handleFinishSendingPayload(JsonPayload payload, boolean cancelled, String errorMessage) { + private synchronized void handleFinishSendingPayload(Payload payload, boolean cancelled, String errorMessage) { sendingFlag = false; // mark sender as 'not busy' try { @@ -159,7 +159,7 @@ public void setListener(Listener listener) { //region Listener public interface Listener { - void onFinishSending(PayloadSender sender, JsonPayload payload, boolean cancelled, String errorMessage); + void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java index 100f3037f..628b79c3a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java @@ -1,23 +1,20 @@ package com.apptentive.android.sdk.storage; -import com.apptentive.android.sdk.model.JsonPayload; +import com.apptentive.android.sdk.model.Payload; import java.util.concurrent.Future; -/** - * @author Sky Kelsey - */ public interface PayloadStore { - public void addPayload(JsonPayload... payloads); + void addPayload(Payload... payloads); - public void deletePayload(JsonPayload payload); + void deletePayload(Payload payload); - public void deleteAllPayloads(); + void deleteAllPayloads(); /* Asynchronous call to retrieve the oldest unsent payload from the data storage. * Calling get() method on the returned Future object will block the caller until the Future has completed, */ - public Future getOldestUnsentPayload() throws Exception; + Future getOldestUnsentPayload() throws Exception; } diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java index f96656204..3bbec0252 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java @@ -8,6 +8,7 @@ import com.apptentive.android.sdk.TestCaseBase; import com.apptentive.android.sdk.model.JsonPayload; +import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestManager; import com.apptentive.android.sdk.network.HttpRequestMethod; @@ -41,7 +42,7 @@ public void testSendPayload() throws Exception { PayloadSender sender = new PayloadSender(requestSender, new HttpRequestRetryPolicyDefault()); sender.setListener(new PayloadSender.Listener() { @Override - public void onFinishSending(PayloadSender sender, JsonPayload payload, boolean cancelled, String errorMessage) { + public void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage) { if (cancelled) { addResult("cancelled: " + payload); } else if (errorMessage != null) { @@ -99,8 +100,8 @@ public MockPayload(String key, Object value) { } @Override - protected void initBaseType() { - setBaseType(BaseType.event); + protected void initPayloadType() { + setPayloadType(PayloadType.event); } public MockPayload setResponseCode(int responseCode) { @@ -146,8 +147,7 @@ public MockPayloadRequestSender() { } @Override - public HttpRequest sendPayload(JsonPayload payload, HttpRequest.Listener listener) { - + public HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener) { MockHttpRequest request = new MockHttpRequest("http://apptentive.com"); request.setMockResponseHandler(((MockPayload) payload).getResponseHandler()); request.addListener(listener); From 1134dc5f5a754e78c5284ea20d6144dee01962a0 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 20 Apr 2017 11:02:57 -0700 Subject: [PATCH 244/465] Refactoring Moved PayloadType up in the class hierarchy --- .../apptentive/android/sdk/model/Payload.java | 25 -------------- .../android/sdk/model/PayloadType.java | 33 +++++++++++++++++++ .../android/sdk/network/RawHttpRequest.java | 10 ------ .../sdk/storage/ApptentiveDatabaseHelper.java | 5 +-- .../sdk/storage/JsonPayloadSenderTest.java | 1 + 5 files changed, 37 insertions(+), 37 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadType.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index fa0fd74ad..1e85300ff 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -6,7 +6,6 @@ package com.apptentive.android.sdk.model; -import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.network.HttpRequestMethod; import java.util.List; @@ -118,30 +117,6 @@ protected void setPayloadType(PayloadType payloadType) { this.payloadType = payloadType; } - public enum PayloadType { - message, - event, - device, - sdk, - app_release, - sdk_and_app_release, - person, - logout, - unknown, - outgoing, - // Legacy - survey; - - public static PayloadType parse(String type) { - try { - return PayloadType.valueOf(type); - } catch (IllegalArgumentException e) { - ApptentiveLog.v("Error parsing unknown Payload.PayloadType: " + type); - } - return unknown; - } - } - public abstract byte[] getData(); //region Http-request diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadType.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadType.java new file mode 100644 index 000000000..b7dc4e6f2 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadType.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.model; + +import com.apptentive.android.sdk.ApptentiveLog; + +public enum PayloadType { + message, + event, + device, + sdk, + app_release, + sdk_and_app_release, + person, + logout, + unknown, + outgoing, + // Legacy + survey; + + public static PayloadType parse(String type) { + try { + return PayloadType.valueOf(type); + } catch (IllegalArgumentException e) { + ApptentiveLog.v("Error parsing unknown Payload.PayloadType: " + type); + } + return unknown; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/RawHttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/RawHttpRequest.java index c9be8a0ed..3a65bfacc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/RawHttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/RawHttpRequest.java @@ -11,7 +11,6 @@ public class RawHttpRequest extends HttpRequest { private final byte[] data; - String response; public RawHttpRequest(String urlString, byte[] data) { super(urlString); @@ -26,13 +25,4 @@ public RawHttpRequest(String urlString, byte[] data) { protected byte[] createRequestData() throws IOException { return data; } - - @Override - protected void handleResponse(String response) throws IOException { - this.response = response; - } - - public String getResponseObject() { - return response; - } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index dcb753ca4..bf029fd5a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -17,6 +17,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.model.OutgoingPayload; import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PayloadType; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.model.CompoundMessage; @@ -275,7 +276,7 @@ private void upgradeVersion1to2(SQLiteDatabase db) { // Migrate all pending payload messages // Migrate legacy message types to CompoundMessage Type try { - cursor = db.rawQuery(QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER, new String[]{Payload.PayloadType.message.name()}); + cursor = db.rawQuery(QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER, new String[]{PayloadType.message.name()}); if (cursor.moveToFirst()) { do { String json = cursor.getString(2); @@ -388,7 +389,7 @@ public Payload getOldestUnsentPayload() { OutgoingPayload payload = null; if (cursor.moveToFirst()) { long databaseId = Long.parseLong(cursor.getString(0)); - Payload.PayloadType payloadType = Payload.PayloadType.parse(cursor.getString(1)); + PayloadType payloadType = PayloadType.parse(cursor.getString(1)); String json = cursor.getString(2); String conversationId = cursor.getString(3); String authToken = cursor.getString(4); diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java index 3bbec0252..e63463d3f 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java @@ -9,6 +9,7 @@ import com.apptentive.android.sdk.TestCaseBase; import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PayloadType; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestManager; import com.apptentive.android.sdk.network.HttpRequestMethod; From c3c14080613acc059fbca57d713408568d347ddf Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 20 Apr 2017 11:20:39 -0700 Subject: [PATCH 245/465] Refactoring Removed PayloadType from Payload --- .../android/sdk/model/AppReleasePayload.java | 6 ++---- .../android/sdk/model/ApptentiveMessage.java | 5 ----- .../sdk/model/ConversationTokenRequest.java | 6 ++---- .../android/sdk/model/DevicePayload.java | 4 ---- .../android/sdk/model/EventPayload.java | 5 ----- .../apptentive/android/sdk/model/JsonPayload.java | 2 -- .../android/sdk/model/LogoutPayload.java | 4 ---- .../android/sdk/model/OutgoingPayload.java | 6 ------ .../com/apptentive/android/sdk/model/Payload.java | 15 --------------- .../android/sdk/model/PersonPayload.java | 4 ---- .../sdk/model/SdkAndAppReleasePayload.java | 6 ------ .../apptentive/android/sdk/model/SdkPayload.java | 4 ---- .../android/sdk/model/SurveyResponsePayload.java | 4 ---- .../sdk/storage/ApptentiveDatabaseHelper.java | 1 - .../android/sdk/storage/PayloadSender.java | 2 +- .../sdk/storage/JsonPayloadSenderTest.java | 6 ------ 16 files changed, 5 insertions(+), 75 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java index 81111bc07..c7ba54219 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java @@ -10,6 +10,8 @@ public class AppReleasePayload extends JsonPayload { + public static final String KEY = "app_release"; + private static final String KEY_TYPE = "type"; private static final String KEY_VERSION_NAME = "version_name"; private static final String KEY_VERSION_CODE = "version_code"; @@ -39,10 +41,6 @@ public String getHttpRequestContentType() { //endregion - public void initPayloadType() { - setPayloadType(PayloadType.app_release); - } - public String getType() { return getString(KEY_TYPE); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java index efcedaac6..7129e5548 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java @@ -40,7 +40,6 @@ protected ApptentiveMessage() { super(); state = State.sending; read = true; // This message originated here. - setPayloadType(PayloadType.message); initType(); } @@ -50,10 +49,6 @@ protected ApptentiveMessage(String json) { initType(); } - protected void initPayloadType() { - setPayloadType(PayloadType.message); - } - protected abstract void initType(); public void setId(String id) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java index d8c45c0c4..319232fd8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java @@ -12,8 +12,6 @@ import org.json.JSONObject; public class ConversationTokenRequest extends JSONObject { - - public ConversationTokenRequest() { } @@ -43,9 +41,9 @@ public void setPerson(PersonPayload person) { public void setAppRelease(AppReleasePayload appRelease) { try { - put(appRelease.getPayloadType().name(), appRelease); + put(AppReleasePayload.KEY, appRelease); } catch (JSONException e) { - ApptentiveLog.e("Error adding %s to ConversationTokenRequest", appRelease.getPayloadType().name()); + ApptentiveLog.e("Error adding %s to ConversationTokenRequest", AppReleasePayload.KEY); } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index 067bd9032..265d2c280 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -60,10 +60,6 @@ public String getHttpRequestContentType() { //endregion - public void initPayloadType() { - setPayloadType(PayloadType.device); - } - public void setUuid(String uuid) { put(KEY_UUID, uuid); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index d274978a6..012f2529a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -124,11 +124,6 @@ public String getHttpRequestContentType() { //endregion - @Override - protected void initPayloadType() { - setPayloadType(PayloadType.event); - } - public void putData(Map data) { if (data == null || data.isEmpty()) { return; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index 6364c60d6..88759ef02 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -23,7 +23,6 @@ public abstract class JsonPayload extends Payload { public JsonPayload() { super(); jsonObject = new JSONObject(); - initPayloadType(); } public JsonPayload(String json) { @@ -35,7 +34,6 @@ public JsonPayload(String json) { ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error creating JsonPayload from json string.", e); } jsonObject = (temp == null ? null : temp); - initPayloadType(); } //region Data diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java index bd0f136db..15022bf48 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java @@ -10,10 +10,6 @@ import com.apptentive.android.sdk.util.StringUtils; public class LogoutPayload extends JsonPayload { - @Override - protected void initPayloadType() { - setPayloadType(PayloadType.logout); - } //region Http-request diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/OutgoingPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/OutgoingPayload.java index c6219287b..e0c5a90c8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/OutgoingPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/OutgoingPayload.java @@ -13,7 +13,6 @@ public class OutgoingPayload extends Payload { private byte[] data; public OutgoingPayload(PayloadType payloadType) { - setPayloadType(payloadType); } @Override @@ -25,11 +24,6 @@ public void setData(byte[] data) { this.data = data; } - @Override - protected void initPayloadType() { - setPayloadType(PayloadType.outgoing); - } - @Override public String getHttpEndPoint() { return null; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index 1e85300ff..b5dce56f8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -22,8 +22,6 @@ public abstract class Payload { protected String conversationId; protected List attachments; // TODO: Figure out attachment handling - private PayloadType payloadType; - public String getType() { return type; } @@ -96,11 +94,6 @@ public void setConversationId(String conversationId) { this.conversationId = conversationId; } - /** - * Each subclass must set its type in this method. - */ - protected abstract void initPayloadType(); - public long getDatabaseId() { return databaseId; } @@ -109,14 +102,6 @@ public void setDatabaseId(long databaseId) { this.databaseId = databaseId; } - public PayloadType getPayloadType() { - return payloadType; - } - - protected void setPayloadType(PayloadType payloadType) { - this.payloadType = payloadType; - } - public abstract byte[] getData(); //region Http-request diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index e1a95e7bc..402406f78 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -46,10 +46,6 @@ public String getHttpRequestContentType() { //endregion - public void initPayloadType() { - setPayloadType(PayloadType.person); - } - public String getId() { if (!isNull(KEY_ID)) { return getString(KEY_ID); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index 6bc0d40ff..15e67b121 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -59,12 +59,6 @@ public String getHttpRequestContentType() { //endregion - //region Inheritance - public void initPayloadType() { - setPayloadType(PayloadType.sdk_and_app_release); - } - //endregion - //region Sdk getters/setters public String getVersion() { return sdk.getVersion(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java index eebf89c2b..de991a49c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java @@ -39,10 +39,6 @@ public String getHttpRequestContentType() { //endregion - public void initPayloadType() { - setPayloadType(PayloadType.sdk); - } - public String getVersion() { if (!isNull(KEY_VERSION)) { return getString(KEY_VERSION); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index f56454cc1..3d3bb4b70 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -61,8 +61,4 @@ public String getId() { return getString(KEY_SURVEY_ID); } - @Override - protected void initPayloadType() { - setPayloadType(PayloadType.survey); - } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index bf029fd5a..28380b999 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -344,7 +344,6 @@ public void addPayload(Payload... payloads) { db.beginTransaction(); for (Payload payload : payloads) { ContentValues values = new ContentValues(); - values.put(PAYLOAD_KEY_BASE_TYPE, payload.getPayloadType().name()); values.put(PAYLOAD_KEY_JSON, payload.toString()); values.put(PAYLOAD_KEY_CONVERSATION_ID, payload.getConversationId()); values.put(PAYLOAD_KEY_AUTH_TOKEN, payload.getAuthToken()); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 325ebb869..2a2baf2dd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -92,7 +92,7 @@ synchronized boolean sendPayload(final Payload payload) { * Creates and sends payload Http-request asynchronously (returns immediately) */ private synchronized void sendPayloadRequest(final Payload payload) { - ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getPayloadType(), payload.getDatabaseId(), payload.getConversationId()); + ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getClass().getName(), payload.getDatabaseId(), payload.getConversationId()); // create request object final HttpRequest payloadRequest = requestSender.sendPayload(payload, new HttpRequest.Listener() { diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java index e63463d3f..1b4e1731a 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java @@ -9,7 +9,6 @@ import com.apptentive.android.sdk.TestCaseBase; import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.model.Payload; -import com.apptentive.android.sdk.model.PayloadType; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestManager; import com.apptentive.android.sdk.network.HttpRequestMethod; @@ -100,11 +99,6 @@ public MockPayload(String key, Object value) { setDatabaseId(0L); } - @Override - protected void initPayloadType() { - setPayloadType(PayloadType.event); - } - public MockPayload setResponseCode(int responseCode) { ((DefaultResponseHandler)responseHandler).setResponseCode(responseCode); return this; From aea979680bb6b5e719cf1db35ee9653ddb6ad71b Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 20 Apr 2017 12:05:18 -0700 Subject: [PATCH 246/465] Testing progress --- .../storage/ApptentiveDatabaseHelperTest.java | 27 +++++++++++++++++++ .../sdk/storage/ApptentiveDatabaseHelper.java | 7 ++++- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java new file mode 100644 index 000000000..08301995a --- /dev/null +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +@RunWith(AndroidJUnit4.class) +public class ApptentiveDatabaseHelperTest { + + @Test + public void testFoo() { + final Context context = InstrumentationRegistry.getContext(); + final File apptentive = context.getApplicationContext().getDatabasePath("apptentive"); + System.out.println(apptentive); + } +} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 28380b999..ebbbcc60e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -155,7 +155,12 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { public ApptentiveDatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); + this(context, DATABASE_NAME, DATABASE_VERSION); + } + + /** Convenience constructor for unit testing */ + ApptentiveDatabaseHelper(Context context, String databaseName, int databaseVersion) { + super(context, databaseName, null, databaseVersion); fileDir = context.getFilesDir(); } From 39771a197b7705dfcf6fa0af56f7cc42fd3172f0 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 20 Apr 2017 14:12:54 -0700 Subject: [PATCH 247/465] Progress --- .../src/androidTest/assets/apptentive-v2 | 0 .../storage/ApptentiveDatabaseHelperTest.java | 48 +++++++++++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 apptentive/src/androidTest/assets/apptentive-v2 diff --git a/apptentive/src/androidTest/assets/apptentive-v2 b/apptentive/src/androidTest/assets/apptentive-v2 new file mode 100644 index 000000000..e69de29bb diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java index 08301995a..d2538f31d 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java @@ -7,21 +7,63 @@ package com.apptentive.android.sdk.storage; import android.content.Context; +import android.os.Build; +import android.support.annotation.RequiresApi; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; @RunWith(AndroidJUnit4.class) public class ApptentiveDatabaseHelperTest { + @After + public void tearDown() throws Exception { + deleteDbFile(InstrumentationRegistry.getContext()); + } + @Test - public void testFoo() { + public void testFoo() throws Exception { final Context context = InstrumentationRegistry.getContext(); - final File apptentive = context.getApplicationContext().getDatabasePath("apptentive"); - System.out.println(apptentive); + replaceDbFile(context, "apptentive-v2"); + + ApptentiveDatabaseHelper db = new ApptentiveDatabaseHelper(context); + db.getWritableDatabase(); + Thread.sleep(10000); + + } + + private static void replaceDbFile(Context context, String filename) throws IOException { + InputStream input = context.getAssets().open(filename); + try { + OutputStream output = new FileOutputStream(getDatabaseFile(context)); + try { + byte[] buffer = new byte[1024]; + int read; + while ((read = input.read(buffer)) != -1) { + output.write(buffer, 0, read); + } + } finally { + output.close(); + } + } finally { + input.close(); + } + } + + private static void deleteDbFile(Context context) throws IOException { + getDatabaseFile(context).delete(); + } + + private static File getDatabaseFile(Context context) { + return context.getDatabasePath("apptentive"); } } \ No newline at end of file From 108e44e483fbb113ea147b54d944a69dfae43580 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 20 Apr 2017 18:01:23 -0700 Subject: [PATCH 248/465] Progress --- .../src/androidTest/assets/apptentive-v2 | Bin 0 -> 32768 bytes .../storage/ApptentiveDatabaseHelperTest.java | 24 +- .../android/sdk/model/AppReleasePayload.java | 9 + .../android/sdk/model/ApptentiveMessage.java | 2 +- .../android/sdk/model/ConversationItem.java | 4 +- .../android/sdk/model/DevicePayload.java | 7 + .../android/sdk/model/EventPayload.java | 4 + .../android/sdk/model/JsonPayload.java | 21 +- .../android/sdk/model/LogoutPayload.java | 8 + .../android/sdk/model/PersonPayload.java | 7 + .../sdk/model/SdkAndAppReleasePayload.java | 10 +- .../android/sdk/model/SdkPayload.java | 9 + .../sdk/model/SurveyResponsePayload.java | 6 +- .../messagecenter/model/MessageFactory.java | 2 +- .../sdk/storage/ApptentiveDatabaseHelper.java | 224 +++++-- .../ApptentiveDatabaseLegacyHelper.java | 615 ++++++++++++++++++ .../storage/legacy/LegacyPayloadFactory.java | 51 ++ 17 files changed, 914 insertions(+), 89 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseLegacyHelper.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/LegacyPayloadFactory.java diff --git a/apptentive/src/androidTest/assets/apptentive-v2 b/apptentive/src/androidTest/assets/apptentive-v2 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0ccb4b4a1bbaec5403b09759ef45e55db565eaf9 100644 GIT binary patch literal 32768 zcmeI5&2QVt6~L+3vYk(FP@qPTEvhJJH%3jFoFRv#Q502V$H~TV65IJQuqB2wLx(XX zDiq~78@PZrJrw;9dMbJXty?k;867#$??{1SXe2I>E{ zn$q;Y{r%#lUut6){;Ey>X0m?i*BAeINkRe_B!C2v01`j~NB{{S0VIF~kicmY=xvUT zPc<5)FFwn75G#>1+ex=2{if=4_=fVsC{}(aOVY!3O*B>(>T3&?wffDag~}nkwy?OcQhBg)=U#o~S>^7+vzbaOX$e(XTX?!QQ;8G7W53O_=Yx!9)Kq@k-)xdOvRe4CCLFOTL7-%_D5pe`a{eq&BJ`8>5 z?>_?j4x6fzaTpu-%?7;R8F!}gqLZ|W9n@cFP)iEE&tJKKK{Ra=C|79pC+$g_{W7mpa19iAD&x3n@;>;;^XnTv1jGq zmq$jgjXZ`!a6tlJBZ1!Z<+3*Q#a*=pkvngyMMzb3u4)j*Y}X5P7D}mGq3h|M;(;Cn zfv_za7|J%PGu1Bz{8Z=6n&lCPP-eLv@fN%T4)VL1@RKm?sH{4t6Pp;uOf}|#ilJl( z1*`3aJ6(s(>oM=P#PjM-@AgM!?dsd|AR=i`T4FdM2|3q820xZ-8#dG9#^_0U5DAsa16^iN)bxv$*~UsM58&F|F$0?@ZaaoyTatmXrY{}jSS|@YOIbn>LgMNc=TZ;6(AEhH zn5ztF1V(tYhJ+s9RL60wPcG)NF7C4ivjc}RD)dmAAgk+|df=E+r;f`#;aDNJj+E7O zEb~-kRohVpRDW_I_Zxekq^>l%L4>Uvf`O#W1i#@S)LGl+G>~@SO8W?^rpxRT3wjD} z!!@IbcZR6cq^(*|{8wpK{qck9hegaVkfEiU!VGoGGq~kZEV(%H&b8zSWY8tY!+~?FjOLCh-a~!(5ay;mf1aQv?2%$Qa4rk0zBs_*C!97o` zVeWIC=gF&g@$RCyNkZGaUM_1_-u^(Utw^Xhg;h7wemuEIT4A(NoqJP-(|6;}+uhFd zJS$5!!42lBia%_xHWpV`KCREyAJjcFxcxYhTerLG%iH%J*2Vo*v%N{5+IP*Tu{9ry z?Zp>2mC1sJ^Kvs>S{2Wvv%2}fNnX%93oFL@_LEk}zu#tCkGr34J&xudi1}rH>pp3c z=;p1MuCFeLEZeHzwBv-`wHoz#8YR!%7t!Yr?j_{bO8m0%@^cwQbgR0vv-9!jsCLC{ zs}w@C-&92x;`^I^9wrS6?Cku^7y;FP{$yA6{u)#Yx^es*V!Q`V{v{kc(DBP7_mwZ; z9fB-<4QrYAtRAiA?#k0NQYj=M(d`yJ4knDiv83S$gIVDAuqWt7u`FW!;mB?WPJmO} zT}be>$t-=#<&cwf{Ng~!rp$FH%};IeRyTwWO*buySz26x%#_P0@wY%%cohH+zvp_M zbxS8TR->#&Y9#Oa!sS}LnTuNQ2aZEtUywq~W$PAUYmoPTU$?3g*ERC9gCxoFkam^6 zSI*7b>!z7^vx0=DdGG2ydQcp1M}7>oBJU-W;&$Fm(<0vf9j5x)_wzDaDtncro4ZJs z)^e=|oKyY^&M!u(c}Xb*vO{_!NjBoVg@AJQ6nAQ7EvM8@6WJBeS$|)mCKxIgWm#=^ zI~DusUJ)^GZFKWDK?b%g+PM8L)0;k%SM&V?4@zChTxr0a-EnkFn6~bj(uSU5Xh`OT z0hk|3I%MEMJVNQ|+*)=A96fCP{L50XLKz%l!_h2@kXkgZu9iFeM1~VVb3YaWO%~DAnZkq64 zaxgcKr(k*tm_P_-NFjDeISHaBydgjPqOCSyZiswFh`HJRwfeIO)Zm@_tqA4^=+v4@ zM!Y5y(QU$Eo!Q;3+2Ru4qjgFR=Jw{#WW-Ps<}d>_q$jA$s7XyT2uvIPAn_E9VHwo^4=Ph_*8l(j literal 0 HcmV?d00001 diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java index d2538f31d..c8fa09e94 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java @@ -7,11 +7,18 @@ package com.apptentive.android.sdk.storage; import android.content.Context; -import android.os.Build; -import android.support.annotation.RequiresApi; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import com.apptentive.android.sdk.model.CompoundMessage; +import com.apptentive.android.sdk.model.DevicePayload; +import com.apptentive.android.sdk.model.EventPayload; +import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PersonPayload; +import com.apptentive.android.sdk.model.SdkPayload; +import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; +import com.apptentive.android.sdk.storage.legacy.LegacyPayloadFactory; + import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; @@ -21,6 +28,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.List; @RunWith(AndroidJUnit4.class) public class ApptentiveDatabaseHelperTest { @@ -35,10 +43,14 @@ public void testFoo() throws Exception { final Context context = InstrumentationRegistry.getContext(); replaceDbFile(context, "apptentive-v2"); - ApptentiveDatabaseHelper db = new ApptentiveDatabaseHelper(context); - db.getWritableDatabase(); - Thread.sleep(10000); - + Payload[] expectedPayloads = { + new SdkPayload("{\"version\":\"3.4.1\",\"platform\":\"Android\"}"), + new EventPayload("{\"nonce\":\"338d68d0-0777-4c15-91d5-4af0d69fbc0b\",\"client_created_at\":1.492723292335E9,\"client_created_at_utc_offset\":-25200,\"label\":\"com.apptentive#app#launch\"}"), + new DevicePayload("{\"device\":\"bullhead\",\"integration_config\":{},\"locale_country_code\":\"US\",\"carrier\":\"\",\"uuid\":\"6c0b74d07c064421\",\"build_type\":\"user\",\"cpu\":\"arm64-v8a\",\"os_build\":\"3687331\",\"manufacturer\":\"LGE\",\"radio_version\":\"M8994F-2.6.36.2.20\",\"os_name\":\"Android\",\"build_id\":\"N4F26T\",\"utc_offset\":\"-28800\",\"bootloader_version\":\"BHZ11h\",\"board\":\"bullhead\",\"os_api_level\":\"25\",\"current_carrier\":\"AT&T\",\"network_type\":\"LTE\",\"locale_raw\":\"en_US\",\"brand\":\"google\",\"os_version\":\"7.1.1\",\"product\":\"bullhead\",\"model\":\"Nexus 5X\",\"locale_language_code\":\"en\",\"custom_data\":{}}"), + new PersonPayload("{\"custom_data\":{}}"), + new DevicePayload("{\"integration_config\":{\"apptentive_push\":{\"token\":\"eaQpSCGSRJA:APA91bHVodvHuZNxMQAcOS1pk3X5K1Xl4DlcxGjBe16bC7qkfLScYd7SkP7oj3IER0ZxWns_Op6vVuJvViDPcDNaFO2m2iBFl3ZSEcttvAB5lo6K4CAD3ioY8jizPMo2FRlqCqzdii3v\"}}}"), + MessageFactory.fromJson("{\"nonce\":\"207f2faa-f6aa-4850-addd-c552b79b8404\",\"client_created_at\":1.492723326164E9,\"client_created_at_utc_offset\":-25200,\"type\":\"CompoundMessage\",\"body\":\"Test message\",\"text_only\":false}"), + }; } private static void replaceDbFile(Context context, String filename) throws IOException { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java index c7ba54219..e72acf642 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java @@ -8,6 +8,8 @@ import com.apptentive.android.sdk.network.HttpRequestMethod; +import org.json.JSONException; + public class AppReleasePayload extends JsonPayload { public static final String KEY = "app_release"; @@ -22,6 +24,13 @@ public class AppReleasePayload extends JsonPayload { private static final String KEY_STYLE_OVERRIDE = "overriding_styles"; private static final String KEY_DEBUG = "debug"; + public AppReleasePayload() { + } + + public AppReleasePayload(String json) throws JSONException { + super(json); + } + //region Http-request @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java index 7129e5548..0654a306d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java @@ -43,7 +43,7 @@ protected ApptentiveMessage() { initType(); } - protected ApptentiveMessage(String json) { + protected ApptentiveMessage(String json) throws JSONException { super(json); state = State.unknown; initType(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java index f70337b92..9a5ae9419 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java @@ -8,6 +8,8 @@ import com.apptentive.android.sdk.util.Util; +import org.json.JSONException; + import java.util.UUID; /** @@ -31,7 +33,7 @@ protected ConversationItem() { } - protected ConversationItem(String json) { + protected ConversationItem(String json) throws JSONException { super(json); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index 265d2c280..6278211a7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -41,6 +41,13 @@ public class DevicePayload extends JsonPayload { private static final String KEY_UTC_OFFSET = "utc_offset"; private static final String KEY_INTEGRATION_CONFIG = "integration_config"; + public DevicePayload() { + } + + public DevicePayload(String json) throws JSONException { + super(json); + } + //region Http-request @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index 012f2529a..b68d9a045 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -27,6 +27,10 @@ public class EventPayload extends ConversationItem { private static final String KEY_TRIGGER = "trigger"; private static final String KEY_CUSTOM_DATA = "custom_data"; + public EventPayload(String json) throws JSONException { + super(json); + } + public EventPayload(String label, JSONObject data) { super(); put(KEY_LABEL, label); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index 88759ef02..fb04a094b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -25,15 +25,8 @@ public JsonPayload() { jsonObject = new JSONObject(); } - public JsonPayload(String json) { - super(); - JSONObject temp = null; - try { - temp = new JSONObject(json); - } catch (JSONException e) { - ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error creating JsonPayload from json string.", e); - } - jsonObject = (temp == null ? null : temp); + public JsonPayload(String json) throws JSONException { + jsonObject = new JSONObject(json); } //region Data @@ -131,6 +124,16 @@ protected boolean isNull(String key) { // TODO: rename to containsKey return jsonObject.isNull(key); } + //endregion + + //region String Representation + + @Override + public String toString() { + return jsonObject.toString(); + } + + //endregion //region Getters/Setters diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java index 15022bf48..0d782a54a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java @@ -9,7 +9,15 @@ import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.util.StringUtils; +import org.json.JSONException; + public class LogoutPayload extends JsonPayload { + public LogoutPayload() { + } + + public LogoutPayload(String json) throws JSONException { + super(json); + } //region Http-request diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index 402406f78..bccc84e8a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -27,6 +27,13 @@ public class PersonPayload extends JsonPayload { private static final String KEY_BIRTHDAY = "birthday"; public static final String KEY_CUSTOM_DATA = "custom_data"; + public PersonPayload() { + } + + public PersonPayload(String json) throws JSONException { + super(json); + } + //region Http-request @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index 15e67b121..e1dca6c67 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -18,6 +18,8 @@ import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; +import org.json.JSONException; + /** * A combined payload of {@link SdkPayload} and {@link AppReleasePayload} payloads. * @@ -31,7 +33,6 @@ public class SdkAndAppReleasePayload extends JsonPayload { private final AppReleasePayload appRelease; public SdkAndAppReleasePayload() { - super(); sdk = new SdkPayload(); appRelease = new AppReleasePayload(); @@ -40,6 +41,13 @@ public SdkAndAppReleasePayload() { put("app_release", appRelease.getJsonObject()); } + public SdkAndAppReleasePayload(String json) throws JSONException { + super(json); + + sdk = new SdkPayload(getJSONObject("sdk").toString()); + appRelease = new AppReleasePayload(getJSONObject("app_release").toString()); + } + //region Http-request @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java index de991a49c..d747ee258 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java @@ -8,6 +8,8 @@ import com.apptentive.android.sdk.network.HttpRequestMethod; +import org.json.JSONException; + public class SdkPayload extends JsonPayload { public static final String KEY = "sdk"; @@ -20,6 +22,13 @@ public class SdkPayload extends JsonPayload { private static final String KEY_DISTRIBUTION = "distribution"; private static final String KEY_DISTRIBUTION_VERSION = "distribution_version"; + public SdkPayload() { + } + + public SdkPayload(String json) throws JSONException { + super(json); + } + //region Http-request @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index 3d3bb4b70..cc46058ec 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -23,8 +23,6 @@ public class SurveyResponsePayload extends ConversationItem { private static final String KEY_SURVEY_ANSWERS = "answers"; public SurveyResponsePayload(SurveyInteraction definition, Map answers) { - super(); - try { put(KEY_SURVEY_ID, definition.getId()); JSONObject answersJson = new JSONObject(); @@ -38,6 +36,10 @@ public SurveyResponsePayload(SurveyInteraction definition, Map a } } + public SurveyResponsePayload(String json) throws JSONException { + super(json); + } + //region Http-request @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java index 1ee97066c..247079f2d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java @@ -41,7 +41,7 @@ public static ApptentiveMessage fromJson(String json) { } catch (JSONException e) { // Ignore, senderId would be null } - String personId = ApptentiveInternal.getInstance().getPersonId(); + String personId = ApptentiveInternal.getInstance() != null ? ApptentiveInternal.getInstance().getPersonId() : null; // If senderId is null or same as the locally stored id, construct message as outgoing return new CompoundMessage(json, (senderId == null || (personId != null && senderId.equals(personId)))); case unknown: diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index ebbbcc60e..72a5abcf0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -15,13 +15,13 @@ import android.text.TextUtils; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.model.OutgoingPayload; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.PayloadType; import com.apptentive.android.sdk.model.StoredFile; -import com.apptentive.android.sdk.model.ApptentiveMessage; -import com.apptentive.android.sdk.model.CompoundMessage; -import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.storage.legacy.LegacyPayloadFactory; import org.json.JSONException; import org.json.JSONObject; @@ -45,35 +45,57 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { //region Payload SQL - public static final String TABLE_PAYLOAD = "payload"; - public static final String PAYLOAD_KEY_DB_ID = "_id"; // 0 - public static final String PAYLOAD_KEY_BASE_TYPE = "base_type"; // 1 - public static final String PAYLOAD_KEY_JSON = "json"; // 2 - private static final String PAYLOAD_KEY_CONVERSATION_ID = "conversation_id"; // 3 - private static final String PAYLOAD_KEY_AUTH_TOKEN = "auth_token"; // 4 + private static final class PayloadEntry { + static final String TABLE_NAME = "pending_payload"; + static final DatabaseColumn COLUMN_PRIMARY_KEY = new DatabaseColumn(0, "_id"); + static final DatabaseColumn COLUMN_IDENTIFIER = new DatabaseColumn(1, "identifier"); + static final DatabaseColumn COLUMN_API_VERSION = new DatabaseColumn(2, "api_version"); + static final DatabaseColumn COLUMN_CONTENT_TYPE = new DatabaseColumn(3, "content_type"); + static final DatabaseColumn COLUMN_REQUEST_METHOD = new DatabaseColumn(4, "request_method"); + static final DatabaseColumn COLUMN_PATH = new DatabaseColumn(5, "path"); + static final DatabaseColumn COLUMN_DATA = new DatabaseColumn(6, "data"); + } + + private static final class LegacyPayloadEntry { + static final String TABLE_NAME = "payload"; + static final DatabaseColumn PAYLOAD_KEY_DB_ID = new DatabaseColumn(0, "_id"); + static final DatabaseColumn PAYLOAD_KEY_BASE_TYPE = new DatabaseColumn(1, "base_type"); + static final DatabaseColumn PAYLOAD_KEY_JSON = new DatabaseColumn(2, "json"); + } private static final String TABLE_CREATE_PAYLOAD = - "CREATE TABLE " + TABLE_PAYLOAD + + "CREATE TABLE " + PayloadEntry.TABLE_NAME + " (" + - PAYLOAD_KEY_DB_ID + " INTEGER PRIMARY KEY, " + - PAYLOAD_KEY_BASE_TYPE + " TEXT, " + - PAYLOAD_KEY_JSON + " TEXT," + - PAYLOAD_KEY_CONVERSATION_ID + " TEXT," + - PAYLOAD_KEY_AUTH_TOKEN + " TEXT" + + PayloadEntry.COLUMN_PRIMARY_KEY + " INTEGER PRIMARY KEY, " + + PayloadEntry.COLUMN_IDENTIFIER + " TEXT, " + + PayloadEntry.COLUMN_API_VERSION + " INT," + + PayloadEntry.COLUMN_CONTENT_TYPE + " TEXT," + + PayloadEntry.COLUMN_REQUEST_METHOD + " TEXT," + + PayloadEntry.COLUMN_PATH + " TEXT," + + PayloadEntry.COLUMN_DATA + " BLOB" + ");"; - public static final String QUERY_PAYLOAD_GET_NEXT_TO_SEND = "SELECT * FROM " + TABLE_PAYLOAD + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC LIMIT 1"; - private static final String QUERY_UPDATE_INCOMPLETE_PAYLOADS = StringUtils.format("UPDATE %s SET %s = ?, %s = ? WHERE %s IS NULL OR %s IS NULL", TABLE_PAYLOAD, PAYLOAD_KEY_CONVERSATION_ID, PAYLOAD_KEY_AUTH_TOKEN, PAYLOAD_KEY_CONVERSATION_ID, PAYLOAD_KEY_AUTH_TOKEN); + private static final String SQL_QUERY_PAYLOAD_GET_LEGACY = + "SELECT * FROM " + LegacyPayloadEntry.TABLE_NAME + + " ORDER BY " + LegacyPayloadEntry.PAYLOAD_KEY_DB_ID; + + private static final String SQL_QUERY_PAYLOAD_GET_NEXT_TO_SEND = + "SELECT * FROM " + PayloadEntry.TABLE_NAME + + " ORDER BY " + PayloadEntry.COLUMN_PRIMARY_KEY + + " ASC LIMIT 1"; - private static final String QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER = "SELECT * FROM " + TABLE_PAYLOAD + " WHERE " + PAYLOAD_KEY_BASE_TYPE + " = ?" + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC"; - private static final String UPGRADE_V2_to_v3_ALTER_PAYLOAD_ADD_CONVERSATION_ID = "ALTER TABLE " + TABLE_PAYLOAD + " ADD COLUMN " + PAYLOAD_KEY_CONVERSATION_ID + " TEXT"; - private static final String UPGRADE_V2_to_v3_ALTER_PAYLOAD_ADD_TOKEN = "ALTER TABLE " + TABLE_PAYLOAD + " ADD COLUMN " + PAYLOAD_KEY_AUTH_TOKEN + " TEXT"; + private static final String SQL_QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER = + "SELECT * FROM " + PayloadEntry.TABLE_NAME + + " WHERE " + LegacyPayloadEntry.PAYLOAD_KEY_BASE_TYPE + " = ?" + + " ORDER BY " + PayloadEntry.COLUMN_PRIMARY_KEY + + " ASC"; //endregion - //region Message SQL + //region Message SQL (region) private static final String TABLE_MESSAGE = "message"; + private static final String MESSAGE_KEY_DB_ID = "_id"; // 0 private static final String MESSAGE_KEY_ID = "id"; // 1 private static final String MESSAGE_KEY_CLIENT_CREATED_AT = "client_created_at"; // 2 @@ -102,7 +124,7 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { //endregion - //region File SQL + //region File SQL (legacy) private static final String TABLE_FILESTORE = "file_store"; private static final String FILESTORE_KEY_ID = "id"; // 0 @@ -122,7 +144,8 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { //endregion - //region Compound Message FileStore SQL + //region Compound Message FileStore SQL (legacy) + /* Compound Message FileStore: * For Compound Messages stored in TABLE_MESSAGE, each associated file will add a row to this table * using the message's "nonce" key @@ -153,14 +176,8 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { // endregion - - public ApptentiveDatabaseHelper(Context context) { - this(context, DATABASE_NAME, DATABASE_VERSION); - } - - /** Convenience constructor for unit testing */ - ApptentiveDatabaseHelper(Context context, String databaseName, int databaseVersion) { - super(context, databaseName, null, databaseVersion); + ApptentiveDatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); fileDir = context.getFilesDir(); } @@ -174,6 +191,8 @@ public ApptentiveDatabaseHelper(Context context) { public void onCreate(SQLiteDatabase db) { ApptentiveLog.d(DATABASE, "ApptentiveDatabase.onCreate(db)"); db.execSQL(TABLE_CREATE_PAYLOAD); + + // TODO: remove legacy tables db.execSQL(TABLE_CREATE_MESSAGE); db.execSQL(TABLE_CREATE_FILESTORE); db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); @@ -188,13 +207,13 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { ApptentiveLog.d(DATABASE, "ApptentiveDatabase.onUpgrade(db, %d, %d)", oldVersion, newVersion); switch (oldVersion) { case 1: - upgradeVersion1to2(db); + upgradeVersion1to3(db); case 2: upgradeVersion2to3(db); } } - private void upgradeVersion1to2(SQLiteDatabase db) { + private void upgradeVersion1to3(SQLiteDatabase db) { ApptentiveLog.i(DATABASE, "Upgrading Database from v1 to v2"); db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); Cursor cursor = null; @@ -236,7 +255,7 @@ private void upgradeVersion1to2(SQLiteDatabase db) { if (cursor.moveToFirst()) { do { String json = cursor.getString(6); - JSONObject root = null; + JSONObject root; boolean bUpdateRecord = false; try { root = new JSONObject(json); @@ -281,7 +300,7 @@ private void upgradeVersion1to2(SQLiteDatabase db) { // Migrate all pending payload messages // Migrate legacy message types to CompoundMessage Type try { - cursor = db.rawQuery(QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER, new String[]{PayloadType.message.name()}); + cursor = db.rawQuery(SQL_QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER, new String[]{PayloadType.message.name()}); if (cursor.moveToFirst()) { do { String json = cursor.getString(2); @@ -311,10 +330,10 @@ private void upgradeVersion1to2(SQLiteDatabase db) { break; } if (bUpdateRecord) { - String databaseId = cursor.getString(0); + String databaseId = cursor.getString(LegacyPayloadEntry.PAYLOAD_KEY_DB_ID.index); ContentValues messageValues = new ContentValues(); - messageValues.put(PAYLOAD_KEY_JSON, root.toString()); - db.update(TABLE_PAYLOAD, messageValues, PAYLOAD_KEY_DB_ID + " = ?", new String[]{databaseId}); + messageValues.put(LegacyPayloadEntry.PAYLOAD_KEY_JSON.name, root.toString()); + db.update(PayloadEntry.TABLE_NAME, messageValues, PayloadEntry.COLUMN_PRIMARY_KEY + " = ?", new String[]{databaseId}); } } catch (JSONException e) { ApptentiveLog.v(DATABASE, "Error parsing json as Message: %s", e, json); @@ -330,8 +349,49 @@ private void upgradeVersion1to2(SQLiteDatabase db) { private void upgradeVersion2to3(SQLiteDatabase db) { ApptentiveLog.i(DATABASE, "Upgrading Database from v2 to v3"); - db.execSQL(UPGRADE_V2_to_v3_ALTER_PAYLOAD_ADD_CONVERSATION_ID); - db.execSQL(UPGRADE_V2_to_v3_ALTER_PAYLOAD_ADD_TOKEN); + + Cursor cursor = null; + try { + cursor = db.rawQuery(SQL_QUERY_PAYLOAD_GET_LEGACY, null); + List payloads = new ArrayList<>(cursor.getCount()); + + Payload payload; + while (cursor.moveToNext()) { + PayloadType payloadType = PayloadType.parse(cursor.getString(1)); + String json = cursor.getString(LegacyPayloadEntry.PAYLOAD_KEY_JSON.index); + + payload = LegacyPayloadFactory.createPayload(payloadType, json); + payloads.add(payload); + } + + addPayload(payloads.toArray(new Payload[payloads.size()])); + + } catch (Exception e) { + ApptentiveLog.e(DATABASE, "getOldestUnsentPayload EXCEPTION: " + e.getMessage()); + } finally { + ensureClosed(cursor); + } + } + + List listPayloads(SQLiteDatabase db) throws JSONException { + Cursor cursor = null; + try { + cursor = db.rawQuery(SQL_QUERY_PAYLOAD_GET_LEGACY, null); + List payloads = new ArrayList<>(cursor.getCount()); + + Payload payload; + while (cursor.moveToNext()) { + PayloadType payloadType = PayloadType.parse(cursor.getString(1)); + String json = cursor.getString(LegacyPayloadEntry.PAYLOAD_KEY_JSON.index); + + payload = LegacyPayloadFactory.createPayload(payloadType, json); + payloads.add(payload); + } + + return payloads; + } finally { + ensureClosed(cursor); + } } //endregion @@ -343,16 +403,19 @@ private void upgradeVersion2to3(SQLiteDatabase db) { * a new message is added. */ public void addPayload(Payload... payloads) { - SQLiteDatabase db = null; + SQLiteDatabase db; try { db = getWritableDatabase(); db.beginTransaction(); for (Payload payload : payloads) { ContentValues values = new ContentValues(); - values.put(PAYLOAD_KEY_JSON, payload.toString()); - values.put(PAYLOAD_KEY_CONVERSATION_ID, payload.getConversationId()); - values.put(PAYLOAD_KEY_AUTH_TOKEN, payload.getAuthToken()); - db.insert(TABLE_PAYLOAD, null, values); + values.put(PayloadEntry.COLUMN_IDENTIFIER.name, payload.getNonce()); + values.put(PayloadEntry.COLUMN_API_VERSION.name, payload.getApiVersion()); + values.put(PayloadEntry.COLUMN_CONTENT_TYPE.name, payload.getContentType()); + values.put(PayloadEntry.COLUMN_REQUEST_METHOD.name, payload.getHttpRequestMethod().name()); + values.put(PayloadEntry.COLUMN_PATH.name, payload.getPath()); + values.put(PayloadEntry.COLUMN_DATA.name, payload.getData()); + db.insert(PayloadEntry.TABLE_NAME, null, values); } db.setTransactionSuccessful(); db.endTransaction(); @@ -363,10 +426,14 @@ public void addPayload(Payload... payloads) { public void deletePayload(Payload payload) { if (payload != null) { - SQLiteDatabase db = null; + SQLiteDatabase db; try { db = getWritableDatabase(); - db.delete(TABLE_PAYLOAD, PAYLOAD_KEY_DB_ID + " = ?", new String[]{Long.toString(payload.getDatabaseId())}); + db.delete( + PayloadEntry.TABLE_NAME, + PayloadEntry.COLUMN_PRIMARY_KEY + " = ?", + new String[]{Long.toString(payload.getDatabaseId())} + ); } catch (SQLException sqe) { ApptentiveLog.e(DATABASE, "deletePayload EXCEPTION: " + sqe.getMessage()); } @@ -374,10 +441,10 @@ public void deletePayload(Payload payload) { } public void deleteAllPayloads() { - SQLiteDatabase db = null; + SQLiteDatabase db; try { db = getWritableDatabase(); - db.delete(TABLE_PAYLOAD, "", null); + db.delete(PayloadEntry.TABLE_NAME, "", null); } catch (SQLException sqe) { ApptentiveLog.e(DATABASE, "deleteAllPayloads EXCEPTION: " + sqe.getMessage()); } @@ -385,11 +452,11 @@ public void deleteAllPayloads() { public Payload getOldestUnsentPayload() { - SQLiteDatabase db = null; + SQLiteDatabase db; Cursor cursor = null; try { db = getWritableDatabase(); - cursor = db.rawQuery(QUERY_PAYLOAD_GET_NEXT_TO_SEND, null); + cursor = db.rawQuery(SQL_QUERY_PAYLOAD_GET_NEXT_TO_SEND, null); OutgoingPayload payload = null; if (cursor.moveToFirst()) { long databaseId = Long.parseLong(cursor.getString(0)); @@ -413,23 +480,25 @@ public Payload getOldestUnsentPayload() { } public void updateIncompletePayloads(String conversationId, String token) { - if (StringUtils.isNullOrEmpty(conversationId)) { - throw new IllegalArgumentException("Conversation id is null or empty"); - } - if (StringUtils.isNullOrEmpty(token)) { - throw new IllegalArgumentException("Token is null or empty"); - } - Cursor cursor = null; - try { - SQLiteDatabase db = getWritableDatabase(); - cursor = db.rawQuery(QUERY_UPDATE_INCOMPLETE_PAYLOADS, new String[] { conversationId , token}); - cursor.moveToFirst(); // we need to move a cursor in order to update database - ApptentiveLog.v(DATABASE, "Updated missing conversation ids"); - } catch (SQLException e) { - ApptentiveLog.e(e, "Exception while updating missing conversation ids"); - } finally { - ensureClosed(cursor); - } +// if (StringUtils.isNullOrEmpty(conversationId)) { +// throw new IllegalArgumentException("Conversation id is null or empty"); +// } +// if (StringUtils.isNullOrEmpty(token)) { +// throw new IllegalArgumentException("Token is null or empty"); +// } +// Cursor cursor = null; +// try { +// SQLiteDatabase db = getWritableDatabase(); +// cursor = db.rawQuery(QUERY_UPDATE_INCOMPLETE_PAYLOADS, new String[]{conversationId, token}); +// cursor.moveToFirst(); // we need to move a cursor in order to update database +// ApptentiveLog.v(DATABASE, "Updated missing conversation ids"); +// } catch (SQLException e) { +// ApptentiveLog.e(e, "Exception while updating missing conversation ids"); +// } finally { +// ensureClosed(cursor); +// } + + throw new RuntimeException("Implement me"); } //endregion @@ -574,4 +643,23 @@ public void reset(Context context) { } //endregion + + //region Helper classes + + private static final class DatabaseColumn { + public final String name; + public final int index; + + DatabaseColumn(int index, String name) { + this.index = index; + this.name = name; + } + + @Override + public String toString() { + return name; + } + } + + //endregion } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseLegacyHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseLegacyHelper.java new file mode 100644 index 000000000..748ce9386 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseLegacyHelper.java @@ -0,0 +1,615 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.text.TextUtils; + +import com.apptentive.android.sdk.ApptentiveInternal; +import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.CompoundMessage; +import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PayloadType; +import com.apptentive.android.sdk.model.StoredFile; +import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * There can be only one. SQLiteOpenHelper per database name that is. All new Apptentive tables must be defined here. + * + * @author Sky Kelsey + */ +public class ApptentiveDatabaseLegacyHelper extends SQLiteOpenHelper { + + // COMMON + private static final int DATABASE_VERSION = 2; + public static final String DATABASE_NAME = "apptentive"; + private static final int TRUE = 1; + private static final int FALSE = 0; + + // PAYLOAD + public static final String TABLE_PAYLOAD = "payload"; + public static final String PAYLOAD_KEY_DB_ID = "_id"; // 0 + public static final String PAYLOAD_KEY_BASE_TYPE = "base_type"; // 1 + public static final String PAYLOAD_KEY_JSON = "json"; // 2 + + private static final String TABLE_CREATE_PAYLOAD = + "CREATE TABLE " + TABLE_PAYLOAD + + " (" + + PAYLOAD_KEY_DB_ID + " INTEGER PRIMARY KEY, " + + PAYLOAD_KEY_BASE_TYPE + " TEXT, " + + PAYLOAD_KEY_JSON + " TEXT" + + ");"; + + public static final String QUERY_PAYLOAD_GET_NEXT_TO_SEND = "SELECT * FROM " + TABLE_PAYLOAD + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC LIMIT 1"; + + private static final String QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER = "SELECT * FROM " + TABLE_PAYLOAD + " WHERE " + PAYLOAD_KEY_BASE_TYPE + " = ?" + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC"; + + + // MESSAGE + private static final String TABLE_MESSAGE = "message"; + private static final String MESSAGE_KEY_DB_ID = "_id"; // 0 + private static final String MESSAGE_KEY_ID = "id"; // 1 + private static final String MESSAGE_KEY_CLIENT_CREATED_AT = "client_created_at"; // 2 + private static final String MESSAGE_KEY_NONCE = "nonce"; // 3 + private static final String MESSAGE_KEY_STATE = "state"; // 4 + private static final String MESSAGE_KEY_READ = "read"; // 5 + private static final String MESSAGE_KEY_JSON = "json"; // 6 + + private static final String TABLE_CREATE_MESSAGE = + "CREATE TABLE " + TABLE_MESSAGE + + " (" + + MESSAGE_KEY_DB_ID + " INTEGER PRIMARY KEY, " + + MESSAGE_KEY_ID + " TEXT, " + + MESSAGE_KEY_CLIENT_CREATED_AT + " DOUBLE, " + + MESSAGE_KEY_NONCE + " TEXT, " + + MESSAGE_KEY_STATE + " TEXT, " + + MESSAGE_KEY_READ + " INTEGER, " + + MESSAGE_KEY_JSON + " TEXT" + + ");"; + + private static final String QUERY_MESSAGE_GET_BY_NONCE = "SELECT * FROM " + TABLE_MESSAGE + " WHERE " + MESSAGE_KEY_NONCE + " = ?"; + // Coalesce returns the second arg if the first is null. This forces the entries with null IDs to be ordered last in the list until they do have IDs because they were sent and retrieved from the server. + private static final String QUERY_MESSAGE_GET_ALL_IN_ORDER = "SELECT * FROM " + TABLE_MESSAGE + " ORDER BY COALESCE(" + MESSAGE_KEY_ID + ", 'z') ASC"; + private static final String QUERY_MESSAGE_GET_LAST_ID = "SELECT " + MESSAGE_KEY_ID + " FROM " + TABLE_MESSAGE + " WHERE " + MESSAGE_KEY_STATE + " = '" + ApptentiveMessage.State.saved + "' AND " + MESSAGE_KEY_ID + " NOTNULL ORDER BY " + MESSAGE_KEY_ID + " DESC LIMIT 1"; + private static final String QUERY_MESSAGE_UNREAD = "SELECT " + MESSAGE_KEY_ID + " FROM " + TABLE_MESSAGE + " WHERE " + MESSAGE_KEY_READ + " = " + FALSE + " AND " + MESSAGE_KEY_ID + " NOTNULL"; + + // FileStore + private static final String TABLE_FILESTORE = "file_store"; + private static final String FILESTORE_KEY_ID = "id"; // 0 + private static final String FILESTORE_KEY_MIME_TYPE = "mime_type"; // 1 + private static final String FILESTORE_KEY_ORIGINAL_URL = "original_uri"; // 2 + private static final String FILESTORE_KEY_LOCAL_URL = "local_uri"; // 3 + private static final String FILESTORE_KEY_APPTENTIVE_URL = "apptentive_uri"; // 4 + private static final String TABLE_CREATE_FILESTORE = + "CREATE TABLE " + TABLE_FILESTORE + + " (" + + FILESTORE_KEY_ID + " TEXT PRIMARY KEY, " + + FILESTORE_KEY_MIME_TYPE + " TEXT, " + + FILESTORE_KEY_ORIGINAL_URL + " TEXT, " + + FILESTORE_KEY_LOCAL_URL + " TEXT, " + + FILESTORE_KEY_APPTENTIVE_URL + " TEXT" + + ");"; + + /* Compound Message FileStore: + * For Compound Messages stored in TABLE_MESSAGE, each associated file will add a row to this table + * using the message's "nonce" key + */ + private static final String TABLE_COMPOUND_MESSAGE_FILESTORE = "compound_message_file_store"; // table filePath + private static final String COMPOUND_FILESTORE_KEY_DB_ID = "_id"; // 0 + private static final String COMPOUND_FILESTORE_KEY_MESSAGE_NONCE = "nonce"; // message nonce of the compound message + private static final String COMPOUND_FILESTORE_KEY_MIME_TYPE = "mime_type"; // mine type of the file + private static final String COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI = "local_uri"; // original uriString or file path of source file (empty for received file) + private static final String COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH = "local_path"; // path to the local cached version + private static final String COMPOUND_FILESTORE_KEY_REMOTE_URL = "apptentive_url"; // original server url of received file (empty for sent file) + private static final String COMPOUND_FILESTORE_KEY_CREATION_TIME = "creation_time"; // creation time of the original file + // Create the initial table. Use nonce and local cache path as primary key because both sent/received files will have a local cached copy + private static final String TABLE_CREATE_COMPOUND_FILESTORE = + "CREATE TABLE " + TABLE_COMPOUND_MESSAGE_FILESTORE + + " (" + + COMPOUND_FILESTORE_KEY_DB_ID + " INTEGER PRIMARY KEY, " + + COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " TEXT, " + + COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH + " TEXT, " + + COMPOUND_FILESTORE_KEY_MIME_TYPE + " TEXT, " + + COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI + " TEXT, " + + COMPOUND_FILESTORE_KEY_REMOTE_URL + " TEXT, " + + COMPOUND_FILESTORE_KEY_CREATION_TIME + " LONG" + + ");"; + + // Query all files associated with a given compound message nonce id + private static final String QUERY_MESSAGE_FILES_GET_BY_NONCE = "SELECT * FROM " + TABLE_COMPOUND_MESSAGE_FILESTORE + " WHERE " + COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?"; + + private File fileDir; // data dir of the application + + + public void ensureClosed(SQLiteDatabase db) { + try { + if (db != null) { + db.close(); + } + } catch (Exception e) { + ApptentiveLog.w("Error closing SQLite database.", e); + } + } + + public void ensureClosed(Cursor cursor) { + try { + if (cursor != null) { + cursor.close(); + } + } catch (Exception e) { + ApptentiveLog.w("Error closing SQLite cursor.", e); + } + } + + public ApptentiveDatabaseLegacyHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + fileDir = context.getFilesDir(); + } + + /** + * This function is called only for new installs, and onUpgrade is not called in that case. Therefore, you must include the + * latest complete set of DDL here. + */ + @Override + public void onCreate(SQLiteDatabase db) { + ApptentiveLog.d("ApptentiveDatabase.onCreate(db)"); + db.execSQL(TABLE_CREATE_PAYLOAD); + db.execSQL(TABLE_CREATE_MESSAGE); + db.execSQL(TABLE_CREATE_FILESTORE); + db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); + } + + /** + * This method is called when an app is upgraded. Add alter table statements here for each version in a non-breaking + * switch, so that all the necessary upgrades occur for each older version. + */ + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + ApptentiveLog.d("ApptentiveDatabase.onUpgrade(db, %d, %d)", oldVersion, newVersion); + switch (oldVersion) { + case 1: + if (newVersion == 2) { + db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); + migrateToCompoundMessage(db); + } + } + } + + // PAYLOAD: This table is used to store all the Payloads we want to send to the server. + + /** + * If an item with the same nonce as an item passed in already exists, it is overwritten by the item. Otherwise + * a new message is added. + */ + public void addPayload(Payload payload, PayloadType type) { + SQLiteDatabase db = null; + try { + db = getWritableDatabase(); + db.beginTransaction(); + { + ContentValues values = new ContentValues(); + values.put(PAYLOAD_KEY_BASE_TYPE, type.name()); + values.put(PAYLOAD_KEY_JSON, payload.toString()); + db.insert(TABLE_PAYLOAD, null, values); + } + db.setTransactionSuccessful(); + db.endTransaction(); + } catch (SQLException sqe) { + ApptentiveLog.e("addPayload EXCEPTION: " + sqe.getMessage()); + } + } + + public void deletePayload(Payload payload) { + if (payload != null) { + SQLiteDatabase db = null; + try { + db = getWritableDatabase(); + db.delete(TABLE_PAYLOAD, PAYLOAD_KEY_DB_ID + " = ?", new String[]{Long.toString(payload.getDatabaseId())}); + } catch (SQLException sqe) { + ApptentiveLog.e("deletePayload EXCEPTION: " + sqe.getMessage()); + } + } + } + + public void deleteAllPayloads() { + SQLiteDatabase db = null; + try { + db = getWritableDatabase(); + db.delete(TABLE_PAYLOAD, "", null); + } catch (SQLException sqe) { + ApptentiveLog.e("deleteAllPayloads EXCEPTION: " + sqe.getMessage()); + } + } + + // MessageStore + + /** + * Saves the message into the message table, and also into the payload table so it can be sent to the server. + */ + public void addOrUpdateMessages(ApptentiveMessage... apptentiveMessages) { + SQLiteDatabase db = null; + try { + db = getWritableDatabase(); + for (ApptentiveMessage apptentiveMessage : apptentiveMessages) { + Cursor cursor = null; + try { + cursor = db.rawQuery(QUERY_MESSAGE_GET_BY_NONCE, new String[]{apptentiveMessage.getNonce()}); + if (cursor.moveToFirst()) { + // Update + String databaseId = cursor.getString(0); + ContentValues messageValues = new ContentValues(); + messageValues.put(MESSAGE_KEY_ID, apptentiveMessage.getId()); + messageValues.put(MESSAGE_KEY_STATE, apptentiveMessage.getState().name()); + if (apptentiveMessage.isRead()) { // A apptentiveMessage can't be unread after being read. + messageValues.put(MESSAGE_KEY_READ, TRUE); + } + messageValues.put(MESSAGE_KEY_JSON, apptentiveMessage.toString()); + db.update(TABLE_MESSAGE, messageValues, MESSAGE_KEY_DB_ID + " = ?", new String[]{databaseId}); + } else { + // Insert + db.beginTransaction(); + ContentValues messageValues = new ContentValues(); + messageValues.put(MESSAGE_KEY_ID, apptentiveMessage.getId()); + messageValues.put(MESSAGE_KEY_CLIENT_CREATED_AT, apptentiveMessage.getClientCreatedAt()); + messageValues.put(MESSAGE_KEY_NONCE, apptentiveMessage.getNonce()); + messageValues.put(MESSAGE_KEY_STATE, apptentiveMessage.getState().name()); + messageValues.put(MESSAGE_KEY_READ, apptentiveMessage.isRead() ? TRUE : FALSE); + messageValues.put(MESSAGE_KEY_JSON, apptentiveMessage.toString()); + db.insert(TABLE_MESSAGE, null, messageValues); + db.setTransactionSuccessful(); + db.endTransaction(); + } + } finally { + ensureClosed(cursor); + } + } + } catch (SQLException sqe) { + ApptentiveLog.e("addOrUpdateMessages EXCEPTION: " + sqe.getMessage()); + } + } + + public void updateMessage(ApptentiveMessage apptentiveMessage) { + SQLiteDatabase db = null; + try { + db = getWritableDatabase(); + db.beginTransaction(); + ContentValues values = new ContentValues(); + values.put(MESSAGE_KEY_ID, apptentiveMessage.getId()); + values.put(MESSAGE_KEY_CLIENT_CREATED_AT, apptentiveMessage.getClientCreatedAt()); + values.put(MESSAGE_KEY_NONCE, apptentiveMessage.getNonce()); + values.put(MESSAGE_KEY_STATE, apptentiveMessage.getState().name()); + if (apptentiveMessage.isRead()) { // A apptentiveMessage can't be unread after being read. + values.put(MESSAGE_KEY_READ, TRUE); + } + values.put(MESSAGE_KEY_JSON, apptentiveMessage.toString()); + db.update(TABLE_MESSAGE, values, MESSAGE_KEY_NONCE + " = ?", new String[]{apptentiveMessage.getNonce()}); + db.setTransactionSuccessful(); + } catch (SQLException sqe) { + ApptentiveLog.e("updateMessage EXCEPTION: " + sqe.getMessage()); + } finally { + if (db != null) { + db.endTransaction(); + } + } + } + + public List getAllMessages() { + List apptentiveMessages = new ArrayList(); + SQLiteDatabase db = null; + Cursor cursor = null; + try { + db = getReadableDatabase(); + cursor = db.rawQuery(QUERY_MESSAGE_GET_ALL_IN_ORDER, null); + if (cursor.moveToFirst()) { + do { + String json = cursor.getString(6); + ApptentiveMessage apptentiveMessage = MessageFactory.fromJson(json); + if (apptentiveMessage == null) { + ApptentiveLog.e("Error parsing Record json from database: %s", json); + continue; + } + apptentiveMessage.setDatabaseId(cursor.getLong(0)); + apptentiveMessage.setState(ApptentiveMessage.State.parse(cursor.getString(4))); + apptentiveMessage.setRead(cursor.getInt(5) == TRUE); + apptentiveMessages.add(apptentiveMessage); + } while (cursor.moveToNext()); + } + } catch (SQLException sqe) { + ApptentiveLog.e("getAllMessages EXCEPTION: " + sqe.getMessage()); + } finally { + ensureClosed(cursor); + } + return apptentiveMessages; + } + + public synchronized String getLastReceivedMessageId() { + SQLiteDatabase db = null; + Cursor cursor = null; + String ret = null; + try { + db = getReadableDatabase(); + cursor = db.rawQuery(QUERY_MESSAGE_GET_LAST_ID, null); + if (cursor.moveToFirst()) { + ret = cursor.getString(0); + } + } catch (SQLException sqe) { + ApptentiveLog.e("getLastReceivedMessageId EXCEPTION: " + sqe.getMessage()); + } finally { + ensureClosed(cursor); + } + return ret; + } + + public synchronized int getUnreadMessageCount() { + SQLiteDatabase db = null; + Cursor cursor = null; + try { + db = getWritableDatabase(); + cursor = db.rawQuery(QUERY_MESSAGE_UNREAD, null); + return cursor.getCount(); + } catch (SQLException sqe) { + ApptentiveLog.e("getUnreadMessageCount EXCEPTION: " + sqe.getMessage()); + return 0; + } finally { + ensureClosed(cursor); + } + } + + public synchronized void deleteAllMessages() { + SQLiteDatabase db = null; + try { + db = getWritableDatabase(); + db.delete(TABLE_MESSAGE, "", null); + } catch (SQLException sqe) { + ApptentiveLog.e("deleteAllMessages EXCEPTION: " + sqe.getMessage()); + } + } + + public synchronized void deleteMessage(String nonce) { + SQLiteDatabase db = null; + try { + db = getWritableDatabase(); + int deleted = db.delete(TABLE_MESSAGE, MESSAGE_KEY_NONCE + " = ?", new String[]{nonce}); + ApptentiveLog.d("Deleted %d messages.", deleted); + } catch (SQLException sqe) { + ApptentiveLog.e("deleteMessage EXCEPTION: " + sqe.getMessage()); + } + } + + + // + // File Store + // + private void migrateToCompoundMessage(SQLiteDatabase db) { + Cursor cursor = null; + // Migrate legacy stored files to compound message associated files + try { + cursor = db.rawQuery("SELECT * FROM " + TABLE_FILESTORE, null); + if (cursor.moveToFirst()) { + do { + String file_nonce = cursor.getString(0); + // Stored File id was in the format of "apptentive-file-nonce" + String patten = "apptentive-file-"; + String nonce = file_nonce.substring(file_nonce.indexOf(patten) + patten.length()); + ContentValues values = new ContentValues(); + values.put(COMPOUND_FILESTORE_KEY_MESSAGE_NONCE, nonce); + // Legacy file was stored in db by name only. Need to get the full path when migrated + String localFileName = cursor.getString(3); + values.put(COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH, (new File(fileDir, localFileName).getAbsolutePath())); + values.put(COMPOUND_FILESTORE_KEY_MIME_TYPE, cursor.getString(1)); + // Original file name might not be stored, i.e. sent by API, in which case, local stored file name will be used. + String originalFileName = cursor.getString(2); + if (TextUtils.isEmpty(originalFileName)) { + originalFileName = localFileName; + } + values.put(COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI, originalFileName); + values.put(COMPOUND_FILESTORE_KEY_REMOTE_URL, cursor.getString(4)); + values.put(COMPOUND_FILESTORE_KEY_CREATION_TIME, 0); // we didn't store creation time of legacy file message + db.insert(TABLE_COMPOUND_MESSAGE_FILESTORE, null, values); + + } while (cursor.moveToNext()); + } + } catch (SQLException sqe) { + ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); + } finally { + ensureClosed(cursor); + } + // Migrate legacy message types to CompoundMessage Type + try { + cursor = db.rawQuery(QUERY_MESSAGE_GET_ALL_IN_ORDER, null); + if (cursor.moveToFirst()) { + do { + String json = cursor.getString(6); + JSONObject root = null; + boolean bUpdateRecord = false; + try { + root = new JSONObject(json); + ApptentiveMessage.Type type = ApptentiveMessage.Type.valueOf(root.getString(ApptentiveMessage.KEY_TYPE)); + switch (type) { + case TextMessage: + root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); + root.put(CompoundMessage.KEY_TEXT_ONLY, true); + bUpdateRecord = true; + break; + case FileMessage: + root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); + root.put(CompoundMessage.KEY_TEXT_ONLY, false); + bUpdateRecord = true; + break; + case AutomatedMessage: + root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); + root.put(CompoundMessage.KEY_TEXT_ONLY, true); + root.put(ApptentiveMessage.KEY_AUTOMATED, true); + bUpdateRecord = true; + break; + default: + break; + } + if (bUpdateRecord) { + String databaseId = cursor.getString(0); + ContentValues messageValues = new ContentValues(); + messageValues.put(MESSAGE_KEY_JSON, root.toString()); + db.update(TABLE_MESSAGE, messageValues, MESSAGE_KEY_DB_ID + " = ?", new String[]{databaseId}); + } + } catch (JSONException e) { + ApptentiveLog.v("Error parsing json as Message: %s", e, json); + } + } while (cursor.moveToNext()); + } + } catch (SQLException sqe) { + ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); + } finally { + ensureClosed(cursor); + } + + // Migrate all pending payload messages + // Migrate legacy message types to CompoundMessage Type + try { + cursor = db.rawQuery(QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER, new String[]{}); + if (cursor.moveToFirst()) { + do { + String json = cursor.getString(2); + JSONObject root; + boolean bUpdateRecord = false; + try { + root = new JSONObject(json); + ApptentiveMessage.Type type = ApptentiveMessage.Type.valueOf(root.getString(ApptentiveMessage.KEY_TYPE)); + switch (type) { + case TextMessage: + root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); + root.put(CompoundMessage.KEY_TEXT_ONLY, true); + bUpdateRecord = true; + break; + case FileMessage: + root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); + root.put(CompoundMessage.KEY_TEXT_ONLY, false); + bUpdateRecord = true; + break; + case AutomatedMessage: + root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); + root.put(CompoundMessage.KEY_TEXT_ONLY, true); + root.put(ApptentiveMessage.KEY_AUTOMATED, true); + bUpdateRecord = true; + break; + default: + break; + } + if (bUpdateRecord) { + String databaseId = cursor.getString(0); + ContentValues messageValues = new ContentValues(); + messageValues.put(PAYLOAD_KEY_JSON, root.toString()); + db.update(TABLE_PAYLOAD, messageValues, PAYLOAD_KEY_DB_ID + " = ?", new String[]{databaseId}); + } + } catch (JSONException e) { + ApptentiveLog.v("Error parsing json as Message: %s", e, json); + } + } while (cursor.moveToNext()); + } + } catch (SQLException sqe) { + ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); + } finally { + ensureClosed(cursor); + } + } + + public void deleteAssociatedFiles(String messageNonce) { + SQLiteDatabase db = null; + try { + db = getWritableDatabase(); + int deleted = db.delete(TABLE_COMPOUND_MESSAGE_FILESTORE, COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?", new String[]{messageNonce}); + ApptentiveLog.d("Deleted %d stored files.", deleted); + } catch (SQLException sqe) { + ApptentiveLog.e("deleteAssociatedFiles EXCEPTION: " + sqe.getMessage()); + } + } + + public List getAssociatedFiles(String nonce) { + SQLiteDatabase db = null; + Cursor cursor = null; + List associatedFiles = new ArrayList(); + try { + db = getReadableDatabase(); + cursor = db.rawQuery(QUERY_MESSAGE_FILES_GET_BY_NONCE, new String[]{nonce}); + StoredFile ret; + if (cursor.moveToFirst()) { + do { + ret = new StoredFile(); + ret.setId(nonce); + ret.setLocalFilePath(cursor.getString(2)); + ret.setMimeType(cursor.getString(3)); + ret.setSourceUriOrPath(cursor.getString(4)); + ret.setApptentiveUri(cursor.getString(5)); + ret.setCreationTime(cursor.getLong(6)); + associatedFiles.add(ret); + } while (cursor.moveToNext()); + } + } catch (SQLException sqe) { + ApptentiveLog.e("getAssociatedFiles EXCEPTION: " + sqe.getMessage()); + } finally { + ensureClosed(cursor); + } + return associatedFiles.size() > 0 ? associatedFiles : null; + } + + /* + * Add a list of associated files to compound message file storage + * Caller of this method should ensure all associated files have the same message nonce + * @param associatedFiles list of associated files + * @return true if succeed + */ + public boolean addCompoundMessageFiles(List associatedFiles) { + String messageNonce = associatedFiles.get(0).getId(); + SQLiteDatabase db = null; + long ret = -1; + try { + + db = getWritableDatabase(); + db.beginTransaction(); + // Always delete existing rows with the same nonce to ensure add/update both work + db.delete(TABLE_COMPOUND_MESSAGE_FILESTORE, COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?", new String[]{messageNonce}); + + for (StoredFile file : associatedFiles) { + ContentValues values = new ContentValues(); + values.put(COMPOUND_FILESTORE_KEY_MESSAGE_NONCE, file.getId()); + values.put(COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH, file.getLocalFilePath()); + values.put(COMPOUND_FILESTORE_KEY_MIME_TYPE, file.getMimeType()); + values.put(COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI, file.getSourceUriOrPath()); + values.put(COMPOUND_FILESTORE_KEY_REMOTE_URL, file.getApptentiveUri()); + values.put(COMPOUND_FILESTORE_KEY_CREATION_TIME, file.getCreationTime()); + ret = db.insert(TABLE_COMPOUND_MESSAGE_FILESTORE, null, values); + } + db.setTransactionSuccessful(); + db.endTransaction(); + } catch (SQLException sqe) { + ApptentiveLog.e("addCompoundMessageFiles EXCEPTION: " + sqe.getMessage()); + } finally { + return ret != -1; + } + } + + public void reset(Context context) { + /** + * The following ONLY be used during development and testing. It will delete the database, including all saved + * payloads, messages, and files. + */ + context.deleteDatabase(DATABASE_NAME); + } + +} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/LegacyPayloadFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/LegacyPayloadFactory.java new file mode 100644 index 000000000..9b7d6cf54 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/LegacyPayloadFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage.legacy; + +import com.apptentive.android.sdk.model.AppReleasePayload; +import com.apptentive.android.sdk.model.DevicePayload; +import com.apptentive.android.sdk.model.EventPayload; +import com.apptentive.android.sdk.model.LogoutPayload; +import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PayloadType; +import com.apptentive.android.sdk.model.PersonPayload; +import com.apptentive.android.sdk.model.SdkAndAppReleasePayload; +import com.apptentive.android.sdk.model.SdkPayload; +import com.apptentive.android.sdk.model.SurveyResponsePayload; +import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; + +import org.json.JSONException; + +/** + * We only keep this class for legacy database migration purposes + */ +public final class LegacyPayloadFactory { + public static Payload createPayload(PayloadType type, String json) throws JSONException { + switch (type) { + case message: + return MessageFactory.fromJson(json); + case event: + return new EventPayload(json); + case device: + return new DevicePayload(json); + case sdk: + return new SdkPayload(json); + case app_release: + return new AppReleasePayload(json); + case sdk_and_app_release: + return new SdkAndAppReleasePayload(json); + case person: + return new PersonPayload(json); + case logout: + return new LogoutPayload(json); + case survey: + return new SurveyResponsePayload(json); + default: + throw new IllegalArgumentException("Unexpected payload type: " + type); + } + } +} From b4dd08c78959d2ee5f81e81be40392a78e96b1fc Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 21 Apr 2017 13:05:01 -0700 Subject: [PATCH 249/465] Refactored payload storage and sending --- .../src/androidTest/assets/apptentive-v2 | Bin 32768 -> 32768 bytes .../storage/ApptentiveDatabaseHelperTest.java | 54 +- .../sdk/comm/ApptentiveHttpClient.java | 8 +- .../apptentive/android/sdk/debug/Assert.java | 6 + .../android/sdk/model/AppReleasePayload.java | 2 +- .../android/sdk/model/CompoundMessage.java | 4 +- .../android/sdk/model/ConversationItem.java | 10 +- .../android/sdk/model/DevicePayload.java | 4 +- .../android/sdk/model/EventPayload.java | 4 +- .../android/sdk/model/JsonPayload.java | 2 +- .../android/sdk/model/LogoutPayload.java | 4 +- .../android/sdk/model/OutgoingPayload.java | 41 -- .../apptentive/android/sdk/model/Payload.java | 119 +--- .../android/sdk/model/PayloadData.java | 69 ++ .../android/sdk/model/PayloadType.java | 3 - .../android/sdk/model/PersonPayload.java | 4 +- .../sdk/model/SdkAndAppReleasePayload.java | 4 +- .../android/sdk/model/SdkPayload.java | 2 +- .../sdk/model/SurveyResponsePayload.java | 4 +- .../messagecenter/model/MessageFactory.java | 5 +- .../sdk/storage/ApptentiveDatabaseHelper.java | 213 +++--- .../ApptentiveDatabaseLegacyHelper.java | 615 ------------------ .../sdk/storage/ApptentiveTaskManager.java | 54 +- .../sdk/storage/PayloadRequestSender.java | 4 +- .../android/sdk/storage/PayloadSender.java | 15 +- .../android/sdk/storage/PayloadStore.java | 8 +- .../storage/legacy/LegacyPayloadFactory.java | 9 +- .../sdk/storage/JsonPayloadSenderTest.java | 23 +- 28 files changed, 324 insertions(+), 966 deletions(-) delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/OutgoingPayload.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseLegacyHelper.java diff --git a/apptentive/src/androidTest/assets/apptentive-v2 b/apptentive/src/androidTest/assets/apptentive-v2 index 0ccb4b4a1bbaec5403b09759ef45e55db565eaf9..51b0ff1be4abcebeb112917b7f7648640fd2f344 100644 GIT binary patch delta 1156 zcma)5O-~b16m1Jdut3=uBVd$ap;4yu-n=&-uR)Mt5F!RP#E-;`Z|1$JgLJ0OOq-C3 zbkG$mNyFZS?j_y2;4knK{0YXyozXY2z#t*r-j8$7x#!+iE7xk}K8|Gzd*hSY++K1c zTR3?6SsCdo%w&p2v2XbK;H|-d!puQ(r!bu?_T@8U@AFk2MYO`3k{|dEm&y{=O{N3g zk%5V*j4{z=s&OE@z_1&s^Pk#2eg?PiNYM66mUiY#KI}vCW7qLO|9W9cU(j zriy8MMjP)(1|-B3Q;*18`J_`en zNmEiokn;lH@@X|qpb{=Y5%!oMe33`7?W^_~J>K z-Tu{u5jf(0ig&unE}G7|*Ew8SxY@s(DZ=4rL%W@ck+(yu$#Ty?W-XbUXb$yu0f`mi zb|}tB;nn&EjX6_*GIz3fCB1?6*LS1aDjQz&O^%}37 z2lrrp>9H~|oC~S4^!TBr0%!mXR0|p^Qc-EWR(bk;|97&MyL`+%DV(8hoPW&Sa39;Y zHH{h2gcu=*a22}@iC55V1M*bB#ne|lC$5J)QWmLS3F>jA9M{XI5=@}0_(Z~|kz_f0 zcKP=%ddF@QjB;YQ7(%3C?ie<;k;5QzOxJb*W4f+67fU#Q`w$BokRX3d{m1^7zgJCX AO#lD@ literal 32768 zcmeI5&2QVt6~L+3vYk(FP@qPTEvhJJH%3jFoFRv#Q502V$H~TV65IJQuqB2wLx(XX zDiq~78@PZrJrw;9dMbJXty?k;867#$??{1SXe2I>E{ zn$q;Y{r%#lUut6){;Ey>X0m?i*BAeINkRe_B!C2v01`j~NB{{S0VIF~kicmY=xvUT zPc<5)FFwn75G#>1+ex=2{if=4_=fVsC{}(aOVY!3O*B>(>T3&?wffDag~}nkwy?OcQhBg)=U#o~S>^7+vzbaOX$e(XTX?!QQ;8G7W53O_=Yx!9)Kq@k-)xdOvRe4CCLFOTL7-%_D5pe`a{eq&BJ`8>5 z?>_?j4x6fzaTpu-%?7;R8F!}gqLZ|W9n@cFP)iEE&tJKKK{Ra=C|79pC+$g_{W7mpa19iAD&x3n@;>;;^XnTv1jGq zmq$jgjXZ`!a6tlJBZ1!Z<+3*Q#a*=pkvngyMMzb3u4)j*Y}X5P7D}mGq3h|M;(;Cn zfv_za7|J%PGu1Bz{8Z=6n&lCPP-eLv@fN%T4)VL1@RKm?sH{4t6Pp;uOf}|#ilJl( z1*`3aJ6(s(>oM=P#PjM-@AgM!?dsd|AR=i`T4FdM2|3q820xZ-8#dG9#^_0U5DAsa16^iN)bxv$*~UsM58&F|F$0?@ZaaoyTatmXrY{}jSS|@YOIbn>LgMNc=TZ;6(AEhH zn5ztF1V(tYhJ+s9RL60wPcG)NF7C4ivjc}RD)dmAAgk+|df=E+r;f`#;aDNJj+E7O zEb~-kRohVpRDW_I_Zxekq^>l%L4>Uvf`O#W1i#@S)LGl+G>~@SO8W?^rpxRT3wjD} z!!@IbcZR6cq^(*|{8wpK{qck9hegaVkfEiU!VGoGGq~kZEV(%H&b8zSWY8tY!+~?FjOLCh-a~!(5ay;mf1aQv?2%$Qa4rk0zBs_*C!97o` zVeWIC=gF&g@$RCyNkZGaUM_1_-u^(Utw^Xhg;h7wemuEIT4A(NoqJP-(|6;}+uhFd zJS$5!!42lBia%_xHWpV`KCREyAJjcFxcxYhTerLG%iH%J*2Vo*v%N{5+IP*Tu{9ry z?Zp>2mC1sJ^Kvs>S{2Wvv%2}fNnX%93oFL@_LEk}zu#tCkGr34J&xudi1}rH>pp3c z=;p1MuCFeLEZeHzwBv-`wHoz#8YR!%7t!Yr?j_{bO8m0%@^cwQbgR0vv-9!jsCLC{ zs}w@C-&92x;`^I^9wrS6?Cku^7y;FP{$yA6{u)#Yx^es*V!Q`V{v{kc(DBP7_mwZ; z9fB-<4QrYAtRAiA?#k0NQYj=M(d`yJ4knDiv83S$gIVDAuqWt7u`FW!;mB?WPJmO} zT}be>$t-=#<&cwf{Ng~!rp$FH%};IeRyTwWO*buySz26x%#_P0@wY%%cohH+zvp_M zbxS8TR->#&Y9#Oa!sS}LnTuNQ2aZEtUywq~W$PAUYmoPTU$?3g*ERC9gCxoFkam^6 zSI*7b>!z7^vx0=DdGG2ydQcp1M}7>oBJU-W;&$Fm(<0vf9j5x)_wzDaDtncro4ZJs z)^e=|oKyY^&M!u(c}Xb*vO{_!NjBoVg@AJQ6nAQ7EvM8@6WJBeS$|)mCKxIgWm#=^ zI~DusUJ)^GZFKWDK?b%g+PM8L)0;k%SM&V?4@zChTxr0a-EnkFn6~bj(uSU5Xh`OT z0hk|3I%MEMJVNQ|+*)=A96fCP{L50XLKz%l!_h2@kXkgZu9iFeM1~VVb3YaWO%~DAnZkq64 zaxgcKr(k*tm_P_-NFjDeISHaBydgjPqOCSyZiswFh`HJRwfeIO)Zm@_tqA4^=+v4@ zM!Y5y(QU$Eo!Q;3+2Ru4qjgFR=Jw{#WW-Ps<}d>_q$jA$s7XyT2uvIPAn_E9VHwo^4=Ph_*8l(j diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java index c8fa09e94..5598f3778 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java @@ -7,18 +7,20 @@ package com.apptentive.android.sdk.storage; import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; -import com.apptentive.android.sdk.model.CompoundMessage; +import com.apptentive.android.sdk.model.AppReleasePayload; import com.apptentive.android.sdk.model.DevicePayload; import com.apptentive.android.sdk.model.EventPayload; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.PersonPayload; import com.apptentive.android.sdk.model.SdkPayload; import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; -import com.apptentive.android.sdk.storage.legacy.LegacyPayloadFactory; +import org.json.JSONException; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,6 +30,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.List; @RunWith(AndroidJUnit4.class) @@ -39,17 +42,17 @@ public void tearDown() throws Exception { } @Test - public void testFoo() throws Exception { + public void testMigration() throws Exception { final Context context = InstrumentationRegistry.getContext(); replaceDbFile(context, "apptentive-v2"); Payload[] expectedPayloads = { + new AppReleasePayload("{\"type\":\"android\",\"version_name\":\"3.4.1\",\"identifier\":\"com.apptentive.dev\",\"version_code\":51,\"target_sdk_version\":\"24\",\"inheriting_styles\":true,\"overriding_styles\":true,\"debug\":true}"), new SdkPayload("{\"version\":\"3.4.1\",\"platform\":\"Android\"}"), - new EventPayload("{\"nonce\":\"338d68d0-0777-4c15-91d5-4af0d69fbc0b\",\"client_created_at\":1.492723292335E9,\"client_created_at_utc_offset\":-25200,\"label\":\"com.apptentive#app#launch\"}"), + new EventPayload("{\"nonce\":\"b9a91f27-87b4-4bd9-b9a0-5c605891824a\",\"client_created_at\":1.492737199856E9,\"client_created_at_utc_offset\":-25200,\"label\":\"com.apptentive#app#launch\"}"), new DevicePayload("{\"device\":\"bullhead\",\"integration_config\":{},\"locale_country_code\":\"US\",\"carrier\":\"\",\"uuid\":\"6c0b74d07c064421\",\"build_type\":\"user\",\"cpu\":\"arm64-v8a\",\"os_build\":\"3687331\",\"manufacturer\":\"LGE\",\"radio_version\":\"M8994F-2.6.36.2.20\",\"os_name\":\"Android\",\"build_id\":\"N4F26T\",\"utc_offset\":\"-28800\",\"bootloader_version\":\"BHZ11h\",\"board\":\"bullhead\",\"os_api_level\":\"25\",\"current_carrier\":\"AT&T\",\"network_type\":\"LTE\",\"locale_raw\":\"en_US\",\"brand\":\"google\",\"os_version\":\"7.1.1\",\"product\":\"bullhead\",\"model\":\"Nexus 5X\",\"locale_language_code\":\"en\",\"custom_data\":{}}"), new PersonPayload("{\"custom_data\":{}}"), - new DevicePayload("{\"integration_config\":{\"apptentive_push\":{\"token\":\"eaQpSCGSRJA:APA91bHVodvHuZNxMQAcOS1pk3X5K1Xl4DlcxGjBe16bC7qkfLScYd7SkP7oj3IER0ZxWns_Op6vVuJvViDPcDNaFO2m2iBFl3ZSEcttvAB5lo6K4CAD3ioY8jizPMo2FRlqCqzdii3v\"}}}"), - MessageFactory.fromJson("{\"nonce\":\"207f2faa-f6aa-4850-addd-c552b79b8404\",\"client_created_at\":1.492723326164E9,\"client_created_at_utc_offset\":-25200,\"type\":\"CompoundMessage\",\"body\":\"Test message\",\"text_only\":false}"), + MessageFactory.fromJson("{\"nonce\":\"a68d606c-083a-4496-a5e0-f07bcdff52a4\",\"client_created_at\":1.492737257565E9,\"client_created_at_utc_offset\":-25200,\"type\":\"CompoundMessage\",\"body\":\"Test message\",\"text_only\":false}") }; } @@ -78,4 +81,43 @@ private static void deleteDbFile(Context context) throws IOException { private static File getDatabaseFile(Context context) { return context.getDatabasePath("apptentive"); } + + class ApptentiveDatabaseMockHelper extends ApptentiveDatabaseHelper { + private final String SQL_QUERY_PAYLOAD_LIST = + "SELECT * FROM " + PayloadEntry.TABLE_NAME + + " ORDER BY " + PayloadEntry.COLUMN_PRIMARY_KEY; + + ApptentiveDatabaseMockHelper(Context context) { + super(context, new DataSource() { + @Override + public String getConversationId() { + throw new RuntimeException("Implement me"); + } + + @Override + public String getAuthToken() { + throw new RuntimeException("Implement me"); + } + }); + } + + List listPayloads(SQLiteDatabase db) throws JSONException { + Cursor cursor = null; + try { + cursor = db.rawQuery(SQL_QUERY_PAYLOAD_LIST, null); + List payloads = new ArrayList<>(cursor.getCount()); + + Payload payload; + while (cursor.moveToNext()) { + // TODO: get data + } + + return payloads; + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 9ce0dfc88..869c6f5d8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -2,7 +2,7 @@ import com.apptentive.android.sdk.model.ConversationTokenRequest; import com.apptentive.android.sdk.model.MultipartPayload; -import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.network.HttpJsonMultipartRequest; import com.apptentive.android.sdk.network.HttpJsonRequest; @@ -13,8 +13,6 @@ import com.apptentive.android.sdk.storage.PayloadRequestSender; import com.apptentive.android.sdk.util.Constants; -import org.json.JSONObject; - import java.util.List; import static android.text.TextUtils.isEmpty; @@ -74,7 +72,7 @@ public HttpRequest findRequest(String tag) { //region PayloadRequestSender @Override - public HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener) { + public HttpRequest sendPayload(PayloadData payload, HttpRequest.Listener listener) { if (payload == null) { throw new IllegalArgumentException("Payload is null"); } @@ -85,7 +83,7 @@ public HttpRequest sendPayload(Payload payload, HttpRequest.Listener T notNull(T reference) { + assertNotNull(reference); + return reference; + } + /** * Asserts that an object isn't null */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java index e72acf642..74b06e51d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java @@ -34,7 +34,7 @@ public AppReleasePayload(String json) throws JSONException { //region Http-request @Override - public String getHttpEndPoint() { + public String getHttpEndPoint(String conversationId) { throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java index 1591553f5..de9078903 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java @@ -66,8 +66,8 @@ public CompoundMessage(String json, boolean bOutgoing) throws JSONException { //region Http-request @Override - public String getHttpEndPoint() { - return StringUtils.format("/conversations/%s/messages", getConversationId()); + public String getHttpEndPoint(String conversationId) { + return StringUtils.format("/conversations/%s/messages", conversationId); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java index 9a5ae9419..88d1c75c2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java @@ -22,27 +22,19 @@ public abstract class ConversationItem extends JsonPayload { protected static final String KEY_CLIENT_CREATED_AT_UTC_OFFSET = "client_created_at_utc_offset"; protected ConversationItem() { - super(); - setNonce(UUID.randomUUID().toString()); + put(KEY_NONCE, getNonce()); double seconds = Util.currentTimeSeconds(); int utcOffset = Util.getUtcOffset(); setClientCreatedAt(seconds); setClientCreatedAtUtcOffset(utcOffset); - } protected ConversationItem(String json) throws JSONException { super(json); } - @Override - public void setNonce(String nonce) { - super.setNonce(nonce); - put(KEY_NONCE, nonce); - } - public Double getClientCreatedAt() { return getDouble(KEY_CLIENT_CREATED_AT); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index 6278211a7..d0ef839e2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -51,8 +51,8 @@ public DevicePayload(String json) throws JSONException { //region Http-request @Override - public String getHttpEndPoint() { - return StringUtils.format("/conversations/%s/devices", getConversationId()); + public String getHttpEndPoint(String conversationId) { + return StringUtils.format("/conversations/%s/devices", conversationId); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index b68d9a045..707aee69c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -112,8 +112,8 @@ public EventPayload(String label, String trigger) { //region Http-request @Override - public String getHttpEndPoint() { - return StringUtils.format("/conversations/%s/event", getConversationId()); + public String getHttpEndPoint(String conversationId) { + return StringUtils.format("/conversations/%s/event", conversationId); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index fb04a094b..9c71438f5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -88,7 +88,7 @@ protected void remove(String key) { // TODO: rename to removeKey jsonObject.remove(key); } - protected String getString(String key) { + public String getString(String key) { return jsonObject.optString(key); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java index 0d782a54a..28e78c4c4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java @@ -22,8 +22,8 @@ public LogoutPayload(String json) throws JSONException { //region Http-request @Override - public String getHttpEndPoint() { - return StringUtils.format("/conversations/%s/logout", getConversationId()); + public String getHttpEndPoint(String conversationId) { + return StringUtils.format("/conversations/%s/logout", conversationId); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/OutgoingPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/OutgoingPayload.java deleted file mode 100644 index e0c5a90c8..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/OutgoingPayload.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.model; - -import com.apptentive.android.sdk.network.HttpRequestMethod; - -public class OutgoingPayload extends Payload { - - private byte[] data; - - public OutgoingPayload(PayloadType payloadType) { - } - - @Override - public byte[] getData() { - return data; - } - - public void setData(byte[] data) { - this.data = data; - } - - @Override - public String getHttpEndPoint() { - return null; - } - - @Override - public HttpRequestMethod getHttpRequestMethod() { - return null; - } - - @Override - public String getHttpRequestContentType() { - return null; - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index b5dce56f8..9cfee58c0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -9,73 +9,57 @@ import com.apptentive.android.sdk.network.HttpRequestMethod; import java.util.List; +import java.util.UUID; public abstract class Payload { - private long databaseId; - protected String type; - protected String nonce; - protected int apiVersion; - protected String contentType; - protected String authToken; - protected String method; - protected String path; - protected String conversationId; - protected List attachments; // TODO: Figure out attachment handling - - public String getType() { - return type; - } + /** + * A value that can be used to correlate a payload with another object + * (for example, to update the sent status of a message) + */ + private String nonce; - public void setType(String type) { - this.type = type; - } + private List attachments; // TODO: Figure out attachment handling - public String getNonce() { - return nonce; + protected Payload() { + nonce = UUID.randomUUID().toString(); } - public void setNonce(String nonce) { - this.nonce = nonce; - } + //region Data - public int getApiVersion() { - return apiVersion; - } + /** + * Binary data to be stored in database + */ + public abstract byte[] getData(); - public void setApiVersion(int apiVersion) { - this.apiVersion = apiVersion; - } + //region - public String getContentType() { - return contentType; - } + //region Http-request - public void setContentType(String contentType) { - this.contentType = contentType; - } + /** + * Http endpoint for sending this payload + */ + public abstract String getHttpEndPoint(String conversationId); - public String getAuthToken() { - return authToken; - } + /** + * Http request method for sending this payload + */ + public abstract HttpRequestMethod getHttpRequestMethod(); - public void setAuthToken(String authToken) { - this.authToken = authToken; - } + /** + * Http content type for sending this payload + */ + public abstract String getHttpRequestContentType(); - public String getMethod() { - return method; - } + //endregion - public void setMethod(String method) { - this.method = method; - } + //region Getters/Setters - public String getPath() { - return path; + public String getNonce() { + return nonce; } - public void setPath(String path) { - this.path = path; + public void setNonce(String nonce) { + this.nonce = nonce; } public List getAttachments() { @@ -86,40 +70,5 @@ public void setAttachments(List attachments) { this.attachments = attachments; } - public String getConversationId() { - return conversationId; - } - - public void setConversationId(String conversationId) { - this.conversationId = conversationId; - } - - public long getDatabaseId() { - return databaseId; - } - - public void setDatabaseId(long databaseId) { - this.databaseId = databaseId; - } - - public abstract byte[] getData(); - - //region Http-request - - /** - * Http endpoint for sending this payload - */ - public abstract String getHttpEndPoint(); - - /** - * Http request method for sending this payload - */ - public abstract HttpRequestMethod getHttpRequestMethod(); - - /** - * Http content type for sending this payload - */ - public abstract String getHttpRequestContentType(); - //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java new file mode 100644 index 000000000..562199fc3 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.model; + +import com.apptentive.android.sdk.network.HttpRequestMethod; + +public class PayloadData { + private String contentType; + private String authToken; + private byte[] data; + private String path; + private HttpRequestMethod httpRequestMethod; + private String payloadIdentifier; + + public PayloadData() { + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getContentType() { + return contentType; + } + + public void setAuthToken(String authToken) { + this.authToken = authToken; + } + + public String getAuthToken() { + return authToken; + } + + public void setData(byte[] data) { + this.data = data; + } + + public byte[] getData() { + return data; + } + + public void setHttpRequestPath(String path) { + this.path = path; + } + + public String getHttpEndPoint() { + return path; + } + + public HttpRequestMethod getHttpRequestMethod() { + return httpRequestMethod; + } + + public void setHttpRequestMethod(HttpRequestMethod httpRequestMethod) { + this.httpRequestMethod = httpRequestMethod; + } + + public String getPayloadIdentifier() { + return payloadIdentifier; + } + + public void setPayloadIdentifier(String payloadIdentifier) { + this.payloadIdentifier = payloadIdentifier; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadType.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadType.java index b7dc4e6f2..69e5077c4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadType.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadType.java @@ -14,11 +14,8 @@ public enum PayloadType { device, sdk, app_release, - sdk_and_app_release, person, - logout, unknown, - outgoing, // Legacy survey; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index bccc84e8a..e8183bac4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -37,8 +37,8 @@ public PersonPayload(String json) throws JSONException { //region Http-request @Override - public String getHttpEndPoint() { - return StringUtils.format("/converations/%s/people", getConversationId()); + public String getHttpEndPoint(String conversationId) { + return StringUtils.format("/converations/%s/people", conversationId); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index e1dca6c67..f64b93236 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -51,8 +51,8 @@ public SdkAndAppReleasePayload(String json) throws JSONException { //region Http-request @Override - public String getHttpEndPoint() { - return StringUtils.format("/conversations/%s/sdkapprelease", getConversationId()); + public String getHttpEndPoint(String conversationId) { + return StringUtils.format("/conversations/%s/sdkapprelease", conversationId); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java index d747ee258..5938b2897 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java @@ -32,7 +32,7 @@ public SdkPayload(String json) throws JSONException { //region Http-request @Override - public String getHttpEndPoint() { + public String getHttpEndPoint(String conversationId) { throw new RuntimeException(getClass().getName() + " is deprecated"); // FIXME: find a better approach } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index cc46058ec..be5a98a03 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -43,8 +43,8 @@ public SurveyResponsePayload(String json) throws JSONException { //region Http-request @Override - public String getHttpEndPoint() { - return StringUtils.format("/conversations/%s/surveys/%s/respond", getConversationId(), getId()); + public String getHttpEndPoint(String conversationId) { + return StringUtils.format("/conversations/%s/surveys/%s/respond", conversationId, getId()); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java index 247079f2d..7185957cf 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java @@ -6,12 +6,11 @@ package com.apptentive.android.sdk.module.messagecenter.model; -import android.text.TextUtils; - import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.model.CompoundMessage; +import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; import org.json.JSONObject; @@ -24,7 +23,7 @@ public static ApptentiveMessage fromJson(String json) { JSONObject root = new JSONObject(json); if (!root.isNull(ApptentiveMessage.KEY_TYPE)) { String typeStr = root.getString(ApptentiveMessage.KEY_TYPE); - if (!TextUtils.isEmpty(typeStr)) { + if (!StringUtils.isNullOrEmpty(typeStr)) { type = ApptentiveMessage.Type.valueOf(typeStr); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 72a5abcf0..5088a60cf 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -17,11 +17,14 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.model.CompoundMessage; -import com.apptentive.android.sdk.model.OutgoingPayload; +import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.model.PayloadType; import com.apptentive.android.sdk.model.StoredFile; +import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.storage.legacy.LegacyPayloadFactory; +import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; import org.json.JSONObject; @@ -29,8 +32,10 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import static com.apptentive.android.sdk.ApptentiveLogTag.DATABASE; +import static com.apptentive.android.sdk.debug.Assert.notNull; /** * There can be only one. SQLiteOpenHelper per database name that is. All new Apptentive tables must be defined here. @@ -41,22 +46,24 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { public static final String DATABASE_NAME = "apptentive"; private static final int TRUE = 1; private static final int FALSE = 0; - private File fileDir; // data dir of the application + private final DataSource dataSource; + private final File fileDir; // data dir of the application //region Payload SQL - private static final class PayloadEntry { + static final class PayloadEntry { static final String TABLE_NAME = "pending_payload"; static final DatabaseColumn COLUMN_PRIMARY_KEY = new DatabaseColumn(0, "_id"); static final DatabaseColumn COLUMN_IDENTIFIER = new DatabaseColumn(1, "identifier"); - static final DatabaseColumn COLUMN_API_VERSION = new DatabaseColumn(2, "api_version"); - static final DatabaseColumn COLUMN_CONTENT_TYPE = new DatabaseColumn(3, "content_type"); - static final DatabaseColumn COLUMN_REQUEST_METHOD = new DatabaseColumn(4, "request_method"); - static final DatabaseColumn COLUMN_PATH = new DatabaseColumn(5, "path"); - static final DatabaseColumn COLUMN_DATA = new DatabaseColumn(6, "data"); + static final DatabaseColumn COLUMN_CONTENT_TYPE = new DatabaseColumn(2, "contentType"); + static final DatabaseColumn COLUMN_AUTH_TOKEN = new DatabaseColumn(3, "authToken"); + static final DatabaseColumn COLUMN_CONVERSATION_ID = new DatabaseColumn(4, "conversationId"); + static final DatabaseColumn COLUMN_REQUEST_METHOD = new DatabaseColumn(5, "requestMethod"); + static final DatabaseColumn COLUMN_PATH = new DatabaseColumn(6, "path"); + static final DatabaseColumn COLUMN_DATA = new DatabaseColumn(7, "data"); } - private static final class LegacyPayloadEntry { + static final class LegacyPayloadEntry { static final String TABLE_NAME = "payload"; static final DatabaseColumn PAYLOAD_KEY_DB_ID = new DatabaseColumn(0, "_id"); static final DatabaseColumn PAYLOAD_KEY_BASE_TYPE = new DatabaseColumn(1, "base_type"); @@ -68,14 +75,15 @@ private static final class LegacyPayloadEntry { " (" + PayloadEntry.COLUMN_PRIMARY_KEY + " INTEGER PRIMARY KEY, " + PayloadEntry.COLUMN_IDENTIFIER + " TEXT, " + - PayloadEntry.COLUMN_API_VERSION + " INT," + PayloadEntry.COLUMN_CONTENT_TYPE + " TEXT," + + PayloadEntry.COLUMN_AUTH_TOKEN + " TEXT," + + PayloadEntry.COLUMN_CONVERSATION_ID + " TEXT," + PayloadEntry.COLUMN_REQUEST_METHOD + " TEXT," + PayloadEntry.COLUMN_PATH + " TEXT," + PayloadEntry.COLUMN_DATA + " BLOB" + ");"; - private static final String SQL_QUERY_PAYLOAD_GET_LEGACY = + private static final String SQL_QUERY_PAYLOAD_LIST_LEGACY = "SELECT * FROM " + LegacyPayloadEntry.TABLE_NAME + " ORDER BY " + LegacyPayloadEntry.PAYLOAD_KEY_DB_ID; @@ -84,6 +92,14 @@ private static final class LegacyPayloadEntry { " ORDER BY " + PayloadEntry.COLUMN_PRIMARY_KEY + " ASC LIMIT 1"; + private static final String SQL_QUERY_UPDATE_INCOMPLETE_PAYLOADS = + "UPDATE " + PayloadEntry.TABLE_NAME + " SET " + + PayloadEntry.COLUMN_AUTH_TOKEN + " = ?, " + + PayloadEntry.COLUMN_CONVERSATION_ID + " = ? " + + "WHERE " + + PayloadEntry.COLUMN_AUTH_TOKEN + " IS NULL OR " + + PayloadEntry.COLUMN_CONVERSATION_ID + " IS NULL"; + private static final String SQL_QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER = "SELECT * FROM " + PayloadEntry.TABLE_NAME + " WHERE " + LegacyPayloadEntry.PAYLOAD_KEY_BASE_TYPE + " = ?" + @@ -176,9 +192,14 @@ private static final class LegacyPayloadEntry { // endregion - ApptentiveDatabaseHelper(Context context) { + ApptentiveDatabaseHelper(Context context, DataSource dataSource) { super(context, DATABASE_NAME, null, DATABASE_VERSION); - fileDir = context.getFilesDir(); + if (dataSource == null) { + throw new IllegalArgumentException("Data source is null"); + } + + this.dataSource = dataSource; + this.fileDir = context.getFilesDir(); } //region Create & Upgrade @@ -352,15 +373,23 @@ private void upgradeVersion2to3(SQLiteDatabase db) { Cursor cursor = null; try { - cursor = db.rawQuery(SQL_QUERY_PAYLOAD_GET_LEGACY, null); + cursor = db.rawQuery(SQL_QUERY_PAYLOAD_LIST_LEGACY, null); List payloads = new ArrayList<>(cursor.getCount()); - Payload payload; + JsonPayload payload; while (cursor.moveToNext()) { PayloadType payloadType = PayloadType.parse(cursor.getString(1)); String json = cursor.getString(LegacyPayloadEntry.PAYLOAD_KEY_JSON.index); payload = LegacyPayloadFactory.createPayload(payloadType, json); + + // the legacy payload format didn't store 'nonce' in the database so we need to extract if from json + String nonce = payload.getString("nonce"); + if (nonce == null) { + nonce = UUID.randomUUID().toString(); // if 'nonce' is missing - generate a new one + } + payload.setNonce(nonce); + payloads.add(payload); } @@ -373,27 +402,6 @@ private void upgradeVersion2to3(SQLiteDatabase db) { } } - List listPayloads(SQLiteDatabase db) throws JSONException { - Cursor cursor = null; - try { - cursor = db.rawQuery(SQL_QUERY_PAYLOAD_GET_LEGACY, null); - List payloads = new ArrayList<>(cursor.getCount()); - - Payload payload; - while (cursor.moveToNext()) { - PayloadType payloadType = PayloadType.parse(cursor.getString(1)); - String json = cursor.getString(LegacyPayloadEntry.PAYLOAD_KEY_JSON.index); - - payload = LegacyPayloadFactory.createPayload(payloadType, json); - payloads.add(payload); - } - - return payloads; - } finally { - ensureClosed(cursor); - } - } - //endregion //region Payloads @@ -402,19 +410,27 @@ List listPayloads(SQLiteDatabase db) throws JSONException { * If an item with the same nonce as an item passed in already exists, it is overwritten by the item. Otherwise * a new message is added. */ - public void addPayload(Payload... payloads) { + void addPayload(Payload... payloads) { SQLiteDatabase db; try { db = getWritableDatabase(); db.beginTransaction(); + + final String conversationId = dataSource.getConversationId(); + final String authToken = dataSource.getAuthToken(); + for (Payload payload : payloads) { ContentValues values = new ContentValues(); - values.put(PayloadEntry.COLUMN_IDENTIFIER.name, payload.getNonce()); - values.put(PayloadEntry.COLUMN_API_VERSION.name, payload.getApiVersion()); - values.put(PayloadEntry.COLUMN_CONTENT_TYPE.name, payload.getContentType()); + values.put(PayloadEntry.COLUMN_IDENTIFIER.name, notNull(payload.getNonce())); + values.put(PayloadEntry.COLUMN_CONTENT_TYPE.name, notNull(payload.getHttpRequestContentType())); + values.put(PayloadEntry.COLUMN_AUTH_TOKEN.name, authToken); // might be null + values.put(PayloadEntry.COLUMN_CONVERSATION_ID.name, conversationId); // might be null values.put(PayloadEntry.COLUMN_REQUEST_METHOD.name, payload.getHttpRequestMethod().name()); - values.put(PayloadEntry.COLUMN_PATH.name, payload.getPath()); - values.put(PayloadEntry.COLUMN_DATA.name, payload.getData()); + values.put(PayloadEntry.COLUMN_PATH.name, payload.getHttpEndPoint( + StringUtils.isNullOrEmpty(conversationId) ? "${conversationId}" : conversationId) // if conversation id is missing we replace it with a place holder and update it later + ); + values.put(PayloadEntry.COLUMN_DATA.name, notNull(payload.getData())); + db.insert(PayloadEntry.TABLE_NAME, null, values); } db.setTransactionSuccessful(); @@ -424,15 +440,15 @@ public void addPayload(Payload... payloads) { } } - public void deletePayload(Payload payload) { - if (payload != null) { + void deletePayload(String payloadIdentifier) { + if (payloadIdentifier != null) { SQLiteDatabase db; try { db = getWritableDatabase(); db.delete( PayloadEntry.TABLE_NAME, - PayloadEntry.COLUMN_PRIMARY_KEY + " = ?", - new String[]{Long.toString(payload.getDatabaseId())} + PayloadEntry.COLUMN_IDENTIFIER + " = ?", + new String[]{payloadIdentifier} ); } catch (SQLException sqe) { ApptentiveLog.e(DATABASE, "deletePayload EXCEPTION: " + sqe.getMessage()); @@ -450,25 +466,24 @@ public void deleteAllPayloads() { } } - public Payload getOldestUnsentPayload() { + public PayloadData getOldestUnsentPayload() { SQLiteDatabase db; Cursor cursor = null; try { db = getWritableDatabase(); cursor = db.rawQuery(SQL_QUERY_PAYLOAD_GET_NEXT_TO_SEND, null); - OutgoingPayload payload = null; + PayloadData payload = null; if (cursor.moveToFirst()) { - long databaseId = Long.parseLong(cursor.getString(0)); - PayloadType payloadType = PayloadType.parse(cursor.getString(1)); - String json = cursor.getString(2); - String conversationId = cursor.getString(3); - String authToken = cursor.getString(4); - payload = new OutgoingPayload(payloadType); - payload.setDatabaseId(databaseId); - payload.setConversationId(conversationId); - payload.setAuthToken(authToken); - payload.setData(json.getBytes()); + final String conversationId = cursor.getString(PayloadEntry.COLUMN_CONVERSATION_ID.index); + final String httpRequestPath = updatePayloadRequestPath(cursor.getString(PayloadEntry.COLUMN_PATH.index), conversationId); + + payload = new PayloadData(); + payload.setHttpRequestPath(httpRequestPath); + payload.setHttpRequestMethod(HttpRequestMethod.valueOf(cursor.getString(PayloadEntry.COLUMN_REQUEST_METHOD.index))); + payload.setContentType(cursor.getString(PayloadEntry.COLUMN_CONTENT_TYPE.index)); + payload.setAuthToken(cursor.getString(PayloadEntry.COLUMN_AUTH_TOKEN.index)); + payload.setData(cursor.getBlob(PayloadEntry.COLUMN_DATA.index)); } return payload; } catch (SQLException sqe) { @@ -479,73 +494,37 @@ public Payload getOldestUnsentPayload() { } } - public void updateIncompletePayloads(String conversationId, String token) { -// if (StringUtils.isNullOrEmpty(conversationId)) { -// throw new IllegalArgumentException("Conversation id is null or empty"); -// } -// if (StringUtils.isNullOrEmpty(token)) { -// throw new IllegalArgumentException("Token is null or empty"); -// } -// Cursor cursor = null; -// try { -// SQLiteDatabase db = getWritableDatabase(); -// cursor = db.rawQuery(QUERY_UPDATE_INCOMPLETE_PAYLOADS, new String[]{conversationId, token}); -// cursor.moveToFirst(); // we need to move a cursor in order to update database -// ApptentiveLog.v(DATABASE, "Updated missing conversation ids"); -// } catch (SQLException e) { -// ApptentiveLog.e(e, "Exception while updating missing conversation ids"); -// } finally { -// ensureClosed(cursor); -// } - - throw new RuntimeException("Implement me"); + private String updatePayloadRequestPath(String path, String conversationId) { + return path.replace("${conversationId}", conversationId); } - //endregion - - //region Messages - - public synchronized int getUnreadMessageCount() { - SQLiteDatabase db = null; + public void updateIncompletePayloads(String conversationId, String authToken) { + if (StringUtils.isNullOrEmpty(conversationId)) { + throw new IllegalArgumentException("Conversation id is null or empty"); + } + if (StringUtils.isNullOrEmpty(authToken)) { + throw new IllegalArgumentException("Token is null or empty"); + } Cursor cursor = null; try { - db = getWritableDatabase(); - cursor = db.rawQuery(QUERY_MESSAGE_UNREAD, null); - return cursor.getCount(); - } catch (SQLException sqe) { - ApptentiveLog.e(DATABASE, "getUnreadMessageCount EXCEPTION: " + sqe.getMessage()); - return 0; + SQLiteDatabase db = getWritableDatabase(); + cursor = db.rawQuery(SQL_QUERY_UPDATE_INCOMPLETE_PAYLOADS, new String[] { + authToken, conversationId + }); + cursor.moveToFirst(); // we need to move a cursor in order to update database + ApptentiveLog.v(DATABASE, "Updated missing conversation ids"); + } catch (SQLException e) { + ApptentiveLog.e(e, "Exception while updating missing conversation ids"); } finally { ensureClosed(cursor); } } - public synchronized void deleteAllMessages() { - SQLiteDatabase db = null; - try { - db = getWritableDatabase(); - db.delete(TABLE_MESSAGE, "", null); - } catch (SQLException sqe) { - ApptentiveLog.e(DATABASE, "deleteAllMessages EXCEPTION: " + sqe.getMessage()); - } - } - - public synchronized void deleteMessage(String nonce) { - SQLiteDatabase db = null; - try { - db = getWritableDatabase(); - int deleted = db.delete(TABLE_MESSAGE, MESSAGE_KEY_NONCE + " = ?", new String[]{nonce}); - ApptentiveLog.d(DATABASE, "Deleted %d messages.", deleted); - } catch (SQLException sqe) { - ApptentiveLog.e(DATABASE, "deleteMessage EXCEPTION: " + sqe.getMessage()); - } - } - //endregion //region Files - public void deleteAssociatedFiles(String messageNonce) { + void deleteAssociatedFiles(String messageNonce) { SQLiteDatabase db = null; try { db = getWritableDatabase(); @@ -556,7 +535,7 @@ public void deleteAssociatedFiles(String messageNonce) { } } - public List getAssociatedFiles(String nonce) { + List getAssociatedFiles(String nonce) { SQLiteDatabase db = null; Cursor cursor = null; List associatedFiles = new ArrayList(); @@ -590,7 +569,7 @@ public List getAssociatedFiles(String nonce) { * @param associatedFiles list of associated files * @return true if succeed */ - public boolean addCompoundMessageFiles(List associatedFiles) { + boolean addCompoundMessageFiles(List associatedFiles) { String messageNonce = associatedFiles.get(0).getId(); SQLiteDatabase db = null; long ret = -1; @@ -646,6 +625,12 @@ public void reset(Context context) { //region Helper classes + interface DataSource { + String getConversationId(); + + String getAuthToken(); + } + private static final class DatabaseColumn { public final String name; public final int index; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseLegacyHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseLegacyHelper.java deleted file mode 100644 index 748ce9386..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseLegacyHelper.java +++ /dev/null @@ -1,615 +0,0 @@ -/* - * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.storage; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.text.TextUtils; - -import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.model.ApptentiveMessage; -import com.apptentive.android.sdk.model.CompoundMessage; -import com.apptentive.android.sdk.model.Payload; -import com.apptentive.android.sdk.model.PayloadType; -import com.apptentive.android.sdk.model.StoredFile; -import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -/** - * There can be only one. SQLiteOpenHelper per database name that is. All new Apptentive tables must be defined here. - * - * @author Sky Kelsey - */ -public class ApptentiveDatabaseLegacyHelper extends SQLiteOpenHelper { - - // COMMON - private static final int DATABASE_VERSION = 2; - public static final String DATABASE_NAME = "apptentive"; - private static final int TRUE = 1; - private static final int FALSE = 0; - - // PAYLOAD - public static final String TABLE_PAYLOAD = "payload"; - public static final String PAYLOAD_KEY_DB_ID = "_id"; // 0 - public static final String PAYLOAD_KEY_BASE_TYPE = "base_type"; // 1 - public static final String PAYLOAD_KEY_JSON = "json"; // 2 - - private static final String TABLE_CREATE_PAYLOAD = - "CREATE TABLE " + TABLE_PAYLOAD + - " (" + - PAYLOAD_KEY_DB_ID + " INTEGER PRIMARY KEY, " + - PAYLOAD_KEY_BASE_TYPE + " TEXT, " + - PAYLOAD_KEY_JSON + " TEXT" + - ");"; - - public static final String QUERY_PAYLOAD_GET_NEXT_TO_SEND = "SELECT * FROM " + TABLE_PAYLOAD + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC LIMIT 1"; - - private static final String QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER = "SELECT * FROM " + TABLE_PAYLOAD + " WHERE " + PAYLOAD_KEY_BASE_TYPE + " = ?" + " ORDER BY " + PAYLOAD_KEY_DB_ID + " ASC"; - - - // MESSAGE - private static final String TABLE_MESSAGE = "message"; - private static final String MESSAGE_KEY_DB_ID = "_id"; // 0 - private static final String MESSAGE_KEY_ID = "id"; // 1 - private static final String MESSAGE_KEY_CLIENT_CREATED_AT = "client_created_at"; // 2 - private static final String MESSAGE_KEY_NONCE = "nonce"; // 3 - private static final String MESSAGE_KEY_STATE = "state"; // 4 - private static final String MESSAGE_KEY_READ = "read"; // 5 - private static final String MESSAGE_KEY_JSON = "json"; // 6 - - private static final String TABLE_CREATE_MESSAGE = - "CREATE TABLE " + TABLE_MESSAGE + - " (" + - MESSAGE_KEY_DB_ID + " INTEGER PRIMARY KEY, " + - MESSAGE_KEY_ID + " TEXT, " + - MESSAGE_KEY_CLIENT_CREATED_AT + " DOUBLE, " + - MESSAGE_KEY_NONCE + " TEXT, " + - MESSAGE_KEY_STATE + " TEXT, " + - MESSAGE_KEY_READ + " INTEGER, " + - MESSAGE_KEY_JSON + " TEXT" + - ");"; - - private static final String QUERY_MESSAGE_GET_BY_NONCE = "SELECT * FROM " + TABLE_MESSAGE + " WHERE " + MESSAGE_KEY_NONCE + " = ?"; - // Coalesce returns the second arg if the first is null. This forces the entries with null IDs to be ordered last in the list until they do have IDs because they were sent and retrieved from the server. - private static final String QUERY_MESSAGE_GET_ALL_IN_ORDER = "SELECT * FROM " + TABLE_MESSAGE + " ORDER BY COALESCE(" + MESSAGE_KEY_ID + ", 'z') ASC"; - private static final String QUERY_MESSAGE_GET_LAST_ID = "SELECT " + MESSAGE_KEY_ID + " FROM " + TABLE_MESSAGE + " WHERE " + MESSAGE_KEY_STATE + " = '" + ApptentiveMessage.State.saved + "' AND " + MESSAGE_KEY_ID + " NOTNULL ORDER BY " + MESSAGE_KEY_ID + " DESC LIMIT 1"; - private static final String QUERY_MESSAGE_UNREAD = "SELECT " + MESSAGE_KEY_ID + " FROM " + TABLE_MESSAGE + " WHERE " + MESSAGE_KEY_READ + " = " + FALSE + " AND " + MESSAGE_KEY_ID + " NOTNULL"; - - // FileStore - private static final String TABLE_FILESTORE = "file_store"; - private static final String FILESTORE_KEY_ID = "id"; // 0 - private static final String FILESTORE_KEY_MIME_TYPE = "mime_type"; // 1 - private static final String FILESTORE_KEY_ORIGINAL_URL = "original_uri"; // 2 - private static final String FILESTORE_KEY_LOCAL_URL = "local_uri"; // 3 - private static final String FILESTORE_KEY_APPTENTIVE_URL = "apptentive_uri"; // 4 - private static final String TABLE_CREATE_FILESTORE = - "CREATE TABLE " + TABLE_FILESTORE + - " (" + - FILESTORE_KEY_ID + " TEXT PRIMARY KEY, " + - FILESTORE_KEY_MIME_TYPE + " TEXT, " + - FILESTORE_KEY_ORIGINAL_URL + " TEXT, " + - FILESTORE_KEY_LOCAL_URL + " TEXT, " + - FILESTORE_KEY_APPTENTIVE_URL + " TEXT" + - ");"; - - /* Compound Message FileStore: - * For Compound Messages stored in TABLE_MESSAGE, each associated file will add a row to this table - * using the message's "nonce" key - */ - private static final String TABLE_COMPOUND_MESSAGE_FILESTORE = "compound_message_file_store"; // table filePath - private static final String COMPOUND_FILESTORE_KEY_DB_ID = "_id"; // 0 - private static final String COMPOUND_FILESTORE_KEY_MESSAGE_NONCE = "nonce"; // message nonce of the compound message - private static final String COMPOUND_FILESTORE_KEY_MIME_TYPE = "mime_type"; // mine type of the file - private static final String COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI = "local_uri"; // original uriString or file path of source file (empty for received file) - private static final String COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH = "local_path"; // path to the local cached version - private static final String COMPOUND_FILESTORE_KEY_REMOTE_URL = "apptentive_url"; // original server url of received file (empty for sent file) - private static final String COMPOUND_FILESTORE_KEY_CREATION_TIME = "creation_time"; // creation time of the original file - // Create the initial table. Use nonce and local cache path as primary key because both sent/received files will have a local cached copy - private static final String TABLE_CREATE_COMPOUND_FILESTORE = - "CREATE TABLE " + TABLE_COMPOUND_MESSAGE_FILESTORE + - " (" + - COMPOUND_FILESTORE_KEY_DB_ID + " INTEGER PRIMARY KEY, " + - COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " TEXT, " + - COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH + " TEXT, " + - COMPOUND_FILESTORE_KEY_MIME_TYPE + " TEXT, " + - COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI + " TEXT, " + - COMPOUND_FILESTORE_KEY_REMOTE_URL + " TEXT, " + - COMPOUND_FILESTORE_KEY_CREATION_TIME + " LONG" + - ");"; - - // Query all files associated with a given compound message nonce id - private static final String QUERY_MESSAGE_FILES_GET_BY_NONCE = "SELECT * FROM " + TABLE_COMPOUND_MESSAGE_FILESTORE + " WHERE " + COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?"; - - private File fileDir; // data dir of the application - - - public void ensureClosed(SQLiteDatabase db) { - try { - if (db != null) { - db.close(); - } - } catch (Exception e) { - ApptentiveLog.w("Error closing SQLite database.", e); - } - } - - public void ensureClosed(Cursor cursor) { - try { - if (cursor != null) { - cursor.close(); - } - } catch (Exception e) { - ApptentiveLog.w("Error closing SQLite cursor.", e); - } - } - - public ApptentiveDatabaseLegacyHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - fileDir = context.getFilesDir(); - } - - /** - * This function is called only for new installs, and onUpgrade is not called in that case. Therefore, you must include the - * latest complete set of DDL here. - */ - @Override - public void onCreate(SQLiteDatabase db) { - ApptentiveLog.d("ApptentiveDatabase.onCreate(db)"); - db.execSQL(TABLE_CREATE_PAYLOAD); - db.execSQL(TABLE_CREATE_MESSAGE); - db.execSQL(TABLE_CREATE_FILESTORE); - db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); - } - - /** - * This method is called when an app is upgraded. Add alter table statements here for each version in a non-breaking - * switch, so that all the necessary upgrades occur for each older version. - */ - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - ApptentiveLog.d("ApptentiveDatabase.onUpgrade(db, %d, %d)", oldVersion, newVersion); - switch (oldVersion) { - case 1: - if (newVersion == 2) { - db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); - migrateToCompoundMessage(db); - } - } - } - - // PAYLOAD: This table is used to store all the Payloads we want to send to the server. - - /** - * If an item with the same nonce as an item passed in already exists, it is overwritten by the item. Otherwise - * a new message is added. - */ - public void addPayload(Payload payload, PayloadType type) { - SQLiteDatabase db = null; - try { - db = getWritableDatabase(); - db.beginTransaction(); - { - ContentValues values = new ContentValues(); - values.put(PAYLOAD_KEY_BASE_TYPE, type.name()); - values.put(PAYLOAD_KEY_JSON, payload.toString()); - db.insert(TABLE_PAYLOAD, null, values); - } - db.setTransactionSuccessful(); - db.endTransaction(); - } catch (SQLException sqe) { - ApptentiveLog.e("addPayload EXCEPTION: " + sqe.getMessage()); - } - } - - public void deletePayload(Payload payload) { - if (payload != null) { - SQLiteDatabase db = null; - try { - db = getWritableDatabase(); - db.delete(TABLE_PAYLOAD, PAYLOAD_KEY_DB_ID + " = ?", new String[]{Long.toString(payload.getDatabaseId())}); - } catch (SQLException sqe) { - ApptentiveLog.e("deletePayload EXCEPTION: " + sqe.getMessage()); - } - } - } - - public void deleteAllPayloads() { - SQLiteDatabase db = null; - try { - db = getWritableDatabase(); - db.delete(TABLE_PAYLOAD, "", null); - } catch (SQLException sqe) { - ApptentiveLog.e("deleteAllPayloads EXCEPTION: " + sqe.getMessage()); - } - } - - // MessageStore - - /** - * Saves the message into the message table, and also into the payload table so it can be sent to the server. - */ - public void addOrUpdateMessages(ApptentiveMessage... apptentiveMessages) { - SQLiteDatabase db = null; - try { - db = getWritableDatabase(); - for (ApptentiveMessage apptentiveMessage : apptentiveMessages) { - Cursor cursor = null; - try { - cursor = db.rawQuery(QUERY_MESSAGE_GET_BY_NONCE, new String[]{apptentiveMessage.getNonce()}); - if (cursor.moveToFirst()) { - // Update - String databaseId = cursor.getString(0); - ContentValues messageValues = new ContentValues(); - messageValues.put(MESSAGE_KEY_ID, apptentiveMessage.getId()); - messageValues.put(MESSAGE_KEY_STATE, apptentiveMessage.getState().name()); - if (apptentiveMessage.isRead()) { // A apptentiveMessage can't be unread after being read. - messageValues.put(MESSAGE_KEY_READ, TRUE); - } - messageValues.put(MESSAGE_KEY_JSON, apptentiveMessage.toString()); - db.update(TABLE_MESSAGE, messageValues, MESSAGE_KEY_DB_ID + " = ?", new String[]{databaseId}); - } else { - // Insert - db.beginTransaction(); - ContentValues messageValues = new ContentValues(); - messageValues.put(MESSAGE_KEY_ID, apptentiveMessage.getId()); - messageValues.put(MESSAGE_KEY_CLIENT_CREATED_AT, apptentiveMessage.getClientCreatedAt()); - messageValues.put(MESSAGE_KEY_NONCE, apptentiveMessage.getNonce()); - messageValues.put(MESSAGE_KEY_STATE, apptentiveMessage.getState().name()); - messageValues.put(MESSAGE_KEY_READ, apptentiveMessage.isRead() ? TRUE : FALSE); - messageValues.put(MESSAGE_KEY_JSON, apptentiveMessage.toString()); - db.insert(TABLE_MESSAGE, null, messageValues); - db.setTransactionSuccessful(); - db.endTransaction(); - } - } finally { - ensureClosed(cursor); - } - } - } catch (SQLException sqe) { - ApptentiveLog.e("addOrUpdateMessages EXCEPTION: " + sqe.getMessage()); - } - } - - public void updateMessage(ApptentiveMessage apptentiveMessage) { - SQLiteDatabase db = null; - try { - db = getWritableDatabase(); - db.beginTransaction(); - ContentValues values = new ContentValues(); - values.put(MESSAGE_KEY_ID, apptentiveMessage.getId()); - values.put(MESSAGE_KEY_CLIENT_CREATED_AT, apptentiveMessage.getClientCreatedAt()); - values.put(MESSAGE_KEY_NONCE, apptentiveMessage.getNonce()); - values.put(MESSAGE_KEY_STATE, apptentiveMessage.getState().name()); - if (apptentiveMessage.isRead()) { // A apptentiveMessage can't be unread after being read. - values.put(MESSAGE_KEY_READ, TRUE); - } - values.put(MESSAGE_KEY_JSON, apptentiveMessage.toString()); - db.update(TABLE_MESSAGE, values, MESSAGE_KEY_NONCE + " = ?", new String[]{apptentiveMessage.getNonce()}); - db.setTransactionSuccessful(); - } catch (SQLException sqe) { - ApptentiveLog.e("updateMessage EXCEPTION: " + sqe.getMessage()); - } finally { - if (db != null) { - db.endTransaction(); - } - } - } - - public List getAllMessages() { - List apptentiveMessages = new ArrayList(); - SQLiteDatabase db = null; - Cursor cursor = null; - try { - db = getReadableDatabase(); - cursor = db.rawQuery(QUERY_MESSAGE_GET_ALL_IN_ORDER, null); - if (cursor.moveToFirst()) { - do { - String json = cursor.getString(6); - ApptentiveMessage apptentiveMessage = MessageFactory.fromJson(json); - if (apptentiveMessage == null) { - ApptentiveLog.e("Error parsing Record json from database: %s", json); - continue; - } - apptentiveMessage.setDatabaseId(cursor.getLong(0)); - apptentiveMessage.setState(ApptentiveMessage.State.parse(cursor.getString(4))); - apptentiveMessage.setRead(cursor.getInt(5) == TRUE); - apptentiveMessages.add(apptentiveMessage); - } while (cursor.moveToNext()); - } - } catch (SQLException sqe) { - ApptentiveLog.e("getAllMessages EXCEPTION: " + sqe.getMessage()); - } finally { - ensureClosed(cursor); - } - return apptentiveMessages; - } - - public synchronized String getLastReceivedMessageId() { - SQLiteDatabase db = null; - Cursor cursor = null; - String ret = null; - try { - db = getReadableDatabase(); - cursor = db.rawQuery(QUERY_MESSAGE_GET_LAST_ID, null); - if (cursor.moveToFirst()) { - ret = cursor.getString(0); - } - } catch (SQLException sqe) { - ApptentiveLog.e("getLastReceivedMessageId EXCEPTION: " + sqe.getMessage()); - } finally { - ensureClosed(cursor); - } - return ret; - } - - public synchronized int getUnreadMessageCount() { - SQLiteDatabase db = null; - Cursor cursor = null; - try { - db = getWritableDatabase(); - cursor = db.rawQuery(QUERY_MESSAGE_UNREAD, null); - return cursor.getCount(); - } catch (SQLException sqe) { - ApptentiveLog.e("getUnreadMessageCount EXCEPTION: " + sqe.getMessage()); - return 0; - } finally { - ensureClosed(cursor); - } - } - - public synchronized void deleteAllMessages() { - SQLiteDatabase db = null; - try { - db = getWritableDatabase(); - db.delete(TABLE_MESSAGE, "", null); - } catch (SQLException sqe) { - ApptentiveLog.e("deleteAllMessages EXCEPTION: " + sqe.getMessage()); - } - } - - public synchronized void deleteMessage(String nonce) { - SQLiteDatabase db = null; - try { - db = getWritableDatabase(); - int deleted = db.delete(TABLE_MESSAGE, MESSAGE_KEY_NONCE + " = ?", new String[]{nonce}); - ApptentiveLog.d("Deleted %d messages.", deleted); - } catch (SQLException sqe) { - ApptentiveLog.e("deleteMessage EXCEPTION: " + sqe.getMessage()); - } - } - - - // - // File Store - // - private void migrateToCompoundMessage(SQLiteDatabase db) { - Cursor cursor = null; - // Migrate legacy stored files to compound message associated files - try { - cursor = db.rawQuery("SELECT * FROM " + TABLE_FILESTORE, null); - if (cursor.moveToFirst()) { - do { - String file_nonce = cursor.getString(0); - // Stored File id was in the format of "apptentive-file-nonce" - String patten = "apptentive-file-"; - String nonce = file_nonce.substring(file_nonce.indexOf(patten) + patten.length()); - ContentValues values = new ContentValues(); - values.put(COMPOUND_FILESTORE_KEY_MESSAGE_NONCE, nonce); - // Legacy file was stored in db by name only. Need to get the full path when migrated - String localFileName = cursor.getString(3); - values.put(COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH, (new File(fileDir, localFileName).getAbsolutePath())); - values.put(COMPOUND_FILESTORE_KEY_MIME_TYPE, cursor.getString(1)); - // Original file name might not be stored, i.e. sent by API, in which case, local stored file name will be used. - String originalFileName = cursor.getString(2); - if (TextUtils.isEmpty(originalFileName)) { - originalFileName = localFileName; - } - values.put(COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI, originalFileName); - values.put(COMPOUND_FILESTORE_KEY_REMOTE_URL, cursor.getString(4)); - values.put(COMPOUND_FILESTORE_KEY_CREATION_TIME, 0); // we didn't store creation time of legacy file message - db.insert(TABLE_COMPOUND_MESSAGE_FILESTORE, null, values); - - } while (cursor.moveToNext()); - } - } catch (SQLException sqe) { - ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); - } finally { - ensureClosed(cursor); - } - // Migrate legacy message types to CompoundMessage Type - try { - cursor = db.rawQuery(QUERY_MESSAGE_GET_ALL_IN_ORDER, null); - if (cursor.moveToFirst()) { - do { - String json = cursor.getString(6); - JSONObject root = null; - boolean bUpdateRecord = false; - try { - root = new JSONObject(json); - ApptentiveMessage.Type type = ApptentiveMessage.Type.valueOf(root.getString(ApptentiveMessage.KEY_TYPE)); - switch (type) { - case TextMessage: - root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); - root.put(CompoundMessage.KEY_TEXT_ONLY, true); - bUpdateRecord = true; - break; - case FileMessage: - root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); - root.put(CompoundMessage.KEY_TEXT_ONLY, false); - bUpdateRecord = true; - break; - case AutomatedMessage: - root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); - root.put(CompoundMessage.KEY_TEXT_ONLY, true); - root.put(ApptentiveMessage.KEY_AUTOMATED, true); - bUpdateRecord = true; - break; - default: - break; - } - if (bUpdateRecord) { - String databaseId = cursor.getString(0); - ContentValues messageValues = new ContentValues(); - messageValues.put(MESSAGE_KEY_JSON, root.toString()); - db.update(TABLE_MESSAGE, messageValues, MESSAGE_KEY_DB_ID + " = ?", new String[]{databaseId}); - } - } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as Message: %s", e, json); - } - } while (cursor.moveToNext()); - } - } catch (SQLException sqe) { - ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); - } finally { - ensureClosed(cursor); - } - - // Migrate all pending payload messages - // Migrate legacy message types to CompoundMessage Type - try { - cursor = db.rawQuery(QUERY_PAYLOAD_GET_ALL_MESSAGE_IN_ORDER, new String[]{}); - if (cursor.moveToFirst()) { - do { - String json = cursor.getString(2); - JSONObject root; - boolean bUpdateRecord = false; - try { - root = new JSONObject(json); - ApptentiveMessage.Type type = ApptentiveMessage.Type.valueOf(root.getString(ApptentiveMessage.KEY_TYPE)); - switch (type) { - case TextMessage: - root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); - root.put(CompoundMessage.KEY_TEXT_ONLY, true); - bUpdateRecord = true; - break; - case FileMessage: - root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); - root.put(CompoundMessage.KEY_TEXT_ONLY, false); - bUpdateRecord = true; - break; - case AutomatedMessage: - root.put(ApptentiveMessage.KEY_TYPE, ApptentiveMessage.Type.CompoundMessage.name()); - root.put(CompoundMessage.KEY_TEXT_ONLY, true); - root.put(ApptentiveMessage.KEY_AUTOMATED, true); - bUpdateRecord = true; - break; - default: - break; - } - if (bUpdateRecord) { - String databaseId = cursor.getString(0); - ContentValues messageValues = new ContentValues(); - messageValues.put(PAYLOAD_KEY_JSON, root.toString()); - db.update(TABLE_PAYLOAD, messageValues, PAYLOAD_KEY_DB_ID + " = ?", new String[]{databaseId}); - } - } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as Message: %s", e, json); - } - } while (cursor.moveToNext()); - } - } catch (SQLException sqe) { - ApptentiveLog.e("migrateToCompoundMessage EXCEPTION: " + sqe.getMessage()); - } finally { - ensureClosed(cursor); - } - } - - public void deleteAssociatedFiles(String messageNonce) { - SQLiteDatabase db = null; - try { - db = getWritableDatabase(); - int deleted = db.delete(TABLE_COMPOUND_MESSAGE_FILESTORE, COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?", new String[]{messageNonce}); - ApptentiveLog.d("Deleted %d stored files.", deleted); - } catch (SQLException sqe) { - ApptentiveLog.e("deleteAssociatedFiles EXCEPTION: " + sqe.getMessage()); - } - } - - public List getAssociatedFiles(String nonce) { - SQLiteDatabase db = null; - Cursor cursor = null; - List associatedFiles = new ArrayList(); - try { - db = getReadableDatabase(); - cursor = db.rawQuery(QUERY_MESSAGE_FILES_GET_BY_NONCE, new String[]{nonce}); - StoredFile ret; - if (cursor.moveToFirst()) { - do { - ret = new StoredFile(); - ret.setId(nonce); - ret.setLocalFilePath(cursor.getString(2)); - ret.setMimeType(cursor.getString(3)); - ret.setSourceUriOrPath(cursor.getString(4)); - ret.setApptentiveUri(cursor.getString(5)); - ret.setCreationTime(cursor.getLong(6)); - associatedFiles.add(ret); - } while (cursor.moveToNext()); - } - } catch (SQLException sqe) { - ApptentiveLog.e("getAssociatedFiles EXCEPTION: " + sqe.getMessage()); - } finally { - ensureClosed(cursor); - } - return associatedFiles.size() > 0 ? associatedFiles : null; - } - - /* - * Add a list of associated files to compound message file storage - * Caller of this method should ensure all associated files have the same message nonce - * @param associatedFiles list of associated files - * @return true if succeed - */ - public boolean addCompoundMessageFiles(List associatedFiles) { - String messageNonce = associatedFiles.get(0).getId(); - SQLiteDatabase db = null; - long ret = -1; - try { - - db = getWritableDatabase(); - db.beginTransaction(); - // Always delete existing rows with the same nonce to ensure add/update both work - db.delete(TABLE_COMPOUND_MESSAGE_FILESTORE, COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?", new String[]{messageNonce}); - - for (StoredFile file : associatedFiles) { - ContentValues values = new ContentValues(); - values.put(COMPOUND_FILESTORE_KEY_MESSAGE_NONCE, file.getId()); - values.put(COMPOUND_FILESTORE_KEY_LOCAL_CACHE_PATH, file.getLocalFilePath()); - values.put(COMPOUND_FILESTORE_KEY_MIME_TYPE, file.getMimeType()); - values.put(COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI, file.getSourceUriOrPath()); - values.put(COMPOUND_FILESTORE_KEY_REMOTE_URL, file.getApptentiveUri()); - values.put(COMPOUND_FILESTORE_KEY_CREATION_TIME, file.getCreationTime()); - ret = db.insert(TABLE_COMPOUND_MESSAGE_FILESTORE, null, values); - } - db.setTransactionSuccessful(); - db.endTransaction(); - } catch (SQLException sqe) { - ApptentiveLog.e("addCompoundMessageFiles EXCEPTION: " + sqe.getMessage()); - } finally { - return ret != -1; - } - } - - public void reset(Context context) { - /** - * The following ONLY be used during development and testing. It will delete the database, including all saved - * payloads, messages, and files. - */ - context.deleteDatabase(DATABASE_NAME); - } - -} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index e6bc5417e..cf08130d0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -13,6 +13,7 @@ import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.network.HttpRequestRetryPolicyDefault; import com.apptentive.android.sdk.notifications.ApptentiveNotification; @@ -45,10 +46,10 @@ import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; -public class ApptentiveTaskManager implements PayloadStore, EventStore, ApptentiveNotificationObserver, PayloadSender.Listener { +public class ApptentiveTaskManager implements PayloadStore, EventStore, ApptentiveDatabaseHelper.DataSource, ApptentiveNotificationObserver, PayloadSender.Listener { - private ApptentiveDatabaseHelper dbHelper; - private ThreadPoolExecutor singleThreadExecutor; // TODO: replace with a private concurrent dispatch queue + private final ApptentiveDatabaseHelper dbHelper; + private final ThreadPoolExecutor singleThreadExecutor; // TODO: replace with a private concurrent dispatch queue // Set when receiving an ApptentiveNotification private String currentConversationId; @@ -61,7 +62,7 @@ public class ApptentiveTaskManager implements PayloadStore, EventStore, Apptenti * Creates an asynchronous task manager with one worker thread. This constructor must be invoked on the UI thread. */ public ApptentiveTaskManager(Context context, ApptentiveHttpClient apptentiveHttpClient) { - dbHelper = new ApptentiveDatabaseHelper(context); + dbHelper = new ApptentiveDatabaseHelper(context, this); /* When a new database task is submitted, the executor has the following behaviors: * 1. If the thread pool has no thread yet, it creates a single worker thread. * 2. If the single worker thread is running with tasks, it queues tasks. @@ -99,10 +100,6 @@ public boolean shouldRetryRequest(int responseCode, int retryAttempt) { * a new message is added. */ public void addPayload(final Payload... payloads) { - for (Payload payload : payloads) { - payload.setConversationId(currentConversationId); - payload.setAuthToken(currentConversationToken); - } singleThreadExecutor.execute(new Runnable() { @Override public void run() { @@ -112,12 +109,12 @@ public void run() { }); } - public void deletePayload(final Payload payload) { - if (payload != null) { + public void deletePayload(final String payloadIdentifier) { + if (payloadIdentifier != null) { singleThreadExecutor.execute(new Runnable() { @Override public void run() { - dbHelper.deletePayload(payload); + dbHelper.deletePayload(payloadIdentifier); sendNextPayloadSync(); } }); @@ -133,16 +130,7 @@ public void run() { }); } - public synchronized Future getOldestUnsentPayload() throws Exception { - return singleThreadExecutor.submit(new Callable() { - @Override - public Payload call() throws Exception { - return getOldestUnsentPayloadSync(); - } - }); - } - - private Payload getOldestUnsentPayloadSync() { + private PayloadData getOldestUnsentPayloadSync() { return dbHelper.getOldestUnsentPayload(); } @@ -180,7 +168,7 @@ public void reset(Context context) { //region PayloadSender.Listener @Override - public void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage) { + public void onFinishSending(PayloadSender sender, PayloadData payload, boolean cancelled, String errorMessage) { ApptentiveNotificationCenter.defaultCenter() .postNotification(NOTIFICATION_PAYLOAD_DID_FINISH_SEND, NOTIFICATION_KEY_PAYLOAD, payload, @@ -201,7 +189,7 @@ public void onFinishSending(PayloadSender sender, Payload payload, boolean cance ApptentiveLog.v(PAYLOADS, "Payload was successfully sent: %s", payload); } - deletePayload(payload); + deletePayload(payload.getPayloadIdentifier()); } //endregion @@ -227,7 +215,7 @@ private void sendNextPayloadSync() { return; } - final Payload payload; + final PayloadData payload; try { payload = getOldestUnsentPayloadSync(); } catch (Exception e) { @@ -240,8 +228,8 @@ private void sendNextPayloadSync() { return; } - if (StringUtils.isNullOrEmpty(payload.getConversationId()) || StringUtils.isNullOrEmpty(payload.getAuthToken())) { - ApptentiveLog.v(PAYLOADS, "Can't send the next payload: no conversation id"); + if (StringUtils.isNullOrEmpty(payload.getAuthToken())) { + ApptentiveLog.v(PAYLOADS, "Can't send the next payload: no auth token"); return; } @@ -292,4 +280,18 @@ public void run() { appInBackground = true; } } + + //region ApptentiveDatabaseHelper.DataSource + + @Override + public String getConversationId() { + return currentConversationId; + } + + @Override + public String getAuthToken() { + return currentConversationToken; + } + + //endregion } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java index 7282a1cb2..81282835f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java @@ -6,7 +6,7 @@ package com.apptentive.android.sdk.storage; -import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.network.HttpRequest; /** @@ -20,5 +20,5 @@ public interface PayloadRequestSender { * @param payload to be sent * @param listener Http-request listener for the payload request */ - HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener); + HttpRequest sendPayload(PayloadData payload, HttpRequest.Listener listener); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 2a2baf2dd..fb81fb5d7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -8,6 +8,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestRetryPolicy; import com.apptentive.android.sdk.util.StringUtils; @@ -56,7 +57,7 @@ class PayloadSender { * * @throws IllegalArgumentException is payload is null */ - synchronized boolean sendPayload(final Payload payload) { + synchronized boolean sendPayload(final PayloadData payload) { if (payload == null) { throw new IllegalArgumentException("Payload is null"); } @@ -90,9 +91,10 @@ synchronized boolean sendPayload(final Payload payload) { /** * Creates and sends payload Http-request asynchronously (returns immediately) + * @param payload */ - private synchronized void sendPayloadRequest(final Payload payload) { - ApptentiveLog.d(PAYLOADS, "Sending payload: %s:%d (%s)", payload.getClass().getName(), payload.getDatabaseId(), payload.getConversationId()); + private synchronized void sendPayloadRequest(final PayloadData payload) { + ApptentiveLog.v(PAYLOADS, "Sending payload: %s", payload.getClass().getName()); // create request object final HttpRequest payloadRequest = requestSender.sendPayload(payload, new HttpRequest.Listener() { @@ -122,12 +124,11 @@ public void onFail(HttpRequest request, String reason) { /** * Executed when we're done with the current payload - * - * @param payload - current payload + * @param payload - current payload * @param cancelled - flag indicating if payload Http-request was cancelled * @param errorMessage - if not null - payload request failed */ - private synchronized void handleFinishSendingPayload(Payload payload, boolean cancelled, String errorMessage) { + private synchronized void handleFinishSendingPayload(PayloadData payload, boolean cancelled, String errorMessage) { sendingFlag = false; // mark sender as 'not busy' try { @@ -159,7 +160,7 @@ public void setListener(Listener listener) { //region Listener public interface Listener { - void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage); + void onFinishSending(PayloadSender sender, PayloadData payload, boolean cancelled, String errorMessage); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java index 628b79c3a..8795ea140 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java @@ -8,13 +8,7 @@ public interface PayloadStore { void addPayload(Payload... payloads); - void deletePayload(Payload payload); + void deletePayload(String payloadIdentifier); void deleteAllPayloads(); - - /* Asynchronous call to retrieve the oldest unsent payload from the data storage. - * Calling get() method on the returned Future object will block the caller until the Future has completed, - */ - Future getOldestUnsentPayload() throws Exception; - } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/LegacyPayloadFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/LegacyPayloadFactory.java index 9b7d6cf54..88f7eb0e8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/LegacyPayloadFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/LegacyPayloadFactory.java @@ -9,11 +9,10 @@ import com.apptentive.android.sdk.model.AppReleasePayload; import com.apptentive.android.sdk.model.DevicePayload; import com.apptentive.android.sdk.model.EventPayload; -import com.apptentive.android.sdk.model.LogoutPayload; +import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.PayloadType; import com.apptentive.android.sdk.model.PersonPayload; -import com.apptentive.android.sdk.model.SdkAndAppReleasePayload; import com.apptentive.android.sdk.model.SdkPayload; import com.apptentive.android.sdk.model.SurveyResponsePayload; import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; @@ -24,7 +23,7 @@ * We only keep this class for legacy database migration purposes */ public final class LegacyPayloadFactory { - public static Payload createPayload(PayloadType type, String json) throws JSONException { + public static JsonPayload createPayload(PayloadType type, String json) throws JSONException { switch (type) { case message: return MessageFactory.fromJson(json); @@ -36,12 +35,8 @@ public static Payload createPayload(PayloadType type, String json) throws JSONEx return new SdkPayload(json); case app_release: return new AppReleasePayload(json); - case sdk_and_app_release: - return new SdkAndAppReleasePayload(json); case person: return new PersonPayload(json); - case logout: - return new LogoutPayload(json); case survey: return new SurveyResponsePayload(json); default: diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java index 1b4e1731a..1075887a0 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java @@ -9,6 +9,7 @@ import com.apptentive.android.sdk.TestCaseBase; import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestManager; import com.apptentive.android.sdk.network.HttpRequestMethod; @@ -42,7 +43,7 @@ public void testSendPayload() throws Exception { PayloadSender sender = new PayloadSender(requestSender, new HttpRequestRetryPolicyDefault()); sender.setListener(new PayloadSender.Listener() { @Override - public void onFinishSending(PayloadSender sender, Payload payload, boolean cancelled, String errorMessage) { + public void onFinishSending(PayloadSender sender, PayloadData payload, boolean cancelled, String errorMessage) { if (cancelled) { addResult("cancelled: " + payload); } else if (errorMessage != null) { @@ -89,14 +90,13 @@ public int getResponseCode() { ); } - class MockPayload extends JsonPayload { + class MockPayload extends PayloadData { private final String json; private ResponseHandler responseHandler; public MockPayload(String key, Object value) { json = StringUtils.format("{'%s':'%s'}", key, value); responseHandler = new DefaultResponseHandler(); - setDatabaseId(0L); } public MockPayload setResponseCode(int responseCode) { @@ -113,21 +113,6 @@ public ResponseHandler getResponseHandler() { return responseHandler; } - @Override - public String getHttpEndPoint() { - return null; - } - - @Override - public HttpRequestMethod getHttpRequestMethod() { - return null; - } - - @Override - public String getHttpRequestContentType() { - return null; - } - @Override public String toString() { return json; @@ -142,7 +127,7 @@ public MockPayloadRequestSender() { } @Override - public HttpRequest sendPayload(Payload payload, HttpRequest.Listener listener) { + public HttpRequest sendPayload(PayloadData payload, HttpRequest.Listener listener) { MockHttpRequest request = new MockHttpRequest("http://apptentive.com"); request.setMockResponseHandler(((MockPayload) payload).getResponseHandler()); request.addListener(listener); From 38c8c117e610b5d7269148f62aacc20c357d94db Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 21 Apr 2017 14:53:24 -0700 Subject: [PATCH 250/465] Fixed sending json requests --- .../sdk/comm/ApptentiveHttpClient.java | 28 +++++++++++++++++-- .../sdk/storage/ApptentiveDatabaseHelper.java | 11 +++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 869c6f5d8..3168a8369 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -13,6 +13,8 @@ import com.apptentive.android.sdk.storage.PayloadRequestSender; import com.apptentive.android.sdk.util.Constants; +import org.json.JSONObject; + import java.util.List; import static android.text.TextUtils.isEmpty; @@ -53,8 +55,8 @@ public ApptentiveHttpClient(String apiKey, String serverURL) { //region API Requests - public RawHttpRequest getConversationToken(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { - RawHttpRequest request = createRawRequest(apiKey, ENDPOINT_CONVERSATION, conversationTokenRequest.toString().getBytes(), HttpRequestMethod.POST); + public HttpJsonRequest getConversationToken(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { + HttpJsonRequest request = createJsonRequest(apiKey, ENDPOINT_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST); request.addListener(listener); httpRequestManager.startRequest(request); return request; @@ -101,6 +103,28 @@ private HttpRequest createPayloadRequest(PayloadData payload) { //region Helpers + private HttpJsonRequest createJsonRequest(String oauthToken, String endpoint, JSONObject json, HttpRequestMethod method) { + if (oauthToken == null) { + throw new IllegalArgumentException("OAuth token is null"); + } + if (endpoint == null) { + throw new IllegalArgumentException("Endpoint is null"); + } + if (json == null) { + throw new IllegalArgumentException("Json is null"); + } + if (method == null) { + throw new IllegalArgumentException("Method is null"); + } + + String url = createEndpointURL(endpoint); + HttpJsonRequest request = new HttpJsonRequest(url, json); + setupRequestDefaults(request, oauthToken); + request.setMethod(method); + request.setRequestProperty("Content-Type", "application/json"); + return request; + } + private RawHttpRequest createRawRequest(String oauthToken, String endpoint, byte[] data, HttpRequestMethod method) { if (oauthToken == null) { throw new IllegalArgumentException("OAuth token is null"); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 5088a60cf..48832360b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -476,13 +476,22 @@ public PayloadData getOldestUnsentPayload() { PayloadData payload = null; if (cursor.moveToFirst()) { final String conversationId = cursor.getString(PayloadEntry.COLUMN_CONVERSATION_ID.index); + if (conversationId == null) { + return null; + } + + final String authToken = cursor.getString(PayloadEntry.COLUMN_AUTH_TOKEN.index); + if (authToken == null) { + return null; + } + final String httpRequestPath = updatePayloadRequestPath(cursor.getString(PayloadEntry.COLUMN_PATH.index), conversationId); payload = new PayloadData(); payload.setHttpRequestPath(httpRequestPath); payload.setHttpRequestMethod(HttpRequestMethod.valueOf(cursor.getString(PayloadEntry.COLUMN_REQUEST_METHOD.index))); payload.setContentType(cursor.getString(PayloadEntry.COLUMN_CONTENT_TYPE.index)); - payload.setAuthToken(cursor.getString(PayloadEntry.COLUMN_AUTH_TOKEN.index)); + payload.setAuthToken(authToken); payload.setData(cursor.getBlob(PayloadEntry.COLUMN_DATA.index)); } return payload; From 2965c0ebd240c955dd6eccb7fe5e1a749f1c3f9d Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 24 Apr 2017 13:32:02 -0700 Subject: [PATCH 251/465] Store payload type with each payload entry in the storage --- .../android/sdk/ApptentiveNotifications.java | 4 ++-- .../android/sdk/model/AppReleasePayload.java | 3 ++- .../android/sdk/model/ApptentiveMessage.java | 4 ++-- .../android/sdk/model/ConversationItem.java | 8 ++++--- .../android/sdk/model/DevicePayload.java | 3 ++- .../android/sdk/model/EventPayload.java | 8 +++---- .../android/sdk/model/JsonPayload.java | 7 +++--- .../android/sdk/model/LogoutPayload.java | 3 ++- .../apptentive/android/sdk/model/Payload.java | 13 +++++++++- .../android/sdk/model/PayloadData.java | 8 ++++++- .../android/sdk/model/PayloadType.java | 2 ++ .../android/sdk/model/PersonPayload.java | 3 ++- .../sdk/model/SdkAndAppReleasePayload.java | 4 +++- .../android/sdk/model/SdkPayload.java | 3 ++- .../sdk/model/SurveyResponsePayload.java | 3 ++- .../module/messagecenter/MessageManager.java | 10 ++++---- .../sdk/storage/ApptentiveDatabaseHelper.java | 24 ++++++++++++------- 17 files changed, 75 insertions(+), 35 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java index 60193cbcd..4ece68269 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -36,12 +36,12 @@ public class ApptentiveNotifications { /** * Sent before payload request is sent to the server */ - public static final String NOTIFICATION_PAYLOAD_WILL_START_SEND = "NOTIFICATION_PAYLOAD_WILL_START_SEND"; // { payload: Payload } + public static final String NOTIFICATION_PAYLOAD_WILL_START_SEND = "NOTIFICATION_PAYLOAD_WILL_START_SEND"; // { payload: PayloadData } /** * Sent after payload sending if finished (might be successful or not) */ - public static final String NOTIFICATION_PAYLOAD_DID_FINISH_SEND = "NOTIFICATION_PAYLOAD_DID_FINISH_SEND"; // { successful : boolean, payload: Payload } + public static final String NOTIFICATION_PAYLOAD_DID_FINISH_SEND = "NOTIFICATION_PAYLOAD_DID_FINISH_SEND"; // { successful : boolean, payload: PayloadData } /** * Sent if user requested to close all interactions. diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java index 74b06e51d..cb6ba68d8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java @@ -25,10 +25,11 @@ public class AppReleasePayload extends JsonPayload { private static final String KEY_DEBUG = "debug"; public AppReleasePayload() { + super(PayloadType.app_release); } public AppReleasePayload(String json) throws JSONException { - super(json); + super(PayloadType.app_release, json); } //region Http-request diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java index 0654a306d..10508dcb2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java @@ -37,14 +37,14 @@ public abstract class ApptentiveMessage extends ConversationItem implements Mess protected ApptentiveMessage() { - super(); + super(PayloadType.message); state = State.sending; read = true; // This message originated here. initType(); } protected ApptentiveMessage(String json) throws JSONException { - super(json); + super(PayloadType.message, json); state = State.unknown; initType(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java index 88d1c75c2..7a99c0290 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java @@ -21,7 +21,9 @@ public abstract class ConversationItem extends JsonPayload { protected static final String KEY_CLIENT_CREATED_AT = "client_created_at"; protected static final String KEY_CLIENT_CREATED_AT_UTC_OFFSET = "client_created_at_utc_offset"; - protected ConversationItem() { + protected ConversationItem(PayloadType type) { + super(type); + put(KEY_NONCE, getNonce()); double seconds = Util.currentTimeSeconds(); @@ -31,8 +33,8 @@ protected ConversationItem() { setClientCreatedAtUtcOffset(utcOffset); } - protected ConversationItem(String json) throws JSONException { - super(json); + protected ConversationItem(PayloadType type, String json) throws JSONException { + super(type, json); } public Double getClientCreatedAt() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index d0ef839e2..7d75e1ceb 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -42,10 +42,11 @@ public class DevicePayload extends JsonPayload { private static final String KEY_INTEGRATION_CONFIG = "integration_config"; public DevicePayload() { + super(PayloadType.device); } public DevicePayload(String json) throws JSONException { - super(json); + super(PayloadType.device, json); } //region Http-request diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index 707aee69c..6eb4761bc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -28,11 +28,11 @@ public class EventPayload extends ConversationItem { private static final String KEY_CUSTOM_DATA = "custom_data"; public EventPayload(String json) throws JSONException { - super(json); + super(PayloadType.event, json); } public EventPayload(String label, JSONObject data) { - super(); + super(PayloadType.event); put(KEY_LABEL, label); if (data != null) { put(KEY_DATA, data); @@ -40,7 +40,7 @@ public EventPayload(String label, JSONObject data) { } public EventPayload(String label, Map data) { - super(); + super(PayloadType.event); try { put(KEY_LABEL, label); if (data != null && !data.isEmpty()) { @@ -56,7 +56,7 @@ public EventPayload(String label, Map data) { } public EventPayload(String label, String interactionId, String data, Map customData, ExtendedData... extendedData) { - super(); + super(PayloadType.event); try { put(KEY_LABEL, label); if (interactionId != null) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index 9c71438f5..2f254b83e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -20,12 +20,13 @@ public abstract class JsonPayload extends Payload { // These three are not stored in the JSON, only the DB. - public JsonPayload() { - super(); + public JsonPayload(PayloadType type) { + super(type); jsonObject = new JSONObject(); } - public JsonPayload(String json) throws JSONException { + public JsonPayload(PayloadType type, String json) throws JSONException { + super(type); jsonObject = new JSONObject(json); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java index 28e78c4c4..5622efa53 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java @@ -13,10 +13,11 @@ public class LogoutPayload extends JsonPayload { public LogoutPayload() { + super(PayloadType.logout); } public LogoutPayload(String json) throws JSONException { - super(json); + super(PayloadType.logout, json); } //region Http-request diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index 9cfee58c0..1c74271ee 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -12,6 +12,8 @@ import java.util.UUID; public abstract class Payload { + private final PayloadType payloadType; + /** * A value that can be used to correlate a payload with another object * (for example, to update the sent status of a message) @@ -20,7 +22,12 @@ public abstract class Payload { private List attachments; // TODO: Figure out attachment handling - protected Payload() { + protected Payload(PayloadType type) { + if (type == null) { + throw new IllegalArgumentException("Payload type is null"); + } + + this.payloadType = type; nonce = UUID.randomUUID().toString(); } @@ -54,6 +61,10 @@ protected Payload() { //region Getters/Setters + public PayloadType getPayloadType() { + return payloadType; + } + public String getNonce() { return nonce; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java index 562199fc3..46dae2922 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java @@ -9,6 +9,7 @@ import com.apptentive.android.sdk.network.HttpRequestMethod; public class PayloadData { + private final PayloadType type; private String contentType; private String authToken; private byte[] data; @@ -16,7 +17,8 @@ public class PayloadData { private HttpRequestMethod httpRequestMethod; private String payloadIdentifier; - public PayloadData() { + public PayloadData(PayloadType type) { + this.type = type; } public void setContentType(String contentType) { @@ -55,6 +57,10 @@ public HttpRequestMethod getHttpRequestMethod() { return httpRequestMethod; } + public PayloadType getType() { + return type; + } + public void setHttpRequestMethod(HttpRequestMethod httpRequestMethod) { this.httpRequestMethod = httpRequestMethod; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadType.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadType.java index 69e5077c4..e9146daed 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadType.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadType.java @@ -14,7 +14,9 @@ public enum PayloadType { device, sdk, app_release, + sdk_and_app_release, person, + logout, unknown, // Legacy survey; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index e8183bac4..b46769a01 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -28,10 +28,11 @@ public class PersonPayload extends JsonPayload { public static final String KEY_CUSTOM_DATA = "custom_data"; public PersonPayload() { + super(PayloadType.person); } public PersonPayload(String json) throws JSONException { - super(json); + super(PayloadType.person, json); } //region Http-request diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index f64b93236..f7a7932bd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -33,6 +33,8 @@ public class SdkAndAppReleasePayload extends JsonPayload { private final AppReleasePayload appRelease; public SdkAndAppReleasePayload() { + super(PayloadType.sdk_and_app_release); + sdk = new SdkPayload(); appRelease = new AppReleasePayload(); @@ -42,7 +44,7 @@ public SdkAndAppReleasePayload() { } public SdkAndAppReleasePayload(String json) throws JSONException { - super(json); + super(PayloadType.sdk_and_app_release, json); sdk = new SdkPayload(getJSONObject("sdk").toString()); appRelease = new AppReleasePayload(getJSONObject("app_release").toString()); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java index 5938b2897..20c8ca565 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java @@ -23,10 +23,11 @@ public class SdkPayload extends JsonPayload { private static final String KEY_DISTRIBUTION_VERSION = "distribution_version"; public SdkPayload() { + super(PayloadType.sdk); } public SdkPayload(String json) throws JSONException { - super(json); + super(PayloadType.sdk, json); } //region Http-request diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index be5a98a03..16b49ec76 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -23,6 +23,7 @@ public class SurveyResponsePayload extends ConversationItem { private static final String KEY_SURVEY_ANSWERS = "answers"; public SurveyResponsePayload(SurveyInteraction definition, Map answers) { + super(PayloadType.survey); try { put(KEY_SURVEY_ID, definition.getId()); JSONObject answersJson = new JSONObject(); @@ -37,7 +38,7 @@ public SurveyResponsePayload(SurveyInteraction definition, Map a } public SurveyResponsePayload(String json) throws JSONException { - super(json); + super(PayloadType.survey, json); } //region Http-request diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 2474d2e34..b3eecc8a8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -18,6 +18,8 @@ import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.PayloadData; +import com.apptentive.android.sdk.model.PayloadType; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveToastNotification; import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem; @@ -354,13 +356,13 @@ public void onReceiveNotification(ApptentiveNotification notification) { setCurrentForegroundActivity(null); appWentToBackground(); } else if (notification.hasName(NOTIFICATION_PAYLOAD_WILL_START_SEND)) { - final JsonPayload payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, JsonPayload.class); - if (payload instanceof ApptentiveMessage) { + final PayloadData payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, PayloadData.class); + if (payload.getType().equals(PayloadType.message)) { resumeSending(); } } else if (notification.hasName(NOTIFICATION_PAYLOAD_DID_FINISH_SEND)) { - final JsonPayload payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, JsonPayload.class); - if (payload instanceof ApptentiveMessage) { + final PayloadData payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, PayloadData.class); + if (payload.getType().equals(PayloadType.message)) { // onSentMessage((ApptentiveMessage) payload, ???); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 48832360b..64889087c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -54,13 +54,14 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { static final class PayloadEntry { static final String TABLE_NAME = "pending_payload"; static final DatabaseColumn COLUMN_PRIMARY_KEY = new DatabaseColumn(0, "_id"); - static final DatabaseColumn COLUMN_IDENTIFIER = new DatabaseColumn(1, "identifier"); - static final DatabaseColumn COLUMN_CONTENT_TYPE = new DatabaseColumn(2, "contentType"); - static final DatabaseColumn COLUMN_AUTH_TOKEN = new DatabaseColumn(3, "authToken"); - static final DatabaseColumn COLUMN_CONVERSATION_ID = new DatabaseColumn(4, "conversationId"); - static final DatabaseColumn COLUMN_REQUEST_METHOD = new DatabaseColumn(5, "requestMethod"); - static final DatabaseColumn COLUMN_PATH = new DatabaseColumn(6, "path"); - static final DatabaseColumn COLUMN_DATA = new DatabaseColumn(7, "data"); + static final DatabaseColumn COLUMN_PAYLOAD_TYPE = new DatabaseColumn(1, "payloadType"); + static final DatabaseColumn COLUMN_IDENTIFIER = new DatabaseColumn(2, "identifier"); + static final DatabaseColumn COLUMN_CONTENT_TYPE = new DatabaseColumn(3, "contentType"); + static final DatabaseColumn COLUMN_AUTH_TOKEN = new DatabaseColumn(4, "authToken"); + static final DatabaseColumn COLUMN_CONVERSATION_ID = new DatabaseColumn(5, "conversationId"); + static final DatabaseColumn COLUMN_REQUEST_METHOD = new DatabaseColumn(6, "requestMethod"); + static final DatabaseColumn COLUMN_PATH = new DatabaseColumn(7, "path"); + static final DatabaseColumn COLUMN_DATA = new DatabaseColumn(8, "data"); } static final class LegacyPayloadEntry { @@ -74,6 +75,7 @@ static final class LegacyPayloadEntry { "CREATE TABLE " + PayloadEntry.TABLE_NAME + " (" + PayloadEntry.COLUMN_PRIMARY_KEY + " INTEGER PRIMARY KEY, " + + PayloadEntry.COLUMN_PAYLOAD_TYPE + " TEXT, " + PayloadEntry.COLUMN_IDENTIFIER + " TEXT, " + PayloadEntry.COLUMN_CONTENT_TYPE + " TEXT," + PayloadEntry.COLUMN_AUTH_TOKEN + " TEXT," + @@ -422,6 +424,7 @@ void addPayload(Payload... payloads) { for (Payload payload : payloads) { ContentValues values = new ContentValues(); values.put(PayloadEntry.COLUMN_IDENTIFIER.name, notNull(payload.getNonce())); + values.put(PayloadEntry.COLUMN_PAYLOAD_TYPE.name, notNull(payload.getPayloadType().name())); values.put(PayloadEntry.COLUMN_CONTENT_TYPE.name, notNull(payload.getHttpRequestContentType())); values.put(PayloadEntry.COLUMN_AUTH_TOKEN.name, authToken); // might be null values.put(PayloadEntry.COLUMN_CONVERSATION_ID.name, conversationId); // might be null @@ -485,9 +488,14 @@ public PayloadData getOldestUnsentPayload() { return null; } + final PayloadType payloadType = PayloadType.parse(cursor.getString(PayloadEntry.COLUMN_PAYLOAD_TYPE.index)); + if (PayloadType.unknown.equals(payloadType)) { + return null; + } + final String httpRequestPath = updatePayloadRequestPath(cursor.getString(PayloadEntry.COLUMN_PATH.index), conversationId); - payload = new PayloadData(); + payload = new PayloadData(payloadType); payload.setHttpRequestPath(httpRequestPath); payload.setHttpRequestMethod(HttpRequestMethod.valueOf(cursor.getString(PayloadEntry.COLUMN_REQUEST_METHOD.index))); payload.setContentType(cursor.getString(PayloadEntry.COLUMN_CONTENT_TYPE.index)); From 39a4338ef0a24b0adece815a313d68fa24603d9b Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 24 Apr 2017 13:52:56 -0700 Subject: [PATCH 252/465] Refactoring Added additional fields to the NOTIFICATION_PAYLOAD_DID_FINISH_SEND notification --- .../android/sdk/ApptentiveNotifications.java | 4 ++- .../sdk/conversation/FileMessageStore.java | 2 +- .../android/sdk/model/PayloadData.java | 10 +++---- .../android/sdk/network/HttpRequest.java | 6 ++++- .../sdk/storage/ApptentiveTaskManager.java | 12 ++++++--- .../android/sdk/storage/PayloadSender.java | 27 ++++++++++++------- .../sdk/storage/JsonPayloadSenderTest.java | 8 +++--- 7 files changed, 45 insertions(+), 24 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java index 4ece68269..c29b04133 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -41,7 +41,7 @@ public class ApptentiveNotifications { /** * Sent after payload sending if finished (might be successful or not) */ - public static final String NOTIFICATION_PAYLOAD_DID_FINISH_SEND = "NOTIFICATION_PAYLOAD_DID_FINISH_SEND"; // { successful : boolean, payload: PayloadData } + public static final String NOTIFICATION_PAYLOAD_DID_FINISH_SEND = "NOTIFICATION_PAYLOAD_DID_FINISH_SEND"; // { successful : boolean, payload: PayloadData, responseCode: int, responseData: JSONObject } /** * Sent if user requested to close all interactions. @@ -53,4 +53,6 @@ public class ApptentiveNotifications { public static final String NOTIFICATION_KEY_ACTIVITY = "activity"; public static final String NOTIFICATION_KEY_CONVERSATION = "conversation"; public static final String NOTIFICATION_KEY_PAYLOAD = "payload"; + public static final String NOTIFICATION_KEY_RESPONSE_CODE = "responseCode"; + public static final String NOTIFICATION_KEY_RESPONSE_DATA = "responseData"; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index 87f405768..8ee41eda1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -87,7 +87,7 @@ public synchronized void updateMessage(ApptentiveMessage apptentiveMessage) { if (apptentiveMessage.isRead()) { // A apptentiveMessage can't be unread after being read. entry.isRead = true; } - entry.json = apptentiveMessage.toString(); + entry.json = apptentiveMessage.getJsonObject().toString(); writeToFile(); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java index 46dae2922..ac12e57a2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java @@ -15,7 +15,7 @@ public class PayloadData { private byte[] data; private String path; private HttpRequestMethod httpRequestMethod; - private String payloadIdentifier; + private String nonce; public PayloadData(PayloadType type) { this.type = type; @@ -65,11 +65,11 @@ public void setHttpRequestMethod(HttpRequestMethod httpRequestMethod) { this.httpRequestMethod = httpRequestMethod; } - public String getPayloadIdentifier() { - return payloadIdentifier; + public String getNonce() { + return nonce; } - public void setPayloadIdentifier(String payloadIdentifier) { - this.payloadIdentifier = payloadIdentifier; + public void setNonce(String nonce) { + this.nonce = nonce; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 2010a0086..2f892d813 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -528,10 +528,14 @@ public void setCallbackQueue(DispatchQueue callbackQueue) { this.callbackQueue = callbackQueue; } - String getResponseData() { + public String getResponseData() { return responseData; } + public int getResponseCode() { + return responseCode; + } + public HttpRequest setRetryPolicy(HttpRequestRetryPolicy retryPolicy) { if (retryPolicy == null) { throw new IllegalArgumentException("Retry policy is null"); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index cf08130d0..a8767d7f9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -23,6 +23,8 @@ import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; +import org.json.JSONObject; + import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Future; @@ -36,6 +38,8 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_PAYLOAD; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_RESPONSE_CODE; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_RESPONSE_DATA; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_SUCCESSFUL; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_DID_FINISH_SEND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_WILL_START_SEND; @@ -168,11 +172,13 @@ public void reset(Context context) { //region PayloadSender.Listener @Override - public void onFinishSending(PayloadSender sender, PayloadData payload, boolean cancelled, String errorMessage) { + public void onFinishSending(PayloadSender sender, PayloadData payload, boolean cancelled, String errorMessage, int responseCode, JSONObject responseData) { ApptentiveNotificationCenter.defaultCenter() .postNotification(NOTIFICATION_PAYLOAD_DID_FINISH_SEND, NOTIFICATION_KEY_PAYLOAD, payload, - NOTIFICATION_KEY_SUCCESSFUL, errorMessage == null && !cancelled ? TRUE : FALSE); + NOTIFICATION_KEY_SUCCESSFUL, errorMessage == null && !cancelled ? TRUE : FALSE, + NOTIFICATION_KEY_RESPONSE_CODE, responseCode, + NOTIFICATION_KEY_RESPONSE_DATA, responseData); if (cancelled) { ApptentiveLog.v(PAYLOADS, "Payload sending was cancelled: %s", payload); @@ -189,7 +195,7 @@ public void onFinishSending(PayloadSender sender, PayloadData payload, boolean c ApptentiveLog.v(PAYLOADS, "Payload was successfully sent: %s", payload); } - deletePayload(payload.getPayloadIdentifier()); + deletePayload(payload.getNonce()); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index fb81fb5d7..760d83e85 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -7,12 +7,13 @@ package com.apptentive.android.sdk.storage; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestRetryPolicy; import com.apptentive.android.sdk.util.StringUtils; +import org.json.JSONObject; + import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; /** @@ -83,7 +84,7 @@ synchronized boolean sendPayload(final PayloadData payload) { } // if an exception was thrown - mark payload as failed - handleFinishSendingPayload(payload, false, message); + handleFinishSendingPayload(payload, false, message, -1, null); // FIXME: a better approach } return true; @@ -100,17 +101,23 @@ private synchronized void sendPayloadRequest(final PayloadData payload) { final HttpRequest payloadRequest = requestSender.sendPayload(payload, new HttpRequest.Listener() { @Override public void onFinish(HttpRequest request) { - handleFinishSendingPayload(payload, false, null); + try { + final JSONObject responseData = new JSONObject(request.getResponseData()); + handleFinishSendingPayload(payload, false, null, request.getResponseCode(), responseData); + } catch (Exception e) { + ApptentiveLog.e(PAYLOADS, "Exception while handling payload send response"); + handleFinishSendingPayload(payload, false, null, -1, null); + } } @Override public void onCancel(HttpRequest request) { - handleFinishSendingPayload(payload, true, null); + handleFinishSendingPayload(payload, true, null, request.getResponseCode(), null); } @Override public void onFail(HttpRequest request, String reason) { - handleFinishSendingPayload(payload, false, reason); + handleFinishSendingPayload(payload, false, reason, request.getResponseCode(), null); } }); @@ -124,16 +131,18 @@ public void onFail(HttpRequest request, String reason) { /** * Executed when we're done with the current payload - * @param payload - current payload + * @param payload - current payload * @param cancelled - flag indicating if payload Http-request was cancelled * @param errorMessage - if not null - payload request failed + * @param responseCode - http-request response code + * @param responseData - http-reqeust response json (or null if failed) */ - private synchronized void handleFinishSendingPayload(PayloadData payload, boolean cancelled, String errorMessage) { + private synchronized void handleFinishSendingPayload(PayloadData payload, boolean cancelled, String errorMessage, int responseCode, JSONObject responseData) { sendingFlag = false; // mark sender as 'not busy' try { if (listener != null) { - listener.onFinishSending(this, payload, cancelled, errorMessage); + listener.onFinishSending(this, payload, cancelled, errorMessage, responseCode, responseData); } } catch (Exception e) { ApptentiveLog.e(e, "Exception while notifying payload listener"); @@ -160,7 +169,7 @@ public void setListener(Listener listener) { //region Listener public interface Listener { - void onFinishSending(PayloadSender sender, PayloadData payload, boolean cancelled, String errorMessage); + void onFinishSending(PayloadSender sender, PayloadData payload, boolean cancelled, String errorMessage, int responseCode, JSONObject responseData); } //endregion diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java index 1075887a0..a78fcf82b 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java @@ -7,12 +7,10 @@ package com.apptentive.android.sdk.storage; import com.apptentive.android.sdk.TestCaseBase; -import com.apptentive.android.sdk.model.JsonPayload; -import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.PayloadData; +import com.apptentive.android.sdk.model.PayloadType; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestManager; -import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.network.HttpRequestRetryPolicyDefault; import com.apptentive.android.sdk.network.MockHttpRequest; import com.apptentive.android.sdk.network.MockHttpURLConnection.DefaultResponseHandler; @@ -43,7 +41,7 @@ public void testSendPayload() throws Exception { PayloadSender sender = new PayloadSender(requestSender, new HttpRequestRetryPolicyDefault()); sender.setListener(new PayloadSender.Listener() { @Override - public void onFinishSending(PayloadSender sender, PayloadData payload, boolean cancelled, String errorMessage) { + public void onFinishSending(PayloadSender sender, PayloadData payload, boolean cancelled, String errorMessage, int responseCode) { if (cancelled) { addResult("cancelled: " + payload); } else if (errorMessage != null) { @@ -95,6 +93,8 @@ class MockPayload extends PayloadData { private ResponseHandler responseHandler; public MockPayload(String key, Object value) { + super(PayloadType.unknown); // TODO: figure out a better type + json = StringUtils.format("{'%s':'%s'}", key, value); responseHandler = new DefaultResponseHandler(); } From 30accd1d5a7a6bb081242cb167390a8a0a223ac6 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 24 Apr 2017 14:24:21 -0700 Subject: [PATCH 253/465] Fixed handling message payload sending in MessageManager --- .../sdk/conversation/FileMessageStore.java | 14 ++++++++ .../fragment/MessageCenterFragment.java | 11 +++--- .../module/messagecenter/MessageManager.java | 36 +++++++++++++------ .../android/sdk/storage/MessageStore.java | 2 ++ .../sdk/storage/JsonPayloadSenderTest.java | 3 +- 5 files changed, 49 insertions(+), 17 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index 8ee41eda1..078995c29 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -156,6 +156,20 @@ public synchronized void deleteMessage(String nonce) { } } + @Override + public ApptentiveMessage findMessage(String nonce) { + fetchEntries(); + + for (int i = 0; i < messageEntries.size(); ++i) { + final MessageEntry messageEntry = messageEntries.get(i); + if (StringUtils.equal(nonce, messageEntry.nonce)) { + return MessageFactory.fromJson(messageEntry.json); + } + } + + return null; + } + //endregion //region File save/load diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index c60795a00..6c4757d29 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -42,7 +42,6 @@ import com.apptentive.android.sdk.ApptentiveViewActivity; import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; @@ -726,10 +725,12 @@ public void showAttachmentDialog(final ImageItem image) { @SuppressWarnings("unchecked") // We should never get a message passed in that is not appropriate for the view it goes into. - public synchronized void onMessageSent(ApptentiveHttpResponse response, final ApptentiveMessage apptentiveMessage) { - if (response.isSuccessful() || response.isRejectedPermanently() || response.isBadPayload()) { - messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_MESSAGE_SENT, - apptentiveMessage)); + public synchronized void onMessageSent(int responseCode, final ApptentiveMessage apptentiveMessage) { + final boolean isRejectedPermanently = responseCode >= 400 && responseCode < 500; + final boolean isSuccessful = responseCode >= 200 && responseCode < 300; + + if (isSuccessful || isRejectedPermanently || responseCode == -1) { + messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_MESSAGE_SENT, apptentiveMessage)); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index b3eecc8a8..6a81fb1e8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -16,7 +16,6 @@ import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; -import com.apptentive.android.sdk.model.JsonPayload; import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.model.PayloadType; @@ -39,6 +38,7 @@ import org.json.JSONObject; import java.lang.ref.WeakReference; +import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -50,8 +50,11 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_ACTIVITY; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_PAYLOAD; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_RESPONSE_CODE; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_RESPONSE_DATA; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_DID_FINISH_SEND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_WILL_START_SEND; +import static com.apptentive.android.sdk.debug.Assert.assertNotNull; public class MessageManager implements Destroyable, ApptentiveNotificationObserver { @@ -274,26 +277,36 @@ public void pauseSending(int reason_code) { } } - public void onSentMessage(ApptentiveMessage apptentiveMessage, ApptentiveHttpResponse response) { + public void onSentMessage(String nonce, int responseCode, JSONObject responseJson) { - if (response.isRejectedPermanently() || response.isBadPayload()) { + final ApptentiveMessage apptentiveMessage = messageStore.findMessage(nonce); + assertNotNull(apptentiveMessage, "Can't find a message with nonce: %s", nonce); + if (apptentiveMessage == null) { + return; // should not happen but we want to stay safe + } + + final boolean isRejectedPermanently = responseCode >= 400 && responseCode < 500; + final boolean isSuccessful = responseCode >= 200 && responseCode < 300; + final boolean isRejectedTemporarily = !(isSuccessful || isRejectedPermanently); + + if (isRejectedPermanently || responseCode == -1) { if (apptentiveMessage instanceof CompoundMessage) { apptentiveMessage.setCreatedAt(Double.MIN_VALUE); messageStore.updateMessage(apptentiveMessage); if (afterSendMessageListener != null && afterSendMessageListener.get() != null) { - afterSendMessageListener.get().onMessageSent(response, apptentiveMessage); + afterSendMessageListener.get().onMessageSent(responseCode, apptentiveMessage); } } return; } - if (response.isRejectedTemporarily()) { + if (isRejectedTemporarily) { pauseSending(SEND_PAUSE_REASON_SERVER); return; } - if (response.isSuccessful()) { + if (isSuccessful) { // Don't store hidden messages once sent. Delete them. if (apptentiveMessage.isHidden()) { ((CompoundMessage) apptentiveMessage).deleteAssociatedFiles(); @@ -301,8 +314,7 @@ public void onSentMessage(ApptentiveMessage apptentiveMessage, ApptentiveHttpRes return; } try { - JSONObject responseJson = new JSONObject(response.getContent()); - + // TODO: update the database with these values apptentiveMessage.setState(ApptentiveMessage.State.sent); apptentiveMessage.setId(responseJson.getString(ApptentiveMessage.KEY_ID)); @@ -313,7 +325,7 @@ public void onSentMessage(ApptentiveMessage apptentiveMessage, ApptentiveHttpRes messageStore.updateMessage(apptentiveMessage); if (afterSendMessageListener != null && afterSendMessageListener.get() != null) { - afterSendMessageListener.get().onMessageSent(response, apptentiveMessage); + afterSendMessageListener.get().onMessageSent(responseCode, apptentiveMessage); } } } @@ -362,8 +374,10 @@ public void onReceiveNotification(ApptentiveNotification notification) { } } else if (notification.hasName(NOTIFICATION_PAYLOAD_DID_FINISH_SEND)) { final PayloadData payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, PayloadData.class); + final Integer responseCode = notification.getRequiredUserInfo(NOTIFICATION_KEY_RESPONSE_CODE, Integer.class); + final JSONObject responseData = notification.getRequiredUserInfo(NOTIFICATION_KEY_RESPONSE_DATA, JSONObject.class); if (payload.getType().equals(PayloadType.message)) { - // onSentMessage((ApptentiveMessage) payload, ???); + onSentMessage(payload.getNonce(), responseCode, responseData); } } } @@ -382,7 +396,7 @@ public void destroy() { // Listeners public interface AfterSendMessageListener { - void onMessageSent(ApptentiveHttpResponse response, ApptentiveMessage apptentiveMessage); + void onMessageSent(int responseCode, ApptentiveMessage apptentiveMessage); void onPauseSending(int reason); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/MessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/MessageStore.java index a6837b15d..03fecb88c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/MessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/MessageStore.java @@ -29,4 +29,6 @@ public interface MessageStore { void deleteAllMessages(); void deleteMessage(String nonce); + + ApptentiveMessage findMessage(String nonce); } diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java index a78fcf82b..9f0e692fd 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java @@ -18,6 +18,7 @@ import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.threading.MockDispatchQueue; +import org.json.JSONObject; import org.junit.Before; import org.junit.Test; @@ -41,7 +42,7 @@ public void testSendPayload() throws Exception { PayloadSender sender = new PayloadSender(requestSender, new HttpRequestRetryPolicyDefault()); sender.setListener(new PayloadSender.Listener() { @Override - public void onFinishSending(PayloadSender sender, PayloadData payload, boolean cancelled, String errorMessage, int responseCode) { + public void onFinishSending(PayloadSender sender, PayloadData payload, boolean cancelled, String errorMessage, int responseCode, JSONObject responseData) { if (cancelled) { addResult("cancelled: " + payload); } else if (errorMessage != null) { From 14d5c74b3ea0ca917d1a6e7e09a57c2597c09383 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 24 Apr 2017 15:41:27 -0700 Subject: [PATCH 254/465] Fixed payload sending --- .../sdk/comm/ApptentiveHttpClient.java | 6 +- .../android/sdk/model/PayloadData.java | 99 +++++++++++-------- .../sdk/storage/ApptentiveDatabaseHelper.java | 45 +++++---- .../sdk/storage/JsonPayloadSenderTest.java | 2 +- 4 files changed, 86 insertions(+), 66 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 3168a8369..acdb31709 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -87,16 +87,16 @@ public HttpRequest sendPayload(PayloadData payload, HttpRequest.Listener associatedFiles = ((MultipartPayload) payload).getAssociatedFiles(); - return createMultipartRequest(token, endPoint, payload.getData(), associatedFiles, requestMethod); + return createMultipartRequest(token, httpPath, payload.getData(), associatedFiles, requestMethod); } - return createRawRequest(token, endPoint, payload.getData(), requestMethod); + return createRawRequest(token, httpPath, payload.getData(), requestMethod); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java index ac12e57a2..69be77b16 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java @@ -7,69 +7,90 @@ package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.util.StringUtils; public class PayloadData { private final PayloadType type; - private String contentType; - private String authToken; - private byte[] data; - private String path; - private HttpRequestMethod httpRequestMethod; - private String nonce; - - public PayloadData(PayloadType type) { - this.type = type; - } + private final String nonce; + private final byte[] data; + private final String authToken; + private final String contentType; + private final String httpRequestPath; + private final HttpRequestMethod httpRequestMethod; - public void setContentType(String contentType) { - this.contentType = contentType; - } - public String getContentType() { - return contentType; - } + public PayloadData(PayloadType type, String nonce, byte[] data, String authToken, String contentType, String httpRequestPath, HttpRequestMethod httpRequestMethod) { + if (type == null) { + throw new IllegalArgumentException("Payload type is null"); + } - public void setAuthToken(String authToken) { - this.authToken = authToken; - } + if (nonce == null) { + throw new IllegalArgumentException("Nonce is null"); + } - public String getAuthToken() { - return authToken; - } + if (authToken == null) { + throw new IllegalArgumentException("Auth token is null"); + } - public void setData(byte[] data) { + if (contentType == null) { + throw new IllegalArgumentException("Content type is null"); + } + + if (httpRequestPath == null) { + throw new IllegalArgumentException("Path is null"); + } + + if (httpRequestMethod == null) { + throw new IllegalArgumentException("Http request method is null"); + } + + this.type = type; + this.nonce = nonce; this.data = data; + this.authToken = authToken; + this.contentType = contentType; + this.httpRequestPath = httpRequestPath; + this.httpRequestMethod = httpRequestMethod; } - public byte[] getData() { - return data; + //region String representation + + @Override + public String toString() { + return StringUtils.format("type=%s nonce=%s authToken=%s httpRequestPath=%s", type, nonce, authToken, httpRequestPath); } - public void setHttpRequestPath(String path) { - this.path = path; + //endregion + + //region Getters + + public PayloadType getType() { + return type; } - public String getHttpEndPoint() { - return path; + public String getNonce() { + return nonce; } - public HttpRequestMethod getHttpRequestMethod() { - return httpRequestMethod; + public byte[] getData() { + return data; } - public PayloadType getType() { - return type; + public String getAuthToken() { + return authToken; } - public void setHttpRequestMethod(HttpRequestMethod httpRequestMethod) { - this.httpRequestMethod = httpRequestMethod; + public String getContentType() { + return contentType; } - public String getNonce() { - return nonce; + public String getHttpRequestPath() { + return httpRequestPath; } - public void setNonce(String nonce) { - this.nonce = nonce; + public HttpRequestMethod getHttpRequestMethod() { + return httpRequestMethod; } + + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 64889087c..a84fd7891 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -444,18 +444,20 @@ void addPayload(Payload... payloads) { } void deletePayload(String payloadIdentifier) { - if (payloadIdentifier != null) { - SQLiteDatabase db; - try { - db = getWritableDatabase(); - db.delete( - PayloadEntry.TABLE_NAME, - PayloadEntry.COLUMN_IDENTIFIER + " = ?", - new String[]{payloadIdentifier} - ); - } catch (SQLException sqe) { - ApptentiveLog.e(DATABASE, "deletePayload EXCEPTION: " + sqe.getMessage()); - } + if (payloadIdentifier == null) { + throw new IllegalArgumentException("Payload identifier is null"); + } + + SQLiteDatabase db; + try { + db = getWritableDatabase(); + db.delete( + PayloadEntry.TABLE_NAME, + PayloadEntry.COLUMN_IDENTIFIER + " = ?", + new String[]{payloadIdentifier} + ); + } catch (SQLException sqe) { + ApptentiveLog.e(DATABASE, "deletePayload EXCEPTION: " + sqe.getMessage()); } } @@ -476,7 +478,6 @@ public PayloadData getOldestUnsentPayload() { try { db = getWritableDatabase(); cursor = db.rawQuery(SQL_QUERY_PAYLOAD_GET_NEXT_TO_SEND, null); - PayloadData payload = null; if (cursor.moveToFirst()) { final String conversationId = cursor.getString(PayloadEntry.COLUMN_CONVERSATION_ID.index); if (conversationId == null) { @@ -494,16 +495,14 @@ public PayloadData getOldestUnsentPayload() { } final String httpRequestPath = updatePayloadRequestPath(cursor.getString(PayloadEntry.COLUMN_PATH.index), conversationId); - - payload = new PayloadData(payloadType); - payload.setHttpRequestPath(httpRequestPath); - payload.setHttpRequestMethod(HttpRequestMethod.valueOf(cursor.getString(PayloadEntry.COLUMN_REQUEST_METHOD.index))); - payload.setContentType(cursor.getString(PayloadEntry.COLUMN_CONTENT_TYPE.index)); - payload.setAuthToken(authToken); - payload.setData(cursor.getBlob(PayloadEntry.COLUMN_DATA.index)); + final String nonce = notNull(cursor.getString(PayloadEntry.COLUMN_IDENTIFIER.index)); + final byte[] data = notNull(cursor.getBlob(PayloadEntry.COLUMN_DATA.index)); + final String contentType = notNull(cursor.getString(PayloadEntry.COLUMN_CONTENT_TYPE.index)); + final HttpRequestMethod httpRequestMethod = HttpRequestMethod.valueOf(notNull(cursor.getString(PayloadEntry.COLUMN_REQUEST_METHOD.index))); + return new PayloadData(payloadType, nonce, data, authToken, contentType, httpRequestPath, httpRequestMethod); } - return payload; - } catch (SQLException sqe) { + return null; + } catch (Exception sqe) { ApptentiveLog.e(DATABASE, "getOldestUnsentPayload EXCEPTION: " + sqe.getMessage()); return null; } finally { @@ -525,7 +524,7 @@ public void updateIncompletePayloads(String conversationId, String authToken) { Cursor cursor = null; try { SQLiteDatabase db = getWritableDatabase(); - cursor = db.rawQuery(SQL_QUERY_UPDATE_INCOMPLETE_PAYLOADS, new String[] { + cursor = db.rawQuery(SQL_QUERY_UPDATE_INCOMPLETE_PAYLOADS, new String[]{ authToken, conversationId }); cursor.moveToFirst(); // we need to move a cursor in order to update database diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java index 9f0e692fd..3075867ee 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java @@ -94,7 +94,7 @@ class MockPayload extends PayloadData { private ResponseHandler responseHandler; public MockPayload(String key, Object value) { - super(PayloadType.unknown); // TODO: figure out a better type + super(PayloadType.unknown, nonce, data, authToken, contentType, path, httpRequestMethod); // TODO: figure out a better type json = StringUtils.format("{'%s':'%s'}", key, value); responseHandler = new DefaultResponseHandler(); From 2af40983f4bf9e30823560a62f28844de3f587e7 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 24 Apr 2017 17:20:52 -0700 Subject: [PATCH 255/465] Combined AppRelease and SDK payloads --- .../sdk/model/SdkAndAppReleasePayload.java | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index f7a7932bd..dda4440af 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -18,36 +18,35 @@ import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; -import org.json.JSONException; - /** * A combined payload of {@link SdkPayload} and {@link AppReleasePayload} payloads. - * + *

* This class effectively contains the source code from both {@link SdkPayload} * and {@link AppReleasePayload} payloads (which still kept for backward compatibility * purposes). */ public class SdkAndAppReleasePayload extends JsonPayload { - private final SdkPayload sdk; - private final AppReleasePayload appRelease; + private static final String KEY_TYPE = "type"; + private static final String KEY_VERSION_NAME = "version_name"; + private static final String KEY_VERSION_CODE = "version_code"; + private static final String KEY_IDENTIFIER = "identifier"; + private static final String KEY_TARGET_SDK_VERSION = "target_sdk_version"; + private static final String KEY_APP_STORE = "app_store"; + private static final String KEY_STYLE_INHERIT = "inheriting_styles"; + private static final String KEY_STYLE_OVERRIDE = "overriding_styles"; + private static final String KEY_DEBUG = "debug"; + + private static final String KEY_VERSION = "sdk_version"; + private static final String KEY_PROGRAMMING_LANGUAGE = "sdk_programming_language"; + private static final String KEY_AUTHOR_NAME = "sdk_author_name"; + private static final String KEY_AUTHOR_EMAIL = "sdk_author_email"; + private static final String KEY_PLATFORM = "sdk_platform"; + private static final String KEY_DISTRIBUTION = "sdk_distribution"; + private static final String KEY_DISTRIBUTION_VERSION = "sdk_distribution_version"; public SdkAndAppReleasePayload() { super(PayloadType.sdk_and_app_release); - - sdk = new SdkPayload(); - appRelease = new AppReleasePayload(); - - // TODO: a better solution - put("sdk", sdk.getJsonObject()); - put("app_release", appRelease.getJsonObject()); - } - - public SdkAndAppReleasePayload(String json) throws JSONException { - super(PayloadType.sdk_and_app_release, json); - - sdk = new SdkPayload(getJSONObject("sdk").toString()); - appRelease = new AppReleasePayload(getJSONObject("app_release").toString()); } //region Http-request @@ -71,135 +70,136 @@ public String getHttpRequestContentType() { //region Sdk getters/setters public String getVersion() { - return sdk.getVersion(); + return getString(KEY_VERSION); } public void setVersion(String version) { - sdk.setVersion(version); + put(KEY_VERSION, version); } public String getProgrammingLanguage() { - return sdk.getProgrammingLanguage(); + return getString(KEY_PROGRAMMING_LANGUAGE); } public void setProgrammingLanguage(String programmingLanguage) { - sdk.setProgrammingLanguage(programmingLanguage); + put(KEY_PROGRAMMING_LANGUAGE, programmingLanguage); } public String getAuthorName() { - return sdk.getAuthorName(); + return getString(KEY_AUTHOR_NAME); } public void setAuthorName(String authorName) { - sdk.setAuthorName(authorName); + put(KEY_AUTHOR_NAME, authorName); } public String getAuthorEmail() { - return sdk.getAuthorEmail(); + return getString(KEY_AUTHOR_EMAIL); } public void setAuthorEmail(String authorEmail) { - sdk.setAuthorEmail(authorEmail); + put(KEY_AUTHOR_EMAIL, authorEmail); } public String getPlatform() { - return sdk.getPlatform(); + return getString(KEY_PLATFORM); } public void setPlatform(String platform) { - sdk.setPlatform(platform); + put(KEY_PLATFORM, platform); } public String getDistribution() { - return sdk.getDistribution(); + return getString(KEY_DISTRIBUTION); } public void setDistribution(String distribution) { - sdk.setDistribution(distribution); + put(KEY_DISTRIBUTION, distribution); } public String getDistributionVersion() { - return sdk.getDistributionVersion(); + return getString(KEY_DISTRIBUTION_VERSION); } public void setDistributionVersion(String distributionVersion) { - sdk.setDistributionVersion(distributionVersion); + put(KEY_DISTRIBUTION_VERSION, distributionVersion); } //endregion //region AppRelease getters/setters + public String getType() { - return appRelease.getType(); + return getString(KEY_TYPE); } public void setType(String type) { - appRelease.setType(type); + put(KEY_TYPE, type); } public String getVersionName() { - return appRelease.getVersionName(); + return getString(KEY_VERSION_NAME); } public void setVersionName(String versionName) { - appRelease.setVersionName(versionName); + put(KEY_VERSION_NAME, versionName); } public int getVersionCode() { - return appRelease.getVersionCode(); + return getInt(KEY_VERSION_CODE, -1); } public void setVersionCode(int versionCode) { - appRelease.setVersionCode(versionCode); + put(KEY_VERSION_CODE, versionCode); } public String getIdentifier() { - return appRelease.getIdentifier(); + return getString(KEY_IDENTIFIER); } public void setIdentifier(String identifier) { - appRelease.setIdentifier(identifier); + put(KEY_IDENTIFIER, identifier); } public String getTargetSdkVersion() { - return appRelease.getTargetSdkVersion(); + return getString(KEY_TARGET_SDK_VERSION); } public void setTargetSdkVersion(String targetSdkVersion) { - appRelease.setTargetSdkVersion(targetSdkVersion); + put(KEY_TARGET_SDK_VERSION, targetSdkVersion); } public String getAppStore() { - return appRelease.getAppStore(); + return getString(KEY_APP_STORE); } public void setAppStore(String appStore) { - appRelease.setAppStore(appStore); + put(KEY_APP_STORE, appStore); } // Flag for whether the apptentive is inheriting styles from the host app public boolean getInheritStyle() { - return appRelease.getInheritStyle(); + return getBoolean(KEY_STYLE_INHERIT); } public void setInheritStyle(boolean inheritStyle) { - appRelease.setInheritStyle(inheritStyle); + put(KEY_STYLE_INHERIT, inheritStyle); } // Flag for whether the app is overriding any Apptentive Styles public boolean getOverrideStyle() { - return appRelease.getOverrideStyle(); + return getBoolean(KEY_STYLE_OVERRIDE); } public void setOverrideStyle(boolean overrideStyle) { - appRelease.setOverrideStyle(overrideStyle); + put(KEY_STYLE_OVERRIDE, overrideStyle); } public boolean getDebug() { - return appRelease.getDebug(); + return getBoolean(KEY_DEBUG); } public void setDebug(boolean debug) { - appRelease.setDebug(debug); + put(KEY_DEBUG, debug); } public static AppReleasePayload generateCurrentAppRelease(Context context) { From f8cc88e3e94bce56f0fb62816138c2b870ac0140 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 24 Apr 2017 17:21:04 -0700 Subject: [PATCH 256/465] Fixed unit tests --- .../sdk/conversation/FileMessageStoreTest.java | 17 +++++++++++------ .../sdk/storage/JsonPayloadSenderTest.java | 3 ++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java index c4136d8df..b2096ad1c 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java @@ -11,6 +11,7 @@ import com.apptentive.android.sdk.TestCaseBase; import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.model.CompoundMessage; +import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; import org.json.JSONObject; @@ -22,6 +23,7 @@ import java.io.File; import java.io.IOException; +import java.util.Iterator; import java.util.List; import java.util.UUID; @@ -258,11 +260,14 @@ private ApptentiveMessage createMessage(String nonce, State state, boolean read, message.setId(id); message.setState(state); message.setRead(read); + message.setNonce(nonce); return message; } private File getTempFile() throws IOException { - return tempFolder.newFile(); + final File file = tempFolder.newFile(); + file.delete(); // this file might exist + return file; } private void addResult(List messages) throws JSONException { @@ -273,21 +278,21 @@ private void addResult(List messages) throws JSONException { private String toString(ApptentiveMessage message) throws JSONException { String result = "{"; -/* - // FIXME - final Iterator keys = message.keys(); + final Iterator keys = message.getJsonObject().keys(); while (keys.hasNext()) { String key = keys.next(); if (key.equals("id")) { // 'id' is randomly generated each time (so don't test it) continue; } - result += StringUtils.format("'%s':'%s',", key, message.get(key)); + if (key.equals("type")) { // it's always 'CompoundMessage' + continue; + } + result += StringUtils.format("'%s':'%s',", key, message.getJsonObject().get(key)); } result += StringUtils.format("'state':'%s',", message.getState().name()); result += StringUtils.format("'read':'%s'", message.isRead()); result += "}"; -*/ return result; } } \ No newline at end of file diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java index 3075867ee..f1593d8b7 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java @@ -11,6 +11,7 @@ import com.apptentive.android.sdk.model.PayloadType; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestManager; +import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.network.HttpRequestRetryPolicyDefault; import com.apptentive.android.sdk.network.MockHttpRequest; import com.apptentive.android.sdk.network.MockHttpURLConnection.DefaultResponseHandler; @@ -94,7 +95,7 @@ class MockPayload extends PayloadData { private ResponseHandler responseHandler; public MockPayload(String key, Object value) { - super(PayloadType.unknown, nonce, data, authToken, contentType, path, httpRequestMethod); // TODO: figure out a better type + super(PayloadType.unknown, "nonce", new byte[0], "authToken", "contentType", "path", HttpRequestMethod.GET); // TODO: figure out a better type json = StringUtils.format("{'%s':'%s'}", key, value); responseHandler = new DefaultResponseHandler(); From 3c4043ea19b337af9921e4c4373fd1c4625d9cab Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 24 Apr 2017 17:29:59 -0700 Subject: [PATCH 257/465] Refactoring Changed payload key constants --- .../sdk/model/SdkAndAppReleasePayload.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index dda4440af..866236611 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -37,13 +37,13 @@ public class SdkAndAppReleasePayload extends JsonPayload { private static final String KEY_STYLE_OVERRIDE = "overriding_styles"; private static final String KEY_DEBUG = "debug"; - private static final String KEY_VERSION = "sdk_version"; - private static final String KEY_PROGRAMMING_LANGUAGE = "sdk_programming_language"; - private static final String KEY_AUTHOR_NAME = "sdk_author_name"; - private static final String KEY_AUTHOR_EMAIL = "sdk_author_email"; - private static final String KEY_PLATFORM = "sdk_platform"; - private static final String KEY_DISTRIBUTION = "sdk_distribution"; - private static final String KEY_DISTRIBUTION_VERSION = "sdk_distribution_version"; + private static final String KEY_SDK_VERSION = "sdk_version"; + private static final String KEY_SDK_PROGRAMMING_LANGUAGE = "sdk_programming_language"; + private static final String KEY_SDK_AUTHOR_NAME = "sdk_author_name"; + private static final String KEY_SDK_AUTHOR_EMAIL = "sdk_author_email"; + private static final String KEY_SDK_PLATFORM = "sdk_platform"; + private static final String KEY_SDK_DISTRIBUTION = "sdk_distribution"; + private static final String KEY_SDK_DISTRIBUTION_VERSION = "sdk_distribution_version"; public SdkAndAppReleasePayload() { super(PayloadType.sdk_and_app_release); @@ -70,59 +70,59 @@ public String getHttpRequestContentType() { //region Sdk getters/setters public String getVersion() { - return getString(KEY_VERSION); + return getString(KEY_SDK_VERSION); } public void setVersion(String version) { - put(KEY_VERSION, version); + put(KEY_SDK_VERSION, version); } public String getProgrammingLanguage() { - return getString(KEY_PROGRAMMING_LANGUAGE); + return getString(KEY_SDK_PROGRAMMING_LANGUAGE); } public void setProgrammingLanguage(String programmingLanguage) { - put(KEY_PROGRAMMING_LANGUAGE, programmingLanguage); + put(KEY_SDK_PROGRAMMING_LANGUAGE, programmingLanguage); } public String getAuthorName() { - return getString(KEY_AUTHOR_NAME); + return getString(KEY_SDK_AUTHOR_NAME); } public void setAuthorName(String authorName) { - put(KEY_AUTHOR_NAME, authorName); + put(KEY_SDK_AUTHOR_NAME, authorName); } public String getAuthorEmail() { - return getString(KEY_AUTHOR_EMAIL); + return getString(KEY_SDK_AUTHOR_EMAIL); } public void setAuthorEmail(String authorEmail) { - put(KEY_AUTHOR_EMAIL, authorEmail); + put(KEY_SDK_AUTHOR_EMAIL, authorEmail); } public String getPlatform() { - return getString(KEY_PLATFORM); + return getString(KEY_SDK_PLATFORM); } public void setPlatform(String platform) { - put(KEY_PLATFORM, platform); + put(KEY_SDK_PLATFORM, platform); } public String getDistribution() { - return getString(KEY_DISTRIBUTION); + return getString(KEY_SDK_DISTRIBUTION); } public void setDistribution(String distribution) { - put(KEY_DISTRIBUTION, distribution); + put(KEY_SDK_DISTRIBUTION, distribution); } public String getDistributionVersion() { - return getString(KEY_DISTRIBUTION_VERSION); + return getString(KEY_SDK_DISTRIBUTION_VERSION); } public void setDistributionVersion(String distributionVersion) { - put(KEY_DISTRIBUTION_VERSION, distributionVersion); + put(KEY_SDK_DISTRIBUTION_VERSION, distributionVersion); } //endregion From d14732bad459a78b6aac360f7eecb64867d0d748 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 24 Apr 2017 17:42:43 -0700 Subject: [PATCH 258/465] Bumped API version to 9 --- .../java/com/apptentive/android/sdk/comm/ApptentiveClient.java | 2 +- .../com/apptentive/android/sdk/comm/ApptentiveHttpClient.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 026bc24fe..5c66fa592 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -24,7 +24,7 @@ public class ApptentiveClient { - public static final int API_VERSION = 7; + public static final int API_VERSION = 9; private static final String USER_AGENT_STRING = "Apptentive/%s (Android)"; // Format with SDK version string. diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index acdb31709..9d22b8e25 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -23,7 +23,7 @@ * Class responsible for all client-server network communications using asynchronous HTTP requests */ public class ApptentiveHttpClient implements PayloadRequestSender { - private static final String API_VERSION = "7"; + private static final String API_VERSION = "9"; // TODO: get rid of duplication in ApptentiveClient private static final String USER_AGENT_STRING = "Apptentive/%s (Android)"; // Format with SDK version string. From 97189728b5c2ac9237e22787144cefe270fe7308 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 25 Apr 2017 14:44:01 -0700 Subject: [PATCH 259/465] Added encryption key handling --- .../sdk/comm/ApptentiveHttpClient.java | 10 +++ .../sdk/conversation/Conversation.java | 13 ++++ .../sdk/conversation/ConversationManager.java | 69 ++++++++++++++++++- .../ConversationMetadataItem.java | 24 ++++++- .../android/sdk/network/HttpRequest.java | 2 +- 5 files changed, 113 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 9d22b8e25..da5618e82 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -32,6 +32,7 @@ public class ApptentiveHttpClient implements PayloadRequestSender { // Active API private static final String ENDPOINT_CONVERSATION = "/conversation"; + private static final String ENDPOINT_LOGIN = "/login"; private final String apiKey; private final String serverURL; @@ -62,6 +63,15 @@ public HttpJsonRequest getConversationToken(ConversationTokenRequest conversatio return request; } + public HttpJsonRequest login(String token, HttpRequest.Listener listener) { + JSONObject json = new JSONObject(); // TODO: create an actual payload + + HttpJsonRequest request = createJsonRequest(apiKey, ENDPOINT_LOGIN, json, HttpRequestMethod.POST); + request.addListener(listener); + httpRequestManager.startRequest(request); + return request; + } + /** * Returns the first request with a given tag or null is not found */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 0eb7d232e..6c316529d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -51,6 +51,11 @@ public class Conversation implements DataChangedListener, Destroyable { */ private ConversationData conversationData; + /** + * Encryption key for payloads + */ + private String encryptionKey; + /** * File which represents serialized conversation data on the disk */ @@ -478,5 +483,13 @@ synchronized File getConversationMessagesFile() { return conversationMessagesFile; } + public String getEncryptionKey() { + return encryptionKey; + } + + void setEncryptionKey(String encryptionKey) { + this.encryptionKey = encryptionKey; + } + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index ebc3ed059..9974b9738 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -360,6 +360,11 @@ private void updateMetadataItems(Conversation conversation) { } } + // delete all existing encryption keys + for (ConversationMetadataItem item : conversationMetadata) { + item.encryptionKey = null; + } + // update the state of the corresponding item ConversationMetadataItem item = conversationMetadata.findItem(conversation); if (item == null) { @@ -368,6 +373,11 @@ private void updateMetadataItems(Conversation conversation) { } item.state = conversation.getState(); + // update encryption key (if necessary) + if (conversation.hasState(LOGGED_IN)) { + item.encryptionKey = notNull(conversation.getEncryptionKey()); + } + // apply changes saveMetadata(); } @@ -494,8 +504,63 @@ public void onFail(HttpRequest request, String reason) { } } - private void sendLoginRequest(String token, LoginCallback callback) { - callback.onLoginFail("login not yet implemented"); // FIXME: kick off login request + private void sendLoginRequest(String token, final LoginCallback callback) { + getHttpClient().login(token, new HttpRequest.Listener() { + @Override + public void onFinish(HttpJsonRequest request) { + try { + final JSONObject responseObject = request.getResponseObject(); + final String encryptionKey = responseObject.getString("encryption_key"); + notifyLoginFinished(encryptionKey); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while parsing login response"); + } + } + + @Override + public void onCancel(HttpJsonRequest request) { + notifyLoginFailed("Login request was cancelled"); + } + + @Override + public void onFail(HttpJsonRequest request, String reason) { + notifyLoginFailed(reason); + } + + private void notifyLoginFinished(final String encryptionKey) { + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + assertFalse(StringUtils.isNullOrEmpty(encryptionKey)); + + try { + if (activeConversation != null) { + activeConversation.setEncryptionKey(encryptionKey); + activeConversation.setState(LOGGED_IN); + handleConversationStateChange(activeConversation); + + // notify delegate + callback.onLoginFinish(); + } else { + notifyLoginFailed("Missing active conversation"); + } + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while creating logged-in conversation"); + notifyLoginFailed("Internal error"); + } + } + }); + } + + private void notifyLoginFailed(final String reason) { + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + callback.onLoginFail(reason); + } + }); + } + }); } public void logout() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index 3c59c9a0e..111f510d5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -13,6 +13,11 @@ */ public class ConversationMetadataItem implements SerializableObject { + /** + * We store an empty string for a missing key + */ + private static final String EMPTY_ENCRYPTION_KEY = ""; + /** * The state of the target conversation */ @@ -33,8 +38,12 @@ public class ConversationMetadataItem implements SerializableObject { */ final File messagesFile; - public ConversationMetadataItem(String conversationId, File dataFile, File messagesFile) - { + /** + * Key for encrypting payloads + */ + String encryptionKey; + + public ConversationMetadataItem(String conversationId, File dataFile, File messagesFile) { if (StringUtils.isNullOrEmpty(conversationId)) { throw new IllegalArgumentException("Conversation id is null or empty"); } @@ -57,6 +66,7 @@ public ConversationMetadataItem(DataInput in) throws IOException { dataFile = new File(in.readUTF()); messagesFile = new File(in.readUTF()); state = ConversationState.valueOf(in.readByte()); + encryptionKey = readEncryptionKey(in); } @Override @@ -65,6 +75,16 @@ public void writeExternal(DataOutput out) throws IOException { out.writeUTF(dataFile.getAbsolutePath()); out.writeUTF(messagesFile.getAbsolutePath()); out.writeByte(state.ordinal()); + writeEncryptionKey(out, encryptionKey); + } + + private static String readEncryptionKey(DataInput in) throws IOException { + final String key = in.readLine(); + return !StringUtils.equal(key, EMPTY_ENCRYPTION_KEY) ? key : null; + } + + private void writeEncryptionKey(DataOutput out, String key) throws IOException { + out.writeUTF(key != null ? key : EMPTY_ENCRYPTION_KEY); } public String getConversationId() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 2f892d813..788b9f333 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -246,7 +246,7 @@ protected void execute() { private void sendRequestSync() throws IOException { try { URL url = new URL(urlString); - ApptentiveLog.d(NETWORK, "Performing request: %s", url); + ApptentiveLog.d(NETWORK, "Performing request: %s %s", method, url); retrying = false; From edea10f94d8fe4c1757b8ebc10f7a2e536859dff Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 25 Apr 2017 17:02:23 -0700 Subject: [PATCH 260/465] Refactoring --- .../android/sdk/conversation/ConversationMetadataItem.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index 111f510d5..2f3aeea13 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -94,4 +94,8 @@ public String getConversationId() { public ConversationState getState() { return state; } + + public String getEncryptionKey() { + return encryptionKey; + } } From 69dec034dab9c86d1aef7b623d8c3e111286d77e Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 26 Apr 2017 15:32:05 -0700 Subject: [PATCH 261/465] Add `Encryptor` and use it to encrypt `JsonPayload` data before it's saved into the send queue. --- .../android/sdk/encryption/EncryptorTest.java | 51 +++++++++++ .../sdk/conversation/Conversation.java | 18 ++++ .../android/sdk/encryption/Encryptor.java | 89 +++++++++++++++++++ .../android/sdk/model/JsonPayload.java | 24 +++-- 4 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 apptentive/src/androidTest/java/com/apptentive/android/sdk/encryption/EncryptorTest.java create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/encryption/Encryptor.java diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/encryption/EncryptorTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/encryption/EncryptorTest.java new file mode 100644 index 000000000..7cb9d1789 --- /dev/null +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/encryption/EncryptorTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.encryption; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Random; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class EncryptorTest { + + private static final int TEST_DATA_SIZE = 8096; + private Encryptor encryptor; + private byte[] testData; + + @Before + public void setupEncryptor() throws Exception { + // Generate a key and setup the crypto + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(256); + SecretKey secretKey = keyGen.generateKey(); + encryptor = new Encryptor(secretKey.getEncoded()); + + // Set up the test data + testData = new byte[TEST_DATA_SIZE]; + new Random().nextBytes(testData); + } + + @Test + public void testRoundTripEncryption() throws Exception { + long start = System.currentTimeMillis(); + byte[] cipherText = encryptor.encrypt(testData); + assertNotNull(cipherText); + byte[] plainText = encryptor.decrypt(cipherText); + long stop = System.currentTimeMillis(); + System.out.println(String.format("Round trip encryption took: %dms", stop - start)); + assertNotNull(plainText); + assertTrue(Arrays.equals(plainText, testData)); + } +} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 6c316529d..d8a37abf1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -13,6 +13,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; +import com.apptentive.android.sdk.encryption.Encryptor; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.InteractionManifest; @@ -53,9 +54,18 @@ public class Conversation implements DataChangedListener, Destroyable { /** * Encryption key for payloads + * TODO: Don't store this in the object. Store it in the Conversation MetaData. It's going to be needed to decrypt the Conversation itself. */ private String encryptionKey; + /** + * A utility for encrypting the data in payloads, files, and the conversation itself. + * Should be set on this object when it becomes LOGGED_IN. + * TODO: Create an encryptor, use it to decrypt this Conversation object as it's streamed from disk, + * then set the encryptor on the Conversation. + */ + private Encryptor encryptor; + /** * File which represents serialized conversation data on the disk */ @@ -491,5 +501,13 @@ void setEncryptionKey(String encryptionKey) { this.encryptionKey = encryptionKey; } + public Encryptor getEncryptor() { + return encryptor; + } + + public void setEncryptor(Encryptor encryptor) { + this.encryptor = encryptor; + } + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/encryption/Encryptor.java b/apptentive/src/main/java/com/apptentive/android/sdk/encryption/Encryptor.java new file mode 100644 index 000000000..ded7d2323 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/encryption/Encryptor.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.encryption; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherOutputStream; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class Encryptor { + + private static final int IV_SIZE = 16; + + private SecretKeySpec key; + + public Encryptor(byte[] keyBytes) { + this.key = new SecretKeySpec(keyBytes, "AES"); + } + + public byte[] encrypt(byte[] plainText) throws UnsupportedEncodingException, + NoSuchPaddingException, + NoSuchAlgorithmException, + IllegalBlockSizeException, + BadPaddingException, + InvalidAlgorithmParameterException, + InvalidKeyException { + byte[] iv = new byte[IV_SIZE]; + new SecureRandom().nextBytes(iv); + byte[] cipherText = encrypt(iv, plainText); + byte[] ret = new byte[iv.length + cipherText.length]; + System.arraycopy(iv, 0, ret, 0, iv.length); + System.arraycopy(cipherText, 0, ret, iv.length, cipherText.length); + return ret; + } + + public byte[] encrypt(byte[] iv, byte[] plainText) throws NoSuchAlgorithmException, + NoSuchPaddingException, + InvalidAlgorithmParameterException, + InvalidKeyException, + BadPaddingException, + IllegalBlockSizeException { + + AlgorithmParameterSpec ivParameterSpec = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec); + return cipher.doFinal(plainText); + } + + public byte[] decrypt(byte[] iv, byte[] cipherText) throws NoSuchPaddingException, + NoSuchAlgorithmException, + BadPaddingException, + IllegalBlockSizeException, + InvalidAlgorithmParameterException, + InvalidKeyException { + AlgorithmParameterSpec ivParameterSpec = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec); + return cipher.doFinal(cipherText); + } + + public byte[] decrypt(byte[] ivAndCipherText) throws NoSuchPaddingException, + InvalidKeyException, + NoSuchAlgorithmException, + IllegalBlockSizeException, + BadPaddingException, + InvalidAlgorithmParameterException { + byte[] iv = new byte[IV_SIZE]; + byte[] cipherText = new byte[ivAndCipherText.length - IV_SIZE]; + System.arraycopy(ivAndCipherText, 0, iv, 0, IV_SIZE); + System.arraycopy(ivAndCipherText, IV_SIZE, cipherText, 0, ivAndCipherText.length - IV_SIZE); + return decrypt(iv, cipherText); + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index 2f254b83e..8a2ed259b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -8,26 +8,34 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.ApptentiveLogTag; +import com.apptentive.android.sdk.encryption.Encryptor; import org.json.JSONException; import org.json.JSONObject; -import java.io.UnsupportedEncodingException; - public abstract class JsonPayload extends Payload { private final JSONObject jsonObject; + private final Encryptor encryptor; // These three are not stored in the JSON, only the DB. public JsonPayload(PayloadType type) { super(type); jsonObject = new JSONObject(); + this.encryptor = null; + } + + public JsonPayload(PayloadType type, Encryptor encryptor) { + super(type); + jsonObject = new JSONObject(); + this.encryptor = encryptor; } public JsonPayload(PayloadType type, String json) throws JSONException { super(type); jsonObject = new JSONObject(json); + this.encryptor = null; } //region Data @@ -35,10 +43,16 @@ public JsonPayload(PayloadType type, String json) throws JSONException { @Override public byte[] getData() { try { - return jsonObject.toString().getBytes("utf-8"); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException(e); + byte[] bytes = jsonObject.toString().getBytes(); + if (encryptor != null) { + return encryptor.encrypt(bytes); + } else { + return bytes; + } + } catch (Exception e) { + ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error encrypting payload data", e); } + return null; } //endregion From b1316eaa7dc8e976f071ee2fae2bd4548d9a2ced Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 26 Apr 2017 15:33:17 -0700 Subject: [PATCH 262/465] Provide a default implementation of `getHttpRequestMethod()` and `getHttpRequstContentType()` in `JsonPayload`. Override where necessary. --- .../android/sdk/model/DevicePayload.java | 10 ---------- .../android/sdk/model/EventPayload.java | 4 ---- .../apptentive/android/sdk/model/JsonPayload.java | 15 +++++++++++++++ .../android/sdk/model/LogoutPayload.java | 5 ----- .../android/sdk/model/PersonPayload.java | 10 ---------- .../sdk/model/SdkAndAppReleasePayload.java | 10 ---------- .../android/sdk/model/SurveyResponsePayload.java | 5 ----- 7 files changed, 15 insertions(+), 44 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index 7d75e1ceb..9586ae105 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -56,16 +56,6 @@ public String getHttpEndPoint(String conversationId) { return StringUtils.format("/conversations/%s/devices", conversationId); } - @Override - public HttpRequestMethod getHttpRequestMethod() { - return HttpRequestMethod.PUT; - } - - @Override - public String getHttpRequestContentType() { - return "application/json"; - } - //endregion public void setUuid(String uuid) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index 6eb4761bc..724cf4593 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -121,10 +121,6 @@ public HttpRequestMethod getHttpRequestMethod() { return HttpRequestMethod.POST; } - @Override - public String getHttpRequestContentType() { - return "application/json"; - } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index 8a2ed259b..4e7def4a6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -9,6 +9,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.ApptentiveLogTag; import com.apptentive.android.sdk.encryption.Encryptor; +import com.apptentive.android.sdk.network.HttpRequestMethod; import org.json.JSONException; import org.json.JSONObject; @@ -157,5 +158,19 @@ public JSONObject getJsonObject() { return jsonObject; } + @Override + public HttpRequestMethod getHttpRequestMethod() { + return HttpRequestMethod.PUT; + } + + @Override + public String getHttpRequestContentType() { + if (encryptor != null) { + return "application/json"; + } else { + return "application/octet-stream"; + } + } + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java index 5622efa53..996f2bbbc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java @@ -32,10 +32,5 @@ public HttpRequestMethod getHttpRequestMethod() { return HttpRequestMethod.POST; } - @Override - public String getHttpRequestContentType() { - return "application/json"; // TODO: application/octet-stream - } - //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index b46769a01..2d2a0652b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -42,16 +42,6 @@ public String getHttpEndPoint(String conversationId) { return StringUtils.format("/converations/%s/people", conversationId); } - @Override - public HttpRequestMethod getHttpRequestMethod() { - return HttpRequestMethod.PUT; - } - - @Override - public String getHttpRequestContentType() { - return "application/json"; - } - //endregion public String getId() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index 866236611..9d9e16519 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -56,16 +56,6 @@ public String getHttpEndPoint(String conversationId) { return StringUtils.format("/conversations/%s/sdkapprelease", conversationId); } - @Override - public HttpRequestMethod getHttpRequestMethod() { - return HttpRequestMethod.PUT; - } - - @Override - public String getHttpRequestContentType() { - return "application/json"; - } - //endregion //region Sdk getters/setters diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index 16b49ec76..93844cd02 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -53,11 +53,6 @@ public HttpRequestMethod getHttpRequestMethod() { return HttpRequestMethod.POST; } - @Override - public String getHttpRequestContentType() { - return "application/json"; - } - //endregion public String getId() { From 711dfb13c4618bd3fddc11deb06bfcdff2bac990 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 26 Apr 2017 16:42:59 -0700 Subject: [PATCH 263/465] Properly handle login/logout --- .../sdk/conversation/Conversation.java | 13 +++ .../sdk/conversation/ConversationManager.java | 77 ++++++++++++----- .../ConversationMetadataItem.java | 13 +++ .../com/apptentive/android/sdk/util/Jwt.java | 83 +++++++++++++++++++ 4 files changed, 164 insertions(+), 22 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/Jwt.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 6c316529d..016ca3a0b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -56,6 +56,11 @@ public class Conversation implements DataChangedListener, Destroyable { */ private String encryptionKey; + /** + * Optional user id for logged-in conversations + */ + private String userId; + /** * File which represents serialized conversation data on the disk */ @@ -491,5 +496,13 @@ void setEncryptionKey(String encryptionKey) { this.encryptionKey = encryptionKey; } + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 9974b9738..9de36dbf7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -22,6 +22,7 @@ import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.storage.SerializerException; +import com.apptentive.android.sdk.util.Jwt; import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; @@ -176,6 +177,7 @@ private Conversation loadConversation(ConversationMetadataItem item) throws Seri final Conversation conversation = new Conversation(item.dataFile, item.messagesFile); conversation.loadConversationData(); conversation.setState(item.getState()); // set the state same as the item's state + conversation.setUserId(item.getUserId()); return conversation; } @@ -376,6 +378,7 @@ private void updateMetadataItems(Conversation conversation) { // update encryption key (if necessary) if (conversation.hasState(LOGGED_IN)) { item.encryptionKey = notNull(conversation.getEncryptionKey()); + item.userId = notNull(conversation.getUserId()); } // apply changes @@ -432,12 +435,16 @@ public void onLoginFail(String errorMessage) { public void login(final String token, final LoginCallback callback) { // we only deal with an active conversation on the main thread - DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - requestLoggedInConversation(token, callback != null ? callback : NULL_LOGIN_CALLBACK); // avoid constant null-pointer checking - } - }); + if (Looper.getMainLooper() == Looper.myLooper()) { + requestLoggedInConversation(token, callback != null ? callback : NULL_LOGIN_CALLBACK); // avoid constant null-pointer checking + } else { + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + requestLoggedInConversation(token, callback != null ? callback : NULL_LOGIN_CALLBACK); // avoid constant null-pointer checking + } + }); + } } private void requestLoggedInConversation(final String token, final LoginCallback callback) { @@ -504,55 +511,81 @@ public void onFail(HttpRequest request, String reason) { } } - private void sendLoginRequest(String token, final LoginCallback callback) { + private void sendLoginRequest(final String token, final LoginCallback callback) { getHttpClient().login(token, new HttpRequest.Listener() { @Override public void onFinish(HttpJsonRequest request) { + final String userId; + + try { + final Jwt jwt = Jwt.decode(token); + userId = jwt.getPayload().getString("user_id"); + } catch (Exception e) { + handleLoginFailed(e.getMessage()); + return; + } + try { final JSONObject responseObject = request.getResponseObject(); final String encryptionKey = responseObject.getString("encryption_key"); - notifyLoginFinished(encryptionKey); + handleLoginFinished(userId, encryptionKey); } catch (Exception e) { ApptentiveLog.e(e, "Exception while parsing login response"); + handleLoginFailed("Internal error"); } } @Override public void onCancel(HttpJsonRequest request) { - notifyLoginFailed("Login request was cancelled"); + handleLoginFailed("Login request was cancelled"); } @Override public void onFail(HttpJsonRequest request, String reason) { - notifyLoginFailed(reason); + handleLoginFailed(reason); } - private void notifyLoginFinished(final String encryptionKey) { + private void handleLoginFinished(final String userId, final String encryptionKey) { DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { @Override protected void execute() { assertFalse(StringUtils.isNullOrEmpty(encryptionKey)); try { - if (activeConversation != null) { - activeConversation.setEncryptionKey(encryptionKey); - activeConversation.setState(LOGGED_IN); - handleConversationStateChange(activeConversation); - - // notify delegate - callback.onLoginFinish(); - } else { - notifyLoginFailed("Missing active conversation"); + // if we were previously logged out we might end up with no active conversation + if (activeConversation == null) { + // attempt to find previous logged out conversation + final ConversationMetadataItem conversationItem = conversationMetadata.findItem(new Filter() { + @Override + public boolean accept(ConversationMetadataItem item) { + return StringUtils.equal(item.getUserId(), userId); + } + }); + + if (conversationItem == null) { + handleLoginFailed("Unable to find an existing conversation with for user: '" + userId + "'"); + return; + } + + activeConversation = loadConversation(conversationItem); } + + activeConversation.setEncryptionKey(encryptionKey); + activeConversation.setUserId(userId); + activeConversation.setState(LOGGED_IN); + handleConversationStateChange(activeConversation); + + // notify delegate + callback.onLoginFinish(); } catch (Exception e) { ApptentiveLog.e(e, "Exception while creating logged-in conversation"); - notifyLoginFailed("Internal error"); + handleLoginFailed("Internal error"); } } }); } - private void notifyLoginFailed(final String reason) { + private void handleLoginFailed(final String reason) { DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { @Override protected void execute() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index 2f3aeea13..eac75da10 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -43,6 +43,11 @@ public class ConversationMetadataItem implements SerializableObject { */ String encryptionKey; + /** + * An optional user ID for logged in conversations + */ + String userId; + public ConversationMetadataItem(String conversationId, File dataFile, File messagesFile) { if (StringUtils.isNullOrEmpty(conversationId)) { throw new IllegalArgumentException("Conversation id is null or empty"); @@ -98,4 +103,12 @@ public ConversationState getState() { public String getEncryptionKey() { return encryptionKey; } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Jwt.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Jwt.java new file mode 100644 index 000000000..a838d13db --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Jwt.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util; + +import android.util.Base64; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; + +/** + * Utility class for Jwt handling + */ + +public class Jwt { + private final String alg; + private final String type; + private final JSONObject payload; + + public Jwt(String alg, String type, JSONObject payload) { + if (alg == null) { + throw new IllegalArgumentException("Alg is null"); + } + if (type == null) { + throw new IllegalArgumentException("Type is null"); + } + if (payload == null) { + throw new IllegalArgumentException("Payload is null"); + } + this.alg = alg; + this.type = type; + this.payload = payload; + } + + public static Jwt decode(String data) { + if (data == null) { + throw new IllegalArgumentException("Data string is null"); + } + + final String[] tokens = data.split("\\."); + if (tokens.length != 3) { + throw new IllegalArgumentException("Invalid JWT data format: '" + data + "'"); + } + + final JSONObject headerJson = decodeBase64Json(tokens[0]); + final String alg = headerJson.optString("alg", null); + final String type = headerJson.optString("typ", null); + if (alg == null || type == null) { + throw new IllegalArgumentException("Invalid jwt header: '" + headerJson + "'"); + } + + final JSONObject payloadJson = decodeBase64Json(tokens[1]); + return new Jwt(alg, type, payloadJson); + } + + private static JSONObject decodeBase64Json(String data) { + try { + final String text = new String(Base64.decode(data, Base64.DEFAULT), "UTF-8"); + return new JSONObject(text); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException(e); + } catch (JSONException e) { + throw new IllegalArgumentException(e); + } + } + + public String getAlg() { + return alg; + } + + public String getType() { + return type; + } + + public JSONObject getPayload() { + return payload; + } +} From a5a0b8dadf4b84ec1bba49a23e6be2bef1ffe0e8 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 27 Apr 2017 14:10:32 -0700 Subject: [PATCH 264/465] Handle pushes with session identifier --- .../android/sdk/ApptentiveInternal.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 82773f032..949fa22e1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -122,6 +122,7 @@ public class ApptentiveInternal { private Map customData; private static final String PUSH_ACTION = "action"; + private static final String PUSH_CONVERSATION_ID = "conversationid"; private enum PushAction { pmc, // Present Message Center. @@ -832,6 +833,25 @@ static PendingIntent generatePendingIntentFromApptentivePushData(String apptenti if (!TextUtils.isEmpty(apptentivePushData)) { try { JSONObject pushJson = new JSONObject(apptentivePushData); + + // we need to check if current user is actually the receiver of this notification + final String conversationId = pushJson.optString(PUSH_CONVERSATION_ID, null); + if (conversationId != null) { + final Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + + // do we have a conversation right now? + if (conversation == null) { + ApptentiveLog.e("Can't generate pending intent from Apptentive push data: no active conversation"); + return null; + } + + // is it an actual receiver? + if (!StringUtils.equal(conversation.getConversationId(), conversationId)) { + ApptentiveLog.e("Can't generate pending intent from Apptentive push data: push conversation id doesn't match active conversation"); + return null; + } + } + ApptentiveInternal.PushAction action = ApptentiveInternal.PushAction.unknown; if (pushJson.has(ApptentiveInternal.PUSH_ACTION)) { action = ApptentiveInternal.PushAction.parse(pushJson.getString(ApptentiveInternal.PUSH_ACTION)); From 17f72ad5c83e346f9fa8606f1d83864f7eaa25a8 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 28 Apr 2017 10:33:52 -0700 Subject: [PATCH 265/465] Fixed log level --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 949fa22e1..d522aa74a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -841,13 +841,13 @@ static PendingIntent generatePendingIntentFromApptentivePushData(String apptenti // do we have a conversation right now? if (conversation == null) { - ApptentiveLog.e("Can't generate pending intent from Apptentive push data: no active conversation"); + ApptentiveLog.i("Can't generate pending intent from Apptentive push data: no active conversation"); return null; } // is it an actual receiver? if (!StringUtils.equal(conversation.getConversationId(), conversationId)) { - ApptentiveLog.e("Can't generate pending intent from Apptentive push data: push conversation id doesn't match active conversation"); + ApptentiveLog.i("Can't generate pending intent from Apptentive push data: push conversation id doesn't match active conversation"); return null; } } From 6ade04f16580acd1fb6c227f5f6fd57ed4aa7575 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 28 Apr 2017 13:04:34 -0700 Subject: [PATCH 266/465] Fixed fetching interactions --- .../apptentive/android/sdk/comm/ApptentiveClient.java | 11 ++++++++--- .../android/sdk/conversation/Conversation.java | 6 +++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 5c66fa592..6c3f77ab7 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -12,6 +12,7 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; import java.io.*; @@ -36,7 +37,7 @@ public class ApptentiveClient { private static final String ENDPOINT_CONVERSATION_FETCH = ENDPOINT_CONVERSATION + "?count=%s&after_id=%s&before_id=%s"; private static final String ENDPOINT_CONFIGURATION = ENDPOINT_CONVERSATION + "/configuration"; - private static final String ENDPOINT_INTERACTIONS = "/interactions"; + private static final String ENDPOINT_INTERACTIONS = "/conversations/%s/interactions"; // Deprecated API // private static final String ENDPOINT_RECORDS = ENDPOINT_BASE + "/records"; @@ -56,8 +57,12 @@ public static ApptentiveHttpResponse getMessages(Integer count, String afterId, return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), uri, Method.GET, null); } - public static ApptentiveHttpResponse getInteractions() { - return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_INTERACTIONS, Method.GET, null); + public static ApptentiveHttpResponse getInteractions(String conversationId) { + if (conversationId == null) { + throw new IllegalArgumentException("Conversation id is null"); + } + final String endPoint = StringUtils.format(ENDPOINT_INTERACTIONS, conversationId); + return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), endPoint, Method.GET, null); } /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index d919d9ecc..9bf6b6b99 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -92,7 +92,7 @@ public class Conversation implements DataChangedListener, Destroyable { private final DispatchTask fetchInteractionsTask = new DispatchTask() { @Override protected void execute() { - final boolean updateSuccessful = fetchInteractionsSync(); + final boolean updateSuccessful = fetchInteractionsSync(getConversationId()); dispatchDebugEvent(EVT_CONVERSATION_FETCH_INTERACTIONS, updateSuccessful); // Update pending state on UI thread after finishing the task @@ -175,9 +175,9 @@ boolean fetchInteractions(Context context) { /** * Fetches interaction synchronously. Returns true if succeed. */ - private boolean fetchInteractionsSync() { + private boolean fetchInteractionsSync(String conversationId) { ApptentiveLog.v(CONVERSATION, "Fetching Interactions"); - ApptentiveHttpResponse response = ApptentiveClient.getInteractions(); + ApptentiveHttpResponse response = ApptentiveClient.getInteractions(conversationId); // TODO: Move this to global config SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); From f04e173b640307bb068c1dbf08f63924548bf068 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 28 Apr 2017 13:05:06 -0700 Subject: [PATCH 267/465] Fixed login endpoint --- .../sdk/comm/ApptentiveHttpClient.java | 15 +++++-- .../sdk/conversation/ConversationManager.java | 45 ++++++++++++------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index da5618e82..fc87ac2da 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -12,6 +12,7 @@ import com.apptentive.android.sdk.network.RawHttpRequest; import com.apptentive.android.sdk.storage.PayloadRequestSender; import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONObject; @@ -32,7 +33,7 @@ public class ApptentiveHttpClient implements PayloadRequestSender { // Active API private static final String ENDPOINT_CONVERSATION = "/conversation"; - private static final String ENDPOINT_LOGIN = "/login"; + private static final String ENDPOINT_LOGIN = "/conversations/%s/session"; private final String apiKey; private final String serverURL; @@ -63,10 +64,18 @@ public HttpJsonRequest getConversationToken(ConversationTokenRequest conversatio return request; } - public HttpJsonRequest login(String token, HttpRequest.Listener listener) { + public HttpJsonRequest login(String conversationId, String token, HttpRequest.Listener listener) { + if (conversationId == null) { + throw new IllegalArgumentException("Conversation id is null"); + } + if (token == null) { + throw new IllegalArgumentException("Token is null"); + } + JSONObject json = new JSONObject(); // TODO: create an actual payload - HttpJsonRequest request = createJsonRequest(apiKey, ENDPOINT_LOGIN, json, HttpRequestMethod.POST); + String endPoint = StringUtils.format(ENDPOINT_LOGIN, conversationId); + HttpJsonRequest request = createJsonRequest(apiKey, endPoint, json, HttpRequestMethod.POST); request.addListener(listener); httpRequestManager.startRequest(request); return request; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 9de36dbf7..b54fa1a31 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -454,10 +454,35 @@ private void requestLoggedInConversation(final String token, final LoginCallback throw new IllegalArgumentException("Callback is null"); } + final String userId; + try { + final Jwt jwt = Jwt.decode(token); + userId = jwt.getPayload().getString("user_id"); + } catch (Exception e) { + ApptentiveLog.e(e, "Error while extracting user id"); + callback.onLoginFail(e.getMessage()); + return; + } + // check if active conversation exists if (activeConversation == null) { ApptentiveLog.d(CONVERSATION, "No active conversation. Performing login..."); - sendLoginRequest(token, callback); + + // attempt to find previous logged out conversation + final ConversationMetadataItem conversationItem = conversationMetadata.findItem(new Filter() { + @Override + public boolean accept(ConversationMetadataItem item) { + return StringUtils.equal(item.getUserId(), userId); + } + }); + + if (conversationItem == null) { + ApptentiveLog.e("Unable to find an existing conversation with for user: '" + userId + "'"); + callback.onLoginFail("No previous conversation found"); + return; + } + + sendLoginRequest(conversationItem.conversationId, userId, token, callback); return; } @@ -479,7 +504,7 @@ public void onFinish(HttpRequest request) { if (activeConversation != null && activeConversation.hasState(ANONYMOUS)) { ApptentiveLog.d(CONVERSATION, "Conversation fetching complete. Performing login..."); - sendLoginRequest(token, callback); + sendLoginRequest(activeConversation.getConversationId(), userId, token, callback); } else { callback.onLoginFail("Conversation fetching completed abnormally"); } @@ -499,7 +524,7 @@ public void onFail(HttpRequest request, String reason) { }); break; case ANONYMOUS: - sendLoginRequest(token, callback); + sendLoginRequest(activeConversation.getConversationId(), userId, token, callback); break; case LOGGED_IN: callback.onLoginFail("already logged in"); // TODO: force logout? @@ -511,20 +536,10 @@ public void onFail(HttpRequest request, String reason) { } } - private void sendLoginRequest(final String token, final LoginCallback callback) { - getHttpClient().login(token, new HttpRequest.Listener() { + private void sendLoginRequest(String conversationId, final String userId, final String token, final LoginCallback callback) { + getHttpClient().login(conversationId, token, new HttpRequest.Listener() { @Override public void onFinish(HttpJsonRequest request) { - final String userId; - - try { - final Jwt jwt = Jwt.decode(token); - userId = jwt.getPayload().getString("user_id"); - } catch (Exception e) { - handleLoginFailed(e.getMessage()); - return; - } - try { final JSONObject responseObject = request.getResponseObject(); final String encryptionKey = responseObject.getString("encryption_key"); From 9ce22a6f5d16ea9b6ae99ddf92f805f120725be1 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 28 Apr 2017 13:09:16 -0700 Subject: [PATCH 268/465] Fixed more endpoints --- .../java/com/apptentive/android/sdk/model/DevicePayload.java | 2 +- .../java/com/apptentive/android/sdk/model/EventPayload.java | 2 +- .../java/com/apptentive/android/sdk/model/PersonPayload.java | 2 +- .../apptentive/android/sdk/model/SdkAndAppReleasePayload.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index 9586ae105..1de9354f0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -53,7 +53,7 @@ public DevicePayload(String json) throws JSONException { @Override public String getHttpEndPoint(String conversationId) { - return StringUtils.format("/conversations/%s/devices", conversationId); + return StringUtils.format("/conversations/%s/device", conversationId); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index 724cf4593..eaea20528 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -113,7 +113,7 @@ public EventPayload(String label, String trigger) { @Override public String getHttpEndPoint(String conversationId) { - return StringUtils.format("/conversations/%s/event", conversationId); + return StringUtils.format("/conversations/%s/events", conversationId); } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index 2d2a0652b..d1c54c0d7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -39,7 +39,7 @@ public PersonPayload(String json) throws JSONException { @Override public String getHttpEndPoint(String conversationId) { - return StringUtils.format("/converations/%s/people", conversationId); + return StringUtils.format("/conversations/%s/person", conversationId); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index 9d9e16519..509912d03 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -53,7 +53,7 @@ public SdkAndAppReleasePayload() { @Override public String getHttpEndPoint(String conversationId) { - return StringUtils.format("/conversations/%s/sdkapprelease", conversationId); + return StringUtils.format("/conversations/%s/apprelease", conversationId); } //endregion From 11467bfc0612ad61c2213ffc5088821dfd560081 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 28 Apr 2017 13:12:35 -0700 Subject: [PATCH 269/465] Fixed messages endpoints --- .../android/sdk/comm/ApptentiveClient.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 6c3f77ab7..137c24f6a 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -11,6 +11,7 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; @@ -34,7 +35,7 @@ public class ApptentiveClient { // Active API private static final String ENDPOINT_CONVERSATION = "/conversation"; - private static final String ENDPOINT_CONVERSATION_FETCH = ENDPOINT_CONVERSATION + "?count=%s&after_id=%s&before_id=%s"; + private static final String ENDPOINT_MESSAGES = "/conversations/%s/messages?count=%s&after_id=%s&before_id=%s"; private static final String ENDPOINT_CONFIGURATION = ENDPOINT_CONVERSATION + "/configuration"; private static final String ENDPOINT_INTERACTIONS = "/conversations/%s/interactions"; @@ -53,8 +54,23 @@ public static ApptentiveHttpResponse getAppConfiguration() { * @return An ApptentiveHttpResponse object with the HTTP response code, reason, and content. */ public static ApptentiveHttpResponse getMessages(Integer count, String afterId, String beforeId) { - String uri = String.format(ENDPOINT_CONVERSATION_FETCH, count == null ? "" : count.toString(), afterId == null ? "" : afterId, beforeId == null ? "" : beforeId); - return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), uri, Method.GET, null); + final Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation == null) { + throw new IllegalStateException("Conversation is null"); + } + + final String conversationId = conversation.getConversationId(); + if (conversationId == null) { + throw new IllegalStateException("Conversation id is null"); + } + + final String conversationToken = conversation.getConversationToken(); + if (conversationToken == null) { + throw new IllegalStateException("Conversation token is null"); + } + + String uri = String.format(ENDPOINT_MESSAGES, conversationId, count == null ? "" : count.toString(), afterId == null ? "" : afterId, beforeId == null ? "" : beforeId); + return performHttpRequest(conversationToken, uri, Method.GET, null); } public static ApptentiveHttpResponse getInteractions(String conversationId) { From a2ad4563b3c276170a9557dce2aedc3d3ee7d282 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 28 Apr 2017 13:15:32 -0700 Subject: [PATCH 270/465] Fixed configuration endpoints --- .../android/sdk/comm/ApptentiveClient.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 137c24f6a..f9a18d83a 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -34,9 +34,8 @@ public class ApptentiveClient { public static final int DEFAULT_HTTP_SOCKET_TIMEOUT = 45000; // Active API - private static final String ENDPOINT_CONVERSATION = "/conversation"; private static final String ENDPOINT_MESSAGES = "/conversations/%s/messages?count=%s&after_id=%s&before_id=%s"; - private static final String ENDPOINT_CONFIGURATION = ENDPOINT_CONVERSATION + "/configuration"; + private static final String ENDPOINT_CONFIGURATION = "/conversations/%s/configuration"; private static final String ENDPOINT_INTERACTIONS = "/conversations/%s/interactions"; @@ -45,7 +44,23 @@ public class ApptentiveClient { // private static final String ENDPOINT_SURVEYS_FETCH = ENDPOINT_BASE + "/surveys"; public static ApptentiveHttpResponse getAppConfiguration() { - return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), ENDPOINT_CONFIGURATION, Method.GET, null); + final Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation == null) { + throw new IllegalStateException("Conversation is null"); + } + + final String conversationId = conversation.getConversationId(); + if (conversationId == null) { + throw new IllegalStateException("Conversation id is null"); + } + + final String conversationToken = conversation.getConversationToken(); + if (conversationToken == null) { + throw new IllegalStateException("Conversation token is null"); + } + + final String endPoint = StringUtils.format(ENDPOINT_CONFIGURATION, conversationId); + return performHttpRequest(conversationToken, endPoint, Method.GET, null); } /** From c1b772f61f333cb2198ee3e3c5c9d7db947133d7 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 28 Apr 2017 13:26:15 -0700 Subject: [PATCH 271/465] Fixed assertion --- .../android/sdk/module/messagecenter/MessageManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 6a81fb1e8..769fb1082 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -52,6 +52,7 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_PAYLOAD; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_RESPONSE_CODE; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_RESPONSE_DATA; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_SUCCESSFUL; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_DID_FINISH_SEND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_WILL_START_SEND; import static com.apptentive.android.sdk.debug.Assert.assertNotNull; @@ -373,9 +374,10 @@ public void onReceiveNotification(ApptentiveNotification notification) { resumeSending(); } } else if (notification.hasName(NOTIFICATION_PAYLOAD_DID_FINISH_SEND)) { + final boolean successful = notification.getRequiredUserInfo(NOTIFICATION_KEY_SUCCESSFUL, Boolean.class); final PayloadData payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, PayloadData.class); final Integer responseCode = notification.getRequiredUserInfo(NOTIFICATION_KEY_RESPONSE_CODE, Integer.class); - final JSONObject responseData = notification.getRequiredUserInfo(NOTIFICATION_KEY_RESPONSE_DATA, JSONObject.class); + final JSONObject responseData = successful ? notification.getRequiredUserInfo(NOTIFICATION_KEY_RESPONSE_DATA, JSONObject.class) : null; if (payload.getType().equals(PayloadType.message)) { onSentMessage(payload.getNonce(), responseCode, responseData); } From 3afb904685af830f5860de5ae76ebc96e11195f4 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 28 Apr 2017 13:55:25 -0700 Subject: [PATCH 272/465] Added more checks --- .../android/sdk/conversation/ConversationData.java | 8 ++++++++ .../android/sdk/util/threading/DispatchTask.java | 3 +++ 2 files changed, 11 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java index e8113deac..74f12183f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java @@ -84,6 +84,10 @@ public String getConversationToken() { } public void setConversationToken(String conversationToken) { + if (conversationToken == null) { + throw new IllegalArgumentException("Conversation token is null"); + } + if (!StringUtils.equal(this.conversationToken, conversationToken)) { this.conversationToken = conversationToken; notifyDataChanged(); @@ -95,6 +99,10 @@ public String getConversationId() { } public void setConversationId(String conversationId) { + if (conversationToken == null) { + throw new IllegalArgumentException("Conversation id is null"); + } + if (!StringUtils.equal(this.conversationId, conversationId)) { this.conversationId = conversationId; notifyDataChanged(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java index f3546f68c..00afe7a27 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchTask.java @@ -8,6 +8,8 @@ import com.apptentive.android.sdk.ApptentiveLog; +import static com.apptentive.android.sdk.debug.Tester.dispatchException; + /** * A basic class for any dispatch runnable task. Tracks its "schedule" state */ @@ -29,6 +31,7 @@ public void run() { execute(); } catch (Exception e) { ApptentiveLog.e(e, "Exception while executing task"); + dispatchException(e); } finally { setScheduled(false); } From ae834bec1148b4c0fdd552f22ae71ce3d268d1a6 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 28 Apr 2017 14:14:43 -0700 Subject: [PATCH 273/465] Fixed conversation data setter --- .../apptentive/android/sdk/conversation/ConversationData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java index 74f12183f..e9dbd1412 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java @@ -99,7 +99,7 @@ public String getConversationId() { } public void setConversationId(String conversationId) { - if (conversationToken == null) { + if (conversationId == null) { throw new IllegalArgumentException("Conversation id is null"); } From 254dbee30d6ad12d75a067cf83ed0ebe9e537028 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 28 Apr 2017 14:40:56 -0700 Subject: [PATCH 274/465] Fixed Logout payload --- .../java/com/apptentive/android/sdk/model/LogoutPayload.java | 4 ++-- .../com/apptentive/android/sdk/network/HttpRequestMethod.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java index 996f2bbbc..50969cf31 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/LogoutPayload.java @@ -24,12 +24,12 @@ public LogoutPayload(String json) throws JSONException { @Override public String getHttpEndPoint(String conversationId) { - return StringUtils.format("/conversations/%s/logout", conversationId); + return StringUtils.format("/conversations/%s/session", conversationId); } @Override public HttpRequestMethod getHttpRequestMethod() { - return HttpRequestMethod.POST; + return HttpRequestMethod.DELETE; } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestMethod.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestMethod.java index f8cafdfed..22594a53d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestMethod.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestMethod.java @@ -3,5 +3,6 @@ public enum HttpRequestMethod { GET, POST, - PUT + PUT, + DELETE } From cba4be8e264e261d62e7f4fd249edc22dc147bfa Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 1 May 2017 10:51:01 -0700 Subject: [PATCH 275/465] =?UTF-8?q?Changed=20=E2=80=98apprelease=E2=80=99?= =?UTF-8?q?=20endpoint=20to=20=E2=80=98app=5Frelease=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apptentive/android/sdk/model/SdkAndAppReleasePayload.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index 509912d03..929d29196 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -53,7 +53,7 @@ public SdkAndAppReleasePayload() { @Override public String getHttpEndPoint(String conversationId) { - return StringUtils.format("/conversations/%s/apprelease", conversationId); + return StringUtils.format("/conversations/%s/app_release", conversationId); } //endregion From b6ef64f3c2cbdb28c883cfa300e1db96da8b11c8 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 1 May 2017 18:58:55 -0700 Subject: [PATCH 276/465] When sent from a LOGGED_IN Conversation, payload data should be wrapped in an object that also contains the active Conversation's token, and encrypted before being saved into the queue. Also stop storing the Encryptor instance on the Conversation, and assume keys are hex strings. ANDROID-939 --- .../sdk/conversation/Conversation.java | 21 +-------------- .../sdk/conversation/ConversationManager.java | 5 ++-- .../android/sdk/encryption/Encryptor.java | 19 ++++++++----- .../android/sdk/model/JsonPayload.java | 27 +++++++++---------- .../apptentive/android/sdk/model/Payload.java | 19 +++++++++++++ .../sdk/storage/ApptentiveTaskManager.java | 9 +++++++ .../android/sdk/util/StringUtils.java | 12 +++++++++ 7 files changed, 69 insertions(+), 43 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 9bf6b6b99..4d9ba8a14 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -13,7 +13,6 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; -import com.apptentive.android.sdk.encryption.Encryptor; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.InteractionManifest; @@ -53,8 +52,7 @@ public class Conversation implements DataChangedListener, Destroyable { private ConversationData conversationData; /** - * Encryption key for payloads - * TODO: Don't store this in the object. Store it in the Conversation MetaData. It's going to be needed to decrypt the Conversation itself. + * Encryption key for payloads. A hex encoded String. */ private String encryptionKey; @@ -62,14 +60,6 @@ public class Conversation implements DataChangedListener, Destroyable { * Optional user id for logged-in conversations */ private String userId; - - /** - * A utility for encrypting the data in payloads, files, and the conversation itself. - * Should be set on this object when it becomes LOGGED_IN. - * TODO: Create an encryptor, use it to decrypt this Conversation object as it's streamed from disk, - * then set the encryptor on the Conversation. - */ - private Encryptor encryptor; /** * File which represents serialized conversation data on the disk @@ -513,14 +503,5 @@ public String getUserId() { public void setUserId(String userId) { this.userId = userId; } - - public Encryptor getEncryptor() { - return encryptor; - } - - public void setEncryptor(Encryptor encryptor) { - this.encryptor = encryptor; - } - //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index b54fa1a31..ad50d4d4e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -464,7 +464,7 @@ private void requestLoggedInConversation(final String token, final LoginCallback return; } - // check if active conversation exists + // Check if there is an active conversation if (activeConversation == null) { ApptentiveLog.d(CONVERSATION, "No active conversation. Performing login..."); @@ -527,7 +527,8 @@ public void onFail(HttpRequest request, String reason) { sendLoginRequest(activeConversation.getConversationId(), userId, token, callback); break; case LOGGED_IN: - callback.onLoginFail("already logged in"); // TODO: force logout? + // FIXME: If they are attempting to login to a different conversation, we need to gracefully end the active conversation here and kick off a login request to the desired conversation. + callback.onLoginFail("already logged in"); break; default: assertFail("Unexpected conversation state: " + activeConversation.getState()); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/encryption/Encryptor.java b/apptentive/src/main/java/com/apptentive/android/sdk/encryption/Encryptor.java index ded7d2323..44a556b78 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/encryption/Encryptor.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/encryption/Encryptor.java @@ -6,8 +6,8 @@ package com.apptentive.android.sdk.encryption; -import java.io.IOException; -import java.io.OutputStream; +import com.apptentive.android.sdk.util.StringUtils; + import java.io.UnsupportedEncodingException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -17,7 +17,6 @@ import javax.crypto.BadPaddingException; import javax.crypto.Cipher; -import javax.crypto.CipherOutputStream; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; @@ -29,7 +28,15 @@ public class Encryptor { private SecretKeySpec key; - public Encryptor(byte[] keyBytes) { + /** + * Initializes the Encryptor + * @param hexKey A hex encoded String with the key data. + */ + public Encryptor(String hexKey) { + this.key = new SecretKeySpec(StringUtils.hexToBytes(hexKey), "AES"); + } + + Encryptor(byte[] keyBytes) { this.key = new SecretKeySpec(keyBytes, "AES"); } @@ -49,7 +56,7 @@ public byte[] encrypt(byte[] plainText) throws UnsupportedEncodingException, return ret; } - public byte[] encrypt(byte[] iv, byte[] plainText) throws NoSuchAlgorithmException, + private byte[] encrypt(byte[] iv, byte[] plainText) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, @@ -62,7 +69,7 @@ public byte[] encrypt(byte[] iv, byte[] plainText) throws NoSuchAlgorithmExcepti return cipher.doFinal(plainText); } - public byte[] decrypt(byte[] iv, byte[] cipherText) throws NoSuchPaddingException, + private byte[] decrypt(byte[] iv, byte[] cipherText) throws NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index 4e7def4a6..abc024b2e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -17,26 +17,17 @@ public abstract class JsonPayload extends Payload { private final JSONObject jsonObject; - private final Encryptor encryptor; // These three are not stored in the JSON, only the DB. public JsonPayload(PayloadType type) { super(type); jsonObject = new JSONObject(); - this.encryptor = null; - } - - public JsonPayload(PayloadType type, Encryptor encryptor) { - super(type); - jsonObject = new JSONObject(); - this.encryptor = encryptor; } public JsonPayload(PayloadType type, String json) throws JSONException { super(type); jsonObject = new JSONObject(json); - this.encryptor = null; } //region Data @@ -44,11 +35,17 @@ public JsonPayload(PayloadType type, String json) throws JSONException { @Override public byte[] getData() { try { - byte[] bytes = jsonObject.toString().getBytes(); - if (encryptor != null) { + if (encryptionKey != null) { + JSONObject wrapper = new JSONObject(); + wrapper.put("token", token); + wrapper.put("payload", jsonObject); + byte[] bytes = jsonObject.toString().getBytes(); + Encryptor encryptor = new Encryptor(encryptionKey); + ApptentiveLog.v(ApptentiveLogTag.PAYLOADS, "Getting data for encrypted payload."); return encryptor.encrypt(bytes); } else { - return bytes; + ApptentiveLog.v(ApptentiveLogTag.PAYLOADS, "Getting data for plaintext payload."); + return jsonObject.toString().getBytes(); } } catch (Exception e) { ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error encrypting payload data", e); @@ -165,10 +162,10 @@ public HttpRequestMethod getHttpRequestMethod() { @Override public String getHttpRequestContentType() { - if (encryptor != null) { - return "application/json"; - } else { + if (encryptionKey != null) { return "application/octet-stream"; + } else { + return "application/json"; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index 1c74271ee..b836974ee 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -14,6 +14,17 @@ public abstract class Payload { private final PayloadType payloadType; + /** + * If set, this payload should be encrypted in getData(). + */ + protected String encryptionKey; + + /** + * Encrypted Payloads need to include the Conversation JWT inside them so that the server can + * authenticate each payload after it is decrypted. + */ + protected String token; + /** * A value that can be used to correlate a payload with another object * (for example, to update the sent status of a message) @@ -65,6 +76,14 @@ public PayloadType getPayloadType() { return payloadType; } + public void setEncryptionKey(String encryptionKey) { + this.encryptionKey = encryptionKey; + } + + public void setToken(String token) { + this.token = token; + } + public String getNonce() { return nonce; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index a8767d7f9..fb27a7c79 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -58,6 +58,7 @@ public class ApptentiveTaskManager implements PayloadStore, EventStore, Apptenti // Set when receiving an ApptentiveNotification private String currentConversationId; private String currentConversationToken; + private String conversationEncryptionKey; private final PayloadSender payloadSender; private boolean appInBackground; @@ -104,6 +105,13 @@ public boolean shouldRetryRequest(int responseCode, int retryAttempt) { * a new message is added. */ public void addPayload(final Payload... payloads) { + // Set the current encryptor on each payload as they are added. + if (conversationEncryptionKey != null) { + for (Payload payload : payloads) { + payload.setEncryptionKey(conversationEncryptionKey); + payload.setToken(currentConversationToken); + } + } singleThreadExecutor.execute(new Runnable() { @Override public void run() { @@ -261,6 +269,7 @@ public void onReceiveNotification(ApptentiveNotification notification) { Assert.assertNotNull(currentConversationId); currentConversationToken = conversation.getConversationToken(); + conversationEncryptionKey = conversation.getEncryptionKey(); Assert.assertNotNull(currentConversationToken); // when the Conversation ID comes back from the server, we need to update diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java index 41f41875b..50da5be8a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java @@ -178,4 +178,16 @@ public static String asJson(String key, Object value) { return null; } } + + /** + * Converts a hex String to a byte array. + */ + public static byte[] hexToBytes(String hex) { + int length = hex.length(); + byte[] ret = new byte[length / 2]; + for (int i = 0; i < length; i += 2) { + ret[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16)); + } + return ret; + } } From 304c0b27d23925223337abfedf10c0279716df5e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 2 May 2017 13:13:04 -0700 Subject: [PATCH 277/465] Replaced API key with App Key and App Signature --- .../apptentive/android/sdk/Apptentive.java | 10 +- .../android/sdk/ApptentiveInternal.java | 99 +++++++++++-------- .../sdk/comm/ApptentiveHttpClient.java | 22 +++-- .../android/sdk/util/Constants.java | 7 +- .../com/apptentive/android/sdk/util/Util.java | 30 ++++++ 5 files changed, 110 insertions(+), 58 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 1502396b0..338b7ff45 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -52,16 +52,16 @@ public class Apptentive { * @param application The {@link Application} object for this app. */ public static void register(Application application) { - Apptentive.register(application, null); + Apptentive.register(application, null, null); } - public static void register(Application application, String apptentiveApiKey) { - register(application, apptentiveApiKey, null); + public static void register(Application application, String apptentiveAppKey, String apptentiveAppSignature) { + register(application, apptentiveAppKey, apptentiveAppSignature, null); } - private static void register(Application application, String apptentiveApiKey, String serverUrl) { + private static void register(Application application, String apptentiveAppKey, String apptentiveAppSignature, String serverUrl) { ApptentiveLog.i("Registering Apptentive."); - ApptentiveInternal.createInstance(application, apptentiveApiKey, serverUrl); + ApptentiveInternal.createInstance(application, apptentiveAppKey, apptentiveAppSignature, serverUrl); } //region Global Data Methods diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index d522aa74a..83880f4b8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -91,7 +91,8 @@ public class ApptentiveInternal { private boolean appIsInForeground; private final SharedPreferences globalSharedPrefs; - private final String apiKey; + private final String appKey; + private final String appSignature; private String serverUrl; private String personId; private String androidId; // FIXME: remove this field (never used) @@ -145,7 +146,8 @@ public static PushAction parse(String name) { protected ApptentiveInternal() { taskManager = null; globalSharedPrefs = null; - apiKey = null; + appKey = null; + appSignature = null; apptentiveHttpClient = null; conversationManager = null; appContext = null; @@ -154,14 +156,23 @@ protected ApptentiveInternal() { lifecycleCallbacks = null; } - private ApptentiveInternal(Application application, String apiKey, String serverUrl) { - this.apiKey = apiKey; + private ApptentiveInternal(Application application, String appKey, String appSignature, String serverUrl) { + if (StringUtils.isNullOrEmpty(appKey)) { + throw new IllegalArgumentException("App key is null or empty"); + } + + if (StringUtils.isNullOrEmpty(appSignature)) { + throw new IllegalArgumentException("App signature is null or empty"); + } + + this.appKey = appKey; + this.appSignature = appSignature; this.serverUrl = serverUrl; appContext = application.getApplicationContext(); globalSharedPrefs = application.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); - apptentiveHttpClient = new ApptentiveHttpClient(apiKey, getEndpointBase(globalSharedPrefs)); + apptentiveHttpClient = new ApptentiveHttpClient(appKey, appSignature, getEndpointBase(globalSharedPrefs)); conversationManager = new ConversationManager(appContext, Util.getInternalDir(appContext, "conversations", true)); appRelease = AppReleaseManager.generateCurrentAppRelease(application, this); @@ -178,7 +189,7 @@ public static boolean isApptentiveRegistered() { /** * Create a new or return a existing thread-safe instance of the Apptentive SDK. If this * or any other {@link #getInstance()} has already been called in the application's lifecycle, the - * API key will be ignored and the current instance will be returned. + * App key will be ignored and the current instance will be returned. *

* This will be called from the application's onCreate(), before any other application objects have been * created. Since the time spent in this function directly impacts the performance of starting the first activity, @@ -187,7 +198,7 @@ public static boolean isApptentiveRegistered() { * * @param application the context of the app that is creating the instance */ - static void createInstance(Application application, String apptentiveApiKey, final String serverUrl) { + static void createInstance(Application application, String apptentiveAppKey, String apptentiveAppSignature, final String serverUrl) { if (application == null) { throw new IllegalArgumentException("Application is null"); } @@ -196,16 +207,24 @@ static void createInstance(Application application, String apptentiveApiKey, fin if (sApptentiveInternal == null) { // trim spaces - apptentiveApiKey = Util.trim(apptentiveApiKey); + apptentiveAppKey = Util.trim(apptentiveAppKey); + apptentiveAppSignature = Util.trim(apptentiveAppSignature); - // if API key is not defined - try loading from AndroidManifest.xml - if (StringUtils.isNullOrEmpty(apptentiveApiKey)) { - apptentiveApiKey = resolveManifestApiKey(application); - // TODO: check if apptentive key is still empty + // if App key is not defined - try loading from AndroidManifest.xml + if (StringUtils.isNullOrEmpty(apptentiveAppKey)) { + apptentiveAppKey = Util.getManifestMetadataString(application, Constants.MANIFEST_KEY_APPTENTIVE_APP_KEY); + // TODO: check if app key is still empty + } + + // if App signature is not defined - try loading from AndroidManifest.xml + if (StringUtils.isNullOrEmpty(apptentiveAppKey)) { + apptentiveAppSignature = Util.getManifestMetadataString(application, Constants.MANIFEST_KEY_APPTENTIVE_APP_SIGNATURE); + // TODO: check if signature key is still empty } try { - sApptentiveInternal = new ApptentiveInternal(application, apptentiveApiKey, serverUrl); + ApptentiveLog.v("Initializing Apptentive instance: appKey=%s appSignature=%s", apptentiveAppKey, apptentiveAppSignature); + sApptentiveInternal = new ApptentiveInternal(application, apptentiveAppKey, apptentiveAppSignature, serverUrl); sApptentiveInternal.start(); // TODO: check the result of this call application.registerActivityLifecycleCallbacks(sApptentiveInternal.lifecycleCallbacks); } catch (Exception e) { @@ -217,27 +236,6 @@ static void createInstance(Application application, String apptentiveApiKey, fin } } - /** - * Helper method for resolving API key from AndroidManifest.xml - * - * @return null if API key is missing or exception is thrown - */ - private static String resolveManifestApiKey(Context context) { - try { - String appPackageName = context.getPackageName(); - PackageManager packageManager = context.getPackageManager(); - PackageInfo packageInfo = packageManager.getPackageInfo(appPackageName, PackageManager.GET_META_DATA | PackageManager.GET_RECEIVERS); - Bundle metaData = packageInfo.applicationInfo.metaData; - if (metaData != null) { - return Util.trim(metaData.getString(Constants.MANIFEST_KEY_APPTENTIVE_API_KEY)); - } - } catch (Exception e) { - ApptentiveLog.e("Unexpected error while reading application or package info.", e); - } - - return null; - } - /** * Retrieve the existing instance of the Apptentive class. If {@link Apptentive#register(Application)} is * not called prior to this, it will return null; Otherwise, it will return the singleton instance initialized. @@ -358,8 +356,8 @@ public Conversation getConversation() { return conversationManager.getActiveConversation(); } - public String getApptentiveApiKey() { - return apiKey; + public String getApptentiveAppKey() { + return appKey; } public String getServerUrl() { @@ -573,20 +571,35 @@ private boolean start() { } ApptentiveLog.i("Debug mode enabled? %b", appRelease.isDebug()); - // The apiKey can be passed in programmatically, or we can fallback to checking in the manifest. - if (TextUtils.isEmpty(apiKey) || apiKey.contains(Constants.EXAMPLE_API_KEY_VALUE)) { - String errorMessage = "The Apptentive API Key is not defined. You may provide your Apptentive API Key in Apptentive.register(), or in as meta-data in your AndroidManifest.xml.\n" + - ""; + // The app key can be passed in programmatically, or we can fallback to checking in the manifest. + if (TextUtils.isEmpty(appKey) || appKey.contains(Constants.EXAMPLE_APP_KEY_VALUE)) { + String errorMessage = "The Apptentive App Key is not defined. You may provide your Apptentive API Key in Apptentive.register(), or in as meta-data in your AndroidManifest.xml.\n" + + ""; + if (appRelease.isDebug()) { + throw new RuntimeException(errorMessage); + } else { + ApptentiveLog.e(errorMessage); + } + } else { + ApptentiveLog.d("Using cached Apptentive App Key"); + } + ApptentiveLog.d("Apptentive App Key: %s", appKey); + + // The app signature can be passed in programmatically, or we can fallback to checking in the manifest. + if (TextUtils.isEmpty(appSignature) || appSignature.contains(Constants.EXAMPLE_APP_KEY_VALUE)) { + String errorMessage = "The Apptentive App Signature is not defined. You may provide your Apptentive App Signature in Apptentive.register(), or in as meta-data in your AndroidManifest.xml.\n" + + ""; if (appRelease.isDebug()) { throw new RuntimeException(errorMessage); } else { ApptentiveLog.e(errorMessage); } } else { - ApptentiveLog.d("Using cached Apptentive API Key"); + ApptentiveLog.d("Using cached Apptentive App Signature"); } - ApptentiveLog.d("Apptentive API Key: %s", apiKey); + ApptentiveLog.d("Apptentive App Signature: %s", appSignature); // Grab app info we need to access later on. ApptentiveLog.d("Default Locale: %s", Locale.getDefault().toString()); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index fc87ac2da..b19e9e0e4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -35,22 +35,28 @@ public class ApptentiveHttpClient implements PayloadRequestSender { private static final String ENDPOINT_CONVERSATION = "/conversation"; private static final String ENDPOINT_LOGIN = "/conversations/%s/session"; - private final String apiKey; + private final String appKey; + private final String appSignature; private final String serverURL; private final String userAgentString; private final HttpRequestManager httpRequestManager; - public ApptentiveHttpClient(String apiKey, String serverURL) { - if (isEmpty(apiKey)) { - throw new IllegalArgumentException("Illegal API key: '" + apiKey + "'"); + public ApptentiveHttpClient(String appKey, String appSignature, String serverURL) { + if (StringUtils.isNullOrEmpty(appKey)) { + throw new IllegalArgumentException("Illegal app key: '" + appKey + "'"); } - if (isEmpty(serverURL)) { + if (StringUtils.isNullOrEmpty(appSignature)) { + throw new IllegalArgumentException("Illegal app signature: '" + appSignature + "'"); + } + + if (StringUtils.isNullOrEmpty(serverURL)) { throw new IllegalArgumentException("Illegal server URL: '" + serverURL + "'"); } this.httpRequestManager = new HttpRequestManager(); - this.apiKey = apiKey; + this.appKey = appKey; + this.appSignature = appSignature; this.serverURL = serverURL; this.userAgentString = String.format(USER_AGENT_STRING, Constants.APPTENTIVE_SDK_VERSION); } @@ -58,7 +64,7 @@ public ApptentiveHttpClient(String apiKey, String serverURL) { //region API Requests public HttpJsonRequest getConversationToken(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { - HttpJsonRequest request = createJsonRequest(apiKey, ENDPOINT_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST); + HttpJsonRequest request = createJsonRequest(appKey, ENDPOINT_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST); request.addListener(listener); httpRequestManager.startRequest(request); return request; @@ -75,7 +81,7 @@ public HttpJsonRequest login(String conversationId, String token, HttpRequest.Li JSONObject json = new JSONObject(); // TODO: create an actual payload String endPoint = StringUtils.format(ENDPOINT_LOGIN, conversationId); - HttpJsonRequest request = createJsonRequest(apiKey, endPoint, json, HttpRequestMethod.POST); + HttpJsonRequest request = createJsonRequest(appKey, endPoint, json, HttpRequestMethod.POST); request.addListener(listener); httpRequestManager.startRequest(request); return request; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 1c9e3b86f..6c85c7822 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -20,8 +20,10 @@ public class Constants { // Globals public static final String PREF_KEY_SERVER_URL = "serverUrl"; + // Just in case a customer copies the example text verbatim. - public static final String EXAMPLE_API_KEY_VALUE = "YOUR_APPTENTIVE_API_KEY"; + public static final String EXAMPLE_APP_KEY_VALUE = "YOUR_APPTENTIVE_APP_KEY"; + public static final String EXAMPLE_APP_SIGNATURE_VALUE = "YOUR_APPTENTIVE_APP_SIGNATURE"; // Session Data public static final String PREF_KEY_APP_CONFIG_PREFIX = "appConfiguration."; @@ -45,7 +47,8 @@ public class Constants { // Manifest keys public static final String MANIFEST_KEY_APPTENTIVE_LOG_LEVEL = "apptentive_log_level"; - public static final String MANIFEST_KEY_APPTENTIVE_API_KEY = "apptentive_api_key"; + public static final String MANIFEST_KEY_APPTENTIVE_APP_KEY = "apptentive_app_key"; + public static final String MANIFEST_KEY_APPTENTIVE_APP_SIGNATURE = "apptentive_app_signature"; public static final String MANIFEST_KEY_SDK_DISTRIBUTION = "apptentive_sdk_distribution"; public static final String MANIFEST_KEY_SDK_DISTRIBUTION_VERSION = "apptentive_sdk_distribution_version"; public static final String MANIFEST_KEY_INITIALLY_HIDE_BRANDING = "apptentive_initially_hide_branding"; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java index 12e875155..259f3a189 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java @@ -27,6 +27,7 @@ import android.net.ConnectivityManager; import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.provider.Settings; @@ -864,4 +865,33 @@ public static File getInternalDir(Context context, String path, boolean createIf } return internalDir; } + + /** + * Helper method for resolving manifest metadata string value + * + * @return null if key is missing or exception is thrown + */ + public static String getManifestMetadataString(Context context, String key) { + if (context == null) { + throw new IllegalArgumentException("Context is null"); + } + + if (key == null) { + throw new IllegalArgumentException("Key is null"); + } + + try { + String appPackageName = context.getPackageName(); + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(appPackageName, PackageManager.GET_META_DATA | PackageManager.GET_RECEIVERS); + Bundle metaData = packageInfo.applicationInfo.metaData; + if (metaData != null) { + return Util.trim(metaData.getString(key)); + } + } catch (Exception e) { + ApptentiveLog.e("Unexpected error while reading application or package info.", e); + } + + return null; + } } From 0845f3b98b05763ccb64a2e4f22ce272ddf9ebbd Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 2 May 2017 13:36:02 -0700 Subject: [PATCH 278/465] Updated http-request headers --- .../android/sdk/ApptentiveInternal.java | 4 ++++ .../android/sdk/comm/ApptentiveClient.java | 4 ++++ .../android/sdk/comm/ApptentiveHttpClient.java | 15 +++++++++++---- .../com/apptentive/android/sdk/model/Payload.java | 5 +++++ .../apptentive/android/sdk/model/PayloadData.java | 8 +++++++- .../sdk/storage/ApptentiveDatabaseHelper.java | 8 ++++++-- .../sdk/storage/JsonPayloadSenderTest.java | 2 +- 7 files changed, 38 insertions(+), 8 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 83880f4b8..16339cdeb 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -360,6 +360,10 @@ public String getApptentiveAppKey() { return appKey; } + public String getApptentiveAppSignature() { + return appSignature; + } + public String getServerUrl() { if (serverUrl == null) { return Constants.CONFIG_DEFAULT_SERVER_URL; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index f9a18d83a..5e83883d2 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -24,6 +24,8 @@ import java.util.*; import java.util.zip.GZIPInputStream; +import static com.apptentive.android.sdk.debug.Assert.notNull; + public class ApptentiveClient { public static final int API_VERSION = 9; @@ -129,6 +131,8 @@ private static ApptentiveHttpResponse performHttpRequest(String oauthToken, Stri connection.setRequestProperty("Accept-Encoding", "gzip"); connection.setRequestProperty("Accept", "application/json"); connection.setRequestProperty("X-API-Version", String.valueOf(API_VERSION)); + connection.setRequestProperty("APPTENTIVE-APP-KEY", notNull(ApptentiveInternal.getInstance().getApptentiveAppKey())); + connection.setRequestProperty("APPTENTIVE-APP-SIGNATURE", notNull(ApptentiveInternal.getInstance().getApptentiveAppSignature())); switch (method) { case GET: diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index b19e9e0e4..2c05f2237 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -18,8 +18,6 @@ import java.util.List; -import static android.text.TextUtils.isEmpty; - /** * Class responsible for all client-server network communications using asynchronous HTTP requests */ @@ -116,12 +114,19 @@ private HttpRequest createPayloadRequest(PayloadData payload) { final HttpRequestMethod requestMethod = payload.getHttpRequestMethod(); // TODO: figure out a better solution + HttpRequest request; if (payload instanceof MultipartPayload) { final List associatedFiles = ((MultipartPayload) payload).getAssociatedFiles(); - return createMultipartRequest(token, httpPath, payload.getData(), associatedFiles, requestMethod); + request = createMultipartRequest(token, httpPath, payload.getData(), associatedFiles, requestMethod); + } else { + request = createRawRequest(token, httpPath, payload.getData(), requestMethod); + } + + if (payload.isEncrypted()) { + request.setRequestProperty("APPTENTIVE-ENCRYPTED", Boolean.TRUE); } - return createRawRequest(token, httpPath, payload.getData(), requestMethod); + return request; } //endregion @@ -199,6 +204,8 @@ private void setupRequestDefaults(HttpRequest request, String oauthToken) { request.setRequestProperty("Authorization", "OAuth " + oauthToken); request.setRequestProperty("Accept-Encoding", "gzip"); request.setRequestProperty("Accept", "application/json"); + request.setRequestProperty("APPTENTIVE-APP-KEY", appKey); + request.setRequestProperty("APPTENTIVE-APP-SIGNATURE", appSignature); request.setRequestProperty("X-API-Version", API_VERSION); request.setConnectTimeout(DEFAULT_HTTP_CONNECT_TIMEOUT); request.setReadTimeout(DEFAULT_HTTP_SOCKET_TIMEOUT); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index b836974ee..a31dbab29 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -7,6 +7,7 @@ package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.util.StringUtils; import java.util.List; import java.util.UUID; @@ -80,6 +81,10 @@ public void setEncryptionKey(String encryptionKey) { this.encryptionKey = encryptionKey; } + public boolean hasEncryptionKey() { + return !StringUtils.isNullOrEmpty(encryptionKey); + } + public void setToken(String token) { this.token = token; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java index 69be77b16..8aee9fc52 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java @@ -17,9 +17,10 @@ public class PayloadData { private final String contentType; private final String httpRequestPath; private final HttpRequestMethod httpRequestMethod; + private final boolean encrypted; - public PayloadData(PayloadType type, String nonce, byte[] data, String authToken, String contentType, String httpRequestPath, HttpRequestMethod httpRequestMethod) { + public PayloadData(PayloadType type, String nonce, byte[] data, String authToken, String contentType, String httpRequestPath, HttpRequestMethod httpRequestMethod, boolean encrypted) { if (type == null) { throw new IllegalArgumentException("Payload type is null"); } @@ -51,6 +52,7 @@ public PayloadData(PayloadType type, String nonce, byte[] data, String authToken this.contentType = contentType; this.httpRequestPath = httpRequestPath; this.httpRequestMethod = httpRequestMethod; + this.encrypted = encrypted; } //region String representation @@ -92,5 +94,9 @@ public HttpRequestMethod getHttpRequestMethod() { return httpRequestMethod; } + public boolean isEncrypted() { + return encrypted; + } + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index a84fd7891..77184f674 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -62,6 +62,7 @@ static final class PayloadEntry { static final DatabaseColumn COLUMN_REQUEST_METHOD = new DatabaseColumn(6, "requestMethod"); static final DatabaseColumn COLUMN_PATH = new DatabaseColumn(7, "path"); static final DatabaseColumn COLUMN_DATA = new DatabaseColumn(8, "data"); + static final DatabaseColumn COLUMN_ENCRYPTED = new DatabaseColumn(9, "encrypted"); } static final class LegacyPayloadEntry { @@ -82,7 +83,8 @@ static final class LegacyPayloadEntry { PayloadEntry.COLUMN_CONVERSATION_ID + " TEXT," + PayloadEntry.COLUMN_REQUEST_METHOD + " TEXT," + PayloadEntry.COLUMN_PATH + " TEXT," + - PayloadEntry.COLUMN_DATA + " BLOB" + + PayloadEntry.COLUMN_DATA + " BLOB," + + PayloadEntry.COLUMN_ENCRYPTED + " INTEGER" + ");"; private static final String SQL_QUERY_PAYLOAD_LIST_LEGACY = @@ -433,6 +435,7 @@ void addPayload(Payload... payloads) { StringUtils.isNullOrEmpty(conversationId) ? "${conversationId}" : conversationId) // if conversation id is missing we replace it with a place holder and update it later ); values.put(PayloadEntry.COLUMN_DATA.name, notNull(payload.getData())); + values.put(PayloadEntry.COLUMN_ENCRYPTED.name, payload.hasEncryptionKey() ? 1 : 0); db.insert(PayloadEntry.TABLE_NAME, null, values); } @@ -499,7 +502,8 @@ public PayloadData getOldestUnsentPayload() { final byte[] data = notNull(cursor.getBlob(PayloadEntry.COLUMN_DATA.index)); final String contentType = notNull(cursor.getString(PayloadEntry.COLUMN_CONTENT_TYPE.index)); final HttpRequestMethod httpRequestMethod = HttpRequestMethod.valueOf(notNull(cursor.getString(PayloadEntry.COLUMN_REQUEST_METHOD.index))); - return new PayloadData(payloadType, nonce, data, authToken, contentType, httpRequestPath, httpRequestMethod); + final boolean encrypted = cursor.getInt(PayloadEntry.COLUMN_ENCRYPTED.index) == 1; + return new PayloadData(payloadType, nonce, data, authToken, contentType, httpRequestPath, httpRequestMethod, encrypted); } return null; } catch (Exception sqe) { diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java index f1593d8b7..c48c967c1 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java @@ -95,7 +95,7 @@ class MockPayload extends PayloadData { private ResponseHandler responseHandler; public MockPayload(String key, Object value) { - super(PayloadType.unknown, "nonce", new byte[0], "authToken", "contentType", "path", HttpRequestMethod.GET); // TODO: figure out a better type + super(PayloadType.unknown, "nonce", new byte[0], "authToken", "contentType", "path", HttpRequestMethod.GET, false); // TODO: figure out a better type json = StringUtils.format("{'%s':'%s'}", key, value); responseHandler = new DefaultResponseHandler(); From c6c693c20b00860ee0e94574ef45800e60d7caa5 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 2 May 2017 15:14:11 -0700 Subject: [PATCH 279/465] Fixed OAuth token usage --- .../sdk/comm/ApptentiveHttpClient.java | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 2c05f2237..42828bd0b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -18,6 +18,8 @@ import java.util.List; +import static com.apptentive.android.sdk.debug.Assert.notNull; + /** * Class responsible for all client-server network communications using asynchronous HTTP requests */ @@ -62,7 +64,7 @@ public ApptentiveHttpClient(String appKey, String appSignature, String serverURL //region API Requests public HttpJsonRequest getConversationToken(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { - HttpJsonRequest request = createJsonRequest(appKey, ENDPOINT_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST); + HttpJsonRequest request = createJsonRequest(ENDPOINT_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST); request.addListener(listener); httpRequestManager.startRequest(request); return request; @@ -79,7 +81,7 @@ public HttpJsonRequest login(String conversationId, String token, HttpRequest.Li JSONObject json = new JSONObject(); // TODO: create an actual payload String endPoint = StringUtils.format(ENDPOINT_LOGIN, conversationId); - HttpJsonRequest request = createJsonRequest(appKey, endPoint, json, HttpRequestMethod.POST); + HttpJsonRequest request = createJsonRequest(endPoint, json, HttpRequestMethod.POST); request.addListener(listener); httpRequestManager.startRequest(request); return request; @@ -109,19 +111,21 @@ public HttpRequest sendPayload(PayloadData payload, HttpRequest.Listener associatedFiles = ((MultipartPayload) payload).getAssociatedFiles(); - request = createMultipartRequest(token, httpPath, payload.getData(), associatedFiles, requestMethod); + request = createMultipartRequest(httpPath, payload.getData(), associatedFiles, requestMethod); } else { - request = createRawRequest(token, httpPath, payload.getData(), requestMethod); + request = createRawRequest(httpPath, payload.getData(), requestMethod); } + request.setRequestProperty("Authorization", "OAuth " + oauthToken); + if (payload.isEncrypted()) { request.setRequestProperty("APPTENTIVE-ENCRYPTED", Boolean.TRUE); } @@ -133,10 +137,7 @@ private HttpRequest createPayloadRequest(PayloadData payload) { //region Helpers - private HttpJsonRequest createJsonRequest(String oauthToken, String endpoint, JSONObject json, HttpRequestMethod method) { - if (oauthToken == null) { - throw new IllegalArgumentException("OAuth token is null"); - } + private HttpJsonRequest createJsonRequest(String endpoint, JSONObject json, HttpRequestMethod method) { if (endpoint == null) { throw new IllegalArgumentException("Endpoint is null"); } @@ -149,16 +150,13 @@ private HttpJsonRequest createJsonRequest(String oauthToken, String endpoint, JS String url = createEndpointURL(endpoint); HttpJsonRequest request = new HttpJsonRequest(url, json); - setupRequestDefaults(request, oauthToken); + setupRequestDefaults(request); request.setMethod(method); request.setRequestProperty("Content-Type", "application/json"); return request; } - private RawHttpRequest createRawRequest(String oauthToken, String endpoint, byte[] data, HttpRequestMethod method) { - if (oauthToken == null) { - throw new IllegalArgumentException("OAuth token is null"); - } + private RawHttpRequest createRawRequest(String endpoint, byte[] data, HttpRequestMethod method) { if (endpoint == null) { throw new IllegalArgumentException("Endpoint is null"); } @@ -171,16 +169,13 @@ private RawHttpRequest createRawRequest(String oauthToken, String endpoint, byte String url = createEndpointURL(endpoint); RawHttpRequest request = new RawHttpRequest(url, data); - setupRequestDefaults(request, oauthToken); + setupRequestDefaults(request); request.setMethod(method); request.setRequestProperty("Content-Type", "application/json"); return request; } - private HttpJsonMultipartRequest createMultipartRequest(String oauthToken, String endpoint, byte[] data, List files, HttpRequestMethod method) { - if (oauthToken == null) { - throw new IllegalArgumentException("OAuth token is null"); - } + private HttpJsonMultipartRequest createMultipartRequest(String endpoint, byte[] data, List files, HttpRequestMethod method) { if (endpoint == null) { throw new IllegalArgumentException("Endpoint is null"); } @@ -193,15 +188,14 @@ private HttpJsonMultipartRequest createMultipartRequest(String oauthToken, Strin String url = createEndpointURL(endpoint); HttpJsonMultipartRequest request = new HttpJsonMultipartRequest(url, data, files); - setupRequestDefaults(request, oauthToken); + setupRequestDefaults(request); request.setMethod(method); return request; } - private void setupRequestDefaults(HttpRequest request, String oauthToken) { + private void setupRequestDefaults(HttpRequest request) { request.setRequestProperty("User-Agent", userAgentString); request.setRequestProperty("Connection", "Keep-Alive"); - request.setRequestProperty("Authorization", "OAuth " + oauthToken); request.setRequestProperty("Accept-Encoding", "gzip"); request.setRequestProperty("Accept", "application/json"); request.setRequestProperty("APPTENTIVE-APP-KEY", appKey); From 48d0dc94a3ca19a155ff1c435cbb590a6b4fafe7 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 2 May 2017 15:22:13 -0700 Subject: [PATCH 280/465] Fixed constant usage --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 16339cdeb..1854dd64f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -591,7 +591,7 @@ private boolean start() { ApptentiveLog.d("Apptentive App Key: %s", appKey); // The app signature can be passed in programmatically, or we can fallback to checking in the manifest. - if (TextUtils.isEmpty(appSignature) || appSignature.contains(Constants.EXAMPLE_APP_KEY_VALUE)) { + if (TextUtils.isEmpty(appSignature) || appSignature.contains(Constants.EXAMPLE_APP_SIGNATURE_VALUE)) { String errorMessage = "The Apptentive App Signature is not defined. You may provide your Apptentive App Signature in Apptentive.register(), or in as meta-data in your AndroidManifest.xml.\n" + ""; From dfeaaf4db43a8bd484219f4efc3b6bf800c6a4a1 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 2 May 2017 16:26:47 -0700 Subject: [PATCH 281/465] Encrypt wrapper, not original payload. --- .../main/java/com/apptentive/android/sdk/model/JsonPayload.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index abc024b2e..e84d0b0c8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -39,7 +39,7 @@ public byte[] getData() { JSONObject wrapper = new JSONObject(); wrapper.put("token", token); wrapper.put("payload", jsonObject); - byte[] bytes = jsonObject.toString().getBytes(); + byte[] bytes = wrapper.toString().getBytes(); Encryptor encryptor = new Encryptor(encryptionKey); ApptentiveLog.v(ApptentiveLogTag.PAYLOADS, "Getting data for encrypted payload."); return encryptor.encrypt(bytes); From ce16576161326a9c58fe164871e5080cde859b43 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 2 May 2017 16:27:55 -0700 Subject: [PATCH 282/465] Add test for encrypted payloads. --- .../storage/EncryptedPayloadSenderTest.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java new file mode 100644 index 000000000..ee0065d78 --- /dev/null +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import com.apptentive.android.sdk.TestCaseBase; +import com.apptentive.android.sdk.encryption.Encryptor; +import com.apptentive.android.sdk.model.EventPayload; + +import org.json.JSONObject; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class EncryptedPayloadSenderTest extends TestCaseBase { + + private static final String AUTH_TOKEN = "auth_token"; + private static final String ENCRYPTION_KEY = "5C5361D08DA7AD6CD70ACEB572D387BB713A312DE8CE6128B8A42F62A7B381DB"; + private static final String EVENT_LABEL = "com.apptentive#app#launch"; + + @Test + public void testEncryptedPayload() throws Exception { + + final EventPayload original = new EventPayload(EVENT_LABEL, "trigger"); + original.setToken(AUTH_TOKEN); + original.setEncryptionKey(ENCRYPTION_KEY); + + byte[] cipherText = original.getData(); + + Encryptor encryptor = new Encryptor(ENCRYPTION_KEY); + + try { + byte[] plainText = encryptor.decrypt(cipherText); + JSONObject resultJson = new JSONObject(new String(plainText)); + EventPayload result = new EventPayload(resultJson.getJSONObject("payload").toString()); + assertEquals(result.getEventLabel(), EVENT_LABEL); + } catch (Exception e) { + fail(e.getMessage()); + } + } +} \ No newline at end of file From 4ef3105eb2254edff9b8bdc6d0e40d53709be3dc Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 2 May 2017 16:45:07 -0700 Subject: [PATCH 283/465] Added more runtime checks --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 2 +- .../java/com/apptentive/android/sdk/model/PayloadData.java | 4 ++++ .../com/apptentive/android/sdk/storage/PayloadSender.java | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 1854dd64f..bff9cb1b4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -217,7 +217,7 @@ static void createInstance(Application application, String apptentiveAppKey, Str } // if App signature is not defined - try loading from AndroidManifest.xml - if (StringUtils.isNullOrEmpty(apptentiveAppKey)) { + if (StringUtils.isNullOrEmpty(apptentiveAppSignature)) { apptentiveAppSignature = Util.getManifestMetadataString(application, Constants.MANIFEST_KEY_APPTENTIVE_APP_SIGNATURE); // TODO: check if signature key is still empty } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java index 8aee9fc52..7a06b43c5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java @@ -29,6 +29,10 @@ public PayloadData(PayloadType type, String nonce, byte[] data, String authToken throw new IllegalArgumentException("Nonce is null"); } + if (data == null) { + throw new IllegalArgumentException("Data is null"); + } + if (authToken == null) { throw new IllegalArgumentException("Auth token is null"); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 760d83e85..39e897ba9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -74,7 +74,7 @@ synchronized boolean sendPayload(final PayloadData payload) { try { sendPayloadRequest(payload); } catch (Exception e) { - ApptentiveLog.e(PAYLOADS, "Exception while sending payload: %s", payload); + ApptentiveLog.e(e, "Exception while sending payload: %s", payload); // for NullPointerException, the message object would be null, we should handle it separately // TODO: add a helper class for handling that From df70e0e023934409c193b1b11cd52c2614d46fac Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 8 May 2017 18:46:21 -0700 Subject: [PATCH 284/465] Add a migration for Legacy SDK versions. Invoke it when we creating an Anonymous Conversation. Start out by migrating Device and last seen SDk Version. ANDROI-971 --- .../com/apptentive/android/sdk/Migrator.java | 99 +++++++++++++++++++ .../sdk/conversation/ConversationManager.java | 33 ++++++- .../android/sdk/storage/CustomData.java | 17 ++++ 3 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/Migrator.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Migrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/Migrator.java new file mode 100644 index 000000000..32ddda2a1 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Migrator.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.apptentive.android.sdk.conversation.Conversation; +import com.apptentive.android.sdk.storage.CustomData; +import com.apptentive.android.sdk.storage.Device; +import com.apptentive.android.sdk.storage.DeviceManager; +import com.apptentive.android.sdk.storage.IntegrationConfig; +import com.apptentive.android.sdk.storage.IntegrationConfigItem; +import com.apptentive.android.sdk.util.Constants; + +import java.util.Iterator; + +public class Migrator { + + private Conversation conversation; + private SharedPreferences prefs; + private Context context; + + public Migrator(Context context, SharedPreferences prefs, Conversation conversation) { + this.context = context; + this.prefs = prefs; + this.conversation = conversation; + } + + public void migrate() { + // Miscellaneous + conversation.setLastSeenSdkVersion(prefs.getString(Constants.PREF_KEY_LAST_SEEN_SDK_VERSION, null)); + + migrateDevice(); + + } + + private static final String INTEGRATION_APPTENTIVE_PUSH = "apptentive_push"; + private static final String INTEGRATION_PARSE = "parse"; + private static final String INTEGRATION_URBAN_AIRSHIP = "urban_airship"; + private static final String INTEGRATION_AWS_SNS = "aws_sns"; + + private static final String INTEGRATION_PUSH_TOKEN = "token"; + + private void migrateDevice() { + // Device, Device Custom Data, Integration Config + Device device = DeviceManager.generateNewDevice(context); + String deviceDataString = prefs.getString(Constants.PREF_KEY_DEVICE_DATA, null); + if (deviceDataString != null) { + try { + com.apptentive.android.sdk.model.CustomData customDataOld = new com.apptentive.android.sdk.model.CustomData(deviceDataString); + CustomData customData = CustomData.fromJson(customDataOld); + device.setCustomData(customData); + } catch (Exception e) { + ApptentiveLog.e("Error migrating Device Custom Data.", e); + } + } + + String integrationConfigString = prefs.getString(Constants.PREF_KEY_DEVICE_INTEGRATION_CONFIG, null); + if (integrationConfigString != null) { + try { + com.apptentive.android.sdk.model.CustomData integrationConfigOld = new com.apptentive.android.sdk.model.CustomData(integrationConfigString); + IntegrationConfig integrationConfig = new IntegrationConfig(); + Iterator it = integrationConfigOld.keys(); + while (it.hasNext()) { + String key = (String) it.next(); + IntegrationConfigItem item = new IntegrationConfigItem(); + switch (key ) { + case INTEGRATION_APPTENTIVE_PUSH: + item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); + integrationConfig.setApptentive(item); + break; + case INTEGRATION_PARSE: + item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); + integrationConfig.setParse(item); + break; + case INTEGRATION_URBAN_AIRSHIP: + item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); + integrationConfig.setUrbanAirship(item); + break; + case INTEGRATION_AWS_SNS: + item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); + integrationConfig.setAmazonAwsSns(item); + break; + } + } + device.setIntegrationConfig(integrationConfig); + } catch (Exception e) { + ApptentiveLog.e("Error migrating Device Integration Config.", e); + } + } + + conversation.setDevice(device); + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index ad50d4d4e..54ff89fb3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -1,11 +1,14 @@ package com.apptentive.android.sdk.conversation; import android.content.Context; +import android.content.SharedPreferences; import android.os.Looper; +import com.apptentive.android.sdk.Apptentive; import com.apptentive.android.sdk.Apptentive.LoginCallback; import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.Migrator; import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.conversation.ConversationMetadata.Filter; import com.apptentive.android.sdk.model.ConversationTokenRequest; @@ -22,6 +25,7 @@ import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.storage.SerializerException; +import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Jwt; import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; @@ -167,9 +171,32 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali File dataFile = new File(storageDir, Util.generateRandomFilename()); File messagesFile = new File(storageDir, Util.generateRandomFilename()); Conversation anonymousConversation = new Conversation(dataFile, messagesFile); - anonymousConversation.setState(ANONYMOUS_PENDING); - fetchConversationToken(anonymousConversation); - return anonymousConversation; + + // If there is a Legacy Conversation, migrate it into the new Conversation object. + // Check whether migration is needed. + // No Conversations exist in the meta-data. + // Do we have a Legacy Conversation or not? + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); + String lastSeenVersionString = prefs.getString(Constants.PREF_KEY_LAST_SEEN_SDK_VERSION, null); + Apptentive.Version version4 = new Apptentive.Version(); + version4.setVersion("4.0.0"); + Apptentive.Version lastSeenVersion = new Apptentive.Version(); + lastSeenVersion.setVersion(lastSeenVersionString); + if (lastSeenVersionString != null && lastSeenVersion.compareTo(version4) < 0) { + + Migrator migrator = new Migrator(getContext(), prefs, anonymousConversation); + migrator.migrate(); + + anonymousConversation.setState(ANONYMOUS_PENDING); + fetchConversationToken(anonymousConversation); + return anonymousConversation; + } else { + + // If there is no Legacy Conversation, then just connect it to the server. + anonymousConversation.setState(ANONYMOUS_PENDING); + fetchConversationToken(anonymousConversation); + return anonymousConversation; + } } private Conversation loadConversation(ConversationMetadataItem item) throws SerializerException { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java index 4a454caed..abb13f0c2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java @@ -9,6 +9,7 @@ import org.json.JSONException; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -74,4 +75,20 @@ public com.apptentive.android.sdk.model.CustomData toJson() { } return null; } + + public static CustomData fromJson(com.apptentive.android.sdk.model.CustomData input) { + try { + CustomData ret = new CustomData(); + Iterator keys = input.keys(); + while(keys.hasNext()) { + String key = (String) keys.next(); + ret.put(key, input.get(key)); + } + return ret; + } catch (JSONException e) { + // This can't happen. + } + return null; + } + } From 2d1fb3e9cf37bbd032166cde2eb00413c1b347d1 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 8 May 2017 18:59:11 -0700 Subject: [PATCH 285/465] Add migration for Sdk. --- .../com/apptentive/android/sdk/Migrator.java | 23 ++- .../android/sdk/migration/v4_0_0/Sdk.java | 159 ++++++++++++++++++ 2 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/Sdk.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Migrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/Migrator.java index 32ddda2a1..65486166a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Migrator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Migrator.java @@ -15,6 +15,7 @@ import com.apptentive.android.sdk.storage.DeviceManager; import com.apptentive.android.sdk.storage.IntegrationConfig; import com.apptentive.android.sdk.storage.IntegrationConfigItem; +import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.util.Constants; import java.util.Iterator; @@ -36,7 +37,7 @@ public void migrate() { conversation.setLastSeenSdkVersion(prefs.getString(Constants.PREF_KEY_LAST_SEEN_SDK_VERSION, null)); migrateDevice(); - + migrateSdk(); } private static final String INTEGRATION_APPTENTIVE_PUSH = "apptentive_push"; @@ -96,4 +97,24 @@ private void migrateDevice() { conversation.setDevice(device); } + + private void migrateSdk() { + String sdkString = prefs.getString(Constants.PREF_KEY_SDK, null); + if (sdkString != null) { + try { + com.apptentive.android.sdk.migration.v4_0_0.Sdk sdkOld = new com.apptentive.android.sdk.migration.v4_0_0.Sdk(sdkString); + Sdk sdk = new Sdk(); + sdk.setVersion(sdkOld.getVersion()); + sdk.setDistribution(sdkOld.getDistribution()); + sdk.setDistributionVersion(sdkOld.getDistributionVersion()); + sdk.setPlatform(sdkOld.getPlatform()); + sdk.setProgrammingLanguage(sdkOld.getProgrammingLanguage()); + sdk.setAuthorName(sdkOld.getAuthorName()); + sdk.setAuthorEmail(sdkOld.getAuthorEmail()); + conversation.setSdk(sdk); + }catch (Exception e) { + ApptentiveLog.e("Error migrating Sdk.", e); + } + } + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/Sdk.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/Sdk.java new file mode 100644 index 000000000..eef52f973 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/Sdk.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.migration.v4_0_0; + +import com.apptentive.android.sdk.ApptentiveLog; +import org.json.JSONException; +import org.json.JSONObject; + +public class Sdk extends JSONObject { + + private static final String KEY_VERSION = "version"; + private static final String KEY_PROGRAMMING_LANGUAGE = "programming_language"; + private static final String KEY_AUTHOR_NAME = "author_name"; + private static final String KEY_AUTHOR_EMAIL = "author_email"; + private static final String KEY_PLATFORM = "platform"; + private static final String KEY_DISTRIBUTION = "distribution"; + private static final String KEY_DISTRIBUTION_VERSION = "distribution_version"; + + public Sdk(String json) throws JSONException { + super(json); + } + + public String getVersion() { + try { + if(!isNull(KEY_VERSION)) { + return getString(KEY_VERSION); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setVersion(String version) { + try { + put(KEY_VERSION, version); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_VERSION); + } + } + + public String getProgrammingLanguage() { + try { + if(!isNull(KEY_PROGRAMMING_LANGUAGE)) { + return getString(KEY_PROGRAMMING_LANGUAGE); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setProgrammingLanguage(String programmingLanguage) { + try { + put(KEY_PROGRAMMING_LANGUAGE, programmingLanguage); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_PROGRAMMING_LANGUAGE); + } + } + + public String getAuthorName() { + try { + if(!isNull(KEY_AUTHOR_NAME)) { + return getString(KEY_AUTHOR_NAME); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setAuthorName(String authorName) { + try { + put(KEY_AUTHOR_NAME, authorName); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_AUTHOR_NAME); + } + } + + public String getAuthorEmail() { + try { + if(!isNull(KEY_AUTHOR_EMAIL)) { + return getString(KEY_AUTHOR_EMAIL); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setAuthorEmail(String authorEmail) { + try { + put(KEY_AUTHOR_EMAIL, authorEmail); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_AUTHOR_EMAIL); + } + } + + public String getPlatform() { + try { + if(!isNull(KEY_PLATFORM)) { + return getString(KEY_PLATFORM); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setPlatform(String platform) { + try { + put(KEY_PLATFORM, platform); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_PLATFORM); + } + } + + public String getDistribution() { + try { + if(!isNull(KEY_DISTRIBUTION)) { + return getString(KEY_DISTRIBUTION); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setDistribution(String distribution) { + try { + put(KEY_DISTRIBUTION, distribution); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_DISTRIBUTION); + } + } + + public String getDistributionVersion() { + try { + if(!isNull(KEY_DISTRIBUTION_VERSION)) { + return getString(KEY_DISTRIBUTION_VERSION); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setDistributionVersion(String distributionVersion) { + try { + put(KEY_DISTRIBUTION_VERSION, distributionVersion); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Sdk.", KEY_DISTRIBUTION_VERSION); + } + } +} \ No newline at end of file From 60a7fc04cc825b8d6e99d930053fe32b1a907227 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 9 May 2017 08:53:44 -0700 Subject: [PATCH 286/465] Add AppRelease migration. Move Migrator into the migration package. --- .../sdk/conversation/ConversationManager.java | 2 +- .../android/sdk/{ => migration}/Migrator.java | 30 +++- .../sdk/migration/v4_0_0/AppRelease.java | 157 ++++++++++++++++++ 3 files changed, 185 insertions(+), 4 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/{ => migration}/Migrator.java (77%) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/AppRelease.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 54ff89fb3..1a167f1f1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -8,7 +8,7 @@ import com.apptentive.android.sdk.Apptentive.LoginCallback; import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.Migrator; +import com.apptentive.android.sdk.migration.Migrator; import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.conversation.ConversationMetadata.Filter; import com.apptentive.android.sdk.model.ConversationTokenRequest; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Migrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java similarity index 77% rename from apptentive/src/main/java/com/apptentive/android/sdk/Migrator.java rename to apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java index 65486166a..4d45b56a5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Migrator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java @@ -4,12 +4,14 @@ * under which redistribution and use of this file is permitted. */ -package com.apptentive.android.sdk; +package com.apptentive.android.sdk.migration; import android.content.Context; import android.content.SharedPreferences; +import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.conversation.Conversation; +import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.CustomData; import com.apptentive.android.sdk.storage.Device; import com.apptentive.android.sdk.storage.DeviceManager; @@ -38,6 +40,7 @@ public void migrate() { migrateDevice(); migrateSdk(); + migrateAppRelease(); } private static final String INTEGRATION_APPTENTIVE_PUSH = "apptentive_push"; @@ -70,7 +73,7 @@ private void migrateDevice() { while (it.hasNext()) { String key = (String) it.next(); IntegrationConfigItem item = new IntegrationConfigItem(); - switch (key ) { + switch (key) { case INTEGRATION_APPTENTIVE_PUSH: item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); integrationConfig.setApptentive(item); @@ -112,9 +115,30 @@ private void migrateSdk() { sdk.setAuthorName(sdkOld.getAuthorName()); sdk.setAuthorEmail(sdkOld.getAuthorEmail()); conversation.setSdk(sdk); - }catch (Exception e) { + } catch (Exception e) { ApptentiveLog.e("Error migrating Sdk.", e); } } } + + private void migrateAppRelease() { + String appReleaseString = prefs.getString(Constants.PREF_KEY_APP_RELEASE, null); + if (appReleaseString != null) { + try { + com.apptentive.android.sdk.migration.v4_0_0.AppRelease appReleaseOld = new com.apptentive.android.sdk.migration.v4_0_0.AppRelease(appReleaseString); + AppRelease appRelease = new AppRelease(); + appRelease.setAppStore(appReleaseOld.getAppStore()); + appRelease.setDebug(appReleaseOld.getDebug()); + appRelease.setIdentifier(appReleaseOld.getIdentifier()); + appRelease.setInheritStyle(appReleaseOld.getInheritStyle()); + appRelease.setOverrideStyle(appReleaseOld.getOverrideStyle()); + appRelease.setTargetSdkVersion(appReleaseOld.getTargetSdkVersion()); + appRelease.setType(appReleaseOld.getType()); + appRelease.setVersionCode(appReleaseOld.getVersionCode()); + appRelease.setVersionName(appReleaseOld.getVersionName()); + } catch (Exception e) { + ApptentiveLog.e("Error migrating AppRelease.", e); + } + } + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/AppRelease.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/AppRelease.java new file mode 100644 index 000000000..e6a7ffcad --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/AppRelease.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.migration.v4_0_0; + +import com.apptentive.android.sdk.ApptentiveLog; + +import org.json.JSONException; +import org.json.JSONObject; + +public class AppRelease extends JSONObject { + + private static final String KEY_TYPE = "type"; + private static final String KEY_VERSION_NAME = "version_name"; + private static final String KEY_VERSION_CODE = "version_code"; + private static final String KEY_IDENTIFIER = "identifier"; + private static final String KEY_TARGET_SDK_VERSION = "target_sdk_version"; + private static final String KEY_APP_STORE = "app_store"; + private static final String KEY_STYLE_INHERIT = "inheriting_styles"; + private static final String KEY_STYLE_OVERRIDE = "overriding_styles"; + private static final String KEY_DEBUG = "debug"; + + public AppRelease(String json) throws JSONException { + super(json); + } + + public String getType() { + if (!isNull(KEY_TYPE)) { + return optString(KEY_TYPE, null); + } + return null; + } + + public void setType(String type) { + try { + put(KEY_TYPE, type); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_TYPE); + } + } + + public String getVersionName() { + if (!isNull(KEY_VERSION_NAME)) { + return optString(KEY_VERSION_NAME, null); + } + return null; + } + + public void setVersionName(String versionName) { + try { + put(KEY_VERSION_NAME, versionName); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_VERSION_NAME); + } + } + + public int getVersionCode() { + if (!isNull(KEY_VERSION_CODE)) { + return optInt(KEY_VERSION_CODE, -1); + } + return -1; + } + + public void setVersionCode(int versionCode) { + try { + put(KEY_VERSION_CODE, versionCode); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_VERSION_CODE); + } + } + + public String getIdentifier() { + if (!isNull(KEY_IDENTIFIER)) { + return optString(KEY_IDENTIFIER, null); + } + return null; + } + + public void setIdentifier(String identifier) { + try { + put(KEY_IDENTIFIER, identifier); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_IDENTIFIER); + } + } + + public String getTargetSdkVersion() { + if (!isNull(KEY_TARGET_SDK_VERSION)) { + return optString(KEY_TARGET_SDK_VERSION); + } + return null; + } + + public void setTargetSdkVersion(String targetSdkVersion) { + try { + put(KEY_TARGET_SDK_VERSION, targetSdkVersion); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_TARGET_SDK_VERSION); + } + } + + public String getAppStore() { + if (!isNull(KEY_APP_STORE)) { + return optString(KEY_APP_STORE, null); + } + return null; + } + + public void setAppStore(String appStore) { + try { + put(KEY_APP_STORE, appStore); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_APP_STORE); + } + } + + // Flag for whether the apptentive is inheriting styles from the host app + public boolean getInheritStyle() { + return optBoolean(KEY_STYLE_INHERIT); + } + + public void setInheritStyle(boolean inheritStyle) { + try { + put(KEY_STYLE_INHERIT, inheritStyle); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_STYLE_INHERIT); + } + } + + // Flag for whether the app is overriding any Apptentive Styles + public boolean getOverrideStyle() { + return optBoolean(KEY_STYLE_OVERRIDE); + } + + public void setOverrideStyle(boolean overrideStyle) { + try { + put(KEY_STYLE_OVERRIDE, overrideStyle); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_STYLE_OVERRIDE); + } + } + + public boolean getDebug() { + return optBoolean(KEY_DEBUG); + } + + public void setDebug(boolean debug) { + try { + put(KEY_DEBUG, debug); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_DEBUG); + } + } +} \ No newline at end of file From 8d34788031327acfab12df8bd18f7c8eb17cebd3 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 9 May 2017 13:58:16 -0700 Subject: [PATCH 287/465] Add migration for stored Person. --- .../android/sdk/migration/Migrator.java | 40 +++ .../android/sdk/migration/v4_0_0/Person.java | 239 ++++++++++++++++++ 2 files changed, 279 insertions(+) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/Person.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java index 4d45b56a5..356644b83 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java @@ -17,9 +17,12 @@ import com.apptentive.android.sdk.storage.DeviceManager; import com.apptentive.android.sdk.storage.IntegrationConfig; import com.apptentive.android.sdk.storage.IntegrationConfigItem; +import com.apptentive.android.sdk.storage.Person; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.util.Constants; +import org.json.JSONObject; + import java.util.Iterator; public class Migrator { @@ -41,6 +44,7 @@ public void migrate() { migrateDevice(); migrateSdk(); migrateAppRelease(); + migratePerson(); } private static final String INTEGRATION_APPTENTIVE_PUSH = "apptentive_push"; @@ -136,9 +140,45 @@ private void migrateAppRelease() { appRelease.setType(appReleaseOld.getType()); appRelease.setVersionCode(appReleaseOld.getVersionCode()); appRelease.setVersionName(appReleaseOld.getVersionName()); + conversation.setAppRelease(appRelease); } catch (Exception e) { ApptentiveLog.e("Error migrating AppRelease.", e); } } } + + private void migratePerson() { + String personString = prefs.getString(Constants.PREF_KEY_PERSON, null); + + if (personString != null) { + try { + Person person = new Person(); + com.apptentive.android.sdk.migration.v4_0_0.Person personOld = new com.apptentive.android.sdk.migration.v4_0_0.Person(personString); + person.setEmail(personOld.getEmail()); + person.setBirthday(personOld.getBirthday()); + person.setCity(personOld.getCity()); + person.setCountry(personOld.getCountry()); + person.setFacebookId(personOld.getFacebookId()); + person.setId(personOld.getId()); + person.setName(personOld.getName()); + person.setPhoneNumber(personOld.getPhoneNumber()); + person.setStreet(personOld.getStreet()); + person.setZip(personOld.getZip()); + + JSONObject customDataOld = personOld.getCustomData(); + if (customDataOld != null) { + CustomData customData = new CustomData(); + Iterator it = customDataOld.keys(); + while (it.hasNext()) { + String key = (String) it.next(); + customData.put(key, customDataOld.get(key)); + } + person.setCustomData(customData); + } + conversation.setPerson(person); + } catch (Exception e) { + ApptentiveLog.e("Error migrating Person.", e); + } + } + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/Person.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/Person.java new file mode 100644 index 000000000..77bc0e9c0 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/Person.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.migration.v4_0_0; + +import org.json.JSONException; +import org.json.JSONObject; + +public class Person extends JSONObject { + + private static final String KEY_ID = "id"; + private static final String KEY_EMAIL = "email"; + private static final String KEY_NAME = "name"; + private static final String KEY_FACEBOOK_ID = "facebook_id"; + private static final String KEY_PHONE_NUMBER = "phone_number"; + private static final String KEY_STREET = "street"; + private static final String KEY_CITY = "city"; + private static final String KEY_ZIP = "zip"; + private static final String KEY_COUNTRY = "country"; + private static final String KEY_BIRTHDAY = "birthday"; + private static final String KEY_CUSTOM_DATA = "custom_data"; + + public Person(String json) throws JSONException { + super(json); + } + + public String getId() { + try { + if (!isNull(KEY_ID)) { + return getString(KEY_ID); + } + } catch (JSONException e) { + // Can't happen + } + return null; + } + + public void setId(String id) { + try { + put(KEY_ID, id); + } catch (JSONException e) { + // Can't happen + } + } + + public String getEmail() { + try { + if (!isNull(KEY_EMAIL)) { + return getString(KEY_EMAIL); + } + } catch (JSONException e) { + // Can't happen + } + return null; + } + + public void setEmail(String email) { + try { + put(KEY_EMAIL, email); + } catch (JSONException e) { + // Can't happen + } + } + + public String getName() { + try { + if (!isNull(KEY_NAME)) { + return getString(KEY_NAME); + } + } catch (JSONException e) { + // Can't happen + } + return null; + } + + public void setName(String name) { + try { + put(KEY_NAME, name); + } catch (JSONException e) { + // Can't happen + } + } + + public String getFacebookId() { + try { + if (!isNull(KEY_FACEBOOK_ID)) { + return getString(KEY_FACEBOOK_ID); + } + } catch (JSONException e) { + // Can't happen + } + return null; + } + + public void setFacebookId(String facebookId) { + try { + put(KEY_FACEBOOK_ID, facebookId); + } catch (JSONException e) { + // Can't happen + } + } + + public String getPhoneNumber() { + try { + if (!isNull(KEY_PHONE_NUMBER)) { + return getString(KEY_PHONE_NUMBER); + } + } catch (JSONException e) { + // Can't happen + } + return null; + } + + public void setPhoneNumber(String phoneNumber) { + try { + put(KEY_PHONE_NUMBER, phoneNumber); + } catch (JSONException e) { + // Can't happen + } + } + + public String getStreet() { + try { + if (!isNull(KEY_STREET)) { + return getString(KEY_STREET); + } + } catch (JSONException e) { + // Can't happen + } + return null; + } + + public void setStreet(String street) { + try { + put(KEY_STREET, street); + } catch (JSONException e) { + // Can't happen + } + } + + public String getCity() { + try { + if (!isNull(KEY_CITY)) { + return getString(KEY_CITY); + } + } catch (JSONException e) { + // Can't happen + } + return null; + } + + public void setCity(String city) { + try { + put(KEY_CITY, city); + } catch (JSONException e) { + // Can't happen + } + } + + public String getZip() { + try { + if (!isNull(KEY_ZIP)) { + return getString(KEY_ZIP); + } + } catch (JSONException e) { + // Can't happen + } + return null; + } + + public void setZip(String zip) { + try { + put(KEY_ZIP, zip); + } catch (JSONException e) { + // Can't happen + } + } + + public String getCountry() { + try { + if (!isNull(KEY_COUNTRY)) { + return getString(KEY_COUNTRY); + } + } catch (JSONException e) { + // Can't happen + } + return null; + } + + public void setCountry(String country) { + try { + put(KEY_COUNTRY, country); + } catch (JSONException e) { + // Can't happen + } + } + + public String getBirthday() { + try { + if (!isNull(KEY_BIRTHDAY)) { + return getString(KEY_BIRTHDAY); + } + } catch (JSONException e) { + // Can't happen + } + return null; + } + + public void setBirthday(String birthday) { + try { + put(KEY_BIRTHDAY, birthday); + } catch (JSONException e) { + // Can't happen + } + } + + @SuppressWarnings("unchecked") // We check it coming in. + public JSONObject getCustomData() { + if (!isNull(KEY_CUSTOM_DATA)) { + try { + return getJSONObject(KEY_CUSTOM_DATA); + } catch (JSONException e) { + // Can't happen + } + } + return null; + } + + public void setCustomData(JSONObject customData) { + try { + put(KEY_CUSTOM_DATA, customData); + } catch (JSONException e) { + // Can't happen + } + } +} \ No newline at end of file From fb1c64cbeb6b13ff83327350c487edb386fa8aa7 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 9 May 2017 15:46:31 -0700 Subject: [PATCH 288/465] Fix migration for Device. --- .../android/sdk/migration/Migrator.java | 117 ++-- .../android/sdk/migration/v4_0_0/Device.java | 523 ++++++++++++++++++ .../android/sdk/storage/CustomData.java | 17 - 3 files changed, 596 insertions(+), 61 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/Device.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java index 356644b83..73f80ccc8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java @@ -14,12 +14,12 @@ import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.CustomData; import com.apptentive.android.sdk.storage.Device; -import com.apptentive.android.sdk.storage.DeviceManager; import com.apptentive.android.sdk.storage.IntegrationConfig; import com.apptentive.android.sdk.storage.IntegrationConfigItem; import com.apptentive.android.sdk.storage.Person; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONObject; @@ -55,54 +55,83 @@ public void migrate() { private static final String INTEGRATION_PUSH_TOKEN = "token"; private void migrateDevice() { - // Device, Device Custom Data, Integration Config - Device device = DeviceManager.generateNewDevice(context); - String deviceDataString = prefs.getString(Constants.PREF_KEY_DEVICE_DATA, null); - if (deviceDataString != null) { - try { - com.apptentive.android.sdk.model.CustomData customDataOld = new com.apptentive.android.sdk.model.CustomData(deviceDataString); - CustomData customData = CustomData.fromJson(customDataOld); - device.setCustomData(customData); - } catch (Exception e) { - ApptentiveLog.e("Error migrating Device Custom Data.", e); - } - } + try { + String deviceString = prefs.getString(Constants.PREF_KEY_DEVICE, null); + if (deviceString != null) { + com.apptentive.android.sdk.migration.v4_0_0.Device deviceOld = new com.apptentive.android.sdk.migration.v4_0_0.Device(deviceString); + Device device = new Device(); + + device.setUuid(deviceOld.getUuid()); + device.setOsName(deviceOld.getOsName()); + device.setOsVersion(deviceOld.getOsVersion()); + device.setOsBuild(deviceOld.getOsBuild()); + String osApiLevel = deviceOld.getOsApiLevel(); + if (!StringUtils.isNullOrEmpty(osApiLevel)) { + device.setOsApiLevel(Integer.parseInt(osApiLevel)); + } + device.setManufacturer(deviceOld.getManufacturer()); + device.setModel(deviceOld.getModel()); + device.setBoard(deviceOld.getBoard()); + device.setProduct(deviceOld.getProduct()); + device.setBrand(deviceOld.getBrand()); + device.setCpu(deviceOld.getCpu()); + device.setDevice(deviceOld.getDevice()); + device.setCarrier(deviceOld.getCarrier()); + device.setCurrentCarrier(deviceOld.getCurrentCarrier()); + device.setNetworkType(deviceOld.getNetworkType()); + device.setBuildType(deviceOld.getBuildType()); + device.setBuildId(deviceOld.getBuildId()); + device.setBootloaderVersion(deviceOld.getBootloaderVersion()); + device.setRadioVersion(deviceOld.getRadioVersion()); + device.setLocaleCountryCode(deviceOld.getLocaleCountryCode()); + device.setLocaleLanguageCode(deviceOld.getLocaleLanguageCode()); + device.setLocaleRaw(deviceOld.getLocaleRaw()); + device.setUtcOffset(deviceOld.getUtcOffset()); + + JSONObject customDataOld = deviceOld.getCustomData(); + if (customDataOld != null) { + CustomData customData = new CustomData(); + Iterator it = customDataOld.keys(); + while (it.hasNext()) { + String key = (String) it.next(); + customData.put(key, customDataOld.get(key)); + } + device.setCustomData(customData); + } - String integrationConfigString = prefs.getString(Constants.PREF_KEY_DEVICE_INTEGRATION_CONFIG, null); - if (integrationConfigString != null) { - try { - com.apptentive.android.sdk.model.CustomData integrationConfigOld = new com.apptentive.android.sdk.model.CustomData(integrationConfigString); - IntegrationConfig integrationConfig = new IntegrationConfig(); - Iterator it = integrationConfigOld.keys(); - while (it.hasNext()) { - String key = (String) it.next(); - IntegrationConfigItem item = new IntegrationConfigItem(); - switch (key) { - case INTEGRATION_APPTENTIVE_PUSH: - item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); - integrationConfig.setApptentive(item); - break; - case INTEGRATION_PARSE: - item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); - integrationConfig.setParse(item); - break; - case INTEGRATION_URBAN_AIRSHIP: - item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); - integrationConfig.setUrbanAirship(item); - break; - case INTEGRATION_AWS_SNS: - item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); - integrationConfig.setAmazonAwsSns(item); - break; + JSONObject integrationConfigOld = deviceOld.getIntegrationConfig(); + if (integrationConfigOld != null) { + IntegrationConfig integrationConfig = new IntegrationConfig(); + Iterator it = integrationConfigOld.keys(); + while (it.hasNext()) { + String key = (String) it.next(); + IntegrationConfigItem item = new IntegrationConfigItem(); + switch (key) { + case INTEGRATION_APPTENTIVE_PUSH: + item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); + integrationConfig.setApptentive(item); + break; + case INTEGRATION_PARSE: + item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); + integrationConfig.setParse(item); + break; + case INTEGRATION_URBAN_AIRSHIP: + item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); + integrationConfig.setUrbanAirship(item); + break; + case INTEGRATION_AWS_SNS: + item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); + integrationConfig.setAmazonAwsSns(item); + break; + } } + device.setIntegrationConfig(integrationConfig); } - device.setIntegrationConfig(integrationConfig); - } catch (Exception e) { - ApptentiveLog.e("Error migrating Device Integration Config.", e); + conversation.setDevice(device); } + } catch (Exception e) { + ApptentiveLog.e("Error migrating Device.", e); } - - conversation.setDevice(device); } private void migrateSdk() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/Device.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/Device.java new file mode 100644 index 000000000..57d568396 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/Device.java @@ -0,0 +1,523 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.migration.v4_0_0; + +import com.apptentive.android.sdk.ApptentiveLog; + +import org.json.JSONException; +import org.json.JSONObject; + +public class Device extends JSONObject { + + private static final String KEY_UUID = "uuid"; + private static final String KEY_OS_NAME = "os_name"; + private static final String KEY_OS_VERSION = "os_version"; + private static final String KEY_OS_BUILD = "os_build"; + private static final String KEY_OS_API_LEVEL = "os_api_level"; + private static final String KEY_MANUFACTURER = "manufacturer"; + private static final String KEY_MODEL = "model"; + private static final String KEY_BOARD = "board"; + private static final String KEY_PRODUCT = "product"; + private static final String KEY_BRAND = "brand"; + private static final String KEY_CPU = "cpu"; + private static final String KEY_DEVICE = "device"; + private static final String KEY_CARRIER = "carrier"; + private static final String KEY_CURRENT_CARRIER = "current_carrier"; + private static final String KEY_NETWORK_TYPE = "network_type"; + private static final String KEY_BUILD_TYPE = "build_type"; + private static final String KEY_BUILD_ID = "build_id"; + private static final String KEY_BOOTLOADER_VERSION = "bootloader_version"; + private static final String KEY_RADIO_VERSION = "radio_version"; + private static final String KEY_CUSTOM_DATA = "custom_data"; + private static final String KEY_LOCALE_COUNTRY_CODE = "locale_country_code"; + private static final String KEY_LOCALE_LANGUAGE_CODE = "locale_language_code"; + private static final String KEY_LOCALE_RAW = "locale_raw"; + private static final String KEY_UTC_OFFSET = "utc_offset"; + private static final String KEY_INTEGRATION_CONFIG = "integration_config"; + + public Device(String json) throws JSONException { + super(json); + } + + public String getUuid() { + try { + if (!isNull(KEY_UUID)) { + return getString(KEY_UUID); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setUuid(String uuid) { + try { + put(KEY_UUID, uuid); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_UUID); + } + } + + public String getOsName() { + try { + if (!isNull(KEY_OS_NAME)) { + return getString(KEY_OS_NAME); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setOsName(String osName) { + try { + put(KEY_OS_NAME, osName); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_OS_NAME); + } + } + + public String getOsVersion() { + try { + if (!isNull(KEY_OS_VERSION)) { + return getString(KEY_OS_VERSION); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setOsVersion(String osVersion) { + try { + put(KEY_OS_VERSION, osVersion); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_OS_VERSION); + } + } + + public String getOsBuild() { + try { + if (!isNull(KEY_OS_BUILD)) { + return getString(KEY_OS_BUILD); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setOsBuild(String osBuild) { + try { + put(KEY_OS_BUILD, osBuild); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_OS_BUILD); + } + } + + public String getOsApiLevel() { + try { + if (!isNull(KEY_OS_API_LEVEL)) { + return getString(KEY_OS_API_LEVEL); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setOsApiLevel(String osApiLevel) { + try { + put(KEY_OS_API_LEVEL, osApiLevel); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_OS_API_LEVEL); + } + } + + public String getManufacturer() { + try { + if (!isNull(KEY_MANUFACTURER)) { + return getString(KEY_MANUFACTURER); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setManufacturer(String manufacturer) { + try { + put(KEY_MANUFACTURER, manufacturer); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_MANUFACTURER); + } + } + + public String getModel() { + try { + if (!isNull(KEY_MODEL)) { + return getString(KEY_MODEL); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setModel(String model) { + try { + put(KEY_MODEL, model); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_MODEL); + } + } + + public String getBoard() { + try { + if (!isNull(KEY_BOARD)) { + return getString(KEY_BOARD); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setBoard(String board) { + try { + put(KEY_BOARD, board); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_BOARD); + } + } + + public String getProduct() { + try { + if (!isNull(KEY_PRODUCT)) { + return getString(KEY_PRODUCT); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setProduct(String product) { + try { + put(KEY_PRODUCT, product); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_PRODUCT); + } + } + + public String getBrand() { + try { + if (!isNull(KEY_BRAND)) { + return getString(KEY_BRAND); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setBrand(String brand) { + try { + put(KEY_BRAND, brand); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_BRAND); + } + } + + public String getCpu() { + try { + if (!isNull(KEY_CPU)) { + return getString(KEY_CPU); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setCpu(String cpu) { + try { + put(KEY_CPU, cpu); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_CPU); + } + } + + public String getDevice() { + try { + if (!isNull(KEY_DEVICE)) { + return getString(KEY_DEVICE); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setDevice(String device) { + try { + put(KEY_DEVICE, device); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_DEVICE); + } + } + + public String getCarrier() { + try { + if (!isNull(KEY_CARRIER)) { + return getString(KEY_CARRIER); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setCarrier(String carrier) { + try { + put(KEY_CARRIER, carrier); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_CARRIER); + } + } + + public String getCurrentCarrier() { + try { + if (!isNull(KEY_CURRENT_CARRIER)) { + return getString(KEY_CURRENT_CARRIER); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setCurrentCarrier(String currentCarrier) { + try { + put(KEY_CURRENT_CARRIER, currentCarrier); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_CURRENT_CARRIER); + } + } + + public String getNetworkType() { + try { + if (!isNull(KEY_NETWORK_TYPE)) { + return getString(KEY_NETWORK_TYPE); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setNetworkType(String networkType) { + try { + put(KEY_NETWORK_TYPE, networkType); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_NETWORK_TYPE); + } + } + + public String getBuildType() { + try { + if (!isNull(KEY_BUILD_TYPE)) { + return getString(KEY_BUILD_TYPE); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setBuildType(String buildType) { + try { + put(KEY_BUILD_TYPE, buildType); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_BUILD_TYPE); + } + } + + public String getBuildId() { + try { + if (!isNull(KEY_BUILD_ID)) { + return getString(KEY_BUILD_ID); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setBuildId(String buildId) { + try { + put(KEY_BUILD_ID, buildId); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_BUILD_ID); + } + } + + public String getBootloaderVersion() { + try { + if (!isNull(KEY_BOOTLOADER_VERSION)) { + return getString(KEY_BOOTLOADER_VERSION); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setBootloaderVersion(String bootloaderVersion) { + try { + put(KEY_BOOTLOADER_VERSION, bootloaderVersion); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_BOOTLOADER_VERSION); + } + } + + public String getRadioVersion() { + try { + if (!isNull(KEY_RADIO_VERSION)) { + return getString(KEY_RADIO_VERSION); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setRadioVersion(String radioVersion) { + try { + put(KEY_RADIO_VERSION, radioVersion); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_RADIO_VERSION); + } + } + + @SuppressWarnings("unchecked") // We check it coming in. + public JSONObject getCustomData() { + if (!isNull(KEY_CUSTOM_DATA)) { + try { + return getJSONObject(KEY_CUSTOM_DATA); + } catch (JSONException e) { + // Ignore + } + } + return null; + } + + public void setCustomData(JSONObject customData) { + try { + put(KEY_CUSTOM_DATA, customData); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_CUSTOM_DATA); + } + } + + @SuppressWarnings("unchecked") // We check it coming in. + public JSONObject getIntegrationConfig() { + if (!isNull(KEY_INTEGRATION_CONFIG)) { + try { + return getJSONObject(KEY_INTEGRATION_CONFIG); + } catch (JSONException e) { + // Ignore + } + } + return null; + } + + public void setIntegrationConfig(JSONObject integrationConfig) { + try { + put(KEY_INTEGRATION_CONFIG, integrationConfig); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_INTEGRATION_CONFIG); + } + } + + public String getLocaleCountryCode() { + try { + if (!isNull(KEY_LOCALE_COUNTRY_CODE)) { + return getString(KEY_LOCALE_COUNTRY_CODE); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setLocaleCountryCode(String localeCountryCode) { + try { + put(KEY_LOCALE_COUNTRY_CODE, localeCountryCode); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_LOCALE_COUNTRY_CODE); + } + } + + public String getLocaleLanguageCode() { + try { + if (!isNull(KEY_LOCALE_LANGUAGE_CODE)) { + return getString(KEY_LOCALE_LANGUAGE_CODE); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setLocaleLanguageCode(String localeLanguageCode) { + try { + put(KEY_LOCALE_LANGUAGE_CODE, localeLanguageCode); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_LOCALE_LANGUAGE_CODE); + } + } + + public String getLocaleRaw() { + try { + if (!isNull(KEY_LOCALE_RAW)) { + return getString(KEY_LOCALE_RAW); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setLocaleRaw(String localeRaw) { + try { + put(KEY_LOCALE_RAW, localeRaw); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_LOCALE_RAW); + } + } + + public String getUtcOffset() { + try { + if (!isNull(KEY_UTC_OFFSET)) { + return getString(KEY_UTC_OFFSET); + } + } catch (JSONException e) { + // Ignore + } + return null; + } + + public void setUtcOffset(String utcOffset) { + try { + put(KEY_UTC_OFFSET, utcOffset); + } catch (JSONException e) { + ApptentiveLog.w("Error adding %s to Device.", KEY_UTC_OFFSET); + } + } + +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java index abb13f0c2..4a454caed 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java @@ -9,7 +9,6 @@ import org.json.JSONException; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -75,20 +74,4 @@ public com.apptentive.android.sdk.model.CustomData toJson() { } return null; } - - public static CustomData fromJson(com.apptentive.android.sdk.model.CustomData input) { - try { - CustomData ret = new CustomData(); - Iterator keys = input.keys(); - while(keys.hasNext()) { - String key = (String) keys.next(); - ret.put(key, input.get(key)); - } - return ret; - } catch (JSONException e) { - // This can't happen. - } - return null; - } - } From 63f6c7033c16d374cfd50fdbf2c683f837b472d4 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 9 May 2017 15:59:43 -0700 Subject: [PATCH 289/465] Add migration for Message Center properties. --- .../java/com/apptentive/android/sdk/migration/Migrator.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java index 73f80ccc8..9a6cba7a6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java @@ -45,6 +45,7 @@ public void migrate() { migrateSdk(); migrateAppRelease(); migratePerson(); + migrateMessageCenter(); } private static final String INTEGRATION_APPTENTIVE_PUSH = "apptentive_push"; @@ -210,4 +211,9 @@ private void migratePerson() { } } } + + private void migrateMessageCenter() { + conversation.setMessageCenterFeatureUsed(prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_FEATURE_USED, false)); + conversation.setMessageCenterWhoCardPreviouslyDisplayed(prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE, false)); + } } From 4c9cc716c568fb452bb8b6f02283c636c0a3cd89 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 9 May 2017 16:42:22 -0700 Subject: [PATCH 290/465] Add migration for Version History. --- .../android/sdk/migration/Migrator.java | 23 +++++++++++++++++++ .../v4_0_0}/VersionHistoryEntry.java | 4 ++-- .../v4_0_0}/VersionHistoryStore.java | 3 ++- .../v4_0_0}/VersionHistoryStoreMigrator.java | 8 +++---- 4 files changed, 31 insertions(+), 7 deletions(-) rename apptentive/src/main/java/com/apptentive/android/sdk/{storage/legacy => migration/v4_0_0}/VersionHistoryEntry.java (91%) rename apptentive/src/main/java/com/apptentive/android/sdk/{storage/legacy => migration/v4_0_0}/VersionHistoryStore.java (98%) rename apptentive/src/main/java/com/apptentive/android/sdk/{storage/legacy => migration/v4_0_0}/VersionHistoryStoreMigrator.java (91%) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java index 9a6cba7a6..84eef7beb 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java @@ -11,6 +11,8 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.conversation.Conversation; +import com.apptentive.android.sdk.migration.v4_0_0.VersionHistoryEntry; +import com.apptentive.android.sdk.migration.v4_0_0.VersionHistoryStore; import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.CustomData; import com.apptentive.android.sdk.storage.Device; @@ -18,9 +20,11 @@ import com.apptentive.android.sdk.storage.IntegrationConfigItem; import com.apptentive.android.sdk.storage.Person; import com.apptentive.android.sdk.storage.Sdk; +import com.apptentive.android.sdk.storage.VersionHistory; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.StringUtils; +import org.json.JSONArray; import org.json.JSONObject; import java.util.Iterator; @@ -46,6 +50,7 @@ public void migrate() { migrateAppRelease(); migratePerson(); migrateMessageCenter(); + migrateVersionHistory(); } private static final String INTEGRATION_APPTENTIVE_PUSH = "apptentive_push"; @@ -216,4 +221,22 @@ private void migrateMessageCenter() { conversation.setMessageCenterFeatureUsed(prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_FEATURE_USED, false)); conversation.setMessageCenterWhoCardPreviouslyDisplayed(prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE, false)); } + + private void migrateVersionHistory() { + // An existing static initializer will trigger the V1 to V2 migration of VersionHistory when VersionHistoryStore is loaded below. + + // Then migrate to V3, which is stored in the Conversation object. + JSONArray versionHistoryOld = VersionHistoryStore.getBaseArray(); + try { + if (versionHistoryOld != null && versionHistoryOld.length() > 0) { + VersionHistory versionHistory = conversation.getVersionHistory(); + for (int i = 0; i < versionHistoryOld.length(); i++) { + VersionHistoryEntry versionHistoryEntryOld = new VersionHistoryEntry((JSONObject) versionHistoryOld.get(i)); + versionHistory.updateVersionHistory(versionHistoryEntryOld.getTimestamp(), versionHistoryEntryOld.getVersionCode(), versionHistoryEntryOld.getVersionName()); + } + } + } catch (Exception e) { + ApptentiveLog.w("Error migrating VersionHistory entries V2 to V3.", e); + } + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryEntry.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/VersionHistoryEntry.java similarity index 91% rename from apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryEntry.java rename to apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/VersionHistoryEntry.java index 45f9c9a56..7c99ad607 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryEntry.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/VersionHistoryEntry.java @@ -4,7 +4,7 @@ * under which redistribution and use of this file is permitted. */ -package com.apptentive.android.sdk.storage.legacy; +package com.apptentive.android.sdk.migration.v4_0_0; import org.json.JSONException; import org.json.JSONObject; @@ -15,7 +15,7 @@ public class VersionHistoryEntry extends JSONObject { private static final String KEY_VERSION_NAME = "versionName"; private static final String KEY_TIMESTAMP = "timestamp"; - VersionHistoryEntry(JSONObject jsonObject) throws JSONException { + public VersionHistoryEntry(JSONObject jsonObject) throws JSONException { this(jsonObject.toString()); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/VersionHistoryStore.java similarity index 98% rename from apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStore.java rename to apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/VersionHistoryStore.java index fb256f505..b15ccfb87 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/VersionHistoryStore.java @@ -4,7 +4,7 @@ * under which redistribution and use of this file is permitted. */ -package com.apptentive.android.sdk.storage.legacy; +package com.apptentive.android.sdk.migration.v4_0_0; import android.content.SharedPreferences; @@ -163,6 +163,7 @@ public static synchronized VersionHistoryEntry getLastVersionSeen() { * @return the base JSONArray */ public static JSONArray getBaseArray() { + ensureLoaded(); JSONArray baseArray = new JSONArray(); for (VersionHistoryEntry entry : versionHistoryEntries) { baseArray.put(entry); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStoreMigrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/VersionHistoryStoreMigrator.java similarity index 91% rename from apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStoreMigrator.java rename to apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/VersionHistoryStoreMigrator.java index 9a1b033c6..31320d004 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/VersionHistoryStoreMigrator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/VersionHistoryStoreMigrator.java @@ -1,10 +1,10 @@ /* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ -package com.apptentive.android.sdk.storage.legacy; +package com.apptentive.android.sdk.migration.v4_0_0; import android.content.SharedPreferences; @@ -12,7 +12,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.util.Constants; -public class VersionHistoryStoreMigrator { +class VersionHistoryStoreMigrator { private static final String OLD_ENTRY_SEP = "__"; private static final String OLD_FIELD_SEP = "--"; @@ -22,7 +22,7 @@ public class VersionHistoryStoreMigrator { private static boolean migrated_to_v2; - public static void migrateV1ToV2(String oldFormat) { + static void migrateV1ToV2(String oldFormat) { ApptentiveLog.i("Migrating VersionHistoryStore V1 to V2."); ApptentiveLog.i("V1: %s", oldFormat); try { From aa293274f3e0f9ca43a41265550a2a972e472182 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 9 May 2017 17:24:22 -0700 Subject: [PATCH 291/465] Add FIXME for connecting legacy conversation to server. --- .../apptentive/android/sdk/conversation/ConversationManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 1a167f1f1..937cc81b0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -187,6 +187,7 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali Migrator migrator = new Migrator(getContext(), prefs, anonymousConversation); migrator.migrate(); + // FIXME: Figure out how to transition this conversation into a full ANONYMOUS state. anonymousConversation.setState(ANONYMOUS_PENDING); fetchConversationToken(anonymousConversation); return anonymousConversation; From 8b2f8658993f030ab6d3a3967d77f750566339be Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 10 May 2017 12:40:05 -0700 Subject: [PATCH 292/465] Add migration for CodePointStore / EventData. --- .../android/sdk/migration/Migrator.java | 24 ++ .../sdk/migration/v4_0_0/CodePointStore.java | 122 ++++++++ .../android/sdk/storage/EventData.java | 21 +- .../android/sdk/storage/EventRecord.java | 28 ++ .../sdk/storage/legacy/CodePointStore.java | 262 ------------------ 5 files changed, 193 insertions(+), 264 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/CodePointStore.java delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/CodePointStore.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java index 84eef7beb..f16de6814 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java @@ -11,11 +11,14 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.conversation.Conversation; +import com.apptentive.android.sdk.migration.v4_0_0.CodePointStore; import com.apptentive.android.sdk.migration.v4_0_0.VersionHistoryEntry; import com.apptentive.android.sdk.migration.v4_0_0.VersionHistoryStore; import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.CustomData; import com.apptentive.android.sdk.storage.Device; +import com.apptentive.android.sdk.storage.EventData; +import com.apptentive.android.sdk.storage.EventRecord; import com.apptentive.android.sdk.storage.IntegrationConfig; import com.apptentive.android.sdk.storage.IntegrationConfigItem; import com.apptentive.android.sdk.storage.Person; @@ -25,9 +28,11 @@ import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import java.util.Iterator; +import java.util.Map; public class Migrator { @@ -51,6 +56,7 @@ public void migrate() { migratePerson(); migrateMessageCenter(); migrateVersionHistory(); + migrateEventData(); } private static final String INTEGRATION_APPTENTIVE_PUSH = "apptentive_push"; @@ -239,4 +245,22 @@ private void migrateVersionHistory() { ApptentiveLog.w("Error migrating VersionHistory entries V2 to V3.", e); } } + + private void migrateEventData() { + EventData eventData = conversation.getEventData(); + String codePointString = prefs.getString(Constants.PREF_KEY_CODE_POINT_STORE, null); + try { + CodePointStore codePointStore = new CodePointStore(codePointString); + Map migratedEvents = codePointStore.migrateCodePoints(); + Map migratedInteractions = codePointStore.migrateInteractions(); + if (migratedEvents != null) { + eventData.setEvents(migratedEvents); + } + if (migratedInteractions != null) { + eventData.setInteractions(migratedInteractions); + } + } catch (JSONException e) { + ApptentiveLog.w("Error migrating Event Data.", e); + } + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/CodePointStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/CodePointStore.java new file mode 100644 index 000000000..f656bdc46 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/CodePointStore.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.migration.v4_0_0; + +import com.apptentive.android.sdk.storage.EventRecord; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + *

All public methods altering code point values should be synchronized.

+ *

Example:

+ *
+ * {
+ *   "code_point": {
+ *     "codePoint1": {
+ *       "last": 1234567890,
+ *       "total": 6,
+ *       "version": {
+ *         "1.1": 4,
+ *         "1.2": 2
+ *       },
+ *       "build": {
+ *         "5": 4,
+ *         "6": 2
+ *       }
+ *     }
+ *   },
+ *   "interactions": {
+ *     "526fe2836dd8bf546a00000c": {
+ *       "last": 1234567890.4,
+ *       "total": 6,
+ *       "version": {
+ *         "1.1": 4,
+ *         "1.2": 2
+ *       },
+ *       "build": {
+ *         "5": 4,
+ *         "6": 2
+ *       }
+ *     }
+ *   }
+ * }
+ * 
+ */ +public class CodePointStore { + + private JSONObject store; + + private static final String KEY_CODE_POINT = "code_point"; + private static final String KEY_INTERACTIONS = "interactions"; + private static final String KEY_LAST = "last"; // The last time this codepoint was seen. + private static final String KEY_TOTAL = "total"; // The total times this code point was seen. + private static final String KEY_VERSION_NAME = "version"; + private static final String KEY_VERSION_CODE = "build"; + + public CodePointStore(String json) throws JSONException { + store = new JSONObject(json); + } + + public Map migrateCodePoints() throws JSONException { + return migrateRecords(false); + } + + public Map migrateInteractions() throws JSONException { + return migrateRecords(true); + } + + public Map migrateRecords(boolean interaction) throws JSONException { + JSONObject recordContainer = store.optJSONObject(interaction ? KEY_INTERACTIONS : KEY_CODE_POINT); + if (recordContainer != null) { + Map ret = new HashMap<>(); + Iterator recordNames = recordContainer.keys(); + while (recordNames.hasNext()) { + String recordName = recordNames.next(); + JSONObject record = recordContainer.getJSONObject(recordName); + EventRecord eventRecord = new EventRecord(); + eventRecord.setLast(record.getDouble(KEY_LAST)); + eventRecord.setTotal(record.getLong(KEY_TOTAL)); + + Map versionCodes = new HashMap<>(); + JSONObject versionCodesOld = record.getJSONObject(KEY_VERSION_CODE); + if (versionCodesOld != null) { + Iterator versionCodesIterator = versionCodesOld.keys(); + if (versionCodesIterator != null) { + while (versionCodesIterator.hasNext()) { + String versionCodeOld = versionCodesIterator.next(); + Long count = versionCodesOld.getLong(versionCodeOld); + versionCodes.put(Integer.parseInt(versionCodeOld), count); + } + } + eventRecord.setVersionCodes(versionCodes); + } + + Map versionNames = new HashMap<>(); + JSONObject versionNamesOld = record.getJSONObject(KEY_VERSION_NAME); + if (versionNamesOld != null) { + Iterator versionNamesIterator = versionNamesOld.keys(); + if (versionNamesIterator != null) { + while (versionNamesIterator.hasNext()) { + String versionNameOld = versionNamesIterator.next(); + Long count = versionNamesOld.getLong(versionNameOld); + versionNames.put(versionNameOld, count); + } + } + eventRecord.setVersionNames(versionNames); + } + ret.put(recordName, eventRecord); + } + return ret; + } + return null; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java index 919608154..bd1f09c3c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java @@ -6,8 +6,6 @@ package com.apptentive.android.sdk.storage; -import com.apptentive.android.sdk.ApptentiveInternal; - import java.util.HashMap; import java.util.Map; @@ -143,4 +141,23 @@ public String toString() { } return builder.toString(); } + + //region Getters & Setters + + /** + * Used for migration only. + */ + public void setEvents(Map events) { + this.events = events; + notifyDataChanged(); + } + + /** + * Used for migration only. + */ + public void setInteractions(Map interactions) { + this.interactions = interactions; + notifyDataChanged(); + } + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventRecord.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventRecord.java index 627943929..542390999 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventRecord.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventRecord.java @@ -75,6 +75,34 @@ public Long getCountForVersionCode(Integer versionCode) { return 0L; } + /** + * Only access directly for migration. + */ + public void setLast(double last) { + this.last = last; + } + + /** + * Only access directly for migration. + */ + public void setTotal(long total) { + this.total = total; + } + + /** + * Only access directly for migration. + */ + public void setVersionCodes(Map versionCodes) { + this.versionCodes = versionCodes; + } + + /** + * Only access directly for migration. + */ + public void setVersionNames(Map versionNames) { + this.versionNames = versionNames; + } + @Override public String toString() { return "EventRecord{" + diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/CodePointStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/CodePointStore.java deleted file mode 100644 index a11319ade..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/CodePointStore.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.storage.legacy; - -import android.content.SharedPreferences; - -import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.util.Constants; -import com.apptentive.android.sdk.util.Util; - -import org.json.JSONException; -import org.json.JSONObject; - -/** - *

All public methods altering code point values should be synchronized.

- *

Example:

- *
- * {
- *   "code_point": {
- *     "codePoint1": {
- *       "last": 1234567890,
- *       "total": 6,
- *       "version": {
- *         "1.1": 4,
- *         "1.2": 2
- *       },
- *       "build": {
- *         "5": 4,
- *         "6": 2
- *       }
- *     }
- *   },
- *   "interactions": {
- *     "526fe2836dd8bf546a00000c": {
- *       "last": 1234567890.4,
- *       "total": 6,
- *       "version": {
- *         "1.1": 4,
- *         "1.2": 2
- *       },
- *       "build": {
- *         "5": 4,
- *         "6": 2
- *       }
- *     }
- *   }
- * }
- * 
- */ -public class CodePointStore { - - private JSONObject store; - - public static final String KEY_CODE_POINT = "code_point"; - public static final String KEY_INTERACTIONS = "interactions"; - public static final String KEY_LAST = "last"; // The last time this codepoint was seen. - public static final String KEY_TOTAL = "total"; // The total times this code point was seen. - public static final String KEY_VERSION_NAME = "version"; - public static final String KEY_VERSION_CODE = "build"; - - public CodePointStore() { - - } - - public void init() { - store = loadFromPreference(); - } - - private void saveToPreference() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - prefs.edit().putString(Constants.PREF_KEY_CODE_POINT_STORE, store.toString()).apply(); - } - - private JSONObject loadFromPreference() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - String json = prefs.getString(Constants.PREF_KEY_CODE_POINT_STORE, null); - try { - if (json != null) { - return new JSONObject(json); - } - } catch (JSONException e) { - ApptentiveLog.e("Error loading CodePointStore from SharedPreferences.", e); - } - return new JSONObject(); - } - - - public synchronized void storeCodePointForCurrentAppVersion(String fullCodePoint) { - storeRecordForCurrentAppVersion(false, fullCodePoint); - } - - public synchronized void storeInteractionForCurrentAppVersion(String fullCodePoint) { - storeRecordForCurrentAppVersion(true, fullCodePoint); - } - - private void storeRecordForCurrentAppVersion(boolean isInteraction, String fullCodePoint) { - String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); - int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); - storeRecord(isInteraction, fullCodePoint, versionName, versionCode); - } - - public synchronized void storeRecord(boolean isInteraction, String fullCodePoint, String versionName, int versionCode) { - storeRecord(isInteraction, fullCodePoint, versionName, versionCode, Util.currentTimeSeconds()); - } - - public synchronized void storeRecord(boolean isInteraction, String fullCodePoint, String versionName, int versionCode, double currentTimeSeconds) { - String versionCodeString = String.valueOf(versionCode); - if (fullCodePoint != null && versionName != null) { - try { - String recordTypeKey = isInteraction ? CodePointStore.KEY_INTERACTIONS : CodePointStore.KEY_CODE_POINT; - JSONObject recordType; - if (!store.isNull(recordTypeKey)) { - recordType = store.getJSONObject(recordTypeKey); - } else { - recordType = new JSONObject(); - store.put(recordTypeKey, recordType); - } - - // Get or create code point object. - JSONObject codePointJson; - if (!recordType.isNull(fullCodePoint)) { - codePointJson = recordType.getJSONObject(fullCodePoint); - } else { - codePointJson = new JSONObject(); - recordType.put(fullCodePoint, codePointJson); - } - - // Set the last time this code point was seen. - codePointJson.put(KEY_LAST, currentTimeSeconds); - - // Increment the total times this code point was seen. - int total = 0; - if (codePointJson.has(KEY_TOTAL)) { - total = codePointJson.getInt(KEY_TOTAL); - } - codePointJson.put(KEY_TOTAL, total + 1); - - // Get or create versionName object. - JSONObject versionNameJson; - if (!codePointJson.isNull(KEY_VERSION_NAME)) { - versionNameJson = codePointJson.getJSONObject(KEY_VERSION_NAME); - } else { - versionNameJson = new JSONObject(); - codePointJson.put(KEY_VERSION_NAME, versionNameJson); - } - - // Set count for current versionName. - int existingVersionNameCount = 0; - if (!versionNameJson.isNull(versionName)) { - existingVersionNameCount = versionNameJson.getInt(versionName); - } - versionNameJson.put(versionName, existingVersionNameCount + 1); - - // Get or create versionCode object. - JSONObject versionCodeJson; - if (!codePointJson.isNull(KEY_VERSION_CODE)) { - versionCodeJson = codePointJson.getJSONObject(KEY_VERSION_CODE); - } else { - versionCodeJson = new JSONObject(); - codePointJson.put(KEY_VERSION_CODE, versionCodeJson); - } - - // Set count for the current versionCode - int existingVersionCodeCount = 0; - if (!versionCodeJson.isNull(versionCodeString)) { - existingVersionCodeCount = versionCodeJson.getInt(versionCodeString); - } - versionCodeJson.put(versionCodeString, existingVersionCodeCount + 1); - - saveToPreference(); - } catch (JSONException e) { - ApptentiveLog.w("Unable to store code point %s.", e, fullCodePoint); - } - } - } - - public JSONObject getRecord(boolean interaction, String name) { - String recordTypeKey = interaction ? KEY_INTERACTIONS : KEY_CODE_POINT; - try { - if (!store.isNull(recordTypeKey)) { - if (store.has(recordTypeKey)) { - JSONObject recordType = store.getJSONObject(recordTypeKey); - if (recordType.has(name)) { - return recordType.getJSONObject(name); - } - } - } - } catch (JSONException e) { - ApptentiveLog.w("Error loading code point record for \"%s\"", name); - } - return null; - } - - public Long getTotalInvokes(boolean interaction, String name) { - try { - JSONObject record = getRecord(interaction, name); - if (record != null && record.has(KEY_TOTAL)) { - return record.getLong(KEY_TOTAL); - } - } catch (JSONException e) { - // Ignore - } - return 0L; - } - - public Double getLastInvoke(boolean interaction, String name) { - try { - JSONObject record = getRecord(interaction, name); - if (record != null && record.has(KEY_LAST)) { - return record.getDouble(KEY_LAST); - } - } catch (JSONException e) { - // Ignore - } - return null; - } - - public Long getVersionNameInvokes(boolean interaction, String name, String versionName) { - try { - JSONObject record = getRecord(interaction, name); - if (record != null && record.has(KEY_VERSION_NAME)) { - JSONObject versionNameJson = record.getJSONObject(KEY_VERSION_NAME); - if (versionNameJson.has(versionName)) { - return versionNameJson.getLong(versionName); - } - } - } catch (JSONException e) { - // Ignore - } - return 0L; - } - - public Long getVersionCodeInvokes(boolean interaction, String name, String versionCode) { - try { - JSONObject record = getRecord(interaction, name); - if (record != null && record.has(KEY_VERSION_CODE)) { - JSONObject versionCodeJson = record.getJSONObject(KEY_VERSION_CODE); - if (versionCodeJson.has(versionCode)) { - return versionCodeJson.getLong(versionCode); - } - } - } catch (JSONException e) { - // Ignore - } - return 0L; - } - - public String toString() { - return "CodePointStore: " + store.toString(); - } - - public void clear() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - prefs.edit().remove(Constants.PREF_KEY_CODE_POINT_STORE).apply(); - store = new JSONObject(); - } -} From 98537e3a4de12db564077c7576376d9340ee67d7 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 10 May 2017 12:45:56 -0700 Subject: [PATCH 293/465] Reorganize Constants since we've stopped using some keys, and use some others only during migration now. --- .../android/sdk/util/Constants.java | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 6c85c7822..6c4147e07 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -20,22 +20,25 @@ public class Constants { // Globals public static final String PREF_KEY_SERVER_URL = "serverUrl"; + public static final String PREF_KEY_APP_CONFIG_PREFIX = "appConfiguration."; + public static final String PREF_KEY_APP_CONFIG_JSON = PREF_KEY_APP_CONFIG_PREFIX+"json"; // Just in case a customer copies the example text verbatim. public static final String EXAMPLE_APP_KEY_VALUE = "YOUR_APPTENTIVE_APP_KEY"; public static final String EXAMPLE_APP_SIGNATURE_VALUE = "YOUR_APPTENTIVE_APP_SIGNATURE"; - // Session Data - public static final String PREF_KEY_APP_CONFIG_PREFIX = "appConfiguration."; - public static final String PREF_KEY_APP_CONFIG_JSON = PREF_KEY_APP_CONFIG_PREFIX+"json"; // Engagement // Used to turn off Interaction polling so that contrived payloads can be manually tested. + // FIXME: Migrate into global data. + public static final String PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT = "messageCenterServerErrorLastAttempt"; + public static final String PREF_KEY_POLL_FOR_INTERACTIONS = "pollForInteractions"; // Config Defaults public static final String CONFIG_DEFAULT_SERVER_URL = "https://api.apptentive.com"; + //region Default Values public static final int CONFIG_DEFAULT_INTERACTION_CACHE_EXPIRATION_DURATION_SECONDS = 28800; // 8 hours public static final int CONFIG_DEFAULT_APP_CONFIG_EXPIRATION_MILLIS = 0; public static final int CONFIG_DEFAULT_APP_CONFIG_EXPIRATION_DURATION_SECONDS = 86400; // 24 hours @@ -44,8 +47,9 @@ public class Constants { public static final boolean CONFIG_DEFAULT_MESSAGE_CENTER_ENABLED = false; public static final boolean CONFIG_DEFAULT_MESSAGE_CENTER_NOTIFICATION_POPUP_ENABLED = false; public static final boolean CONFIG_DEFAULT_HIDE_BRANDING = false; + //endregion - // Manifest keys + // region Android Manifest Keys public static final String MANIFEST_KEY_APPTENTIVE_LOG_LEVEL = "apptentive_log_level"; public static final String MANIFEST_KEY_APPTENTIVE_APP_KEY = "apptentive_app_key"; public static final String MANIFEST_KEY_APPTENTIVE_APP_SIGNATURE = "apptentive_app_signature"; @@ -53,8 +57,38 @@ public class Constants { public static final String MANIFEST_KEY_SDK_DISTRIBUTION_VERSION = "apptentive_sdk_distribution_version"; public static final String MANIFEST_KEY_INITIALLY_HIDE_BRANDING = "apptentive_initially_hide_branding"; public static final String MANIFEST_KEY_APPTENTIVE_DEBUG = "apptentive_debug"; + //endregion - // OLD KEYS USED IN PREVIOUS SDK VERSIONS + // region Keys used to access old data for migration + public static final String PREF_KEY_CONVERSATION_TOKEN = "conversationToken"; + public static final String PREF_KEY_CONVERSATION_ID = "conversationId"; + public static final String PREF_KEY_PERSON_ID = "personId"; + public static final String PREF_KEY_DEVICE = "device"; + public static final String PREF_KEY_SDK = "sdk"; + public static final String PREF_KEY_APP_RELEASE = "app_release"; + public static final String PREF_KEY_PERSON = "person"; + public static final String PREF_KEY_LAST_SEEN_SDK_VERSION = "lastSeenSdkVersion"; + public static final String PREF_KEY_MESSAGE_CENTER_FEATURE_USED = "messageCenterFeatureUsed"; + public static final String PREF_KEY_CODE_POINT_STORE = "codePointStore"; + public static final String PREF_KEY_VERSION_HISTORY = "versionHistory"; + public static final String PREF_KEY_VERSION_HISTORY_V2 = "versionHistoryV2"; + public static final String PREF_KEY_VERSION_HISTORY_V2_MIGRATED = "versionHistoryV2Migrated"; + public static final String PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE = "messageCenterWhoCardSet"; + public static final String PREF_KEY_INTERACTIONS_PAYLOAD_CACHE_EXPIRATION = "interactionsCacheExpiration"; + //endregion + + + //region Old keys no longer used + public static final String PREF_KEY_APP_ACTIVITY_STATE_QUEUE = "appActivityStateQueue"; + public static final String PREF_KEY_PERSON_EMAIL = "personEmail"; + public static final String PREF_KEY_PERSON_NAME = "personName"; + public static final String PREF_KEY_DEVICE_DATA = "deviceData"; + public static final String PREF_KEY_DEVICE_INTEGRATION_CONFIG = "integrationConfig"; + public static final String PREF_KEY_PERSON_DATA = "personData"; + public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE = "messageCenterPendingComposingMessage"; + public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS = "messageCenterPendingComposingAttachments"; + public static final String PREF_KEY_INTERACTIONS = "interactions"; + public static final String PREF_KEY_TARGETS = "targets"; public static final String MANIFEST_KEY_MESSAGE_CENTER_ENABLED = "apptentive_message_center_enabled"; public static final String MANIFEST_KEY_EMAIL_REQUIRED = "apptentive_email_required"; public static final String PREF_KEY_APP_CONFIG_EXPIRATION = PREF_KEY_APP_CONFIG_PREFIX+"cache-expiration"; @@ -79,39 +113,7 @@ public class Constants { public static final String PREF_KEY_MESSAGE_CENTER_SHOULD_SHOW_INTRO_DIALOG = "messageCenterShouldShowIntroDialog"; public static final String MANIFEST_KEY_USE_STAGING_SERVER = "apptentive_use_staging_server"; public static final String PREF_KEY_PENDING_PUSH_NOTIFICATION = "pendingPushNotification"; - - // FIXME: Migrate into session data - public static final String PREF_KEY_APP_ACTIVITY_STATE_QUEUE = "appActivityStateQueue"; - public static final String PREF_KEY_CONVERSATION_TOKEN = "conversationToken"; - public static final String PREF_KEY_CONVERSATION_ID = "conversationId"; - public static final String PREF_KEY_PERSON_ID = "personId"; - public static final String PREF_KEY_PERSON_EMAIL = "personEmail"; - public static final String PREF_KEY_PERSON_NAME = "personName"; - public static final String PREF_KEY_DEVICE = "device"; - public static final String PREF_KEY_DEVICE_DATA = "deviceData"; - public static final String PREF_KEY_DEVICE_INTEGRATION_CONFIG = "integrationConfig"; - public static final String PREF_KEY_SDK = "sdk"; - public static final String PREF_KEY_APP_RELEASE = "app_release"; - public static final String PREF_KEY_PERSON = "person"; - public static final String PREF_KEY_PERSON_DATA = "personData"; - public static final String PREF_KEY_LAST_SEEN_SDK_VERSION = "lastSeenSdkVersion"; - public static final String PREF_KEY_MESSAGE_CENTER_FEATURE_USED = "messageCenterFeatureUsed"; - public static final String PREF_KEY_CODE_POINT_STORE = "codePointStore"; - public static final String PREF_KEY_VERSION_HISTORY = "versionHistory"; - public static final String PREF_KEY_VERSION_HISTORY_V2 = "versionHistoryV2"; - // Boolean true if migration from v1 to V2 has occurred. - public static final String PREF_KEY_VERSION_HISTORY_V2_MIGRATED = "versionHistoryV2Migrated"; - public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE = "messageCenterPendingComposingMessage"; - public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS = "messageCenterPendingComposingAttachments"; - public static final String PREF_KEY_INTERACTIONS = "interactions"; - public static final String PREF_KEY_TARGETS = "targets"; - public static final String PREF_KEY_INTERACTIONS_PAYLOAD_CACHE_EXPIRATION = "interactionsCacheExpiration"; - public static final String PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE = "messageCenterWhoCardSet"; - - // FIXME: Migrate into global data. - public static final String PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT = "messageCenterServerErrorLastAttempt"; - public static final String PREF_KEY_POLL_FOR_INTERACTIONS = "pollForInteractions"; - + //region public interface FragmentConfigKeys { @@ -164,5 +166,4 @@ public static String networkTypeAsString(int networkTypeAsInt) { return networkTypeLookup[0]; } } - } From 9eebca2d5bff0abeb772c64ed7b5c81ac5524d5d Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 11 May 2017 11:51:30 -0700 Subject: [PATCH 294/465] Use updated header names for Apptentive Key and Apptentive Signature. --- .../com/apptentive/android/sdk/comm/ApptentiveClient.java | 4 ++-- .../com/apptentive/android/sdk/comm/ApptentiveHttpClient.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 5e83883d2..8df4747ea 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -131,8 +131,8 @@ private static ApptentiveHttpResponse performHttpRequest(String oauthToken, Stri connection.setRequestProperty("Accept-Encoding", "gzip"); connection.setRequestProperty("Accept", "application/json"); connection.setRequestProperty("X-API-Version", String.valueOf(API_VERSION)); - connection.setRequestProperty("APPTENTIVE-APP-KEY", notNull(ApptentiveInternal.getInstance().getApptentiveAppKey())); - connection.setRequestProperty("APPTENTIVE-APP-SIGNATURE", notNull(ApptentiveInternal.getInstance().getApptentiveAppSignature())); + connection.setRequestProperty("APPTENTIVE-KEY", notNull(ApptentiveInternal.getInstance().getApptentiveAppKey())); + connection.setRequestProperty("APPTENTIVE-SIGNATURE", notNull(ApptentiveInternal.getInstance().getApptentiveAppSignature())); switch (method) { case GET: diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 42828bd0b..257973268 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -198,8 +198,8 @@ private void setupRequestDefaults(HttpRequest request) { request.setRequestProperty("Connection", "Keep-Alive"); request.setRequestProperty("Accept-Encoding", "gzip"); request.setRequestProperty("Accept", "application/json"); - request.setRequestProperty("APPTENTIVE-APP-KEY", appKey); - request.setRequestProperty("APPTENTIVE-APP-SIGNATURE", appSignature); + request.setRequestProperty("APPTENTIVE-KEY", appKey); + request.setRequestProperty("APPTENTIVE-SIGNATURE", appSignature); request.setRequestProperty("X-API-Version", API_VERSION); request.setConnectTimeout(DEFAULT_HTTP_CONNECT_TIMEOUT); request.setReadTimeout(DEFAULT_HTTP_SOCKET_TIMEOUT); From 6c52f03e87421bfbd204f49579c0999e9d39490b Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 11 May 2017 15:45:33 -0700 Subject: [PATCH 295/465] Update names of keys: App Key => Apptentive Key App Signature => Apptentive Signature --- .../apptentive/android/sdk/Apptentive.java | 12 +-- .../android/sdk/ApptentiveInternal.java | 74 +++++++++---------- .../android/sdk/comm/ApptentiveClient.java | 4 +- .../sdk/comm/ApptentiveHttpClient.java | 22 +++--- .../android/sdk/util/Constants.java | 8 +- 5 files changed, 60 insertions(+), 60 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 338b7ff45..6f04c4abb 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -42,7 +42,7 @@ import java.util.Map; /** - * This class contains the complete API for accessing Apptentive features from within your app. + * This class contains the complete public API for accessing Apptentive features from within your app. */ public class Apptentive { @@ -55,13 +55,13 @@ public static void register(Application application) { Apptentive.register(application, null, null); } - public static void register(Application application, String apptentiveAppKey, String apptentiveAppSignature) { - register(application, apptentiveAppKey, apptentiveAppSignature, null); + public static void register(Application application, String apptentiveKey, String apptentiveSignature) { + register(application, apptentiveKey, apptentiveSignature, null); } - private static void register(Application application, String apptentiveAppKey, String apptentiveAppSignature, String serverUrl) { + private static void register(Application application, String apptentiveKey, String apptentiveSignature, String serverUrl) { ApptentiveLog.i("Registering Apptentive."); - ApptentiveInternal.createInstance(application, apptentiveAppKey, apptentiveAppSignature, serverUrl); + ApptentiveInternal.createInstance(application, apptentiveKey, apptentiveSignature, serverUrl); } //region Global Data Methods diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index bff9cb1b4..f20d611f1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -91,8 +91,8 @@ public class ApptentiveInternal { private boolean appIsInForeground; private final SharedPreferences globalSharedPrefs; - private final String appKey; - private final String appSignature; + private final String apptentiveKey; + private final String apptentiveSignature; private String serverUrl; private String personId; private String androidId; // FIXME: remove this field (never used) @@ -146,8 +146,8 @@ public static PushAction parse(String name) { protected ApptentiveInternal() { taskManager = null; globalSharedPrefs = null; - appKey = null; - appSignature = null; + apptentiveKey = null; + apptentiveSignature = null; apptentiveHttpClient = null; conversationManager = null; appContext = null; @@ -156,23 +156,23 @@ protected ApptentiveInternal() { lifecycleCallbacks = null; } - private ApptentiveInternal(Application application, String appKey, String appSignature, String serverUrl) { - if (StringUtils.isNullOrEmpty(appKey)) { - throw new IllegalArgumentException("App key is null or empty"); + private ApptentiveInternal(Application application, String apptentiveKey, String apptentiveSignature, String serverUrl) { + if (StringUtils.isNullOrEmpty(apptentiveKey)) { + throw new IllegalArgumentException("Apptentive Key is null or empty"); } - if (StringUtils.isNullOrEmpty(appSignature)) { - throw new IllegalArgumentException("App signature is null or empty"); + if (StringUtils.isNullOrEmpty(apptentiveSignature)) { + throw new IllegalArgumentException("Apptentive Signature is null or empty"); } - this.appKey = appKey; - this.appSignature = appSignature; + this.apptentiveKey = apptentiveKey; + this.apptentiveSignature = apptentiveSignature; this.serverUrl = serverUrl; appContext = application.getApplicationContext(); globalSharedPrefs = application.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); - apptentiveHttpClient = new ApptentiveHttpClient(appKey, appSignature, getEndpointBase(globalSharedPrefs)); + apptentiveHttpClient = new ApptentiveHttpClient(apptentiveKey, apptentiveSignature, getEndpointBase(globalSharedPrefs)); conversationManager = new ConversationManager(appContext, Util.getInternalDir(appContext, "conversations", true)); appRelease = AppReleaseManager.generateCurrentAppRelease(application, this); @@ -198,7 +198,7 @@ public static boolean isApptentiveRegistered() { * * @param application the context of the app that is creating the instance */ - static void createInstance(Application application, String apptentiveAppKey, String apptentiveAppSignature, final String serverUrl) { + static void createInstance(Application application, String apptentiveKey, String apptentiveSignature, final String serverUrl) { if (application == null) { throw new IllegalArgumentException("Application is null"); } @@ -207,24 +207,24 @@ static void createInstance(Application application, String apptentiveAppKey, Str if (sApptentiveInternal == null) { // trim spaces - apptentiveAppKey = Util.trim(apptentiveAppKey); - apptentiveAppSignature = Util.trim(apptentiveAppSignature); + apptentiveKey = Util.trim(apptentiveKey); + apptentiveSignature = Util.trim(apptentiveSignature); // if App key is not defined - try loading from AndroidManifest.xml - if (StringUtils.isNullOrEmpty(apptentiveAppKey)) { - apptentiveAppKey = Util.getManifestMetadataString(application, Constants.MANIFEST_KEY_APPTENTIVE_APP_KEY); - // TODO: check if app key is still empty + if (StringUtils.isNullOrEmpty(apptentiveKey)) { + apptentiveKey = Util.getManifestMetadataString(application, Constants.MANIFEST_KEY_APPTENTIVE_KEY); + // TODO: check if Apptentive Key is still empty } // if App signature is not defined - try loading from AndroidManifest.xml - if (StringUtils.isNullOrEmpty(apptentiveAppSignature)) { - apptentiveAppSignature = Util.getManifestMetadataString(application, Constants.MANIFEST_KEY_APPTENTIVE_APP_SIGNATURE); - // TODO: check if signature key is still empty + if (StringUtils.isNullOrEmpty(apptentiveSignature)) { + apptentiveSignature = Util.getManifestMetadataString(application, Constants.MANIFEST_KEY_APPTENTIVE_SIGNATURE); + // TODO: check if Apptentive Signature is still empty } try { - ApptentiveLog.v("Initializing Apptentive instance: appKey=%s appSignature=%s", apptentiveAppKey, apptentiveAppSignature); - sApptentiveInternal = new ApptentiveInternal(application, apptentiveAppKey, apptentiveAppSignature, serverUrl); + ApptentiveLog.v("Initializing Apptentive instance: apptentiveKey=%s apptentiveSignature=%s", apptentiveKey, apptentiveSignature); + sApptentiveInternal = new ApptentiveInternal(application, apptentiveKey, apptentiveSignature, serverUrl); sApptentiveInternal.start(); // TODO: check the result of this call application.registerActivityLifecycleCallbacks(sApptentiveInternal.lifecycleCallbacks); } catch (Exception e) { @@ -356,12 +356,12 @@ public Conversation getConversation() { return conversationManager.getActiveConversation(); } - public String getApptentiveAppKey() { - return appKey; + public String getApptentiveKey() { + return apptentiveKey; } - public String getApptentiveAppSignature() { - return appSignature; + public String getApptentiveSignature() { + return apptentiveSignature; } public String getServerUrl() { @@ -576,10 +576,10 @@ private boolean start() { ApptentiveLog.i("Debug mode enabled? %b", appRelease.isDebug()); // The app key can be passed in programmatically, or we can fallback to checking in the manifest. - if (TextUtils.isEmpty(appKey) || appKey.contains(Constants.EXAMPLE_APP_KEY_VALUE)) { - String errorMessage = "The Apptentive App Key is not defined. You may provide your Apptentive API Key in Apptentive.register(), or in as meta-data in your AndroidManifest.xml.\n" + - ""; + if (TextUtils.isEmpty(apptentiveKey) || apptentiveKey.contains(Constants.EXAMPLE_APPTENTIVE_KEY_VALUE)) { + String errorMessage = "The Apptentive Key is not defined. You may provide your Apptentive Key in Apptentive.register(), or in as meta-data in your AndroidManifest.xml.\n" + + ""; if (appRelease.isDebug()) { throw new RuntimeException(errorMessage); } else { @@ -588,13 +588,13 @@ private boolean start() { } else { ApptentiveLog.d("Using cached Apptentive App Key"); } - ApptentiveLog.d("Apptentive App Key: %s", appKey); + ApptentiveLog.d("Apptentive App Key: %s", apptentiveKey); // The app signature can be passed in programmatically, or we can fallback to checking in the manifest. - if (TextUtils.isEmpty(appSignature) || appSignature.contains(Constants.EXAMPLE_APP_SIGNATURE_VALUE)) { - String errorMessage = "The Apptentive App Signature is not defined. You may provide your Apptentive App Signature in Apptentive.register(), or in as meta-data in your AndroidManifest.xml.\n" + - ""; + if (TextUtils.isEmpty(apptentiveSignature) || apptentiveSignature.contains(Constants.EXAMPLE_APPTENTIVE_SIGNATURE_VALUE)) { + String errorMessage = "The Apptentive Signature is not defined. You may provide your Apptentive Signature in Apptentive.register(), or in as meta-data in your AndroidManifest.xml.\n" + + ""; if (appRelease.isDebug()) { throw new RuntimeException(errorMessage); } else { @@ -603,7 +603,7 @@ private boolean start() { } else { ApptentiveLog.d("Using cached Apptentive App Signature"); } - ApptentiveLog.d("Apptentive App Signature: %s", appSignature); + ApptentiveLog.d("Apptentive App Signature: %s", apptentiveSignature); // Grab app info we need to access later on. ApptentiveLog.d("Default Locale: %s", Locale.getDefault().toString()); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 8df4747ea..c28eaec2c 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -131,8 +131,8 @@ private static ApptentiveHttpResponse performHttpRequest(String oauthToken, Stri connection.setRequestProperty("Accept-Encoding", "gzip"); connection.setRequestProperty("Accept", "application/json"); connection.setRequestProperty("X-API-Version", String.valueOf(API_VERSION)); - connection.setRequestProperty("APPTENTIVE-KEY", notNull(ApptentiveInternal.getInstance().getApptentiveAppKey())); - connection.setRequestProperty("APPTENTIVE-SIGNATURE", notNull(ApptentiveInternal.getInstance().getApptentiveAppSignature())); + connection.setRequestProperty("APPTENTIVE-KEY", notNull(ApptentiveInternal.getInstance().getApptentiveKey())); + connection.setRequestProperty("APPTENTIVE-SIGNATURE", notNull(ApptentiveInternal.getInstance().getApptentiveSignature())); switch (method) { case GET: diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 257973268..27095b331 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -35,19 +35,19 @@ public class ApptentiveHttpClient implements PayloadRequestSender { private static final String ENDPOINT_CONVERSATION = "/conversation"; private static final String ENDPOINT_LOGIN = "/conversations/%s/session"; - private final String appKey; - private final String appSignature; + private final String apptentiveKey; + private final String apptentiveSignature; private final String serverURL; private final String userAgentString; private final HttpRequestManager httpRequestManager; - public ApptentiveHttpClient(String appKey, String appSignature, String serverURL) { - if (StringUtils.isNullOrEmpty(appKey)) { - throw new IllegalArgumentException("Illegal app key: '" + appKey + "'"); + public ApptentiveHttpClient(String apptentiveKey, String apptentiveSignature, String serverURL) { + if (StringUtils.isNullOrEmpty(apptentiveKey)) { + throw new IllegalArgumentException("Illegal Apptentive Key: '" + apptentiveKey + "'"); } - if (StringUtils.isNullOrEmpty(appSignature)) { - throw new IllegalArgumentException("Illegal app signature: '" + appSignature + "'"); + if (StringUtils.isNullOrEmpty(apptentiveSignature)) { + throw new IllegalArgumentException("Illegal Apptentive Signature: '" + apptentiveSignature + "'"); } if (StringUtils.isNullOrEmpty(serverURL)) { @@ -55,8 +55,8 @@ public ApptentiveHttpClient(String appKey, String appSignature, String serverURL } this.httpRequestManager = new HttpRequestManager(); - this.appKey = appKey; - this.appSignature = appSignature; + this.apptentiveKey = apptentiveKey; + this.apptentiveSignature = apptentiveSignature; this.serverURL = serverURL; this.userAgentString = String.format(USER_AGENT_STRING, Constants.APPTENTIVE_SDK_VERSION); } @@ -198,8 +198,8 @@ private void setupRequestDefaults(HttpRequest request) { request.setRequestProperty("Connection", "Keep-Alive"); request.setRequestProperty("Accept-Encoding", "gzip"); request.setRequestProperty("Accept", "application/json"); - request.setRequestProperty("APPTENTIVE-KEY", appKey); - request.setRequestProperty("APPTENTIVE-SIGNATURE", appSignature); + request.setRequestProperty("APPTENTIVE-KEY", apptentiveKey); + request.setRequestProperty("APPTENTIVE-SIGNATURE", apptentiveSignature); request.setRequestProperty("X-API-Version", API_VERSION); request.setConnectTimeout(DEFAULT_HTTP_CONNECT_TIMEOUT); request.setReadTimeout(DEFAULT_HTTP_SOCKET_TIMEOUT); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 6c4147e07..c269b0f4d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -24,8 +24,8 @@ public class Constants { public static final String PREF_KEY_APP_CONFIG_JSON = PREF_KEY_APP_CONFIG_PREFIX+"json"; // Just in case a customer copies the example text verbatim. - public static final String EXAMPLE_APP_KEY_VALUE = "YOUR_APPTENTIVE_APP_KEY"; - public static final String EXAMPLE_APP_SIGNATURE_VALUE = "YOUR_APPTENTIVE_APP_SIGNATURE"; + public static final String EXAMPLE_APPTENTIVE_KEY_VALUE = "YOUR_APPTENTIVE_KEY"; + public static final String EXAMPLE_APPTENTIVE_SIGNATURE_VALUE = "YOUR_APPTENTIVE_SIGNATURE"; @@ -51,8 +51,8 @@ public class Constants { // region Android Manifest Keys public static final String MANIFEST_KEY_APPTENTIVE_LOG_LEVEL = "apptentive_log_level"; - public static final String MANIFEST_KEY_APPTENTIVE_APP_KEY = "apptentive_app_key"; - public static final String MANIFEST_KEY_APPTENTIVE_APP_SIGNATURE = "apptentive_app_signature"; + public static final String MANIFEST_KEY_APPTENTIVE_KEY = "apptentive_key"; + public static final String MANIFEST_KEY_APPTENTIVE_SIGNATURE = "apptentive_signature"; public static final String MANIFEST_KEY_SDK_DISTRIBUTION = "apptentive_sdk_distribution"; public static final String MANIFEST_KEY_SDK_DISTRIBUTION_VERSION = "apptentive_sdk_distribution_version"; public static final String MANIFEST_KEY_INITIALLY_HIDE_BRANDING = "apptentive_initially_hide_branding"; From c2769be0abd932e5ffc8d01aab61e286e047b6f0 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 11 May 2017 16:53:07 -0700 Subject: [PATCH 296/465] Catch all exceptions in migration. --- .../java/com/apptentive/android/sdk/migration/Migrator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java index f16de6814..0b146e069 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java @@ -259,7 +259,7 @@ private void migrateEventData() { if (migratedInteractions != null) { eventData.setInteractions(migratedInteractions); } - } catch (JSONException e) { + } catch (Exception e) { ApptentiveLog.w("Error migrating Event Data.", e); } } From 03d89351f597d1410312bf5417c7d1b1e4528db0 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 11 May 2017 16:59:30 -0700 Subject: [PATCH 297/465] When migrating Legacy Conversation, put it into the LEGACY_PENDING state. --- .../android/sdk/conversation/ConversationManager.java | 10 +++++++--- .../android/sdk/conversation/ConversationState.java | 6 ++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 937cc81b0..64b59b844 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -187,9 +187,13 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali Migrator migrator = new Migrator(getContext(), prefs, anonymousConversation); migrator.migrate(); - // FIXME: Figure out how to transition this conversation into a full ANONYMOUS state. - anonymousConversation.setState(ANONYMOUS_PENDING); - fetchConversationToken(anonymousConversation); + /* + * FIXME: Figure out how to transition this conversation into a full ANONYMOUS state. + * We can also only go down this path if there is a minimum amount of information migrated, including + * the OAuth token. + */ + anonymousConversation.setState(LEGACY_PENDING); + //fetchConversationToken(anonymousConversation); return anonymousConversation; } else { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationState.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationState.java index faa756d20..7253f621d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationState.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationState.java @@ -12,6 +12,12 @@ public enum ConversationState { */ UNDEFINED, + /** + * A Legacy Conversation has been migrated, but still has an old OAuth token. Waiting to get a new + * JWT and Conversation ID from the server. + */ + LEGACY_PENDING, + /** * No logged in user and no conversation token */ From 5d2beee9cc1184cc6f2aabca777562cb6ac59b33 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 12 May 2017 09:47:19 -0700 Subject: [PATCH 298/465] Make push configuration global to the SDK. Set it on each Conversation when one becomes active, as well as when the push configuration is passed to the SDK while a Conversation is active. --- .../apptentive/android/sdk/Apptentive.java | 37 +++++++------------ .../sdk/conversation/Conversation.java | 27 ++++++++++++++ .../sdk/conversation/ConversationManager.java | 8 ++++ .../android/sdk/util/Constants.java | 2 + 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 6f04c4abb..d70891e36 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -10,6 +10,7 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.support.annotation.NonNull; @@ -29,8 +30,7 @@ import com.apptentive.android.sdk.module.metric.MetricModule; import com.apptentive.android.sdk.module.rating.IRatingProvider; import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; -import com.apptentive.android.sdk.storage.IntegrationConfig; -import com.apptentive.android.sdk.storage.IntegrationConfigItem; +import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; import org.json.JSONException; @@ -312,7 +312,10 @@ public static void removeCustomPersonData(String key) { //region Third Party Integrations - private static final String INTEGRATION_PUSH_TOKEN = "token"; + /** + * For internal use only. + */ + public static final String INTEGRATION_PUSH_TOKEN = "token"; /** * Call {@link #setPushNotificationIntegration(int, String)} with this value to allow Apptentive to send pushes @@ -374,28 +377,16 @@ public static void setPushNotificationIntegration(int pushProvider, String token if (!ApptentiveInternal.isApptentiveRegistered()) { return; } + // Store the push stuff globally + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); + prefs.edit().putInt(Constants.PREF_KEY_PUSH_PROVIDER, pushProvider) + .putString(Constants.PREF_KEY_PUSH_TOKEN, token) + .apply(); + + // Also set it on the active Conversation, if there is one. Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { - IntegrationConfig integrationConfig = ApptentiveInternal.getInstance().getConversation().getDevice().getIntegrationConfig(); - IntegrationConfigItem item = new IntegrationConfigItem(); - item.put(INTEGRATION_PUSH_TOKEN, token); - switch (pushProvider) { - case PUSH_PROVIDER_APPTENTIVE: - integrationConfig.setApptentive(item); - break; - case PUSH_PROVIDER_PARSE: - integrationConfig.setParse(item); - break; - case PUSH_PROVIDER_URBAN_AIRSHIP: - integrationConfig.setUrbanAirship(item); - break; - case PUSH_PROVIDER_AMAZON_AWS_SNS: - integrationConfig.setAmazonAwsSns(item); - break; - default: - ApptentiveLog.e("Invalid pushProvider: %d", pushProvider); - return; - } + conversation.setPushIntegration(pushProvider, token); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 4d9ba8a14..e173dd972 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -9,6 +9,7 @@ import android.content.Context; import android.content.SharedPreferences; +import com.apptentive.android.sdk.Apptentive; import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.comm.ApptentiveClient; @@ -24,6 +25,8 @@ import com.apptentive.android.sdk.storage.Device; import com.apptentive.android.sdk.storage.EventData; import com.apptentive.android.sdk.storage.FileSerializer; +import com.apptentive.android.sdk.storage.IntegrationConfig; +import com.apptentive.android.sdk.storage.IntegrationConfigItem; import com.apptentive.android.sdk.storage.Person; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SerializerException; @@ -503,5 +506,29 @@ public String getUserId() { public void setUserId(String userId) { this.userId = userId; } + + public void setPushIntegration(int pushProvider, String token) { + ApptentiveLog.v("Setting push provider: %d with token %s", pushProvider, token); + IntegrationConfig integrationConfig = ApptentiveInternal.getInstance().getConversation().getDevice().getIntegrationConfig(); + IntegrationConfigItem item = new IntegrationConfigItem(); + item.put(Apptentive.INTEGRATION_PUSH_TOKEN, token); + switch (pushProvider) { + case Apptentive.PUSH_PROVIDER_APPTENTIVE: + integrationConfig.setApptentive(item); + break; + case Apptentive.PUSH_PROVIDER_PARSE: + integrationConfig.setParse(item); + break; + case Apptentive.PUSH_PROVIDER_URBAN_AIRSHIP: + integrationConfig.setUrbanAirship(item); + break; + case Apptentive.PUSH_PROVIDER_AMAZON_AWS_SNS: + integrationConfig.setAmazonAwsSns(item); + break; + default: + ApptentiveLog.e("Invalid pushProvider: %d", pushProvider); + return; + } + } //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 64b59b844..003d7b0ea 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -348,6 +348,14 @@ private void handleConversationStateChange(Conversation conversation) { if (conversation.hasActiveState()) { conversation.fetchInteractions(getContext()); + + // Update conversation with push configuration changes that happened while it wasn't active. + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); + int pushProvider = prefs.getInt(Constants.PREF_KEY_PUSH_PROVIDER, -1); + String pushToken = prefs.getString(Constants.PREF_KEY_PUSH_TOKEN, null); + if (pushProvider != -1 && pushToken != null) { + conversation.setPushIntegration(pushProvider, pushToken); + } } updateMetadataItems(conversation); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index c269b0f4d..5411ddfaa 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -22,6 +22,8 @@ public class Constants { public static final String PREF_KEY_SERVER_URL = "serverUrl"; public static final String PREF_KEY_APP_CONFIG_PREFIX = "appConfiguration."; public static final String PREF_KEY_APP_CONFIG_JSON = PREF_KEY_APP_CONFIG_PREFIX+"json"; + public static final String PREF_KEY_PUSH_PROVIDER = "pushProvider"; + public static final String PREF_KEY_PUSH_TOKEN = "pushToken"; // Just in case a customer copies the example text verbatim. public static final String EXAMPLE_APPTENTIVE_KEY_VALUE = "YOUR_APPTENTIVE_KEY"; From 4d97e1bce8b366d0e1529f129e71f6d489728c5c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 12 May 2017 10:30:33 -0700 Subject: [PATCH 299/465] Fixed tight coupling and method visibility --- .../android/sdk/conversation/Conversation.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index e173dd972..60b4fe112 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -499,17 +499,17 @@ void setEncryptionKey(String encryptionKey) { this.encryptionKey = encryptionKey; } - public String getUserId() { + String getUserId() { return userId; } - public void setUserId(String userId) { + void setUserId(String userId) { this.userId = userId; } public void setPushIntegration(int pushProvider, String token) { - ApptentiveLog.v("Setting push provider: %d with token %s", pushProvider, token); - IntegrationConfig integrationConfig = ApptentiveInternal.getInstance().getConversation().getDevice().getIntegrationConfig(); + ApptentiveLog.v(CONVERSATION, "Setting push provider: %d with token %s", pushProvider, token); + IntegrationConfig integrationConfig = getDevice().getIntegrationConfig(); IntegrationConfigItem item = new IntegrationConfigItem(); item.put(Apptentive.INTEGRATION_PUSH_TOKEN, token); switch (pushProvider) { @@ -527,7 +527,7 @@ public void setPushIntegration(int pushProvider, String token) { break; default: ApptentiveLog.e("Invalid pushProvider: %d", pushProvider); - return; + break; } } //endregion From 13640934249ea53835e97782dbad1a09a3ff3c66 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 12 May 2017 11:38:01 -0700 Subject: [PATCH 300/465] Added EncryptedFileSerializer --- .../sdk/storage/EncryptedFileSerializer.java | 74 +++++++++++++++++++ .../android/sdk/storage/FileSerializer.java | 21 ++++-- .../com/apptentive/android/sdk/util/Util.java | 55 ++++++++++++++ 3 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/storage/EncryptedFileSerializer.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EncryptedFileSerializer.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EncryptedFileSerializer.java new file mode 100644 index 000000000..a2dc89274 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EncryptedFileSerializer.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.storage; + +import com.apptentive.android.sdk.encryption.Encryptor; +import com.apptentive.android.sdk.util.Util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +public class EncryptedFileSerializer extends FileSerializer { + private final String encryptionKey; + + public EncryptedFileSerializer(File file, String encryptionKey) { + super(file); + + if (encryptionKey == null) { + throw new IllegalArgumentException("'encryptionKey' is null"); + } + + this.encryptionKey = encryptionKey; + } + + @Override + protected void serialize(File file, Object object) throws SerializerException { + ByteArrayOutputStream bos = null; + ObjectOutputStream oos = null; + try { + bos = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(bos); + oos.writeObject(object); + final byte[] unencryptedBytes = bos.toByteArray(); + Encryptor encryptor = new Encryptor(encryptionKey); + final byte[] encryptedBytes = encryptor.encrypt(unencryptedBytes); + Util.writeBytes(file, encryptedBytes); + } catch (Exception e) { + throw new SerializerException(e); + } finally { + Util.ensureClosed(bos); + Util.ensureClosed(oos); + } + } + + @Override + protected Object deserialize(File file) throws SerializerException { + try { + final byte[] encryptedBytes = Util.readBytes(file); + Encryptor encryptor = new Encryptor(encryptionKey); + final byte[] unencryptedBytes = encryptor.decrypt(encryptedBytes); + + ByteArrayInputStream bis = null; + ObjectInputStream ois = null; + try { + bis = new ByteArrayInputStream(unencryptedBytes); + ois = new ObjectInputStream(bis); + return ois.readObject(); + } catch (Exception e) { + throw new SerializerException(e); + } finally { + Util.ensureClosed(bis); + Util.ensureClosed(ois); + } + } catch (Exception e) { + throw new SerializerException(e); + } + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java index a02b356b3..351e8ddbc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java @@ -6,7 +6,6 @@ package com.apptentive.android.sdk.storage; -import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.util.Util; import java.io.File; @@ -17,22 +16,33 @@ public class FileSerializer implements Serializer { - private File file; + private final File file; public FileSerializer(File file) { + if (file == null) { + throw new IllegalArgumentException("'file' is null"); + } this.file = file; } @Override public void serialize(Object object) throws SerializerException { + file.getParentFile().mkdirs(); + serialize(file, object); + } + + @Override + public Object deserialize() throws SerializerException { + return deserialize(file); + } + + protected void serialize(File file, Object object) throws SerializerException { FileOutputStream fos = null; ObjectOutputStream oos = null; try { - file.getParentFile().mkdirs(); fos = new FileOutputStream(file); oos = new ObjectOutputStream(fos); oos.writeObject(object); - ApptentiveLog.v("Session data written to file of length: %s", Util.humanReadableByteCount(file.length(), false)); } catch (Exception e) { throw new SerializerException(e); } finally { @@ -41,8 +51,7 @@ public void serialize(Object object) throws SerializerException { } } - @Override - public Object deserialize() throws SerializerException { + protected Object deserialize(File file) throws SerializerException { FileInputStream fis = null; ObjectInputStream ois = null; try { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java index 259f3a189..e618a2ca2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java @@ -667,6 +667,61 @@ public static int copyFile(String from, String to) { } } + public static void writeBytes(File file, byte[] bytes) throws IOException { + if (file == null) { + throw new IllegalArgumentException("'file' is null"); + } + + if (bytes == null) { + throw new IllegalArgumentException("'bytes' is null"); + } + + ByteArrayInputStream input = null; + FileOutputStream output = null; + try { + input = new ByteArrayInputStream(bytes); + output = new FileOutputStream(file); + copy(input, output); + } finally { + ensureClosed(input); + ensureClosed(output); + } + } + + public static byte[] readBytes(File file) throws IOException { + if (file == null) { + throw new IllegalArgumentException("'file' is null"); + } + + if (!file.exists()) { + throw new FileNotFoundException("File does not exist: " + file); + } + + if (file.isDirectory()) { + throw new FileNotFoundException("File is directory: " + file); + } + + FileInputStream input = null; + ByteArrayOutputStream output = null; + try { + input = new FileInputStream(file); + output = new ByteArrayOutputStream(); + copy(input, output); + return output.toByteArray(); + } finally { + ensureClosed(input); + ensureClosed(output); + } + } + + private static void copy(InputStream input, OutputStream output) throws IOException { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = input.read(buffer)) > 0) { + output.write(buffer, 0, bytesRead); + } + } + public static boolean isMimeTypeImage(String mimeType) { if (TextUtils.isEmpty(mimeType)) { return false; From 245bc581ca7073a7001aa0bb58ecefcaa4722d25 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 12 May 2017 12:05:50 -0700 Subject: [PATCH 301/465] Added conversation data decryption --- .../sdk/conversation/Conversation.java | 12 ++++++++++-- .../sdk/conversation/ConversationManager.java | 3 ++- .../android/sdk/util/StopWatch.java | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/StopWatch.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 60b4fe112..bd823250e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -23,6 +23,7 @@ import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.DataChangedListener; import com.apptentive.android.sdk.storage.Device; +import com.apptentive.android.sdk.storage.EncryptedFileSerializer; import com.apptentive.android.sdk.storage.EventData; import com.apptentive.android.sdk.storage.FileSerializer; import com.apptentive.android.sdk.storage.IntegrationConfig; @@ -34,6 +35,7 @@ import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Destroyable; import com.apptentive.android.sdk.util.RuntimeUtils; +import com.apptentive.android.sdk.util.StopWatch; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -235,10 +237,16 @@ synchronized void saveConversationData() throws SerializerException { } synchronized void loadConversationData() throws SerializerException { - ApptentiveLog.d(CONVERSATION, "Loading conversation data"); - FileSerializer serializer = new FileSerializer(conversationDataFile); + StopWatch stopWatch = new StopWatch(); + + FileSerializer serializer = state == LOGGED_IN ? + new EncryptedFileSerializer(conversationDataFile, encryptionKey) : + new FileSerializer(conversationDataFile); + + ApptentiveLog.d(CONVERSATION, "Loading conversation data..."); conversationData = (ConversationData) serializer.deserialize(); conversationData.setDataChangedListener(this); + ApptentiveLog.d(CONVERSATION, "Conversation data loaded (took %d ms)", stopWatch.getElaspedMillis()); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 003d7b0ea..cb6e21660 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -207,9 +207,10 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali private Conversation loadConversation(ConversationMetadataItem item) throws SerializerException { // TODO: use same serialization logic across the project final Conversation conversation = new Conversation(item.dataFile, item.messagesFile); - conversation.loadConversationData(); + conversation.setEncryptionKey(item.getEncryptionKey()); // it's important to sent encryption key before loading data conversation.setState(item.getState()); // set the state same as the item's state conversation.setUserId(item.getUserId()); + conversation.loadConversationData(); return conversation; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/StopWatch.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/StopWatch.java new file mode 100644 index 000000000..e781b826d --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/StopWatch.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.util; + +public class StopWatch { + private final long startTime; + + public StopWatch() { + startTime = System.currentTimeMillis(); + } + + public long getElaspedMillis() { + return System.currentTimeMillis() - startTime; + } +} From 1f6262925e5054039c5487f7ad07b3c09beed20f Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 12 May 2017 12:10:38 -0700 Subject: [PATCH 302/465] Added conversation data encryption --- .../sdk/conversation/Conversation.java | 12 +++++++----- .../android/sdk/util/StopWatch.java | 19 ------------------- 2 files changed, 7 insertions(+), 24 deletions(-) delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/util/StopWatch.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index bd823250e..a92375011 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -35,7 +35,6 @@ import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Destroyable; import com.apptentive.android.sdk.util.RuntimeUtils; -import com.apptentive.android.sdk.util.StopWatch; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -226,18 +225,21 @@ else if (!response.isSuccessful()) { * Saves conversation data to the disk synchronously. Returns true * if succeed. */ - synchronized void saveConversationData() throws SerializerException { + private synchronized void saveConversationData() throws SerializerException { ApptentiveLog.d(CONVERSATION, "Saving Conversation"); ApptentiveLog.v(CONVERSATION, "EventData: %s", getEventData().toString()); // TODO: remove long start = System.currentTimeMillis(); - FileSerializer serializer = new FileSerializer(conversationDataFile); + + FileSerializer serializer = state == LOGGED_IN ? + new EncryptedFileSerializer(conversationDataFile, encryptionKey) : + new FileSerializer(conversationDataFile); serializer.serialize(conversationData); ApptentiveLog.v(CONVERSATION, "Conversation data saved (took %d ms)", System.currentTimeMillis() - start); } synchronized void loadConversationData() throws SerializerException { - StopWatch stopWatch = new StopWatch(); + long start = System.currentTimeMillis(); FileSerializer serializer = state == LOGGED_IN ? new EncryptedFileSerializer(conversationDataFile, encryptionKey) : @@ -246,7 +248,7 @@ synchronized void loadConversationData() throws SerializerException { ApptentiveLog.d(CONVERSATION, "Loading conversation data..."); conversationData = (ConversationData) serializer.deserialize(); conversationData.setDataChangedListener(this); - ApptentiveLog.d(CONVERSATION, "Conversation data loaded (took %d ms)", stopWatch.getElaspedMillis()); + ApptentiveLog.d(CONVERSATION, "Conversation data loaded (took %d ms)", System.currentTimeMillis() - start); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/StopWatch.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/StopWatch.java deleted file mode 100644 index e781b826d..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/StopWatch.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.util; - -public class StopWatch { - private final long startTime; - - public StopWatch() { - startTime = System.currentTimeMillis(); - } - - public long getElaspedMillis() { - return System.currentTimeMillis() - startTime; - } -} From 594f9d61dff8885c80914446305df88733774fb4 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 12 May 2017 12:27:17 -0700 Subject: [PATCH 303/465] Added more runtime checks --- .../sdk/conversation/Conversation.java | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index a92375011..0ab1ea755 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -43,6 +43,9 @@ import java.io.File; +import static com.apptentive.android.sdk.debug.Assert.assertNotNull; +import static com.apptentive.android.sdk.debug.Assert.assertNull; +import static com.apptentive.android.sdk.debug.Assert.assertTrue; import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; import static com.apptentive.android.sdk.ApptentiveLogTag.*; import static com.apptentive.android.sdk.conversation.ConversationState.*; @@ -231,9 +234,15 @@ private synchronized void saveConversationData() throws SerializerException { long start = System.currentTimeMillis(); - FileSerializer serializer = state == LOGGED_IN ? - new EncryptedFileSerializer(conversationDataFile, encryptionKey) : - new FileSerializer(conversationDataFile); + FileSerializer serializer; + if (state == LOGGED_IN) { + assertNotNull(encryptionKey, "Missing encryption key"); + serializer = new EncryptedFileSerializer(conversationDataFile, encryptionKey); + } else { + assertNull(encryptionKey, "Encryption key should be null"); + serializer = new FileSerializer(conversationDataFile); + } + serializer.serialize(conversationData); ApptentiveLog.v(CONVERSATION, "Conversation data saved (took %d ms)", System.currentTimeMillis() - start); } @@ -241,9 +250,14 @@ private synchronized void saveConversationData() throws SerializerException { synchronized void loadConversationData() throws SerializerException { long start = System.currentTimeMillis(); - FileSerializer serializer = state == LOGGED_IN ? - new EncryptedFileSerializer(conversationDataFile, encryptionKey) : - new FileSerializer(conversationDataFile); + FileSerializer serializer; + if (state == LOGGED_IN) { + assertNotNull(encryptionKey, "Missing encryption key"); + serializer = new EncryptedFileSerializer(conversationDataFile, encryptionKey); + } else { + assertNull(encryptionKey, "Encryption key should be null"); + serializer = new FileSerializer(conversationDataFile); + } ApptentiveLog.d(CONVERSATION, "Loading conversation data..."); conversationData = (ConversationData) serializer.deserialize(); From e001e6ab131bdd7d1e4407816e652b525a3df39c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 12 May 2017 12:34:40 -0700 Subject: [PATCH 304/465] Added more logs --- .../com/apptentive/android/sdk/conversation/Conversation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 0ab1ea755..5f2e67ca8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -229,7 +229,7 @@ else if (!response.isSuccessful()) { * if succeed. */ private synchronized void saveConversationData() throws SerializerException { - ApptentiveLog.d(CONVERSATION, "Saving Conversation"); + ApptentiveLog.d(CONVERSATION, "Saving %sconversation data...", state == LOGGED_IN ? "encrypted " : ""); ApptentiveLog.v(CONVERSATION, "EventData: %s", getEventData().toString()); // TODO: remove long start = System.currentTimeMillis(); @@ -259,7 +259,7 @@ synchronized void loadConversationData() throws SerializerException { serializer = new FileSerializer(conversationDataFile); } - ApptentiveLog.d(CONVERSATION, "Loading conversation data..."); + ApptentiveLog.d(CONVERSATION, "Loading %sconversation data...", state == LOGGED_IN ? "encrypted " : ""); conversationData = (ConversationData) serializer.deserialize(); conversationData.setDataChangedListener(this); ApptentiveLog.d(CONVERSATION, "Conversation data loaded (took %d ms)", System.currentTimeMillis() - start); From d762af2d598f9c0412c75d270fc2078ddbf37176 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 12 May 2017 14:00:22 -0700 Subject: [PATCH 305/465] Fixed handling logged-in conversation + added internal consistency check --- .../sdk/conversation/Conversation.java | 35 ++++++++++++++++--- .../sdk/conversation/ConversationManager.java | 2 ++ .../ConversationMetadataItem.java | 28 +++++---------- .../sdk/conversation/FileMessageStore.java | 15 ++------ .../com/apptentive/android/sdk/util/Util.java | 12 +++++++ 5 files changed, 55 insertions(+), 37 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 5f2e67ca8..298e196ab 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -35,6 +35,7 @@ import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Destroyable; import com.apptentive.android.sdk.util.RuntimeUtils; +import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -43,9 +44,9 @@ import java.io.File; +import static com.apptentive.android.sdk.debug.Assert.assertFail; import static com.apptentive.android.sdk.debug.Assert.assertNotNull; import static com.apptentive.android.sdk.debug.Assert.assertNull; -import static com.apptentive.android.sdk.debug.Assert.assertTrue; import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; import static com.apptentive.android.sdk.ApptentiveLogTag.*; import static com.apptentive.android.sdk.conversation.ConversationState.*; @@ -229,13 +230,13 @@ else if (!response.isSuccessful()) { * if succeed. */ private synchronized void saveConversationData() throws SerializerException { - ApptentiveLog.d(CONVERSATION, "Saving %sconversation data...", state == LOGGED_IN ? "encrypted " : ""); + ApptentiveLog.d(CONVERSATION, "Saving %sconversation data...", hasState(LOGGED_IN) ? "encrypted " : ""); ApptentiveLog.v(CONVERSATION, "EventData: %s", getEventData().toString()); // TODO: remove long start = System.currentTimeMillis(); FileSerializer serializer; - if (state == LOGGED_IN) { + if (hasState(LOGGED_IN)) { assertNotNull(encryptionKey, "Missing encryption key"); serializer = new EncryptedFileSerializer(conversationDataFile, encryptionKey); } else { @@ -251,7 +252,7 @@ synchronized void loadConversationData() throws SerializerException { long start = System.currentTimeMillis(); FileSerializer serializer; - if (state == LOGGED_IN) { + if (hasState(LOGGED_IN)) { assertNotNull(encryptionKey, "Missing encryption key"); serializer = new EncryptedFileSerializer(conversationDataFile, encryptionKey); } else { @@ -259,7 +260,7 @@ synchronized void loadConversationData() throws SerializerException { serializer = new FileSerializer(conversationDataFile); } - ApptentiveLog.d(CONVERSATION, "Loading %sconversation data...", state == LOGGED_IN ? "encrypted " : ""); + ApptentiveLog.d(CONVERSATION, "Loading %sconversation data...", hasState(LOGGED_IN) ? "encrypted " : ""); conversationData = (ConversationData) serializer.deserialize(); conversationData.setDataChangedListener(this); ApptentiveLog.d(CONVERSATION, "Conversation data loaded (took %d ms)", System.currentTimeMillis() - start); @@ -554,5 +555,29 @@ public void setPushIntegration(int pushProvider, String token) { break; } } + + /** + * Checks the internal consistency of the conversation object (temporary solution) + */ + void checkInternalConsistency() throws IllegalStateException { + switch (state) { + case LOGGED_IN: + if (StringUtils.isNullOrEmpty(encryptionKey)) { + assertFail("Missing encryption key"); + throw new IllegalStateException("Missing encryption key"); + } + if (StringUtils.isNullOrEmpty(userId)) { + assertFail("Missing user id"); + throw new IllegalStateException("Missing user id"); + } + break; + default: + if (!StringUtils.isNullOrEmpty(encryptionKey)) { + assertFail("Encryption key should be null"); + throw new IllegalStateException("Encryption key should be null"); + } + break; + } + } //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index cb6e21660..3c2a58daa 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -211,6 +211,8 @@ private Conversation loadConversation(ConversationMetadataItem item) throws Seri conversation.setState(item.getState()); // set the state same as the item's state conversation.setUserId(item.getUserId()); conversation.loadConversationData(); + conversation.checkInternalConsistency(); + return conversation; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index eac75da10..f0449d57f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -2,22 +2,21 @@ import com.apptentive.android.sdk.serialization.SerializableObject; import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.util.Util; import java.io.DataInput; import java.io.DataOutput; import java.io.File; import java.io.IOException; +import static com.apptentive.android.sdk.util.Util.readNullableUTF; +import static com.apptentive.android.sdk.util.Util.writeNullableUTF; + /** * A light weight representation of the conversation object stored on the disk. */ public class ConversationMetadataItem implements SerializableObject { - /** - * We store an empty string for a missing key - */ - private static final String EMPTY_ENCRYPTION_KEY = ""; - /** * The state of the target conversation */ @@ -71,7 +70,8 @@ public ConversationMetadataItem(DataInput in) throws IOException { dataFile = new File(in.readUTF()); messagesFile = new File(in.readUTF()); state = ConversationState.valueOf(in.readByte()); - encryptionKey = readEncryptionKey(in); + encryptionKey = readNullableUTF(in); + userId = readNullableUTF(in); } @Override @@ -80,16 +80,8 @@ public void writeExternal(DataOutput out) throws IOException { out.writeUTF(dataFile.getAbsolutePath()); out.writeUTF(messagesFile.getAbsolutePath()); out.writeByte(state.ordinal()); - writeEncryptionKey(out, encryptionKey); - } - - private static String readEncryptionKey(DataInput in) throws IOException { - final String key = in.readLine(); - return !StringUtils.equal(key, EMPTY_ENCRYPTION_KEY) ? key : null; - } - - private void writeEncryptionKey(DataOutput out, String key) throws IOException { - out.writeUTF(key != null ? key : EMPTY_ENCRYPTION_KEY); + writeNullableUTF(out, encryptionKey); + writeNullableUTF(out, userId); } public String getConversationId() { @@ -107,8 +99,4 @@ public String getEncryptionKey() { public String getUserId() { return userId; } - - public void setUserId(String userId) { - this.userId = userId; - } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index 078995c29..eda52c7f7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -26,6 +26,9 @@ import java.util.ArrayList; import java.util.List; +import static com.apptentive.android.sdk.util.Util.readNullableUTF; +import static com.apptentive.android.sdk.util.Util.writeNullableUTF; + class FileMessageStore implements MessageStore { /** * Binary format version @@ -286,18 +289,6 @@ public void writeExternal(DataOutput out) throws IOException { out.writeBoolean(isRead); writeNullableUTF(out, json); } - - private static void writeNullableUTF(DataOutput out, String value) throws IOException { - out.writeBoolean(value != null); - if (value != null) { - out.writeUTF(value); - } - } - - private static String readNullableUTF(DataInput in) throws IOException { - boolean notNull = in.readBoolean(); - return notNull ? in.readUTF() : null; - } } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java index e618a2ca2..f94fd4f73 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java @@ -722,6 +722,18 @@ private static void copy(InputStream input, OutputStream output) throws IOExcept } } + public static void writeNullableUTF(DataOutput out, String value) throws IOException { + out.writeBoolean(value != null); + if (value != null) { + out.writeUTF(value); + } + } + + public static String readNullableUTF(DataInput in) throws IOException { + boolean notNull = in.readBoolean(); + return notNull ? in.readUTF() : null; + } + public static boolean isMimeTypeImage(String mimeType) { if (TextUtils.isEmpty(mimeType)) { return false; From 60c36878224bcffb071ef7c40095c6df1a681468 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 15 May 2017 12:52:35 -0700 Subject: [PATCH 306/465] Legacy conversation fetching progress --- .../sdk/conversation/ConversationManager.java | 63 ++++++++++--------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 3c2a58daa..8f0cdd1c1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -45,6 +45,7 @@ import static com.apptentive.android.sdk.debug.Assert.*; import static com.apptentive.android.sdk.debug.Tester.*; import static com.apptentive.android.sdk.debug.TesterEvent.*; +import static com.apptentive.android.sdk.util.StringUtils.isNullOrEmpty; /** * Class responsible for managing conversations. @@ -177,37 +178,43 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali // No Conversations exist in the meta-data. // Do we have a Legacy Conversation or not? SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - String lastSeenVersionString = prefs.getString(Constants.PREF_KEY_LAST_SEEN_SDK_VERSION, null); - Apptentive.Version version4 = new Apptentive.Version(); - version4.setVersion("4.0.0"); - Apptentive.Version lastSeenVersion = new Apptentive.Version(); - lastSeenVersion.setVersion(lastSeenVersionString); - if (lastSeenVersionString != null && lastSeenVersion.compareTo(version4) < 0) { - - Migrator migrator = new Migrator(getContext(), prefs, anonymousConversation); - migrator.migrate(); - - /* - * FIXME: Figure out how to transition this conversation into a full ANONYMOUS state. - * We can also only go down this path if there is a minimum amount of information migrated, including - * the OAuth token. - */ - anonymousConversation.setState(LEGACY_PENDING); - //fetchConversationToken(anonymousConversation); - return anonymousConversation; - } else { - - // If there is no Legacy Conversation, then just connect it to the server. - anonymousConversation.setState(ANONYMOUS_PENDING); - fetchConversationToken(anonymousConversation); - return anonymousConversation; + String legacyConversationToken = prefs.getString(Constants.PREF_KEY_CONVERSATION_TOKEN, null); + String legacyConversationId = prefs.getString(Constants.PREF_KEY_CONVERSATION_ID, null); + if (!isNullOrEmpty(legacyConversationToken) && !isNullOrEmpty(legacyConversationId)) { + String lastSeenVersionString = prefs.getString(Constants.PREF_KEY_LAST_SEEN_SDK_VERSION, null); + Apptentive.Version version4 = new Apptentive.Version(); + version4.setVersion("4.0.0"); + Apptentive.Version lastSeenVersion = new Apptentive.Version(); + lastSeenVersion.setVersion(lastSeenVersionString); + if (lastSeenVersionString != null && lastSeenVersion.compareTo(version4) < 0) { + + Migrator migrator = new Migrator(getContext(), prefs, anonymousConversation); + migrator.migrate(); + + anonymousConversation.setState(LEGACY_PENDING); + fetchLegacyConversation(anonymousConversation, legacyConversationId, legacyConversationToken); + return anonymousConversation; + } } + + // If there is no Legacy Conversation, then just connect it to the server. + anonymousConversation.setState(ANONYMOUS_PENDING); + fetchConversationToken(anonymousConversation); + return anonymousConversation; + } + + private void fetchLegacyConversation(Conversation conversation, String conversationId, String conversationToken) { + assertEquals(conversation.getState(), ConversationState.LEGACY_PENDING); + assertNotNull(conversationId); + assertNotNull(conversationToken); + + throw new RuntimeException("Implement me"); } private Conversation loadConversation(ConversationMetadataItem item) throws SerializerException { // TODO: use same serialization logic across the project final Conversation conversation = new Conversation(item.dataFile, item.messagesFile); - conversation.setEncryptionKey(item.getEncryptionKey()); // it's important to sent encryption key before loading data + conversation.setEncryptionKey(item.getEncryptionKey()); // it's important to set encryption key before loading data conversation.setState(item.getState()); // set the state same as the item's state conversation.setUserId(item.getUserId()); conversation.loadConversationData(); @@ -282,13 +289,13 @@ public void onFinish(HttpJsonRequest request) { String conversationId = root.getString("id"); ApptentiveLog.d(CONVERSATION, "New Conversation id: %s", conversationId); - if (StringUtils.isNullOrEmpty(conversationToken)) { + if (isNullOrEmpty(conversationToken)) { ApptentiveLog.e(CONVERSATION, "Can't fetch conversation: missing 'token'"); dispatchDebugEvent(EVT_CONVERSATION_DID_FETCH_TOKEN, false); return; } - if (StringUtils.isNullOrEmpty(conversationId)) { + if (isNullOrEmpty(conversationId)) { ApptentiveLog.e(CONVERSATION, "Can't fetch conversation: missing 'id'"); dispatchDebugEvent(EVT_CONVERSATION_DID_FETCH_TOKEN, false); return; @@ -608,7 +615,7 @@ private void handleLoginFinished(final String userId, final String encryptionKey DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { @Override protected void execute() { - assertFalse(StringUtils.isNullOrEmpty(encryptionKey)); + assertFalse(isNullOrEmpty(encryptionKey)); try { // if we were previously logged out we might end up with no active conversation From b2a1bffc877c19dc2101132edb5b099bfcf4d7e2 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 15 May 2017 14:18:17 -0700 Subject: [PATCH 307/465] Added legacy conversation id fetching --- .../sdk/comm/ApptentiveHttpClient.java | 13 +++ .../sdk/conversation/ConversationManager.java | 83 +++++++++++++++++-- 2 files changed, 87 insertions(+), 9 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 27095b331..6abe55c57 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -33,6 +33,7 @@ public class ApptentiveHttpClient implements PayloadRequestSender { // Active API private static final String ENDPOINT_CONVERSATION = "/conversation"; + private static final String ENDPOINT_LEGACY_CONVERSATION = "/conversation/login"; private static final String ENDPOINT_LOGIN = "/conversations/%s/session"; private final String apptentiveKey; @@ -70,6 +71,18 @@ public HttpJsonRequest getConversationToken(ConversationTokenRequest conversatio return request; } + public HttpJsonRequest getLegacyConversationId(String conversationToken, HttpRequest.Listener listener) { + if (StringUtils.isNullOrEmpty(conversationToken)) { + throw new IllegalArgumentException("Conversation token is null or empty"); + } + + HttpJsonRequest request = createJsonRequest(ENDPOINT_LEGACY_CONVERSATION, new JSONObject(), HttpRequestMethod.POST); + request.setRequestProperty("Authorization", "OAuth " + conversationToken); + request.addListener(listener); + httpRequestManager.startRequest(request); + return request; + } + public HttpJsonRequest login(String conversationId, String token, HttpRequest.Listener listener) { if (conversationId == null) { throw new IllegalArgumentException("Conversation id is null"); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 8f0cdd1c1..bcaf43b58 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -177,10 +177,9 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali // Check whether migration is needed. // No Conversations exist in the meta-data. // Do we have a Legacy Conversation or not? - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); + final SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); String legacyConversationToken = prefs.getString(Constants.PREF_KEY_CONVERSATION_TOKEN, null); - String legacyConversationId = prefs.getString(Constants.PREF_KEY_CONVERSATION_ID, null); - if (!isNullOrEmpty(legacyConversationToken) && !isNullOrEmpty(legacyConversationId)) { + if (!isNullOrEmpty(legacyConversationToken)) { String lastSeenVersionString = prefs.getString(Constants.PREF_KEY_LAST_SEEN_SDK_VERSION, null); Apptentive.Version version4 = new Apptentive.Version(); version4.setVersion("4.0.0"); @@ -192,7 +191,17 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali migrator.migrate(); anonymousConversation.setState(LEGACY_PENDING); - fetchLegacyConversation(anonymousConversation, legacyConversationId, legacyConversationToken); + + fetchLegacyConversation(anonymousConversation, legacyConversationToken) + // remove legacy key when request is finished + .addListener(new HttpRequest.Adapter() { + @Override + public void onFinish(HttpRequest request) { + prefs.edit() + .remove(Constants.PREF_KEY_CONVERSATION_TOKEN) + .apply(); + } + }); return anonymousConversation; } } @@ -203,12 +212,68 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali return anonymousConversation; } - private void fetchLegacyConversation(Conversation conversation, String conversationId, String conversationToken) { + private HttpRequest fetchLegacyConversation(final Conversation conversation, final String conversationToken) { + assertNotNull(conversation); + if (conversation == null) { + throw new IllegalArgumentException("Conversation is null"); + } + assertEquals(conversation.getState(), ConversationState.LEGACY_PENDING); - assertNotNull(conversationId); - assertNotNull(conversationToken); - throw new RuntimeException("Implement me"); + assertFalse(isNullOrEmpty(conversationToken)); + if (isNullOrEmpty(conversationToken)) { + throw new IllegalArgumentException("Conversation is null"); + } + + HttpRequest request = getHttpClient() + .getLegacyConversationId(conversationToken, new HttpRequest.Listener() { + @Override + public void onFinish(HttpJsonRequest request) { + assertMainThread(); + + try { + JSONObject root = request.getResponseObject(); + String conversationId = root.getString("conversation_id"); + ApptentiveLog.d(CONVERSATION, "Conversation id: %s", conversationId); + + if (isNullOrEmpty(conversationId)) { + ApptentiveLog.e(CONVERSATION, "Can't fetch legacy conversation: missing 'id'"); + return; + } + + String conversationJWT = root.getString("anonymous_jwt_token"); + if (isNullOrEmpty(conversationId)) { + ApptentiveLog.e(CONVERSATION, "Can't fetch legacy conversation: missing 'anonymous_jwt_token'"); + return; + } + + ApptentiveLog.d(CONVERSATION, "Conversation JWT: %s", conversationJWT); + + // set conversation data + conversation.setState(ANONYMOUS); + conversation.setConversationToken(conversationToken); + conversation.setConversationId(conversationId); + + // handle state change + handleConversationStateChange(conversation); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while handling legacy conversation id"); + } + } + + @Override + public void onCancel(HttpJsonRequest request) { + } + + @Override + public void onFail(HttpJsonRequest request, String reason) { + ApptentiveLog.w("Failed to fetch legacy conversation id: %s", reason); + } + }); + + request.setCallbackQueue(DispatchQueue.mainQueue()); // we only deal with conversation on the main queue + request.setTag(TAG_FETCH_CONVERSATION_TOKEN_REQUEST); + return request; } private Conversation loadConversation(ConversationMetadataItem item) throws SerializerException { @@ -241,7 +306,7 @@ public synchronized boolean endActiveConversation() { //region Conversation Token Fetching /** - * Starts fetching conversation token. Returns immediately if conversation is already fetching. + * Starts fetching conversation. Returns immediately if conversation is already fetching. * * @return a new http-request object if conversation is not currently fetched or an instance of * the existing request From 4ed278eeca962c085d228d72522f93b2d177f034 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 15 May 2017 14:35:34 -0700 Subject: [PATCH 308/465] Handle JWT --- .../android/sdk/conversation/Conversation.java | 14 ++++++++++++++ .../sdk/conversation/ConversationManager.java | 2 ++ .../sdk/conversation/ConversationMetadataItem.java | 11 +++++++++++ 3 files changed, 27 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 298e196ab..e86ee7789 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -69,6 +69,11 @@ public class Conversation implements DataChangedListener, Destroyable { */ private String userId; + /** + * Optional JWT for active conversations + */ + private String JWT; + /** * File which represents serialized conversation data on the disk */ @@ -532,6 +537,14 @@ void setUserId(String userId) { this.userId = userId; } + String getJWT() { + return JWT; + } + + void setJWT(String JWT) { + this.JWT = JWT; + } + public void setPushIntegration(int pushProvider, String token) { ApptentiveLog.v(CONVERSATION, "Setting push provider: %d with token %s", pushProvider, token); IntegrationConfig integrationConfig = getDevice().getIntegrationConfig(); @@ -579,5 +592,6 @@ void checkInternalConsistency() throws IllegalStateException { break; } } + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index bcaf43b58..0b5aa7521 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -282,6 +282,7 @@ private Conversation loadConversation(ConversationMetadataItem item) throws Seri conversation.setEncryptionKey(item.getEncryptionKey()); // it's important to set encryption key before loading data conversation.setState(item.getState()); // set the state same as the item's state conversation.setUserId(item.getUserId()); + conversation.setJWT(item.getJWT()); conversation.loadConversationData(); conversation.checkInternalConsistency(); @@ -489,6 +490,7 @@ private void updateMetadataItems(Conversation conversation) { conversationMetadata.addItem(item); } item.state = conversation.getState(); + item.JWT = conversation.getJWT(); // TODO: not null? // update encryption key (if necessary) if (conversation.hasState(LOGGED_IN)) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index f0449d57f..e9e452696 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -47,6 +47,11 @@ public class ConversationMetadataItem implements SerializableObject { */ String userId; + /** + * A JWT for active conversations + */ + String JWT; + public ConversationMetadataItem(String conversationId, File dataFile, File messagesFile) { if (StringUtils.isNullOrEmpty(conversationId)) { throw new IllegalArgumentException("Conversation id is null or empty"); @@ -72,6 +77,7 @@ public ConversationMetadataItem(DataInput in) throws IOException { state = ConversationState.valueOf(in.readByte()); encryptionKey = readNullableUTF(in); userId = readNullableUTF(in); + JWT = readNullableUTF(in); } @Override @@ -82,6 +88,7 @@ public void writeExternal(DataOutput out) throws IOException { out.writeByte(state.ordinal()); writeNullableUTF(out, encryptionKey); writeNullableUTF(out, userId); + writeNullableUTF(out, JWT); } public String getConversationId() { @@ -99,4 +106,8 @@ public String getEncryptionKey() { public String getUserId() { return userId; } + + public String getJWT() { + return JWT; + } } From 48777cc5a0f99352cf04c5367c00a9742665f6c5 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 15 May 2017 15:03:17 -0700 Subject: [PATCH 309/465] Handle legacy pending conversation while login --- .../sdk/conversation/ConversationManager.java | 78 +++++++++++-------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 0b5aa7521..70362e700 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -191,8 +191,9 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali migrator.migrate(); anonymousConversation.setState(LEGACY_PENDING); + anonymousConversation.setConversationToken(legacyConversationToken); - fetchLegacyConversation(anonymousConversation, legacyConversationToken) + fetchLegacyConversation(anonymousConversation) // remove legacy key when request is finished .addListener(new HttpRequest.Adapter() { @Override @@ -212,7 +213,7 @@ public void onFinish(HttpRequest request) { return anonymousConversation; } - private HttpRequest fetchLegacyConversation(final Conversation conversation, final String conversationToken) { + private HttpRequest fetchLegacyConversation(final Conversation conversation) { assertNotNull(conversation); if (conversation == null) { throw new IllegalArgumentException("Conversation is null"); @@ -220,6 +221,11 @@ private HttpRequest fetchLegacyConversation(final Conversation conversation, fin assertEquals(conversation.getState(), ConversationState.LEGACY_PENDING); + final String conversationToken = conversation.getConversationToken(); + if (isNullOrEmpty(conversationToken)) { + throw new IllegalStateException("Missing conversation token"); + } + assertFalse(isNullOrEmpty(conversationToken)); if (isNullOrEmpty(conversationToken)) { throw new IllegalArgumentException("Conversation is null"); @@ -464,8 +470,8 @@ public boolean accept(ConversationMetadataItem item) { private void updateMetadataItems(Conversation conversation) { - if (conversation.hasState(ANONYMOUS_PENDING)) { - ApptentiveLog.v(CONVERSATION, "Skipping updating metadata since conversation is anonymous and pending"); + if (conversation.hasState(ANONYMOUS_PENDING, LEGACY_PENDING)) { + ApptentiveLog.v(CONVERSATION, "Skipping updating metadata since conversation is %@", conversation.getState()); return; } @@ -490,7 +496,7 @@ private void updateMetadataItems(Conversation conversation) { conversationMetadata.addItem(item); } item.state = conversation.getState(); - item.JWT = conversation.getJWT(); // TODO: not null? + item.JWT = conversation.getJWT(); // TODO: can it be null for active conversations? // update encryption key (if necessary) if (conversation.hasState(LOGGED_IN)) { @@ -605,40 +611,44 @@ public boolean accept(ConversationMetadataItem item) { switch (activeConversation.getState()) { case ANONYMOUS_PENDING: - // start fetching conversation token (if not yet fetched) - final HttpRequest fetchRequest = fetchConversationToken(activeConversation); - if (fetchRequest == null) { - ApptentiveLog.e(CONVERSATION, "Unable to login: fetch request failed to send"); - callback.onLoginFail("fetch request failed to send"); - return; - } + case LEGACY_PENDING: { + // start fetching conversation token (if not yet fetched) + final HttpRequest fetchRequest = activeConversation.hasState(ANONYMOUS_PENDING) ? + fetchConversationToken(activeConversation) : + fetchLegacyConversation(activeConversation); + if (fetchRequest == null) { + ApptentiveLog.e(CONVERSATION, "Unable to login: fetch request failed to send"); + callback.onLoginFail("fetch request failed to send"); + return; + } - // attach a listener to an active request - fetchRequest.addListener(new HttpRequest.Listener() { - @Override - public void onFinish(HttpRequest request) { - assertTrue(activeConversation != null && activeConversation.hasState(ANONYMOUS), "Active conversation is missing or in a wrong state: %s", activeConversation); + // attach a listener to an active request + fetchRequest.addListener(new HttpRequest.Listener() { + @Override + public void onFinish(HttpRequest request) { + assertTrue(activeConversation != null && activeConversation.hasState(ANONYMOUS), "Active conversation is missing or in a wrong state: %s", activeConversation); - if (activeConversation != null && activeConversation.hasState(ANONYMOUS)) { - ApptentiveLog.d(CONVERSATION, "Conversation fetching complete. Performing login..."); - sendLoginRequest(activeConversation.getConversationId(), userId, token, callback); - } else { - callback.onLoginFail("Conversation fetching completed abnormally"); + if (activeConversation != null && activeConversation.hasState(ANONYMOUS)) { + ApptentiveLog.d(CONVERSATION, "Conversation fetching complete. Performing login..."); + sendLoginRequest(activeConversation.getConversationId(), userId, token, callback); + } else { + callback.onLoginFail("Conversation fetching completed abnormally"); + } } - } - @Override - public void onCancel(HttpRequest request) { - ApptentiveLog.d(CONVERSATION, "Unable to login: conversation fetching cancelled."); - callback.onLoginFail("Conversation fetching was cancelled"); - } + @Override + public void onCancel(HttpRequest request) { + ApptentiveLog.d(CONVERSATION, "Unable to login: conversation fetching cancelled."); + callback.onLoginFail("Conversation fetching was cancelled"); + } - @Override - public void onFail(HttpRequest request, String reason) { - ApptentiveLog.d(CONVERSATION, "Unable to login: conversation fetching failed."); - callback.onLoginFail("Conversation fetching failed: " + reason); - } - }); + @Override + public void onFail(HttpRequest request, String reason) { + ApptentiveLog.d(CONVERSATION, "Unable to login: conversation fetching failed."); + callback.onLoginFail("Conversation fetching failed: " + reason); + } + }); + } break; case ANONYMOUS: sendLoginRequest(activeConversation.getConversationId(), userId, token, callback); From c0c1c1d54f1c3fc0be583bbecf789ae1b98e8b2f Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 15 May 2017 15:13:08 -0700 Subject: [PATCH 310/465] Fixed storing JWT --- .../apptentive/android/sdk/conversation/ConversationManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 70362e700..f865e133b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -259,6 +259,7 @@ public void onFinish(HttpJsonRequest request) { conversation.setState(ANONYMOUS); conversation.setConversationToken(conversationToken); conversation.setConversationId(conversationId); + conversation.setJWT(conversationJWT); // handle state change handleConversationStateChange(conversation); From 7bd07f8e1f711ca21e5332dc189340b582cd0848 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 15 May 2017 15:16:52 -0700 Subject: [PATCH 311/465] Fixed log output --- .../android/sdk/conversation/ConversationManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index f865e133b..ebcb8c5a6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -472,7 +472,7 @@ public boolean accept(ConversationMetadataItem item) { private void updateMetadataItems(Conversation conversation) { if (conversation.hasState(ANONYMOUS_PENDING, LEGACY_PENDING)) { - ApptentiveLog.v(CONVERSATION, "Skipping updating metadata since conversation is %@", conversation.getState()); + ApptentiveLog.v(CONVERSATION, "Skipping updating metadata since conversation is %s", conversation.getState()); return; } From 804ae8a0ad6d85ba45a330f8c52878ee2b503263 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 22 May 2017 12:35:10 -0700 Subject: [PATCH 312/465] Fix Conversation creation request. It was stringifying each piece of the request, instead of treating them as sub objects. --- .../android/sdk/model/ConversationTokenRequest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java index 319232fd8..86d65ca01 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java @@ -17,7 +17,7 @@ public ConversationTokenRequest() { public void setDevice(DevicePayload device) { try { - put(DevicePayload.KEY, device); + put(DevicePayload.KEY, device == null ? null : device.getJsonObject()); } catch (JSONException e) { ApptentiveLog.e("Error adding %s to ConversationTokenRequest", DevicePayload.KEY); } @@ -25,7 +25,7 @@ public void setDevice(DevicePayload device) { public void setSdk(SdkPayload sdk) { try { - put(SdkPayload.KEY, sdk); + put(SdkPayload.KEY, sdk == null ? null : sdk.getJsonObject()); } catch (JSONException e) { ApptentiveLog.e("Error adding %s to ConversationTokenRequest", SdkPayload.KEY); } @@ -33,7 +33,7 @@ public void setSdk(SdkPayload sdk) { public void setPerson(PersonPayload person) { try { - put(PersonPayload.KEY, person); + put(PersonPayload.KEY, person == null ? null : person.getJsonObject()); } catch (JSONException e) { ApptentiveLog.e("Error adding %s to ConversationTokenRequest", PersonPayload.KEY); } @@ -41,7 +41,7 @@ public void setPerson(PersonPayload person) { public void setAppRelease(AppReleasePayload appRelease) { try { - put(AppReleasePayload.KEY, appRelease); + put(AppReleasePayload.KEY, appRelease == null ? null : appRelease.getJsonObject()); } catch (JSONException e) { ApptentiveLog.e("Error adding %s to ConversationTokenRequest", AppReleasePayload.KEY); } From 071f1d45c6d6a4dae9b2c96784c501a3c8df00a5 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 23 May 2017 18:05:58 -0700 Subject: [PATCH 313/465] Send Bearer header with JWT, OAuth header with legacy token. --- .../android/sdk/comm/ApptentiveClient.java | 17 +++++++++++------ .../android/sdk/comm/ApptentiveHttpClient.java | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index c28eaec2c..ad0304874 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -62,7 +62,7 @@ public static ApptentiveHttpResponse getAppConfiguration() { } final String endPoint = StringUtils.format(ENDPOINT_CONFIGURATION, conversationId); - return performHttpRequest(conversationToken, endPoint, Method.GET, null); + return performHttpRequest(conversationToken, true, endPoint, Method.GET, null); } /** @@ -87,7 +87,7 @@ public static ApptentiveHttpResponse getMessages(Integer count, String afterId, } String uri = String.format(ENDPOINT_MESSAGES, conversationId, count == null ? "" : count.toString(), afterId == null ? "" : afterId, beforeId == null ? "" : beforeId); - return performHttpRequest(conversationToken, uri, Method.GET, null); + return performHttpRequest(conversationToken, true, uri, Method.GET, null); } public static ApptentiveHttpResponse getInteractions(String conversationId) { @@ -95,19 +95,20 @@ public static ApptentiveHttpResponse getInteractions(String conversationId) { throw new IllegalArgumentException("Conversation id is null"); } final String endPoint = StringUtils.format(ENDPOINT_INTERACTIONS, conversationId); - return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), endPoint, Method.GET, null); + return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), true, endPoint, Method.GET, null); } /** * Perform a Http request. * - * @param oauthToken authorization token for the current connection + * @param authToken authorization token for the current connection. Might be an OAuth token for legacy conversations, or Bearer JWT for modern conversations. + * @param bearer If true, the token is a bearer JWT, else it is an OAuth token. * @param uri server url. * @param method Get/Post/Put * @param body Data to be POSTed/Put, not used for GET * @return ApptentiveHttpResponse containing content and response returned from the server. */ - private static ApptentiveHttpResponse performHttpRequest(String oauthToken, String uri, Method method, String body) { + private static ApptentiveHttpResponse performHttpRequest(String authToken, boolean bearer, String uri, Method method, String body) { uri = getEndpointBase() + uri; ApptentiveLog.d("Performing %s request to %s", method.name(), uri); //ApptentiveLog.e("OAUTH Token: %s", oauthToken); @@ -127,7 +128,11 @@ private static ApptentiveHttpResponse performHttpRequest(String oauthToken, Stri connection.setRequestProperty("Connection", "Keep-Alive"); connection.setConnectTimeout(DEFAULT_HTTP_CONNECT_TIMEOUT); connection.setReadTimeout(DEFAULT_HTTP_SOCKET_TIMEOUT); - connection.setRequestProperty("Authorization", "OAuth " + oauthToken); + if (bearer) { + connection.setRequestProperty("Authorization", "Bearer " + authToken); + } else { + connection.setRequestProperty("Authorization", "OAuth " + authToken); + } connection.setRequestProperty("Accept-Encoding", "gzip"); connection.setRequestProperty("Accept", "application/json"); connection.setRequestProperty("X-API-Version", String.valueOf(API_VERSION)); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 6abe55c57..501817ebb 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -137,7 +137,7 @@ private HttpRequest createPayloadRequest(PayloadData payload) { request = createRawRequest(httpPath, payload.getData(), requestMethod); } - request.setRequestProperty("Authorization", "OAuth " + oauthToken); + request.setRequestProperty("Authorization", "Bearer " + oauthToken); if (payload.isEncrypted()) { request.setRequestProperty("APPTENTIVE-ENCRYPTED", Boolean.TRUE); From 53c7dfe9a3764dc86aadf4180c73d3949ce18116 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 23 May 2017 18:06:39 -0700 Subject: [PATCH 314/465] The User ID is stored in the JWT claims as `sub`, not `user_id` --- .../android/sdk/conversation/ConversationManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index ebcb8c5a6..1dce73b6a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -581,9 +581,9 @@ private void requestLoggedInConversation(final String token, final LoginCallback final String userId; try { final Jwt jwt = Jwt.decode(token); - userId = jwt.getPayload().getString("user_id"); + userId = jwt.getPayload().getString("sub"); } catch (Exception e) { - ApptentiveLog.e(e, "Error while extracting user id"); + ApptentiveLog.e(e, "Error while extracting user id: Missing field \"sub\""); callback.onLoginFail(e.getMessage()); return; } From 3a9b57b483e9c38bbc9d735effc98ae1962cb38d Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 23 May 2017 18:07:50 -0700 Subject: [PATCH 315/465] Not all responses will have data. pply the assert only to places that need response data. --- .../android/sdk/module/messagecenter/MessageManager.java | 1 + .../android/sdk/notifications/ApptentiveNotification.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 769fb1082..278e9ef8c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -282,6 +282,7 @@ public void onSentMessage(String nonce, int responseCode, JSONObject responseJso final ApptentiveMessage apptentiveMessage = messageStore.findMessage(nonce); assertNotNull(apptentiveMessage, "Can't find a message with nonce: %s", nonce); + assertNotNull(responseJson, "Missing required responseJson."); if (apptentiveMessage == null) { return; // should not happen but we want to stay safe } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java index 7549492d7..8ed969bae 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotification.java @@ -41,7 +41,8 @@ public boolean hasName(String name) { public T getRequiredUserInfo(String key, Class valueClass) { final T userInfo = getUserInfo(key, valueClass); - Assert.assertNotNull(userInfo, "Missing required user info '%s' for '%s' notification", key, name); + // FIXME: Why was this assert here? Not all requests will have response data. + //Assert.assertNotNull(userInfo, "Missing required user info '%s' for '%s' notification", key, name); return userInfo; } From 9fa647058946b052e6ca9737ee4c6203b34abbb6 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 24 May 2017 15:29:07 -0700 Subject: [PATCH 316/465] Fix login by providing token in login request. --- .../android/sdk/comm/ApptentiveHttpClient.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 501817ebb..2123604b9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -14,6 +14,7 @@ import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.StringUtils; +import org.json.JSONException; import org.json.JSONObject; import java.util.List; @@ -91,8 +92,12 @@ public HttpJsonRequest login(String conversationId, String token, HttpRequest.Li throw new IllegalArgumentException("Token is null"); } - JSONObject json = new JSONObject(); // TODO: create an actual payload - + JSONObject json = new JSONObject(); + try { + json.put("token", token); + } catch (JSONException e) { + // Can't happen + } String endPoint = StringUtils.format(ENDPOINT_LOGIN, conversationId); HttpJsonRequest request = createJsonRequest(endPoint, json, HttpRequestMethod.POST); request.addListener(listener); From 94b01566e25466bd14e0d7f0c692e45e5ad010d1 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 24 May 2017 15:29:33 -0700 Subject: [PATCH 317/465] Bump SDK version to 4.0.0 --- .../main/java/com/apptentive/android/sdk/util/Constants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 5411ddfaa..31c6b0897 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -8,7 +8,7 @@ public class Constants { - public static final String APPTENTIVE_SDK_VERSION = "3.4.1"; + public static final String APPTENTIVE_SDK_VERSION = "4.0.0"; public static final int REQUEST_CODE_PHOTO_FROM_SYSTEM_PICKER = 10; From 4183db622dd0b4cee9e734240aa736289895ee21 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 25 May 2017 14:03:20 -0700 Subject: [PATCH 318/465] Add more logging when Conversation state changes --- .../main/java/com/apptentive/android/sdk/debug/Assert.java | 6 ++++++ .../android/sdk/storage/ApptentiveTaskManager.java | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java index d7dc0e1b1..aba736bc2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -148,6 +148,12 @@ public static void assertEquals(Object expected, Object actual) { } } + public static void assertNotEquals(Object first, Object second) { + if (imp != null && ObjectUtils.equal(first, second)) { + imp.assertFailed(StringUtils.format("Expected '%s' and '%s' to differ, but they are the same.", first, second)); + } + } + //endregion //region Threading diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index fb27a7c79..9ebe1eefc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -45,8 +45,8 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_PAYLOAD_WILL_START_SEND; import static com.apptentive.android.sdk.conversation.ConversationState.ANONYMOUS; import static com.apptentive.android.sdk.conversation.ConversationState.UNDEFINED; +import static com.apptentive.android.sdk.debug.Assert.assertNotEquals; import static com.apptentive.android.sdk.debug.Assert.assertNotNull; -import static com.apptentive.android.sdk.debug.Assert.assertTrue; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; @@ -262,7 +262,8 @@ private void sendNextPayloadSync() { public void onReceiveNotification(ApptentiveNotification notification) { if (notification.hasName(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE)) { Conversation conversation = notification.getUserInfo(NOTIFICATION_KEY_CONVERSATION, Conversation.class); - assertTrue(conversation != null && !conversation.hasState(UNDEFINED)); // sanity check + assertNotNull(conversation); // sanity check + assertNotEquals(conversation.getState(), UNDEFINED); if (conversation.hasActiveState()) { assertNotNull(conversation.getConversationId()); currentConversationId = conversation.getConversationId(); @@ -271,7 +272,7 @@ public void onReceiveNotification(ApptentiveNotification notification) { currentConversationToken = conversation.getConversationToken(); conversationEncryptionKey = conversation.getEncryptionKey(); Assert.assertNotNull(currentConversationToken); - + ApptentiveLog.d("Conversation %s state changed to %s.", currentConversationId, conversation.getState()); // when the Conversation ID comes back from the server, we need to update // the payloads that may have already been enqueued so // that they each have the Conversation ID. From 8883952a519671a2aab800ebb7962cf7772c8070 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 25 May 2017 14:04:18 -0700 Subject: [PATCH 319/465] Messages must be wrapped in a container. --- .../android/sdk/model/CompoundMessage.java | 16 +++++++++++++--- .../android/sdk/model/JsonPayload.java | 8 ++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java index de9078903..2dc5764d6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java @@ -25,12 +25,11 @@ public class CompoundMessage extends ApptentiveMessage implements MultipartPayload, MessageCenterUtil.CompoundMessageCommonInterface { - private static final String KEY_BODY = "body"; + public static final String KEY_MESSAGE = "message"; + private static final String KEY_BODY = "body"; public static final String KEY_TEXT_ONLY = "text_only"; - private static final String KEY_TITLE = "title"; - private static final String KEY_ATTACHMENTS = "attachments"; private boolean isLast; @@ -265,4 +264,15 @@ public int getListItemType() { return MESSAGE_INCOMING; } } + + @Override + protected JSONObject marshallForSending() { + JSONObject wrapper = new JSONObject(); + try { + wrapper.put(KEY_MESSAGE, super.marshallForSending()); + } catch (JSONException e) { + // Can't happen. + } + return wrapper; + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index e84d0b0c8..29aa89543 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -38,14 +38,14 @@ public byte[] getData() { if (encryptionKey != null) { JSONObject wrapper = new JSONObject(); wrapper.put("token", token); - wrapper.put("payload", jsonObject); + wrapper.put("payload", marshallForSending()); byte[] bytes = wrapper.toString().getBytes(); Encryptor encryptor = new Encryptor(encryptionKey); ApptentiveLog.v(ApptentiveLogTag.PAYLOADS, "Getting data for encrypted payload."); return encryptor.encrypt(bytes); } else { ApptentiveLog.v(ApptentiveLogTag.PAYLOADS, "Getting data for plaintext payload."); - return jsonObject.toString().getBytes(); + return marshallForSending().toString().getBytes(); } } catch (Exception e) { ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error encrypting payload data", e); @@ -170,4 +170,8 @@ public String getHttpRequestContentType() { } //endregion + + protected JSONObject marshallForSending() { + return jsonObject; + } } From a7319ba51a5d4a0348ae81bc2cf75e8ada0334cf Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 26 May 2017 09:14:43 -0700 Subject: [PATCH 320/465] Fix encrypted payload sending. --- .../sdk/comm/ApptentiveHttpClient.java | 26 +++++++++++++------ .../sdk/conversation/ConversationManager.java | 15 ++++++++--- .../android/sdk/model/JsonPayload.java | 3 +-- .../apptentive/android/sdk/model/Payload.java | 4 +++ .../android/sdk/model/PayloadData.java | 4 --- .../sdk/network/HttpJsonMultipartRequest.java | 4 +-- .../sdk/storage/ApptentiveDatabaseHelper.java | 8 +++--- .../sdk/storage/ApptentiveTaskManager.java | 6 ----- 8 files changed, 40 insertions(+), 30 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 2123604b9..426e7f5e2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -129,20 +129,24 @@ public HttpRequest sendPayload(PayloadData payload, HttpRequest.Listener associatedFiles = ((MultipartPayload) payload).getAssociatedFiles(); - request = createMultipartRequest(httpPath, payload.getData(), associatedFiles, requestMethod); + request = createMultipartRequest(httpPath, payload.getData(), associatedFiles, requestMethod, contentType); } else { - request = createRawRequest(httpPath, payload.getData(), requestMethod); + request = createRawRequest(httpPath, payload.getData(), requestMethod, contentType); } - request.setRequestProperty("Authorization", "Bearer " + oauthToken); + // Encrypted requests don't use an Auth token on the request. It's stored in the encrypted body. + if (!StringUtils.isNullOrEmpty(authToken)) { + request.setRequestProperty("Authorization", "Bearer " + authToken); + } if (payload.isEncrypted()) { request.setRequestProperty("APPTENTIVE-ENCRYPTED", Boolean.TRUE); @@ -174,7 +178,7 @@ private HttpJsonRequest createJsonRequest(String endpoint, JSONObject json, Http return request; } - private RawHttpRequest createRawRequest(String endpoint, byte[] data, HttpRequestMethod method) { + private RawHttpRequest createRawRequest(String endpoint, byte[] data, HttpRequestMethod method, String contentType) { if (endpoint == null) { throw new IllegalArgumentException("Endpoint is null"); } @@ -184,16 +188,19 @@ private RawHttpRequest createRawRequest(String endpoint, byte[] data, HttpReques if (method == null) { throw new IllegalArgumentException("Method is null"); } + if (contentType == null) { + throw new IllegalArgumentException("ContentType is null"); + } String url = createEndpointURL(endpoint); RawHttpRequest request = new RawHttpRequest(url, data); setupRequestDefaults(request); request.setMethod(method); - request.setRequestProperty("Content-Type", "application/json"); + request.setRequestProperty("Content-Type", contentType); return request; } - private HttpJsonMultipartRequest createMultipartRequest(String endpoint, byte[] data, List files, HttpRequestMethod method) { + private HttpJsonMultipartRequest createMultipartRequest(String endpoint, byte[] data, List files, HttpRequestMethod method, String contentType) { if (endpoint == null) { throw new IllegalArgumentException("Endpoint is null"); } @@ -203,9 +210,12 @@ private HttpJsonMultipartRequest createMultipartRequest(String endpoint, byte[] if (method == null) { throw new IllegalArgumentException("Method is null"); } + if (contentType == null) { + throw new IllegalArgumentException("ContentType is null"); + } String url = createEndpointURL(endpoint); - HttpJsonMultipartRequest request = new HttpJsonMultipartRequest(url, data, files); + HttpJsonMultipartRequest request = new HttpJsonMultipartRequest(url, data, files, contentType); setupRequestDefaults(request); request.setMethod(method); return request; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 1dce73b6a..c4f1ea744 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -655,8 +655,13 @@ public void onFail(HttpRequest request, String reason) { sendLoginRequest(activeConversation.getConversationId(), userId, token, callback); break; case LOGGED_IN: + if (activeConversation.getUserId().equals(userId)) { + ApptentiveLog.w("Already logged in as \"%s\"", userId); + callback.onLoginFinish(); + return; + } // FIXME: If they are attempting to login to a different conversation, we need to gracefully end the active conversation here and kick off a login request to the desired conversation. - callback.onLoginFail("already logged in"); + callback.onLoginFail("Already logged in. You must log out first."); break; default: assertFail("Unexpected conversation state: " + activeConversation.getState()); @@ -672,7 +677,7 @@ public void onFinish(HttpJsonRequest request) { try { final JSONObject responseObject = request.getResponseObject(); final String encryptionKey = responseObject.getString("encryption_key"); - handleLoginFinished(userId, encryptionKey); + handleLoginFinished(userId, token, encryptionKey); } catch (Exception e) { ApptentiveLog.e(e, "Exception while parsing login response"); handleLoginFailed("Internal error"); @@ -689,11 +694,12 @@ public void onFail(HttpJsonRequest request, String reason) { handleLoginFailed(reason); } - private void handleLoginFinished(final String userId, final String encryptionKey) { + private void handleLoginFinished(final String userId, final String token, final String encryptionKey) { DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { @Override protected void execute() { - assertFalse(isNullOrEmpty(encryptionKey)); + assertFalse(isNullOrEmpty(encryptionKey),"Login finished with missing encryption key."); + assertFalse(isNullOrEmpty(token), "Login finished with missing token."); try { // if we were previously logged out we might end up with no active conversation @@ -715,6 +721,7 @@ public boolean accept(ConversationMetadataItem item) { } activeConversation.setEncryptionKey(encryptionKey); + activeConversation.setConversationToken(token); activeConversation.setUserId(userId); activeConversation.setState(LOGGED_IN); handleConversationStateChange(activeConversation); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index 29aa89543..034465b8c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -36,9 +36,8 @@ public JsonPayload(PayloadType type, String json) throws JSONException { public byte[] getData() { try { if (encryptionKey != null) { - JSONObject wrapper = new JSONObject(); + JSONObject wrapper = marshallForSending(); wrapper.put("token", token); - wrapper.put("payload", marshallForSending()); byte[] bytes = wrapper.toString().getBytes(); Encryptor encryptor = new Encryptor(encryptionKey); ApptentiveLog.v(ApptentiveLogTag.PAYLOADS, "Getting data for encrypted payload."); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index a31dbab29..8d53df883 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -85,6 +85,10 @@ public boolean hasEncryptionKey() { return !StringUtils.isNullOrEmpty(encryptionKey); } + public String getToken() { + return token; + } + public void setToken(String token) { this.token = token; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java index 7a06b43c5..4a07e88b9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java @@ -33,10 +33,6 @@ public PayloadData(PayloadType type, String nonce, byte[] data, String authToken throw new IllegalArgumentException("Data is null"); } - if (authToken == null) { - throw new IllegalArgumentException("Auth token is null"); - } - if (contentType == null) { throw new IllegalArgumentException("Content type is null"); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java index eaac36901..0192cc76d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java @@ -30,7 +30,7 @@ public class HttpJsonMultipartRequest extends HttpRequest { private final List files; private final String boundary; - public HttpJsonMultipartRequest(String urlString, byte[] requestData, List files) { + public HttpJsonMultipartRequest(String urlString, byte[] requestData, List files, String contentType) { super(urlString); if (files == null) { @@ -40,7 +40,7 @@ public HttpJsonMultipartRequest(String urlString, byte[] requestData, List Date: Fri, 26 May 2017 09:46:31 -0700 Subject: [PATCH 321/465] Fix event sending. We weren't setting the auth token properly on Anonymous conversations payloads. --- .../android/sdk/storage/ApptentiveTaskManager.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index c078cd2b1..573592d8d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -104,10 +104,13 @@ public boolean shouldRetryRequest(int responseCode, int retryAttempt) { * a new message is added. */ public void addPayload(final Payload... payloads) { - // Set the current encryptor on each payload as they are added. - if (conversationEncryptionKey != null) { - for (Payload payload : payloads) { + + // Provide each payload with the information it will need to send itself to the server securely. + for (Payload payload : payloads) { + if (conversationEncryptionKey != null) { payload.setEncryptionKey(conversationEncryptionKey); + } + if (currentConversationToken != null) { payload.setToken(currentConversationToken); } } From c8f988bd16ef523ffe85f434424c96aa3a46e1cf Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 26 May 2017 09:47:02 -0700 Subject: [PATCH 322/465] Store the last sent device, and sdk version used during conversation creation. --- .../android/sdk/conversation/ConversationManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index c4f1ea744..5fd04f5cd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -379,8 +379,10 @@ public void onFinish(HttpJsonRequest request) { conversation.setConversationToken(conversationToken); conversation.setConversationId(conversationId); conversation.setDevice(device); - conversation.setSdk(sdk); + conversation.setLastSentDevice(device); conversation.setAppRelease(appRelease); + conversation.setSdk(sdk); + conversation.setLastSeenSdkVersion(sdk.getVersion()); String personId = root.getString("person_id"); ApptentiveLog.d(CONVERSATION, "PersonId: " + personId); From 6ae4f826e076e750b9812e6b0cc4fa7f15668ea7 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 26 May 2017 10:04:43 -0700 Subject: [PATCH 323/465] Rename notifications for clarity. --- .../apptentive/android/sdk/ApptentiveInternal.java | 8 ++++---- .../android/sdk/ApptentiveNotifications.java | 8 ++++---- .../sdk/conversation/ConversationManager.java | 2 +- .../sdk/module/messagecenter/MessageManager.java | 13 ++++++------- .../android/sdk/storage/ApptentiveTaskManager.java | 12 ++++++------ 5 files changed, 21 insertions(+), 22 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index f20d611f1..e453e3d55 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -67,8 +67,8 @@ import static com.apptentive.android.sdk.ApptentiveLogTag.CONVERSATION; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_RESUMED; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_STARTED; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_BACKGROUND; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTERED_BACKGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTERED_FOREGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_INTERACTIONS_SHOULD_DISMISS; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_ACTIVITY; @@ -430,7 +430,7 @@ public void onAppEnterForeground() { appIsInForeground = true; // Post a notification - ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_APP_ENTER_FOREGROUND); + ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_APP_ENTERED_FOREGROUND); } public void onAppEnterBackground() { @@ -438,7 +438,7 @@ public void onAppEnterBackground() { currentTaskStackTopActivity = null; // Post a notification - ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_APP_ENTER_BACKGROUND); + ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_APP_ENTERED_BACKGROUND); } /* Apply Apptentive styling layers to the theme to be used by interaction. The layers include diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java index c29b04133..e4c54d959 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -24,14 +24,14 @@ public class ApptentiveNotifications { public static final String NOTIFICATION_ACTIVITY_RESUMED = "NOTIFICATION_ACTIVITY_RESUMED"; // { activity : Activity } /** - * Sent if app enter foreground + * Sent if app entered foreground */ - public static final String NOTIFICATION_APP_ENTER_FOREGROUND = "NOTIFICATION_APP_ENTER_FOREGROUND"; + public static final String NOTIFICATION_APP_ENTERED_FOREGROUND = "NOTIFICATION_APP_ENTERED_FOREGROUND"; /** - * Sent if app enter background + * Sent if app entered background */ - public static final String NOTIFICATION_APP_ENTER_BACKGROUND = "NOTIFICATION_APP_ENTER_BACKGROUND"; + public static final String NOTIFICATION_APP_ENTERED_BACKGROUND = "NOTIFICATION_APP_ENTERED_BACKGROUND"; /** * Sent before payload request is sent to the server diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 5fd04f5cd..800507325 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -87,7 +87,7 @@ public ConversationManager(Context context, File storageDir) { @Override public void onReceiveNotification(ApptentiveNotification notification) { if (activeConversation != null && activeConversation.hasActiveState()) { - ApptentiveLog.v(CONVERSATION, "Activity 'start' notification received. Trying to fetch interactions..."); + ApptentiveLog.v(CONVERSATION, "App entered foreground notification received. Trying to fetch interactions..."); final Context context = getContext(); if (context != null) { activeConversation.fetchInteractions(context); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 278e9ef8c..a24ac1dc1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -38,7 +38,6 @@ import org.json.JSONObject; import java.lang.ref.WeakReference; -import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -46,8 +45,8 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_RESUMED; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_STARTED; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_BACKGROUND; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTERED_BACKGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTERED_FOREGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_ACTIVITY; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_PAYLOAD; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_RESPONSE_CODE; @@ -348,8 +347,8 @@ private void registerNotifications() { ApptentiveNotificationCenter.defaultCenter() .addObserver(NOTIFICATION_ACTIVITY_STARTED, this) .addObserver(NOTIFICATION_ACTIVITY_RESUMED, this) - .addObserver(NOTIFICATION_APP_ENTER_FOREGROUND, this) - .addObserver(NOTIFICATION_APP_ENTER_BACKGROUND, this) + .addObserver(NOTIFICATION_APP_ENTERED_FOREGROUND, this) + .addObserver(NOTIFICATION_APP_ENTERED_BACKGROUND, this) .addObserver(NOTIFICATION_PAYLOAD_WILL_START_SEND, this) .addObserver(NOTIFICATION_PAYLOAD_DID_FINISH_SEND, this); } @@ -364,9 +363,9 @@ public void onReceiveNotification(ApptentiveNotification notification) { notification.hasName(NOTIFICATION_ACTIVITY_RESUMED)) { final Activity activity = notification.getRequiredUserInfo(NOTIFICATION_KEY_ACTIVITY, Activity.class); setCurrentForegroundActivity(activity); - } else if (notification.hasName(NOTIFICATION_APP_ENTER_FOREGROUND)) { + } else if (notification.hasName(NOTIFICATION_APP_ENTERED_FOREGROUND)) { appWentToForeground(); - } else if (notification.hasName(NOTIFICATION_APP_ENTER_BACKGROUND)) { + } else if (notification.hasName(NOTIFICATION_APP_ENTERED_BACKGROUND)) { setCurrentForegroundActivity(null); appWentToBackground(); } else if (notification.hasName(NOTIFICATION_PAYLOAD_WILL_START_SEND)) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 573592d8d..5b8827f2c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -32,8 +32,8 @@ import java.util.concurrent.TimeUnit; import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_BACKGROUND; -import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTER_FOREGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTERED_BACKGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTERED_FOREGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_STATE_DID_CHANGE; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_PAYLOAD; @@ -95,8 +95,8 @@ public boolean shouldRetryRequest(int responseCode, int retryAttempt) { ApptentiveNotificationCenter.defaultCenter() .addObserver(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, this) - .addObserver(NOTIFICATION_APP_ENTER_BACKGROUND, this) - .addObserver(NOTIFICATION_APP_ENTER_FOREGROUND, this); + .addObserver(NOTIFICATION_APP_ENTERED_BACKGROUND, this) + .addObserver(NOTIFICATION_APP_ENTERED_FOREGROUND, this); } /** @@ -286,10 +286,10 @@ public void run() { } else { currentConversationId = null; } - } else if (notification.hasName(NOTIFICATION_APP_ENTER_FOREGROUND)) { + } else if (notification.hasName(NOTIFICATION_APP_ENTERED_FOREGROUND)) { appInBackground = false; sendNextPayload(); // when the app comes back from the background - we need to resume sending payloads - } else if (notification.hasName(NOTIFICATION_APP_ENTER_BACKGROUND)) { + } else if (notification.hasName(NOTIFICATION_APP_ENTERED_BACKGROUND)) { appInBackground = true; } } From 90357009ae095746265665a0999a0c275754bfae Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 26 May 2017 10:07:40 -0700 Subject: [PATCH 324/465] Move interaction fetching attempt to `NOTIFICATION_APP_ENTERED_FOREGROUND` instead of `NOTIFICATION_ACTIVITY_STARTED` --- .../android/sdk/conversation/ConversationManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 800507325..91bd388a9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -83,7 +83,7 @@ public ConversationManager(Context context, File storageDir) { this.storageDir = storageDir; ApptentiveNotificationCenter.defaultCenter() - .addObserver(NOTIFICATION_ACTIVITY_STARTED, new ApptentiveNotificationObserver() { + .addObserver(NOTIFICATION_APP_ENTERED_FOREGROUND, new ApptentiveNotificationObserver() { @Override public void onReceiveNotification(ApptentiveNotification notification) { if (activeConversation != null && activeConversation.hasActiveState()) { From b7c813562e8e1c4f573fc21fde5b6b0fc09a1132 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 30 May 2017 13:00:36 -0700 Subject: [PATCH 325/465] =?UTF-8?q?Replaced=20=E2=80=98items=E2=80=99=20wi?= =?UTF-8?q?th=20=E2=80=98messages=E2=80=99=20for=20the=20message=20request?= =?UTF-8?q?=20json=20response?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/sdk/module/messagecenter/MessageManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 769fb1082..02686b1d6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -251,8 +251,8 @@ public void updateMessage(ApptentiveMessage apptentiveMessage) { private List parseMessagesString(String messageString) throws JSONException { List ret = new ArrayList<>(); JSONObject root = new JSONObject(messageString); - if (!root.isNull("items")) { - JSONArray items = root.getJSONArray("items"); + if (!root.isNull("messages")) { + JSONArray items = root.getJSONArray("messages"); for (int i = 0; i < items.length(); i++) { String json = items.getJSONObject(i).toString(); ApptentiveMessage apptentiveMessage = MessageFactory.fromJson(json); From a12985aa04541388aacaec8d05bd231107915763 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 30 May 2017 22:47:50 -0700 Subject: [PATCH 326/465] Fix multipart plain text payload sending. Refactor how payloads are marshalled for sending. --- .../sdk/comm/ApptentiveHttpClient.java | 1 + .../android/sdk/model/CompoundMessage.java | 66 ++++++++++++++++++- .../android/sdk/model/JsonPayload.java | 34 +++++----- 3 files changed, 85 insertions(+), 16 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 426e7f5e2..0b6ff59d2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -135,6 +135,7 @@ private HttpRequest createPayloadRequest(PayloadData payload) { final String contentType = notNull(payload.getContentType()); // TODO: figure out a better solution + // TODO: This isn't working. The payload is only ever of type PayloadData. This never got migrated. HttpRequest request; if (payload instanceof MultipartPayload) { final List associatedFiles = ((MultipartPayload) payload).getAssociatedFiles(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java index 2dc5764d6..13d9366ab 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java @@ -20,6 +20,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -38,6 +39,8 @@ public class CompoundMessage extends ApptentiveMessage implements MultipartPaylo private boolean isOutgoing = true; + private final String boundary; + /* For incoming message, this array stores attachment Urls * StoredFile::apptentiveUri is set by the "url" of the remote attachment file * StoredFile:localFilePath is set by the "thumbnail_url" of the remote attachment (maybe empty) @@ -47,6 +50,7 @@ public class CompoundMessage extends ApptentiveMessage implements MultipartPaylo // Default constructor will only be called when the message is created from local, a.k.a outgoing public CompoundMessage() { super(); + boundary = UUID.randomUUID().toString(); isOutgoing = true; } @@ -57,6 +61,7 @@ public CompoundMessage() { */ public CompoundMessage(String json, boolean bOutgoing) throws JSONException { super(json); + boundary = UUID.randomUUID().toString(); parseAttachmentsArray(json); hasNoAttachments = getTextOnly(); isOutgoing = bOutgoing; @@ -76,7 +81,7 @@ public HttpRequestMethod getHttpRequestMethod() { @Override public String getHttpRequestContentType() { - return "multipart/mixed;boundary=xxx"; + return String.format("%s;boundary=%s", encryptionKey != null ? "multipart/encrypted" : "multipart/mixed", boundary); } //endregion @@ -115,6 +120,8 @@ public void setTextOnly(boolean bVal) { } + private List attachedFiles; + public boolean setAssociatedImages(List attachedImages) { if (attachedImages == null || attachedImages.size() == 0) { @@ -136,6 +143,9 @@ public boolean setAssociatedImages(List attachedImages) { storedFile.setCreationTime(image.time); attachmentStoredFiles.add(storedFile); } + + attachedFiles = attachmentStoredFiles; + boolean bRet = false; try { Future future = ApptentiveInternal.getInstance().getApptentiveTaskManager().addCompoundMessageFiles(attachmentStoredFiles); @@ -149,6 +159,8 @@ public boolean setAssociatedImages(List attachedImages) { public boolean setAssociatedFiles(List attachedFiles) { + this.attachedFiles = attachedFiles; + if (attachedFiles == null || attachedFiles.size() == 0) { hasNoAttachments = true; return false; @@ -267,6 +279,7 @@ public int getListItemType() { @Override protected JSONObject marshallForSending() { +/* JSONObject wrapper = new JSONObject(); try { wrapper.put(KEY_MESSAGE, super.marshallForSending()); @@ -274,5 +287,56 @@ protected JSONObject marshallForSending() { // Can't happen. } return wrapper; +*/ + return super.marshallForSending(); + } + + private static final String lineEnd = "\r\n"; + private static final String twoHyphens = "--"; + + /** + * This is a multipart request. To accomplish this, we will create a data blog that is the entire contents + * of the request after the request's headers. Each part of the body includes its own headers, + * boundary, and data, but that is all rolled into one byte array to be stored pending sending. + * This enables the contents to be stores securely via encryption the moment it is created, and + * not read again as plain text while it sits on the device. + * @return a Byte array that can be set on the payload request. + */ + @Override + public byte[] getData() { + + // First write the message body out as the first "part". + StringBuilder bodyData = new StringBuilder(twoHyphens); + bodyData + .append(boundary).append(lineEnd) + .append("Content-Disposition: form-data; name=\"message\"").append(lineEnd) + .append("Content-Type: application/json;charset=UTF-8").append(lineEnd) + .append(lineEnd) + .append(marshallForSending().toString()).append(lineEnd); + + // TODO: Then write out each attached file. + if (attachedFiles != null) { + for (StoredFile storedFile : attachedFiles) { + storedFile.getLocalFilePath(); + } + } + + bodyData.append(twoHyphens).append(boundary).append(twoHyphens).append(lineEnd); + + byte[] plainTextData = bodyData.toString().getBytes(); + return plainTextData; +/* + if (encryptionKey != null) { + Encryptor encryptor = new Encryptor(encryptionKey); + try { + return encryptor.encrypt(plainTextData); + } catch (Exception e) { + ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error encrypting payload data", e); + } + } else { + return plainTextData; + } + return null; +*/ } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index 034465b8c..cd7fecb5a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -18,8 +18,6 @@ public abstract class JsonPayload extends Payload { private final JSONObject jsonObject; - // These three are not stored in the JSON, only the DB. - public JsonPayload(PayloadType type) { super(type); jsonObject = new JSONObject(); @@ -34,22 +32,20 @@ public JsonPayload(PayloadType type, String json) throws JSONException { @Override public byte[] getData() { - try { - if (encryptionKey != null) { - JSONObject wrapper = marshallForSending(); - wrapper.put("token", token); - byte[] bytes = wrapper.toString().getBytes(); - Encryptor encryptor = new Encryptor(encryptionKey); - ApptentiveLog.v(ApptentiveLogTag.PAYLOADS, "Getting data for encrypted payload."); + if (encryptionKey != null) { + byte[] bytes = marshallForSending().toString().getBytes(); + Encryptor encryptor = new Encryptor(encryptionKey); + ApptentiveLog.v(ApptentiveLogTag.PAYLOADS, "Getting data for encrypted payload."); + try { return encryptor.encrypt(bytes); - } else { - ApptentiveLog.v(ApptentiveLogTag.PAYLOADS, "Getting data for plaintext payload."); - return marshallForSending().toString().getBytes(); + } catch (Exception e) { + ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error encrypting payload data", e); } - } catch (Exception e) { - ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error encrypting payload data", e); + return null; + } else { + ApptentiveLog.v(ApptentiveLogTag.PAYLOADS, "Getting data for plaintext payload."); + return marshallForSending().toString().getBytes(); } - return null; } //endregion @@ -171,6 +167,14 @@ public String getHttpRequestContentType() { //endregion protected JSONObject marshallForSending() { + try { + if (encryptionKey != null) { + JSONObject wrapper = new JSONObject(); + wrapper.put("token", token); + } + } catch (Exception e) { + // Can't happen. + } return jsonObject; } } From 5f645f63283e40579840a8f15ea329fc62a48677 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 31 May 2017 13:59:29 -0700 Subject: [PATCH 327/465] Modify Util.writeBytes() to create path to file if it doesn't yet exist. --- .../src/main/java/com/apptentive/android/sdk/util/Util.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java index f94fd4f73..ce1a94655 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java @@ -676,6 +676,11 @@ public static void writeBytes(File file, byte[] bytes) throws IOException { throw new IllegalArgumentException("'bytes' is null"); } + File parentFile = file.getParentFile(); + if (!parentFile.exists() && !parentFile.mkdirs()) { + throw new IOException("Parent file could not be created: " + parentFile); + } + ByteArrayInputStream input = null; FileOutputStream output = null; try { From dcc01ad45802f0e409df3932d8856687f9440d0f Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 31 May 2017 14:00:16 -0700 Subject: [PATCH 328/465] Modify CompoundMessage storage to store multipart request body in file outside of the database. --- .../storage/EncryptedPayloadSenderTest.java | 5 +- .../android/sdk/model/CompoundMessage.java | 130 +++++++++++++----- .../android/sdk/model/JsonPayload.java | 2 +- .../apptentive/android/sdk/model/Payload.java | 4 +- .../sdk/storage/ApptentiveDatabaseHelper.java | 31 ++++- 5 files changed, 127 insertions(+), 45 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java index ee0065d78..cf8b390f2 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java @@ -25,11 +25,12 @@ public class EncryptedPayloadSenderTest extends TestCaseBase { @Test public void testEncryptedPayload() throws Exception { +/* final EventPayload original = new EventPayload(EVENT_LABEL, "trigger"); original.setToken(AUTH_TOKEN); original.setEncryptionKey(ENCRYPTION_KEY); - byte[] cipherText = original.getData(); + byte[] cipherText = original.getData(file); Encryptor encryptor = new Encryptor(ENCRYPTION_KEY); @@ -41,5 +42,7 @@ public void testEncryptedPayload() throws Exception { } catch (Exception e) { fail(e.getMessage()); } +*/ + throw new Exception("FIXME"); } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java index 13d9366ab..afa641d90 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java @@ -8,16 +8,21 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.ApptentiveLogTag; +import com.apptentive.android.sdk.encryption.Encryptor; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterUtil; import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.image.ImageItem; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -26,6 +31,8 @@ public class CompoundMessage extends ApptentiveMessage implements MultipartPayload, MessageCenterUtil.CompoundMessageCommonInterface { + public static final int READ_CHUNK_SIZE = 128 * 1024; + public static final String KEY_MESSAGE = "message"; private static final String KEY_BODY = "body"; @@ -277,20 +284,6 @@ public int getListItemType() { } } - @Override - protected JSONObject marshallForSending() { -/* - JSONObject wrapper = new JSONObject(); - try { - wrapper.put(KEY_MESSAGE, super.marshallForSending()); - } catch (JSONException e) { - // Can't happen. - } - return wrapper; -*/ - return super.marshallForSending(); - } - private static final String lineEnd = "\r\n"; private static final String twoHyphens = "--"; @@ -300,43 +293,104 @@ protected JSONObject marshallForSending() { * boundary, and data, but that is all rolled into one byte array to be stored pending sending. * This enables the contents to be stores securely via encryption the moment it is created, and * not read again as plain text while it sits on the device. + * * @return a Byte array that can be set on the payload request. + * TODO: Refactor this API so that the resulting byte array is streamed to a file for later retrieval. */ @Override - public byte[] getData() { + public byte[] renderData() { + boolean encrypted = encryptionKey != null; + Encryptor encryptor = null; + if (encrypted) { + encryptor = new Encryptor(encryptionKey); + } + ByteArrayOutputStream data = new ByteArrayOutputStream(); // First write the message body out as the first "part". - StringBuilder bodyData = new StringBuilder(twoHyphens); - bodyData - .append(boundary).append(lineEnd) + StringBuilder header = new StringBuilder(); + header.append(twoHyphens).append(boundary).append(lineEnd); + + StringBuilder part = new StringBuilder(); + part .append("Content-Disposition: form-data; name=\"message\"").append(lineEnd) .append("Content-Type: application/json;charset=UTF-8").append(lineEnd) .append(lineEnd) .append(marshallForSending().toString()).append(lineEnd); + byte[] partBytes = part.toString().getBytes(); - // TODO: Then write out each attached file. - if (attachedFiles != null) { - for (StoredFile storedFile : attachedFiles) { - storedFile.getLocalFilePath(); + try { + if (encrypted) { + header + .append("Content-Disposition: form-data; name=\"message\"").append(lineEnd) + .append("Content-Type: application/octet-stream").append(lineEnd) + .append(lineEnd); + data.write(header.toString().getBytes()); + data.write(encryptor.encrypt(partBytes)); + } else { + data.write(header.toString().getBytes()); + data.write(partBytes); } - } - - bodyData.append(twoHyphens).append(boundary).append(twoHyphens).append(lineEnd); - byte[] plainTextData = bodyData.toString().getBytes(); - return plainTextData; -/* - if (encryptionKey != null) { - Encryptor encryptor = new Encryptor(encryptionKey); - try { - return encryptor.encrypt(plainTextData); - } catch (Exception e) { - ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error encrypting payload data", e); + // Then append attachments + if (attachedFiles != null) { + for (StoredFile storedFile : attachedFiles) { + ApptentiveLog.e("Starting and attachment."); + data.write(("--" + boundary + lineEnd).getBytes()); + StringBuilder attachmentEnvelope = new StringBuilder(); + attachmentEnvelope.append(String.format("Content-Disposition: form-data; name=\"file[]\"; filename=\"%s\"", storedFile.getFileName())).append(lineEnd) + .append("Content-Type: ").append(storedFile.getMimeType()).append(lineEnd) + .append(lineEnd); + ByteArrayOutputStream attachmentBytes = new ByteArrayOutputStream(); + FileInputStream fileInputStream = null; + try { + ApptentiveLog.e("Writing attachment envelope: %s", attachmentEnvelope.toString()); + attachmentBytes.write(attachmentEnvelope.toString().getBytes()); + + // TODO: Move this into a utility method that returns a byte[] with a shrunk attachment (if it was an image). + // Look at ImageUtil.createScaledDownImageCacheFile() + try { + byte[] buffer = new byte[READ_CHUNK_SIZE]; + int read; + fileInputStream = new FileInputStream(storedFile.getSourceUriOrPath()); + while ((read = fileInputStream.read(buffer, 0, READ_CHUNK_SIZE)) != -1) { + ApptentiveLog.e("Writing attachment bytes: %d", read); + attachmentBytes.write(buffer, 0, read); + } + } catch (Exception e) { + ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error reading Message Payload attachment: \"%s\".", e, storedFile.getLocalFilePath()); + continue; + } finally { + Util.ensureClosed(fileInputStream); + } + + if (encrypted) { + // If encrypted, each part must be encrypted, and wrapped in a plain text set of headers. + StringBuilder encryptionEnvelope = new StringBuilder(); + encryptionEnvelope + .append("Content-Disposition: form-data; name=\"file[]\"").append(lineEnd) + .append("Content-Type: application/octet-stream").append(lineEnd) + .append(lineEnd); + ApptentiveLog.e("Writing encrypted envelope: %s", encryptionEnvelope.toString()); + data.write(encryptionEnvelope.toString().getBytes()); + ApptentiveLog.e("Encrypting attachment bytes: %d", attachmentBytes.size()); + byte[] encryptedAttachment = encryptor.encrypt(attachmentBytes.toByteArray()); + ApptentiveLog.e("Writing encrypted attachment bytes: %d", encryptedAttachment.length); + data.write(encryptedAttachment); + } else { + ApptentiveLog.e("Writing attachment bytes: %d", attachmentBytes.size()); + data.write(attachmentBytes.toByteArray()); + } + } catch (Exception e) { + ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error getting data for Message Payload attachments.", e); + return null; + } + } } - } else { - return plainTextData; + data.write(("--" + boundary + "--").getBytes()); + } catch (Exception e) { + ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error assembling Message Payload.", e); + return null; } - return null; -*/ + return data.toByteArray(); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index cd7fecb5a..2da901b69 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -31,7 +31,7 @@ public JsonPayload(PayloadType type, String json) throws JSONException { //region Data @Override - public byte[] getData() { + public byte[] renderData() { if (encryptionKey != null) { byte[] bytes = marshallForSending().toString().getBytes(); Encryptor encryptor = new Encryptor(encryptionKey); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index 8d53df883..cd5cff035 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -16,7 +16,7 @@ public abstract class Payload { private final PayloadType payloadType; /** - * If set, this payload should be encrypted in getData(). + * If set, this payload should be encrypted in renderData(). */ protected String encryptionKey; @@ -48,7 +48,7 @@ protected Payload(PayloadType type) { /** * Binary data to be stored in database */ - public abstract byte[] getData(); + public abstract byte[] renderData(); //region diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 0a03aab32..acbc79d92 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -18,6 +18,7 @@ import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.model.JsonPayload; +import com.apptentive.android.sdk.model.MultipartPayload; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.model.PayloadType; @@ -25,6 +26,7 @@ import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.storage.legacy.LegacyPayloadFactory; import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.util.Util; import org.json.JSONException; import org.json.JSONObject; @@ -49,6 +51,9 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { private final DataSource dataSource; private final File fileDir; // data dir of the application + /** We store temporary multipart payload body files here */ + private final File multipartPayloadDataDir; + //region Payload SQL static final class PayloadEntry { @@ -204,6 +209,7 @@ static final class LegacyPayloadEntry { this.dataSource = dataSource; this.fileDir = context.getFilesDir(); + this.multipartPayloadDataDir = new File(fileDir, "multipart-payloads"); // FIXME: figure out a better directory structure } //region Create & Upgrade @@ -437,14 +443,22 @@ void addPayload(Payload... payloads) { values.put(PayloadEntry.COLUMN_PATH.name, payload.getHttpEndPoint( StringUtils.isNullOrEmpty(conversationId) ? "${conversationId}" : conversationId) // if conversation id is missing we replace it with a place holder and update it later ); - values.put(PayloadEntry.COLUMN_DATA.name, notNull(payload.getData())); + + if (payload instanceof MultipartPayload) { + File dest = getMultipartFile(payload.getNonce()); + ApptentiveLog.v(DATABASE, "Save multipart payload: " + dest); + Util.writeBytes(dest, payload.renderData()); + } else { + values.put(PayloadEntry.COLUMN_DATA.name, notNull(payload.renderData())); + } + values.put(PayloadEntry.COLUMN_ENCRYPTED.name, payload.hasEncryptionKey() ? 1 : 0); db.insert(PayloadEntry.TABLE_NAME, null, values); } db.setTransactionSuccessful(); db.endTransaction(); - } catch (SQLException sqe) { + } catch (Exception sqe) { ApptentiveLog.e(DATABASE, "addPayload EXCEPTION: " + sqe.getMessage()); } } @@ -499,7 +513,14 @@ public PayloadData getOldestUnsentPayload() { final String httpRequestPath = updatePayloadRequestPath(cursor.getString(PayloadEntry.COLUMN_PATH.index), conversationId); final String nonce = notNull(cursor.getString(PayloadEntry.COLUMN_IDENTIFIER.index)); - final byte[] data = notNull(cursor.getBlob(PayloadEntry.COLUMN_DATA.index)); + byte[] data = null; + switch (payloadType) { + case message: + data = Util.readBytes(getMultipartFile(nonce)); + break; + default: + data = notNull(cursor.getBlob(PayloadEntry.COLUMN_DATA.index)); + } final String contentType = notNull(cursor.getString(PayloadEntry.COLUMN_CONTENT_TYPE.index)); final HttpRequestMethod httpRequestMethod = HttpRequestMethod.valueOf(notNull(cursor.getString(PayloadEntry.COLUMN_REQUEST_METHOD.index))); final boolean encrypted = cursor.getInt(PayloadEntry.COLUMN_ENCRYPTED.index) == 1; @@ -623,6 +644,10 @@ boolean addCompoundMessageFiles(List associatedFiles) { // region Helpers + private File getMultipartFile(String nonce) { + return new File(multipartPayloadDataDir, nonce + ".multipart"); + } + private void ensureClosed(Cursor cursor) { try { if (cursor != null) { From f89625ffb18b4e7638030a6170d928085b6f10ad Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 5 Jun 2017 09:13:42 -0700 Subject: [PATCH 329/465] Fix multipart payload generation by including newline between content parts and boundary. --- .../java/com/apptentive/android/sdk/model/CompoundMessage.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java index afa641d90..a3c01e170 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java @@ -326,6 +326,7 @@ public byte[] renderData() { .append(lineEnd); data.write(header.toString().getBytes()); data.write(encryptor.encrypt(partBytes)); + data.write("\r\n".getBytes()); } else { data.write(header.toString().getBytes()); data.write(partBytes); @@ -376,6 +377,7 @@ public byte[] renderData() { byte[] encryptedAttachment = encryptor.encrypt(attachmentBytes.toByteArray()); ApptentiveLog.e("Writing encrypted attachment bytes: %d", encryptedAttachment.length); data.write(encryptedAttachment); + data.write("\r\n".getBytes()); } else { ApptentiveLog.e("Writing attachment bytes: %d", attachmentBytes.size()); data.write(attachmentBytes.toByteArray()); From 748a5ce236ee420b76e843bedbadd345feeb365d Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 5 Jun 2017 09:15:05 -0700 Subject: [PATCH 330/465] Include token at correct place inside encrypted payloads. --- .../java/com/apptentive/android/sdk/model/JsonPayload.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index 2da901b69..8c1c97a8b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -169,8 +169,7 @@ public String getHttpRequestContentType() { protected JSONObject marshallForSending() { try { if (encryptionKey != null) { - JSONObject wrapper = new JSONObject(); - wrapper.put("token", token); + jsonObject.put("token", token); } } catch (Exception e) { // Can't happen. From 0be1ae76061acd3256d274af0374a81c766dfa02 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 5 Jun 2017 09:16:15 -0700 Subject: [PATCH 331/465] Database shouldn't care about whether a payload is multipart of not anymore. --- .../sdk/storage/ApptentiveDatabaseHelper.java | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index acbc79d92..261e7ed4e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -18,7 +18,6 @@ import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.model.JsonPayload; -import com.apptentive.android.sdk.model.MultipartPayload; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.model.PayloadType; @@ -444,13 +443,9 @@ void addPayload(Payload... payloads) { StringUtils.isNullOrEmpty(conversationId) ? "${conversationId}" : conversationId) // if conversation id is missing we replace it with a place holder and update it later ); - if (payload instanceof MultipartPayload) { - File dest = getMultipartFile(payload.getNonce()); - ApptentiveLog.v(DATABASE, "Save multipart payload: " + dest); - Util.writeBytes(dest, payload.renderData()); - } else { - values.put(PayloadEntry.COLUMN_DATA.name, notNull(payload.renderData())); - } + File dest = getMultipartFile(payload.getNonce()); + ApptentiveLog.v(DATABASE, "Saving payload body to: %s", dest); + Util.writeBytes(dest, payload.renderData()); values.put(PayloadEntry.COLUMN_ENCRYPTED.name, payload.hasEncryptionKey() ? 1 : 0); @@ -467,7 +462,7 @@ void deletePayload(String payloadIdentifier) { if (payloadIdentifier == null) { throw new IllegalArgumentException("Payload identifier is null"); } - + // First delete the row SQLiteDatabase db; try { db = getWritableDatabase(); @@ -479,9 +474,14 @@ void deletePayload(String payloadIdentifier) { } catch (SQLException sqe) { ApptentiveLog.e(DATABASE, "deletePayload EXCEPTION: " + sqe.getMessage()); } + + // Then delete the data file + File dest = getMultipartFile(payloadIdentifier); + ApptentiveLog.v(DATABASE, "Deleted payload \"%s\" data file successfully? %b", payloadIdentifier, dest.delete()); } public void deleteAllPayloads() { + // FIXME: Delete files too. SQLiteDatabase db; try { db = getWritableDatabase(); @@ -513,14 +513,9 @@ public PayloadData getOldestUnsentPayload() { final String httpRequestPath = updatePayloadRequestPath(cursor.getString(PayloadEntry.COLUMN_PATH.index), conversationId); final String nonce = notNull(cursor.getString(PayloadEntry.COLUMN_IDENTIFIER.index)); - byte[] data = null; - switch (payloadType) { - case message: - data = Util.readBytes(getMultipartFile(nonce)); - break; - default: - data = notNull(cursor.getBlob(PayloadEntry.COLUMN_DATA.index)); - } + + // TODO: We need a migration for existing payload bodies to put them into files. + byte[] data = Util.readBytes(getMultipartFile(nonce)); final String contentType = notNull(cursor.getString(PayloadEntry.COLUMN_CONTENT_TYPE.index)); final HttpRequestMethod httpRequestMethod = HttpRequestMethod.valueOf(notNull(cursor.getString(PayloadEntry.COLUMN_REQUEST_METHOD.index))); final boolean encrypted = cursor.getInt(PayloadEntry.COLUMN_ENCRYPTED.index) == 1; From 397d440057a2446a6c3cac5d60904a9bbb588364 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 5 Jun 2017 09:19:43 -0700 Subject: [PATCH 332/465] Use null safe string comparer. --- .../android/sdk/conversation/ConversationManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 91bd388a9..e0b6255c5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -657,7 +657,7 @@ public void onFail(HttpRequest request, String reason) { sendLoginRequest(activeConversation.getConversationId(), userId, token, callback); break; case LOGGED_IN: - if (activeConversation.getUserId().equals(userId)) { + if (StringUtils.equal(activeConversation.getUserId(), userId)) { ApptentiveLog.w("Already logged in as \"%s\"", userId); callback.onLoginFinish(); return; From 907a3e2a66cfc780c16741a5a2edd42760d4fb5a Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 5 Jun 2017 16:39:33 -0700 Subject: [PATCH 333/465] Pass current Conversation ID in when storing a payload to guarantee payloads use the conversation that generated them. --- .../apptentive/android/sdk/model/JsonPayload.java | 9 +++++---- .../com/apptentive/android/sdk/model/Payload.java | 13 +++++++++++++ .../sdk/storage/ApptentiveDatabaseHelper.java | 7 ++----- .../android/sdk/storage/ApptentiveTaskManager.java | 7 +++++-- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index 8c1c97a8b..bb7f80694 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -7,13 +7,14 @@ package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.ApptentiveLogTag; import com.apptentive.android.sdk.encryption.Encryptor; import com.apptentive.android.sdk.network.HttpRequestMethod; import org.json.JSONException; import org.json.JSONObject; +import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; + public abstract class JsonPayload extends Payload { private final JSONObject jsonObject; @@ -33,17 +34,17 @@ public JsonPayload(PayloadType type, String json) throws JSONException { @Override public byte[] renderData() { if (encryptionKey != null) { + ApptentiveLog.v(PAYLOADS, "Getting data for encrypted payload."); byte[] bytes = marshallForSending().toString().getBytes(); Encryptor encryptor = new Encryptor(encryptionKey); - ApptentiveLog.v(ApptentiveLogTag.PAYLOADS, "Getting data for encrypted payload."); try { return encryptor.encrypt(bytes); } catch (Exception e) { - ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error encrypting payload data", e); + ApptentiveLog.e(PAYLOADS, "Error encrypting payload data", e); } return null; } else { - ApptentiveLog.v(ApptentiveLogTag.PAYLOADS, "Getting data for plaintext payload."); + ApptentiveLog.v(PAYLOADS, "Getting data for plaintext payload."); return marshallForSending().toString().getBytes(); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index cd5cff035..c2e053b8f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -20,6 +20,11 @@ public abstract class Payload { */ protected String encryptionKey; + /** + * The Conversation ID of the payload, if known at this time. + */ + protected String conversationId; + /** * Encrypted Payloads need to include the Conversation JWT inside them so that the server can * authenticate each payload after it is decrypted. @@ -85,6 +90,14 @@ public boolean hasEncryptionKey() { return !StringUtils.isNullOrEmpty(encryptionKey); } + public String getConversationId() { + return conversationId; + } + + public void setConversationId(String conversationId) { + this.conversationId = conversationId; + } + public String getToken() { return token; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 261e7ed4e..86411a15a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -425,9 +425,6 @@ void addPayload(Payload... payloads) { db = getWritableDatabase(); db.beginTransaction(); - final String conversationId = dataSource.getConversationId(); - final String authToken = dataSource.getAuthToken(); - for (Payload payload : payloads) { ContentValues values = new ContentValues(); values.put(PayloadEntry.COLUMN_IDENTIFIER.name, notNull(payload.getNonce())); @@ -437,10 +434,10 @@ void addPayload(Payload... payloads) { if (!payload.hasEncryptionKey()) { values.put(PayloadEntry.COLUMN_AUTH_TOKEN.name, payload.getToken()); // might be null } - values.put(PayloadEntry.COLUMN_CONVERSATION_ID.name, conversationId); // might be null + values.put(PayloadEntry.COLUMN_CONVERSATION_ID.name, payload.getConversationId()); // might be null values.put(PayloadEntry.COLUMN_REQUEST_METHOD.name, payload.getHttpRequestMethod().name()); values.put(PayloadEntry.COLUMN_PATH.name, payload.getHttpEndPoint( - StringUtils.isNullOrEmpty(conversationId) ? "${conversationId}" : conversationId) // if conversation id is missing we replace it with a place holder and update it later + StringUtils.isNullOrEmpty(payload.getConversationId()) ? "${conversationId}" : payload.getConversationId()) // if conversation id is missing we replace it with a place holder and update it later ); File dest = getMultipartFile(payload.getNonce()); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 5b8827f2c..66bc8976b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -107,12 +107,15 @@ public void addPayload(final Payload... payloads) { // Provide each payload with the information it will need to send itself to the server securely. for (Payload payload : payloads) { - if (conversationEncryptionKey != null) { - payload.setEncryptionKey(conversationEncryptionKey); + if (currentConversationId != null) { + payload.setConversationId(currentConversationId); } if (currentConversationToken != null) { payload.setToken(currentConversationToken); } + if (conversationEncryptionKey != null) { + payload.setEncryptionKey(conversationEncryptionKey); + } } singleThreadExecutor.execute(new Runnable() { @Override From 1dd9923033701040ef7f177729870301d1bf6b99 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 5 Jun 2017 16:45:34 -0700 Subject: [PATCH 334/465] Send logout payload on logout. --- .../android/sdk/ApptentiveInternal.java | 14 ++++++++++++- .../android/sdk/ApptentiveNotifications.java | 5 +++++ .../sdk/conversation/ConversationManager.java | 21 ++++++------------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index e453e3d55..622e8a2c1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -33,6 +33,7 @@ import com.apptentive.android.sdk.lifecycle.ApptentiveActivityLifecycleCallbacks; import com.apptentive.android.sdk.model.Configuration; import com.apptentive.android.sdk.model.EventPayload; +import com.apptentive.android.sdk.model.LogoutPayload; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; @@ -41,7 +42,9 @@ import com.apptentive.android.sdk.module.rating.IRatingProvider; import com.apptentive.android.sdk.module.rating.impl.GooglePlayRatingProvider; import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; +import com.apptentive.android.sdk.notifications.ApptentiveNotification; import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; +import com.apptentive.android.sdk.notifications.ApptentiveNotificationObserver; import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.AppReleaseManager; import com.apptentive.android.sdk.storage.ApptentiveTaskManager; @@ -69,13 +72,14 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_STARTED; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTERED_BACKGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTERED_FOREGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_WILL_LOGOUT; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_INTERACTIONS_SHOULD_DISMISS; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_ACTIVITY; /** * This class contains only internal methods. These methods should not be access directly by the host app. */ -public class ApptentiveInternal { +public class ApptentiveInternal implements ApptentiveNotificationObserver { private final ApptentiveTaskManager taskManager; @@ -180,6 +184,7 @@ private ApptentiveInternal(Application application, String apptentiveKey, String cachedExecutor = Executors.newCachedThreadPool(); lifecycleCallbacks = new ApptentiveActivityLifecycleCallbacks(); + ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_CONVERSATION_WILL_LOGOUT, this); } public static boolean isApptentiveRegistered() { @@ -1016,4 +1021,11 @@ void logout() { public static void dismissAllInteractions() { ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_INTERACTIONS_SHOULD_DISMISS); } + + @Override + public void onReceiveNotification(ApptentiveNotification notification) { + if (notification.hasName(NOTIFICATION_CONVERSATION_WILL_LOGOUT)) { + getApptentiveTaskManager().addPayload(new LogoutPayload()); + } + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java index e4c54d959..206e0e729 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -13,6 +13,11 @@ public class ApptentiveNotifications { */ public static final String NOTIFICATION_CONVERSATION_STATE_DID_CHANGE = "CONVERSATION_STATE_DID_CHANGE"; // { conversation : Conversation } + /** + * Sent when conversation is about to be logged out, to allow necessary tasks to be completed within the ending conversation. + */ + public static final String NOTIFICATION_CONVERSATION_WILL_LOGOUT = "CONVERSATION_WILL_LOGOUT"; // { conversation : Conversation } + /** * Sent if a new activity is started. */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index e0b6255c5..f85684416 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -296,19 +296,6 @@ private Conversation loadConversation(ConversationMetadataItem item) throws Seri return conversation; } - /** - * Ends active conversation (user logs out, etc) - */ - public synchronized boolean endActiveConversation() { - if (activeConversation != null) { - activeConversation.setState(LOGGED_OUT); - handleConversationStateChange(activeConversation); - activeConversation = null; - return true; - } - return false; - } - //endregion //region Conversation Token Fetching @@ -764,11 +751,15 @@ protected void execute() { } private void doLogout() { - // 1. Check to make sure we need to log out. if (activeConversation != null) { switch (activeConversation.getState()) { case LOGGED_IN: - endActiveConversation(); + ApptentiveLog.d("Ending active conversation."); + // Post synchronously to ensure logout payload can be sent before destroying the logged in conversation. + ApptentiveNotificationCenter.defaultCenter().postNotificationSync(NOTIFICATION_CONVERSATION_WILL_LOGOUT, ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, activeConversation)); + activeConversation.setState(LOGGED_OUT); + handleConversationStateChange(activeConversation); + activeConversation = null; break; default: ApptentiveLog.w(CONVERSATION, "Attempted to logout() from Conversation, but the Active Conversation was not in LOGGED_IN state."); From e03af58cbacd367b108b60f692215b764150d58a Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 5 Jun 2017 16:47:15 -0700 Subject: [PATCH 335/465] Send body in all requests other than GET. --- .../java/com/apptentive/android/sdk/network/HttpRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 788b9f333..cde7e646d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -263,7 +263,7 @@ private void sendRequestSync() throws IOException { setupRequestProperties(connection, requestProperties); } - if (HttpRequestMethod.POST.equals(method) || HttpRequestMethod.PUT.equals(method)) { + if (!HttpRequestMethod.GET.equals(method)) { connection.setDoInput(true); connection.setDoOutput(true); connection.setUseCaches(false); From f9c47bca2c7d061a0feb1984af13fb273bab5c1d Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 6 Jun 2017 13:00:45 -0700 Subject: [PATCH 336/465] Refactor JSONObject backed model objects. Replace getters that return a terrible default value when a key doesn't exist (String returns ""!?) with opt methods that let you pick the default. --- .../android/sdk/model/AppReleasePayload.java | 15 ++++----- .../android/sdk/model/ApptentiveMessage.java | 7 ++-- .../android/sdk/model/CompoundMessage.java | 4 +-- .../android/sdk/model/DevicePayload.java | 10 ++---- .../android/sdk/model/EventPayload.java | 2 +- .../android/sdk/model/PersonPayload.java | 9 ++---- .../sdk/model/SdkAndAppReleasePayload.java | 26 +++++++-------- .../android/sdk/model/SdkPayload.java | 32 ++++--------------- .../sdk/model/SurveyResponsePayload.java | 2 +- .../sdk/storage/ApptentiveDatabaseHelper.java | 2 +- 10 files changed, 41 insertions(+), 68 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java index cb6ba68d8..0c02cd7fc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleasePayload.java @@ -52,7 +52,7 @@ public String getHttpRequestContentType() { //endregion public String getType() { - return getString(KEY_TYPE); + return optString(KEY_TYPE, null); } public void setType(String type) { @@ -60,7 +60,7 @@ public void setType(String type) { } public String getVersionName() { - return getString(KEY_VERSION_NAME); + return optString(KEY_VERSION_NAME, null); } public void setVersionName(String versionName) { @@ -68,10 +68,7 @@ public void setVersionName(String versionName) { } public int getVersionCode() { - if (!isNull(KEY_VERSION_CODE)) { - return getInt(KEY_VERSION_CODE); - } - return -1; + return optInt(KEY_VERSION_CODE, -1); } public void setVersionCode(int versionCode) { @@ -79,7 +76,7 @@ public void setVersionCode(int versionCode) { } public String getIdentifier() { - return getString(KEY_IDENTIFIER); + return optString(KEY_IDENTIFIER, null); } public void setIdentifier(String identifier) { @@ -87,7 +84,7 @@ public void setIdentifier(String identifier) { } public String getTargetSdkVersion() { - return getString(KEY_TARGET_SDK_VERSION); + return optString(KEY_TARGET_SDK_VERSION, null); } public void setTargetSdkVersion(String targetSdkVersion) { @@ -95,7 +92,7 @@ public void setTargetSdkVersion(String targetSdkVersion) { } public String getAppStore() { - return getString(KEY_APP_STORE); + return optString(KEY_APP_STORE, null); } public void setAppStore(String appStore) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java index 10508dcb2..e55289be1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java @@ -56,10 +56,10 @@ public void setId(String id) { } public String getId() { - return getString(KEY_ID); + return optString(KEY_ID, null); } - public Double getCreatedAt() { // // FIXME: figure out primitive vs Double + public Double getCreatedAt() { return getDouble(KEY_CREATED_AT); } @@ -71,7 +71,8 @@ public Type getMessageType() { if (isNull(KEY_TYPE)) { return Type.CompoundMessage; } - return Type.parse(getString(KEY_TYPE)); + String typeString = optString(KEY_TYPE, null); + return typeString == null ? Type.unknown : Type.parse(typeString); } protected void setType(Type type) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java index a3c01e170..8abe514c4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java @@ -101,7 +101,7 @@ protected void initType() { // Get text message body, maybe empty @Override public String getBody() { - return getString(KEY_BODY); + return optString(KEY_BODY, null); } // Set text message body, maybe empty @@ -111,7 +111,7 @@ public void setBody(String body) { } public String getTitle() { - return getString(KEY_TITLE); + return optString(KEY_TITLE, null); } public void setTitle(String title) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index 1de9354f0..d63b38e46 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -1,12 +1,11 @@ /* - * Copyright (c) 2014, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ package com.apptentive.android.sdk.model; -import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; @@ -83,10 +82,7 @@ public void setManufacturer(String manufacturer) { } public String getModel() { - if (!isNull(KEY_MODEL)) { - return getString(KEY_MODEL); - } - return null; + return optString(KEY_MODEL, null); } public void setModel(String model) { @@ -110,7 +106,7 @@ public void setCpu(String cpu) { } public String getDevice() { - return getString(KEY_DEVICE); + return optString(KEY_DEVICE, null); } public void setDevice(String device) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index eaea20528..3e04818c1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -84,7 +84,7 @@ public EventPayload(String label, String interactionId, String data, Map customData) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index d1c54c0d7..cd28b11d7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -45,10 +45,7 @@ public String getHttpEndPoint(String conversationId) { //endregion public String getId() { - if (!isNull(KEY_ID)) { - return getString(KEY_ID); - } - return null; + return optString(KEY_ID, null); } public void setId(String id) { @@ -56,7 +53,7 @@ public void setId(String id) { } public String getEmail() { - return getString(KEY_EMAIL); + return optString(KEY_EMAIL, null); } public void setEmail(String email) { @@ -64,7 +61,7 @@ public void setEmail(String email) { } public String getName() { - return getString(KEY_NAME); + return optString(KEY_NAME, null); } public void setName(String name) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java index 929d29196..fc1b7e110 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkAndAppReleasePayload.java @@ -60,7 +60,7 @@ public String getHttpEndPoint(String conversationId) { //region Sdk getters/setters public String getVersion() { - return getString(KEY_SDK_VERSION); + return optString(KEY_SDK_VERSION, null); } public void setVersion(String version) { @@ -68,7 +68,7 @@ public void setVersion(String version) { } public String getProgrammingLanguage() { - return getString(KEY_SDK_PROGRAMMING_LANGUAGE); + return optString(KEY_SDK_PROGRAMMING_LANGUAGE, null); } public void setProgrammingLanguage(String programmingLanguage) { @@ -76,7 +76,7 @@ public void setProgrammingLanguage(String programmingLanguage) { } public String getAuthorName() { - return getString(KEY_SDK_AUTHOR_NAME); + return optString(KEY_SDK_AUTHOR_NAME, null); } public void setAuthorName(String authorName) { @@ -84,7 +84,7 @@ public void setAuthorName(String authorName) { } public String getAuthorEmail() { - return getString(KEY_SDK_AUTHOR_EMAIL); + return optString(KEY_SDK_AUTHOR_EMAIL, null); } public void setAuthorEmail(String authorEmail) { @@ -92,7 +92,7 @@ public void setAuthorEmail(String authorEmail) { } public String getPlatform() { - return getString(KEY_SDK_PLATFORM); + return optString(KEY_SDK_PLATFORM, null); } public void setPlatform(String platform) { @@ -100,7 +100,7 @@ public void setPlatform(String platform) { } public String getDistribution() { - return getString(KEY_SDK_DISTRIBUTION); + return optString(KEY_SDK_DISTRIBUTION, null); } public void setDistribution(String distribution) { @@ -108,7 +108,7 @@ public void setDistribution(String distribution) { } public String getDistributionVersion() { - return getString(KEY_SDK_DISTRIBUTION_VERSION); + return optString(KEY_SDK_DISTRIBUTION_VERSION, null); } public void setDistributionVersion(String distributionVersion) { @@ -119,7 +119,7 @@ public void setDistributionVersion(String distributionVersion) { //region AppRelease getters/setters public String getType() { - return getString(KEY_TYPE); + return optString(KEY_TYPE, null); } public void setType(String type) { @@ -127,7 +127,7 @@ public void setType(String type) { } public String getVersionName() { - return getString(KEY_VERSION_NAME); + return optString(KEY_VERSION_NAME, null); } public void setVersionName(String versionName) { @@ -135,7 +135,7 @@ public void setVersionName(String versionName) { } public int getVersionCode() { - return getInt(KEY_VERSION_CODE, -1); + return optInt(KEY_VERSION_CODE, -1); } public void setVersionCode(int versionCode) { @@ -143,7 +143,7 @@ public void setVersionCode(int versionCode) { } public String getIdentifier() { - return getString(KEY_IDENTIFIER); + return optString(KEY_IDENTIFIER, null); } public void setIdentifier(String identifier) { @@ -151,7 +151,7 @@ public void setIdentifier(String identifier) { } public String getTargetSdkVersion() { - return getString(KEY_TARGET_SDK_VERSION); + return optString(KEY_TARGET_SDK_VERSION, null); } public void setTargetSdkVersion(String targetSdkVersion) { @@ -159,7 +159,7 @@ public void setTargetSdkVersion(String targetSdkVersion) { } public String getAppStore() { - return getString(KEY_APP_STORE); + return optString(KEY_APP_STORE, null); } public void setAppStore(String appStore) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java index 20c8ca565..b1215c195 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SdkPayload.java @@ -50,10 +50,7 @@ public String getHttpRequestContentType() { //endregion public String getVersion() { - if (!isNull(KEY_VERSION)) { - return getString(KEY_VERSION); - } - return null; + return optString(KEY_VERSION, null); } public void setVersion(String version) { @@ -61,7 +58,7 @@ public void setVersion(String version) { } public String getProgrammingLanguage() { - return getString(KEY_PROGRAMMING_LANGUAGE); + return optString(KEY_PROGRAMMING_LANGUAGE, null); } public void setProgrammingLanguage(String programmingLanguage) { @@ -69,10 +66,7 @@ public void setProgrammingLanguage(String programmingLanguage) { } public String getAuthorName() { - if (!isNull(KEY_AUTHOR_NAME)) { - return getString(KEY_AUTHOR_NAME); - } - return null; + return optString(KEY_AUTHOR_NAME, null); } public void setAuthorName(String authorName) { @@ -80,10 +74,7 @@ public void setAuthorName(String authorName) { } public String getAuthorEmail() { - if (!isNull(KEY_AUTHOR_EMAIL)) { - return getString(KEY_AUTHOR_EMAIL); - } - return null; + return optString(KEY_AUTHOR_EMAIL, null); } public void setAuthorEmail(String authorEmail) { @@ -91,10 +82,7 @@ public void setAuthorEmail(String authorEmail) { } public String getPlatform() { - if (!isNull(KEY_PLATFORM)) { - return getString(KEY_PLATFORM); - } - return null; + return optString(KEY_PLATFORM, null); } public void setPlatform(String platform) { @@ -102,10 +90,7 @@ public void setPlatform(String platform) { } public String getDistribution() { - if (!isNull(KEY_DISTRIBUTION)) { - return getString(KEY_DISTRIBUTION); - } - return null; + return optString(KEY_DISTRIBUTION, null); } public void setDistribution(String distribution) { @@ -113,10 +98,7 @@ public void setDistribution(String distribution) { } public String getDistributionVersion() { - if (!isNull(KEY_DISTRIBUTION_VERSION)) { - return getString(KEY_DISTRIBUTION_VERSION); - } - return null; + return optString(KEY_DISTRIBUTION_VERSION, null); } public void setDistributionVersion(String distributionVersion) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index 93844cd02..9cdb68e71 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -56,7 +56,7 @@ public HttpRequestMethod getHttpRequestMethod() { //endregion public String getId() { - return getString(KEY_SURVEY_ID); + return optString(KEY_SURVEY_ID, null); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 86411a15a..600e0a576 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -393,7 +393,7 @@ private void upgradeVersion2to3(SQLiteDatabase db) { payload = LegacyPayloadFactory.createPayload(payloadType, json); // the legacy payload format didn't store 'nonce' in the database so we need to extract if from json - String nonce = payload.getString("nonce"); + String nonce = payload.optString("nonce", null); if (nonce == null) { nonce = UUID.randomUUID().toString(); // if 'nonce' is missing - generate a new one } From f9a33577811ff084326149600b356501328acb2f Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 6 Jun 2017 13:01:43 -0700 Subject: [PATCH 337/465] Move UUID generation into JsonPayload. Pull value from backing json. This fixes message sending. --- .../android/sdk/model/ConversationItem.java | 12 +----- .../android/sdk/model/JsonPayload.java | 37 ++++++++++++++----- .../apptentive/android/sdk/model/Payload.java | 16 +------- 3 files changed, 31 insertions(+), 34 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java index 7a99c0290..b8b844dac 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationItem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -10,22 +10,14 @@ import org.json.JSONException; -import java.util.UUID; - -/** - * @author Sky Kelsey - */ public abstract class ConversationItem extends JsonPayload { - protected static final String KEY_NONCE = "nonce"; protected static final String KEY_CLIENT_CREATED_AT = "client_created_at"; protected static final String KEY_CLIENT_CREATED_AT_UTC_OFFSET = "client_created_at_utc_offset"; protected ConversationItem(PayloadType type) { super(type); - put(KEY_NONCE, getNonce()); - double seconds = Util.currentTimeSeconds(); int utcOffset = Util.getUtcOffset(); @@ -51,6 +43,4 @@ public void setClientCreatedAt(double clientCreatedAt) { public void setClientCreatedAtUtcOffset(int clientCreatedAtUtcOffset) { put(KEY_CLIENT_CREATED_AT_UTC_OFFSET, clientCreatedAtUtcOffset); } - - } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index bb7f80694..e69b08025 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -13,15 +13,20 @@ import org.json.JSONException; import org.json.JSONObject; +import java.util.UUID; + import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; public abstract class JsonPayload extends Payload { + private static final String KEY_NONCE = "nonce"; + private final JSONObject jsonObject; public JsonPayload(PayloadType type) { super(type); jsonObject = new JSONObject(); + setNonce(UUID.randomUUID().toString()); } public JsonPayload(PayloadType type, String json) throws JSONException { @@ -97,15 +102,14 @@ protected void remove(String key) { // TODO: rename to removeKey jsonObject.remove(key); } - public String getString(String key) { - return jsonObject.optString(key); - } - - public int getInt(String key) { - return getInt(key, 0); + public String optString(String key, String fallback) { + if (!jsonObject.isNull(key)) { + return jsonObject.optString(key, fallback); + } + return null; } - public int getInt(String key, int defaultValue) { + public int optInt(String key, int defaultValue) { return jsonObject.optInt(key, defaultValue); } @@ -117,8 +121,13 @@ public boolean getBoolean(String key, boolean defaultValue) { return jsonObject.optBoolean(key, defaultValue); } - protected double getDouble(String key) { - return getDouble(key, 0.0); + protected Double getDouble(String key) { + try { + return jsonObject.getDouble(key); + } catch (Exception e) { + // Ignore. + } + return null; } protected double getDouble(String key, double defaultValue) { @@ -165,6 +174,16 @@ public String getHttpRequestContentType() { } } + @Override + public String getNonce() { + return optString(KEY_NONCE, null); + } + + @Override + public void setNonce(String nonce) { + put(KEY_NONCE, nonce); + } + //endregion protected JSONObject marshallForSending() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index c2e053b8f..d637ccfd4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -10,7 +10,6 @@ import com.apptentive.android.sdk.util.StringUtils; import java.util.List; -import java.util.UUID; public abstract class Payload { private final PayloadType payloadType; @@ -31,12 +30,6 @@ public abstract class Payload { */ protected String token; - /** - * A value that can be used to correlate a payload with another object - * (for example, to update the sent status of a message) - */ - private String nonce; - private List attachments; // TODO: Figure out attachment handling protected Payload(PayloadType type) { @@ -45,7 +38,6 @@ protected Payload(PayloadType type) { } this.payloadType = type; - nonce = UUID.randomUUID().toString(); } //region Data @@ -106,13 +98,9 @@ public void setToken(String token) { this.token = token; } - public String getNonce() { - return nonce; - } + public abstract String getNonce(); - public void setNonce(String nonce) { - this.nonce = nonce; - } + public abstract void setNonce(String nonce); public List getAttachments() { return attachments; From f2c4d5605490ed6b5c1571ed9ef1f76584beddf7 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 6 Jun 2017 13:56:14 -0700 Subject: [PATCH 338/465] Start using Person object for email, name, and ID, instead of keeping a copy in the Conversation. Also, when conversation is required for an API method, check for one instead of just checking for an initialized SDK. --- .../apptentive/android/sdk/Apptentive.java | 81 ++++++++++++------- .../android/sdk/ApptentiveInternal.java | 11 ++- .../sdk/conversation/Conversation.java | 24 ------ .../sdk/conversation/ConversationData.java | 39 +-------- .../sdk/conversation/ConversationManager.java | 2 +- .../messagecenter/model/MessageFactory.java | 8 +- .../sdk/storage/ConversationDataTest.java | 19 ----- 7 files changed, 66 insertions(+), 118 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index d70891e36..1e049d950 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -80,7 +80,7 @@ public static void setPersonEmail(String email) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { // FIXME: Make sure Person object diff is sent. - conversation.setPersonEmail(email); + conversation.getPerson().setEmail(email); } } } @@ -95,7 +95,7 @@ public static String getPersonEmail() { if (ApptentiveInternal.isApptentiveRegistered()) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { - return conversation.getPersonEmail(); + return conversation.getPerson().getEmail(); } } return null; @@ -115,7 +115,7 @@ public static void setPersonName(String name) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { // FIXME: Make sure Person object diff is sent. - conversation.setPersonName(name); + conversation.getPerson().setName(name); } } } @@ -130,7 +130,7 @@ public static String getPersonName() { if (ApptentiveInternal.isApptentiveRegistered()) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { - return conversation.getPersonName(); + return conversation.getPerson().getName(); } } return null; @@ -769,7 +769,10 @@ public static void setUnreadMessagesListener(UnreadMessagesListener listener) { */ public static void addUnreadMessagesListener(UnreadMessagesListener listener) { if (ApptentiveInternal.isApptentiveRegistered()) { - ApptentiveInternal.getInstance().getMessageManager().addHostUnreadMessagesListener(listener); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getMessageManager().addHostUnreadMessagesListener(listener); + } } } @@ -781,7 +784,10 @@ public static void addUnreadMessagesListener(UnreadMessagesListener listener) { public static int getUnreadMessageCount() { try { if (ApptentiveInternal.isApptentiveRegistered()) { - return ApptentiveInternal.getInstance().getMessageManager().getUnreadMessageCount(); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + return conversation.getMessageManager().getUnreadMessageCount(); + } } } catch (Exception e) { MetricModule.sendError(e, null, null); @@ -796,22 +802,25 @@ public static int getUnreadMessageCount() { * @param text The message you wish to send. */ public static void sendAttachmentText(String text) { - if (ApptentiveInternal.isApptentiveRegistered()) { - try { - CompoundMessage message = new CompoundMessage(); - message.setBody(text); - message.setRead(true); - message.setHidden(true); - message.setSenderId(ApptentiveInternal.getInstance().getPersonId()); - message.setAssociatedFiles(null); - MessageManager mgr = ApptentiveInternal.getInstance().getMessageManager(); - if (mgr != null) { - mgr.sendMessage(message); - } - } catch (Exception e) { - ApptentiveLog.w("Error sending attachment text.", e); - MetricModule.sendError(e, null, null); + if (!ApptentiveInternal.isConversationActive()) { + ApptentiveLog.i(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); + return; + } + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + try { + CompoundMessage message = new CompoundMessage(); + message.setBody(text); + message.setRead(true); + message.setHidden(true); + message.setSenderId(conversation.getPerson().getId()); + message.setAssociatedFiles(null); + MessageManager mgr = conversation.getMessageManager(); + if (mgr != null) { + mgr.sendMessage(message); } + } catch (Exception e) { + ApptentiveLog.w("Error sending attachment text.", e); + MetricModule.sendError(e, null, null); } } @@ -823,17 +832,22 @@ public static void sendAttachmentText(String text) { * @param uri The URI of the local resource file. */ public static void sendAttachmentFile(String uri) { - try { - if (TextUtils.isEmpty(uri) || !ApptentiveInternal.isApptentiveRegistered()) { - return; - } + if (!ApptentiveInternal.isConversationActive()) { + ApptentiveLog.i(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); + return; + } + if (TextUtils.isEmpty(uri)) { + return; + } + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + try { CompoundMessage message = new CompoundMessage(); // No body, just attachment message.setBody(null); message.setRead(true); message.setHidden(true); - message.setSenderId(ApptentiveInternal.getInstance().getPersonId()); + message.setSenderId(conversation.getPerson().getId()); ArrayList attachmentStoredFiles = new ArrayList(); /* Make a local copy in the cache dir. By default the file name is "apptentive-api-file + nonce" @@ -905,17 +919,22 @@ public static void sendAttachmentFile(byte[] content, String mimeType) { * @param mimeType The mime type of the file. */ public static void sendAttachmentFile(InputStream is, String mimeType) { - try { - if (is == null || !ApptentiveInternal.isApptentiveRegistered()) { - return; - } + if (!ApptentiveInternal.isConversationActive()) { + ApptentiveLog.i(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); + return; + } + if (is == null) { + return; + } + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + try { CompoundMessage message = new CompoundMessage(); // No body, just attachment message.setBody(null); message.setRead(true); message.setHidden(true); - message.setSenderId(ApptentiveInternal.getInstance().getPersonId()); + message.setSenderId(conversation.getPerson().getId()); ArrayList attachmentStoredFiles = new ArrayList(); String localFilePath = Util.generateCacheFilePathFromNonceOrPrefix(ApptentiveInternal.getInstance().getApplicationContext(), message.getNonce(), null); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 622e8a2c1..1b38af78e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -98,7 +98,6 @@ public class ApptentiveInternal implements ApptentiveNotificationObserver { private final String apptentiveKey; private final String apptentiveSignature; private String serverUrl; - private String personId; private String androidId; // FIXME: remove this field (never used) private String appPackageName; @@ -188,7 +187,11 @@ private ApptentiveInternal(Application application, String apptentiveKey, String } public static boolean isApptentiveRegistered() { - return (sApptentiveInternal != null); + return sApptentiveInternal != null; + } + + public static boolean isConversationActive() { + return sApptentiveInternal != null && sApptentiveInternal.getConversation() != null; } /** @@ -384,10 +387,6 @@ public boolean isApptentiveDebuggable() { return appRelease.isDebug(); } - public String getPersonId() { - return personId; - } - public SharedPreferences getGlobalSharedPrefs() { return globalSharedPrefs; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index e86ee7789..ee1a6bab9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -349,30 +349,6 @@ public void setConversationId(String conversationId) { conversationData.setConversationId(conversationId); } - public String getPersonId() { - return conversationData.getPersonId(); - } - - public void setPersonId(String personId) { - conversationData.setPersonId(personId); - } - - public String getPersonEmail() { - return conversationData.getPersonEmail(); - } - - public void setPersonEmail(String personEmail) { - conversationData.setPersonEmail(personEmail); - } - - public String getPersonName() { - return conversationData.getPersonName(); - } - - public void setPersonName(String personName) { - conversationData.setPersonName(personName); - } - public Device getDevice() { return conversationData.getDevice(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java index e9dbd1412..6745ba320 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java @@ -6,6 +6,7 @@ package com.apptentive.android.sdk.conversation; +import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.DataChangedListener; import com.apptentive.android.sdk.storage.Device; @@ -22,9 +23,6 @@ public class ConversationData implements Saveable, DataChangedListener { private String conversationToken; private String conversationId; - private String personId; - private String personEmail; - private String personName; private Device device; private Device lastSentDevice; private Person person; @@ -109,44 +107,12 @@ public void setConversationId(String conversationId) { } } - public String getPersonId() { - return personId; - } - - public void setPersonId(String personId) { - if (!StringUtils.equal(this.personId, personId)) { - this.personId = personId; - notifyDataChanged(); - } - } - - public String getPersonEmail() { - return personEmail; - } - - public void setPersonEmail(String personEmail) { - if (!StringUtils.equal(this.personEmail, personEmail)) { - this.personEmail = personEmail; - notifyDataChanged(); - } - } - - public String getPersonName() { - return personName; - } - - public void setPersonName(String personName) { - if (!StringUtils.equal(this.personName, personName)) { - this.personName = personName; - notifyDataChanged(); - } - } - public Device getDevice() { return device; } public void setDevice(Device device) { + Assert.assertNotNull(device, "Device may not be null."); this.device = device; device.setDataChangedListener(this); notifyDataChanged(); @@ -167,6 +133,7 @@ public Person getPerson() { } public void setPerson(Person person) { + Assert.assertNotNull(person, "Person may not be null."); this.person = person; this.person.setDataChangedListener(this); notifyDataChanged(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index f85684416..3e3ed50ca 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -373,7 +373,7 @@ public void onFinish(HttpJsonRequest request) { String personId = root.getString("person_id"); ApptentiveLog.d(CONVERSATION, "PersonId: " + personId); - conversation.setPersonId(personId); + conversation.getPerson().setId(personId); dispatchDebugEvent(EVT_CONVERSATION_DID_FETCH_TOKEN, true); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java index 7185957cf..da7615e55 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java @@ -8,6 +8,7 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.ApptentiveLogTag; import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.util.StringUtils; @@ -40,7 +41,12 @@ public static ApptentiveMessage fromJson(String json) { } catch (JSONException e) { // Ignore, senderId would be null } - String personId = ApptentiveInternal.getInstance() != null ? ApptentiveInternal.getInstance().getPersonId() : null; + // TODO: Should we pass the person ID in when we ask this object if it's outgoing instead? + if (!ApptentiveInternal.isConversationActive()) { + ApptentiveLog.d(ApptentiveLogTag.MESSAGES, "Can't load message because no active conversation."); + return null; + } + String personId = ApptentiveInternal.getInstance().getConversation().getPerson().getId(); // If senderId is null or same as the locally stored id, construct message as outgoing return new CompoundMessage(json, (senderId == null || (personId != null && senderId.equals(personId)))); case unknown: diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationDataTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationDataTest.java index 26f7c9a1c..194f714ae 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationDataTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/ConversationDataTest.java @@ -6,7 +6,6 @@ package com.apptentive.android.sdk.storage; -import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.conversation.ConversationData; import com.apptentive.android.sdk.util.Util; @@ -32,9 +31,6 @@ public void testSerialization() { ConversationData expected = new ConversationData(); expected.setConversationId("jvnuveanesndndnadldbj"); expected.setConversationToken("watgsiovncsagjmcneiusdolnfcs"); - expected.setPersonId("sijngmkmvewsnblkfmsd"); - expected.setPersonEmail("nvuewnfaslvbgflkanbx"); - expected.setPersonName("fslkgkdsnbnvwasdibncksd"); expected.setLastSeenSdkVersion("mdvnjfuoivsknbjgfaoskdl"); expected.setMessageCenterFeatureUsed(true); expected.setMessageCenterWhoCardPreviouslyDisplayed(false); @@ -66,9 +62,6 @@ public void testSerialization() { ConversationData actual = (ConversationData) ois.readObject(); assertEquals(expected.getConversationId(), actual.getConversationId()); assertEquals(expected.getConversationToken(), actual.getConversationToken()); - assertEquals(expected.getPersonId(), actual.getPersonId()); - assertEquals(expected.getPersonName(), actual.getPersonName()); - assertEquals(expected.getPersonEmail(), actual.getPersonEmail()); assertEquals(expected.getLastSeenSdkVersion(), actual.getLastSeenSdkVersion()); assertEquals(expected.isMessageCenterFeatureUsed(), actual.isMessageCenterFeatureUsed()); assertEquals(expected.isMessageCenterWhoCardPreviouslyDisplayed(), actual.isMessageCenterWhoCardPreviouslyDisplayed()); @@ -124,18 +117,6 @@ public void onDataChanged() { assertTrue(listenerFired); listenerFired = false; - data.setPersonId("foo"); - assertTrue(listenerFired); - listenerFired = false; - - data.setPersonEmail("foo"); - assertTrue(listenerFired); - listenerFired = false; - - data.setPersonName("foo"); - assertTrue(listenerFired); - listenerFired = false; - data.setLastSeenSdkVersion("foo"); assertTrue(listenerFired); listenerFired = false; From 48b68ddaa3f730e1b8d5e60302d5764af80e658d Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 7 Jun 2017 11:09:28 -0700 Subject: [PATCH 339/465] Add new util method that scales down a bitmap and appends it to a stream. --- .../android/sdk/util/image/ImageUtil.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageUtil.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageUtil.java index faa946dad..af34a388c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageUtil.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageUtil.java @@ -29,6 +29,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.lang.ref.WeakReference; import java.net.URL; @@ -300,6 +301,34 @@ public static boolean createScaledDownImageCacheFile(String sourcePath, String c return true; } + public static boolean appendScaledDownImageToStream(String sourcePath, OutputStream outputStream) { + // Retrieve image orientation + int imageOrientation = 0; + try { + ExifInterface exif = new ExifInterface(sourcePath); + imageOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + } catch (IOException e) { + } + + // Copy the file contents over. + CountingOutputStream cos = null; + try { + cos = new CountingOutputStream(new BufferedOutputStream(outputStream)); + System.gc(); + Bitmap smaller = ImageUtil.createScaledBitmapFromLocalImageSource(sourcePath, MAX_SENT_IMAGE_EDGE, MAX_SENT_IMAGE_EDGE, null, imageOrientation); + smaller.compress(Bitmap.CompressFormat.JPEG, 95, cos); + cos.flush(); + ApptentiveLog.v("Bitmap bytes appended, size = " + (cos.getBytesWritten() / 1024) + "k"); + smaller.recycle(); + return true; + } catch (Exception e) { + ApptentiveLog.a("Error storing image.", e); + return false; + } finally { + Util.ensureClosed(cos); + } + } + private static class DownloadImageTask extends AsyncTask { private WeakReference resultView; From 512e0849598ff19bb30af67d759efe27a148decb Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 7 Jun 2017 11:10:21 -0700 Subject: [PATCH 340/465] Add new util method that streams and appends a file's bytes to an output stream. --- .../com/apptentive/android/sdk/util/Util.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java index ce1a94655..839d192bf 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java @@ -694,6 +694,17 @@ public static void writeBytes(File file, byte[] bytes) throws IOException { } public static byte[] readBytes(File file) throws IOException { + ByteArrayOutputStream output = null; + try { + output = new ByteArrayOutputStream(); + appendFileToStream(file, output); + return output.toByteArray(); + } finally { + ensureClosed(output); + } + } + + public static void appendFileToStream(File file, OutputStream outputStream) throws IOException { if (file == null) { throw new IllegalArgumentException("'file' is null"); } @@ -707,15 +718,11 @@ public static byte[] readBytes(File file) throws IOException { } FileInputStream input = null; - ByteArrayOutputStream output = null; try { input = new FileInputStream(file); - output = new ByteArrayOutputStream(); - copy(input, output); - return output.toByteArray(); + copy(input, outputStream); } finally { ensureClosed(input); - ensureClosed(output); } } From 1f6bf9f32218fcfea95361001ff5b8d58426c62b Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 7 Jun 2017 11:11:24 -0700 Subject: [PATCH 341/465] Use ImageUtil to compress message attachments that are images. Use new method for attaching output files without needing a separate buffer. Clean up log levels. --- .../android/sdk/model/CompoundMessage.java | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java index 8abe514c4..4d2216213 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -8,13 +8,13 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.ApptentiveLogTag; import com.apptentive.android.sdk.encryption.Encryptor; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterUtil; import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.image.ImageItem; +import com.apptentive.android.sdk.util.image.ImageUtil; import org.json.JSONArray; import org.json.JSONException; @@ -29,11 +29,9 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -public class CompoundMessage extends ApptentiveMessage implements MultipartPayload, MessageCenterUtil.CompoundMessageCommonInterface { - - public static final int READ_CHUNK_SIZE = 128 * 1024; +import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; - public static final String KEY_MESSAGE = "message"; +public class CompoundMessage extends ApptentiveMessage implements MultipartPayload, MessageCenterUtil.CompoundMessageCommonInterface { private static final String KEY_BODY = "body"; public static final String KEY_TEXT_ONLY = "text_only"; @@ -335,7 +333,7 @@ public byte[] renderData() { // Then append attachments if (attachedFiles != null) { for (StoredFile storedFile : attachedFiles) { - ApptentiveLog.e("Starting and attachment."); + ApptentiveLog.v(PAYLOADS, "Starting to write an attachment part."); data.write(("--" + boundary + lineEnd).getBytes()); StringBuilder attachmentEnvelope = new StringBuilder(); attachmentEnvelope.append(String.format("Content-Disposition: form-data; name=\"file[]\"; filename=\"%s\"", storedFile.getFileName())).append(lineEnd) @@ -344,21 +342,19 @@ public byte[] renderData() { ByteArrayOutputStream attachmentBytes = new ByteArrayOutputStream(); FileInputStream fileInputStream = null; try { - ApptentiveLog.e("Writing attachment envelope: %s", attachmentEnvelope.toString()); + ApptentiveLog.v(PAYLOADS, "Writing attachment envelope: %s", attachmentEnvelope.toString()); attachmentBytes.write(attachmentEnvelope.toString().getBytes()); - // TODO: Move this into a utility method that returns a byte[] with a shrunk attachment (if it was an image). - // Look at ImageUtil.createScaledDownImageCacheFile() try { - byte[] buffer = new byte[READ_CHUNK_SIZE]; - int read; - fileInputStream = new FileInputStream(storedFile.getSourceUriOrPath()); - while ((read = fileInputStream.read(buffer, 0, READ_CHUNK_SIZE)) != -1) { - ApptentiveLog.e("Writing attachment bytes: %d", read); - attachmentBytes.write(buffer, 0, read); + if (Util.isMimeTypeImage(storedFile.getMimeType())) { + ApptentiveLog.v(PAYLOADS, "Appending image attachment."); + ImageUtil.appendScaledDownImageToStream(storedFile.getSourceUriOrPath(), attachmentBytes); + } else { + ApptentiveLog.v("Appending non-image attachment."); + Util.appendFileToStream(new File(storedFile.getSourceUriOrPath()), attachmentBytes); } } catch (Exception e) { - ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error reading Message Payload attachment: \"%s\".", e, storedFile.getLocalFilePath()); + ApptentiveLog.e(PAYLOADS, "Error reading Message Payload attachment: \"%s\".", e, storedFile.getLocalFilePath()); continue; } finally { Util.ensureClosed(fileInputStream); @@ -371,28 +367,29 @@ public byte[] renderData() { .append("Content-Disposition: form-data; name=\"file[]\"").append(lineEnd) .append("Content-Type: application/octet-stream").append(lineEnd) .append(lineEnd); - ApptentiveLog.e("Writing encrypted envelope: %s", encryptionEnvelope.toString()); + ApptentiveLog.v(PAYLOADS, "Writing encrypted envelope: %s", encryptionEnvelope.toString()); data.write(encryptionEnvelope.toString().getBytes()); - ApptentiveLog.e("Encrypting attachment bytes: %d", attachmentBytes.size()); + ApptentiveLog.v(PAYLOADS, "Encrypting attachment bytes: %d", attachmentBytes.size()); byte[] encryptedAttachment = encryptor.encrypt(attachmentBytes.toByteArray()); - ApptentiveLog.e("Writing encrypted attachment bytes: %d", encryptedAttachment.length); + ApptentiveLog.v(PAYLOADS, "Writing encrypted attachment bytes: %d", encryptedAttachment.length); data.write(encryptedAttachment); data.write("\r\n".getBytes()); } else { - ApptentiveLog.e("Writing attachment bytes: %d", attachmentBytes.size()); + ApptentiveLog.v(PAYLOADS, "Writing attachment bytes: %d", attachmentBytes.size()); data.write(attachmentBytes.toByteArray()); } } catch (Exception e) { - ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error getting data for Message Payload attachments.", e); + ApptentiveLog.e(PAYLOADS, "Error getting data for Message Payload attachments.", e); return null; } } } data.write(("--" + boundary + "--").getBytes()); } catch (Exception e) { - ApptentiveLog.e(ApptentiveLogTag.PAYLOADS, "Error assembling Message Payload.", e); + ApptentiveLog.e(PAYLOADS, "Error assembling Message Payload.", e); return null; } + ApptentiveLog.d(PAYLOADS, "Total payload body bytes: %d", data.size()); return data.toByteArray(); } } From 823f108ff70e2688494ae29a1cf4703f62ea00c3 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 7 Jun 2017 11:20:50 -0700 Subject: [PATCH 342/465] Make sure each part of a multipart request has a carriage return and newline after it, before the next boundary. --- .../java/com/apptentive/android/sdk/model/CompoundMessage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java index 4d2216213..206b359e1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java @@ -373,11 +373,11 @@ public byte[] renderData() { byte[] encryptedAttachment = encryptor.encrypt(attachmentBytes.toByteArray()); ApptentiveLog.v(PAYLOADS, "Writing encrypted attachment bytes: %d", encryptedAttachment.length); data.write(encryptedAttachment); - data.write("\r\n".getBytes()); } else { ApptentiveLog.v(PAYLOADS, "Writing attachment bytes: %d", attachmentBytes.size()); data.write(attachmentBytes.toByteArray()); } + data.write("\r\n".getBytes()); } catch (Exception e) { ApptentiveLog.e(PAYLOADS, "Error getting data for Message Payload attachments.", e); return null; From 7d11799c1971342774583d012a3098276676eae4 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 7 Jun 2017 14:12:44 -0700 Subject: [PATCH 343/465] Set Sender ID properly on messages sent from message center. --- .../interaction/fragment/MessageCenterFragment.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index 6c4757d29..503b6bd86 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -874,6 +874,11 @@ public void onFinishComposing() { compoundMessage.setCustomData(ApptentiveInternal.getInstance().getAndClearCustomData()); compoundMessage.setAssociatedImages(new ArrayList(pendingAttachments)); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null && conversation.hasActiveState()) { + compoundMessage.setSenderId(conversation.getPerson().getId()); + } + messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_START_SENDING, compoundMessage)); composingViewSavedState = null; composerEditText.getText().clear(); From f8907011f03b02ea5248b9ad822cf2c5b48182ba Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 7 Jun 2017 14:55:31 -0700 Subject: [PATCH 344/465] Remove MultipartPayload interface, since we don't need it anymore. --- .../sdk/comm/ApptentiveHttpClient.java | 36 +---- .../android/sdk/model/CompoundMessage.java | 3 +- .../android/sdk/model/MultipartPayload.java | 16 -- .../sdk/network/HttpJsonMultipartRequest.java | 146 ------------------ 4 files changed, 2 insertions(+), 199 deletions(-) delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/MultipartPayload.java delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 0b6ff59d2..e74e9d18b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -1,10 +1,7 @@ package com.apptentive.android.sdk.comm; import com.apptentive.android.sdk.model.ConversationTokenRequest; -import com.apptentive.android.sdk.model.MultipartPayload; import com.apptentive.android.sdk.model.PayloadData; -import com.apptentive.android.sdk.model.StoredFile; -import com.apptentive.android.sdk.network.HttpJsonMultipartRequest; import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestManager; @@ -17,8 +14,6 @@ import org.json.JSONException; import org.json.JSONObject; -import java.util.List; - import static com.apptentive.android.sdk.debug.Assert.notNull; /** @@ -134,15 +129,7 @@ private HttpRequest createPayloadRequest(PayloadData payload) { final HttpRequestMethod requestMethod = notNull(payload.getHttpRequestMethod()); final String contentType = notNull(payload.getContentType()); - // TODO: figure out a better solution - // TODO: This isn't working. The payload is only ever of type PayloadData. This never got migrated. - HttpRequest request; - if (payload instanceof MultipartPayload) { - final List associatedFiles = ((MultipartPayload) payload).getAssociatedFiles(); - request = createMultipartRequest(httpPath, payload.getData(), associatedFiles, requestMethod, contentType); - } else { - request = createRawRequest(httpPath, payload.getData(), requestMethod, contentType); - } + HttpRequest request = createRawRequest(httpPath, payload.getData(), requestMethod, contentType); // Encrypted requests don't use an Auth token on the request. It's stored in the encrypted body. if (!StringUtils.isNullOrEmpty(authToken)) { @@ -201,27 +188,6 @@ private RawHttpRequest createRawRequest(String endpoint, byte[] data, HttpReques return request; } - private HttpJsonMultipartRequest createMultipartRequest(String endpoint, byte[] data, List files, HttpRequestMethod method, String contentType) { - if (endpoint == null) { - throw new IllegalArgumentException("Endpoint is null"); - } - if (data == null) { - throw new IllegalArgumentException("Data is null"); - } - if (method == null) { - throw new IllegalArgumentException("Method is null"); - } - if (contentType == null) { - throw new IllegalArgumentException("ContentType is null"); - } - - String url = createEndpointURL(endpoint); - HttpJsonMultipartRequest request = new HttpJsonMultipartRequest(url, data, files, contentType); - setupRequestDefaults(request); - request.setMethod(method); - return request; - } - private void setupRequestDefaults(HttpRequest request) { request.setRequestProperty("User-Agent", userAgentString); request.setRequestProperty("Connection", "Keep-Alive"); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java index 206b359e1..074ac453d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java @@ -31,7 +31,7 @@ import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; -public class CompoundMessage extends ApptentiveMessage implements MultipartPayload, MessageCenterUtil.CompoundMessageCommonInterface { +public class CompoundMessage extends ApptentiveMessage implements MessageCenterUtil.CompoundMessageCommonInterface { private static final String KEY_BODY = "body"; public static final String KEY_TEXT_ONLY = "text_only"; @@ -185,7 +185,6 @@ public boolean setAssociatedFiles(List attachedFiles) { } } - @Override public List getAssociatedFiles() { if (hasNoAttachments) { return null; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/MultipartPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/MultipartPayload.java deleted file mode 100644 index bd82c936c..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/MultipartPayload.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.model; - -import java.util.List; - -/** - * Interface for payloads which should be send with an http-multipart request - */ -public interface MultipartPayload { - List getAssociatedFiles(); -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java deleted file mode 100644 index 0192cc76d..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpJsonMultipartRequest.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.network; - -import com.apptentive.android.sdk.model.StoredFile; -import com.apptentive.android.sdk.util.StringUtils; -import com.apptentive.android.sdk.util.Util; -import com.apptentive.android.sdk.util.image.ImageUtil; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.List; -import java.util.UUID; - -/** - * Class representing HTTP multipart request with Json body - */ -public class HttpJsonMultipartRequest extends HttpRequest { - private static final String lineEnd = "\r\n"; - private static final String twoHyphens = "--"; - - private final byte[] requestData; - private final List files; - private final String boundary; - - public HttpJsonMultipartRequest(String urlString, byte[] requestData, List files, String contentType) { - super(urlString); - - if (files == null) { - throw new IllegalArgumentException("Files reference is null"); - } - this.files = files; - this.requestData = requestData; - - boundary = UUID.randomUUID().toString(); - setRequestProperty("Content-Type", String.format("%s;boundary=%s", contentType, boundary)); - } - - @Override - protected byte[] createRequestData() throws IOException { - ByteArrayOutputStream stream = null; - try { - // get payload bytes - final byte[] jsonData = super.createRequestData(); - - // write data - stream = new ByteArrayOutputStream(); - writeRequestData(new DataOutputStream(stream), jsonData); - return stream.toByteArray(); - } finally { - Util.ensureClosed(stream); - } - } - - private void writeRequestData(DataOutputStream out, byte[] jsonData) throws IOException { - out.writeBytes(twoHyphens + boundary + lineEnd); - - // Write text message - out.writeBytes("Content-Disposition: form-data; name=\"message\"" + lineEnd); - // Indicate the character encoding is UTF-8 - out.writeBytes("Content-Type: text/plain;charset=UTF-8" + lineEnd); - - out.writeBytes(lineEnd); - - // Explicitly encode message json - out.write(jsonData); - out.writeBytes(lineEnd); - - // Send associated files - if (files != null) { - writeFiles(out, files); - } - out.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); - } - - private void writeFiles(DataOutputStream out, List files) throws IOException { - for (StoredFile file : files) { - writeFile(out, file); - } - } - - private void writeFile(DataOutputStream out, StoredFile file) throws IOException { - String cachedImagePathString = file.getLocalFilePath(); - String originalFilePath = file.getSourceUriOrPath(); - File cachedImageFile = new File(cachedImagePathString); - - // No local cache found - if (!cachedImageFile.exists()) { - boolean cachedCreated = false; - if (Util.isMimeTypeImage(file.getMimeType())) { - // Create a scaled down version of original image - cachedCreated = ImageUtil.createScaledDownImageCacheFile(originalFilePath, cachedImagePathString); - } else { - // For non-image file, just copy to a cache file - if (Util.createLocalStoredFile(originalFilePath, cachedImagePathString, null) != null) { - cachedCreated = true; - } - } - - if (!cachedCreated) { - return; - } - } - out.writeBytes(twoHyphens + boundary + lineEnd); - StringBuilder requestText = new StringBuilder(); - String fileFullPathName = originalFilePath; - if (StringUtils.isNullOrEmpty(fileFullPathName)) { - fileFullPathName = cachedImagePathString; - } - requestText.append(String.format("Content-Disposition: form-data; name=\"file[]\"; filename=\"%s\"", fileFullPathName)).append(lineEnd); - requestText.append("Content-Type: ").append(file.getMimeType()).append(lineEnd); - - // Write file attributes - out.writeBytes(requestText.toString()); - out.writeBytes(lineEnd); - - FileInputStream fis = null; - try { - fis = new FileInputStream(cachedImageFile); - - int bytesAvailable = fis.available(); - int maxBufferSize = 512 * 512; - int bufferSize = Math.min(bytesAvailable, maxBufferSize); - byte[] buffer = new byte[bufferSize]; - - // read image data 0.5MB at a time and write it into buffer - int bytesRead = fis.read(buffer, 0, bufferSize); - while (bytesRead > 0) { - out.write(buffer, 0, bufferSize); - bytesAvailable = fis.available(); - bufferSize = Math.min(bytesAvailable, maxBufferSize); - bytesRead = fis.read(buffer, 0, bufferSize); - } - } finally { - Util.ensureClosed(fis); - } - out.writeBytes(lineEnd); - } -} From 8db78641d592cce7065498336c07cf4a13ab8c45 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 7 Jun 2017 15:11:44 -0700 Subject: [PATCH 345/465] Move `API_VERSION` into `Constants` --- .../apptentive/android/sdk/comm/ApptentiveClient.java | 6 ++---- .../android/sdk/comm/ApptentiveHttpClient.java | 9 +++++++-- .../android/sdk/util/task/ApptentiveDownloaderTask.java | 3 ++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index ad0304874..c8613beee 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -28,8 +28,6 @@ public class ApptentiveClient { - public static final int API_VERSION = 9; - private static final String USER_AGENT_STRING = "Apptentive/%s (Android)"; // Format with SDK version string. public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 45000; @@ -135,7 +133,7 @@ private static ApptentiveHttpResponse performHttpRequest(String authToken, boole } connection.setRequestProperty("Accept-Encoding", "gzip"); connection.setRequestProperty("Accept", "application/json"); - connection.setRequestProperty("X-API-Version", String.valueOf(API_VERSION)); + connection.setRequestProperty("X-API-Version", String.valueOf(Constants.API_VERSION)); connection.setRequestProperty("APPTENTIVE-KEY", notNull(ApptentiveInternal.getInstance().getApptentiveKey())); connection.setRequestProperty("APPTENTIVE-SIGNATURE", notNull(ApptentiveInternal.getInstance().getApptentiveSignature())); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index e74e9d18b..de458097d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + package com.apptentive.android.sdk.comm; import com.apptentive.android.sdk.model.ConversationTokenRequest; @@ -20,7 +26,6 @@ * Class responsible for all client-server network communications using asynchronous HTTP requests */ public class ApptentiveHttpClient implements PayloadRequestSender { - private static final String API_VERSION = "9"; // TODO: get rid of duplication in ApptentiveClient private static final String USER_AGENT_STRING = "Apptentive/%s (Android)"; // Format with SDK version string. @@ -195,7 +200,7 @@ private void setupRequestDefaults(HttpRequest request) { request.setRequestProperty("Accept", "application/json"); request.setRequestProperty("APPTENTIVE-KEY", apptentiveKey); request.setRequestProperty("APPTENTIVE-SIGNATURE", apptentiveSignature); - request.setRequestProperty("X-API-Version", API_VERSION); + request.setRequestProperty("X-API-Version", String.valueOf(Constants.API_VERSION)); request.setConnectTimeout(DEFAULT_HTTP_CONNECT_TIMEOUT); request.setReadTimeout(DEFAULT_HTTP_SOCKET_TIMEOUT); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java index 65470057f..1d69c04ee 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java @@ -26,6 +26,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; +import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; /** @@ -134,7 +135,7 @@ private ApptentiveHttpResponse downloadBitmap(String urlString, String destFileP if (bRequestRedirectThroughApptentive) { connection.setRequestProperty("User-Agent", ApptentiveClient.getUserAgentString()); connection.setRequestProperty("Authorization", "OAuth " + conversationToken); - connection.setRequestProperty("X-API-Version", String.valueOf(ApptentiveClient.API_VERSION)); + connection.setRequestProperty("X-API-Version", String.valueOf(Constants.API_VERSION)); } else if (cookies != null) { connection.setRequestProperty("Cookie", cookies); } From 06e8094a4d2b8249c91c1ddd25c8d9255586e646 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 7 Jun 2017 17:15:24 -0700 Subject: [PATCH 346/465] 1. Rename the payload file paths to indicate they aren't just for multipart. 2. Stop using the DataSource in the DB, since that information comes in on the Payload now. 3. Recover from missing payload data files by deleting them and looking for the next payload to send. --- .../storage/ApptentiveDatabaseHelperTest.java | 12 +---- .../sdk/storage/ApptentiveDatabaseHelper.java | 52 +++++++++---------- .../sdk/storage/ApptentiveTaskManager.java | 18 +------ .../android/sdk/util/Constants.java | 6 +++ 4 files changed, 33 insertions(+), 55 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java index 5598f3778..39ae07744 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java @@ -88,17 +88,7 @@ class ApptentiveDatabaseMockHelper extends ApptentiveDatabaseHelper { " ORDER BY " + PayloadEntry.COLUMN_PRIMARY_KEY; ApptentiveDatabaseMockHelper(Context context) { - super(context, new DataSource() { - @Override - public String getConversationId() { - throw new RuntimeException("Implement me"); - } - - @Override - public String getAuthToken() { - throw new RuntimeException("Implement me"); - } - }); + super(context); } List listPayloads(SQLiteDatabase db) throws JSONException { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 600e0a576..1384a85a3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -24,6 +24,7 @@ import com.apptentive.android.sdk.model.StoredFile; import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.storage.legacy.LegacyPayloadFactory; +import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; @@ -37,6 +38,7 @@ import static com.apptentive.android.sdk.ApptentiveLogTag.DATABASE; import static com.apptentive.android.sdk.debug.Assert.notNull; +import static com.apptentive.android.sdk.util.Constants.PAYLOAD_DATA_FILE_SUFFIX; /** * There can be only one. SQLiteOpenHelper per database name that is. All new Apptentive tables must be defined here. @@ -47,11 +49,9 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { public static final String DATABASE_NAME = "apptentive"; private static final int TRUE = 1; private static final int FALSE = 0; - private final DataSource dataSource; private final File fileDir; // data dir of the application - /** We store temporary multipart payload body files here */ - private final File multipartPayloadDataDir; + private final File payloadDataDir; //region Payload SQL @@ -95,10 +95,10 @@ static final class LegacyPayloadEntry { "SELECT * FROM " + LegacyPayloadEntry.TABLE_NAME + " ORDER BY " + LegacyPayloadEntry.PAYLOAD_KEY_DB_ID; - private static final String SQL_QUERY_PAYLOAD_GET_NEXT_TO_SEND = + private static final String SQL_QUERY_PAYLOAD_GET_IN_SEND_ORDER = "SELECT * FROM " + PayloadEntry.TABLE_NAME + " ORDER BY " + PayloadEntry.COLUMN_PRIMARY_KEY + - " ASC LIMIT 1"; + " ASC"; private static final String SQL_QUERY_UPDATE_INCOMPLETE_PAYLOADS = "UPDATE " + PayloadEntry.TABLE_NAME + " SET " + @@ -200,15 +200,10 @@ static final class LegacyPayloadEntry { // endregion - ApptentiveDatabaseHelper(Context context, DataSource dataSource) { + ApptentiveDatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); - if (dataSource == null) { - throw new IllegalArgumentException("Data source is null"); - } - - this.dataSource = dataSource; this.fileDir = context.getFilesDir(); - this.multipartPayloadDataDir = new File(fileDir, "multipart-payloads"); // FIXME: figure out a better directory structure + this.payloadDataDir = new File(fileDir, Constants.PAYLOAD_DATA_DIR); } //region Create & Upgrade @@ -405,7 +400,7 @@ private void upgradeVersion2to3(SQLiteDatabase db) { addPayload(payloads.toArray(new Payload[payloads.size()])); } catch (Exception e) { - ApptentiveLog.e(DATABASE, "getOldestUnsentPayload EXCEPTION: " + e.getMessage()); + ApptentiveLog.e(DATABASE, "upgradeVersion2to3 EXCEPTION: " + e.getMessage()); } finally { ensureClosed(cursor); } @@ -440,7 +435,7 @@ void addPayload(Payload... payloads) { StringUtils.isNullOrEmpty(payload.getConversationId()) ? "${conversationId}" : payload.getConversationId()) // if conversation id is missing we replace it with a place holder and update it later ); - File dest = getMultipartFile(payload.getNonce()); + File dest = getPayloadBodyFile(payload.getNonce()); ApptentiveLog.v(DATABASE, "Saving payload body to: %s", dest); Util.writeBytes(dest, payload.renderData()); @@ -473,7 +468,7 @@ void deletePayload(String payloadIdentifier) { } // Then delete the data file - File dest = getMultipartFile(payloadIdentifier); + File dest = getPayloadBodyFile(payloadIdentifier); ApptentiveLog.v(DATABASE, "Deleted payload \"%s\" data file successfully? %b", payloadIdentifier, dest.delete()); } @@ -494,8 +489,8 @@ public PayloadData getOldestUnsentPayload() { Cursor cursor = null; try { db = getWritableDatabase(); - cursor = db.rawQuery(SQL_QUERY_PAYLOAD_GET_NEXT_TO_SEND, null); - if (cursor.moveToFirst()) { + cursor = db.rawQuery(SQL_QUERY_PAYLOAD_GET_IN_SEND_ORDER, null); + while(cursor.moveToNext()) { final String conversationId = cursor.getString(PayloadEntry.COLUMN_CONVERSATION_ID.index); if (conversationId == null) { return null; @@ -512,15 +507,22 @@ public PayloadData getOldestUnsentPayload() { final String nonce = notNull(cursor.getString(PayloadEntry.COLUMN_IDENTIFIER.index)); // TODO: We need a migration for existing payload bodies to put them into files. - byte[] data = Util.readBytes(getMultipartFile(nonce)); + + File file = getPayloadBodyFile(nonce); + if (!file.exists()) { + ApptentiveLog.w("Oldest unsent payload had no data file. Deleting."); + deletePayload(nonce); + continue; + } + byte[] data = Util.readBytes(file); final String contentType = notNull(cursor.getString(PayloadEntry.COLUMN_CONTENT_TYPE.index)); final HttpRequestMethod httpRequestMethod = HttpRequestMethod.valueOf(notNull(cursor.getString(PayloadEntry.COLUMN_REQUEST_METHOD.index))); final boolean encrypted = cursor.getInt(PayloadEntry.COLUMN_ENCRYPTED.index) == 1; return new PayloadData(payloadType, nonce, data, authToken, contentType, httpRequestPath, httpRequestMethod, encrypted); } return null; - } catch (Exception sqe) { - ApptentiveLog.e(DATABASE, "getOldestUnsentPayload EXCEPTION: " + sqe.getMessage()); + } catch (Exception e) { + ApptentiveLog.e("Error getting oldest unsent payload.", e); return null; } finally { ensureClosed(cursor); @@ -636,8 +638,8 @@ boolean addCompoundMessageFiles(List associatedFiles) { // region Helpers - private File getMultipartFile(String nonce) { - return new File(multipartPayloadDataDir, nonce + ".multipart"); + private File getPayloadBodyFile(String nonce) { + return new File(payloadDataDir, nonce + PAYLOAD_DATA_FILE_SUFFIX); } private void ensureClosed(Cursor cursor) { @@ -662,12 +664,6 @@ public void reset(Context context) { //region Helper classes - interface DataSource { - String getConversationId(); - - String getAuthToken(); - } - private static final class DatabaseColumn { public final String name; public final int index; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 66bc8976b..5af12c7b0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -49,7 +49,7 @@ import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; -public class ApptentiveTaskManager implements PayloadStore, EventStore, ApptentiveDatabaseHelper.DataSource, ApptentiveNotificationObserver, PayloadSender.Listener { +public class ApptentiveTaskManager implements PayloadStore, EventStore, ApptentiveNotificationObserver, PayloadSender.Listener { private final ApptentiveDatabaseHelper dbHelper; private final ThreadPoolExecutor singleThreadExecutor; // TODO: replace with a private concurrent dispatch queue @@ -66,7 +66,7 @@ public class ApptentiveTaskManager implements PayloadStore, EventStore, Apptenti * Creates an asynchronous task manager with one worker thread. This constructor must be invoked on the UI thread. */ public ApptentiveTaskManager(Context context, ApptentiveHttpClient apptentiveHttpClient) { - dbHelper = new ApptentiveDatabaseHelper(context, this); + dbHelper = new ApptentiveDatabaseHelper(context); /* When a new database task is submitted, the executor has the following behaviors: * 1. If the thread pool has no thread yet, it creates a single worker thread. * 2. If the single worker thread is running with tasks, it queues tasks. @@ -296,18 +296,4 @@ public void run() { appInBackground = true; } } - - //region ApptentiveDatabaseHelper.DataSource - - @Override - public String getConversationId() { - return currentConversationId; - } - - @Override - public String getAuthToken() { - return currentConversationToken; - } - - //endregion } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 31c6b0897..87356cb16 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -8,6 +8,7 @@ public class Constants { + public static final int API_VERSION = 9; public static final String APPTENTIVE_SDK_VERSION = "4.0.0"; @@ -61,6 +62,11 @@ public class Constants { public static final String MANIFEST_KEY_APPTENTIVE_DEBUG = "apptentive_debug"; //endregion + //region Database and File Storage + public static final String PAYLOAD_DATA_DIR = "\"payload-datas\""; + public static final String PAYLOAD_DATA_FILE_SUFFIX = ".payloads"; + //endregion + // region Keys used to access old data for migration public static final String PREF_KEY_CONVERSATION_TOKEN = "conversationToken"; public static final String PREF_KEY_CONVERSATION_ID = "conversationId"; From 9c508c17d5ea262273597ab59d3f898a24c21d8f Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 8 Jun 2017 13:28:28 -0700 Subject: [PATCH 347/465] Add proper and consistent checks for active SDK or Conversation, and wrap contents in try/catch for public API methods. --- .../apptentive/android/sdk/Apptentive.java | 155 +++++++++++------- .../module/engagement/EngagementModule.java | 7 +- 2 files changed, 102 insertions(+), 60 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 1e049d950..05cd5672f 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -720,11 +720,13 @@ public static boolean showMessageCenter(Context context) { */ public static boolean showMessageCenter(Context context, Map customData) { try { - if (ApptentiveInternal.isApptentiveRegistered()) { - return ApptentiveInternal.getInstance().showMessageCenterInternal(context, customData); + if (!ApptentiveInternal.isConversationActive()) { + ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "No active Conversation."); + return false; } + return ApptentiveInternal.getInstance().showMessageCenterInternal(context, customData); } catch (Exception e) { - ApptentiveLog.w("Error starting Apptentive Activity.", e); + ApptentiveLog.w("Error in Apptentive.showMessageCenter()", e); MetricModule.sendError(e, null, null); } return false; @@ -737,8 +739,15 @@ public static boolean showMessageCenter(Context context, Map cus * @return true if a call to {@link #showMessageCenter(Context)} will display Message Center, else false. */ public static boolean canShowMessageCenter() { - if (ApptentiveInternal.isApptentiveRegistered()) { + try { + if (!ApptentiveInternal.isConversationActive()) { + ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "No active Conversation."); + return false; + } return ApptentiveInternal.getInstance().canShowMessageCenterInternal(); + } catch (Exception e) { + ApptentiveLog.w("Error in Apptentive.canShowMessageCenter()", e); + MetricModule.sendError(e, null, null); } return false; } @@ -755,8 +764,15 @@ public static boolean canShowMessageCenter() { */ @Deprecated public static void setUnreadMessagesListener(UnreadMessagesListener listener) { - if (ApptentiveInternal.isApptentiveRegistered()) { + try { + if (!ApptentiveInternal.isConversationActive()) { + ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "No active Conversation."); + return; + } ApptentiveInternal.getInstance().getMessageManager().setHostUnreadMessagesListener(listener); + } catch (Exception e) { + ApptentiveLog.w("Error in Apptentive.setUnreadMessagesListener()", e); + MetricModule.sendError(e, null, null); } } @@ -768,11 +784,18 @@ public static void setUnreadMessagesListener(UnreadMessagesListener listener) { * allows us to keep a weak reference to avoid memory leaks. */ public static void addUnreadMessagesListener(UnreadMessagesListener listener) { - if (ApptentiveInternal.isApptentiveRegistered()) { + try { + if (!ApptentiveInternal.isConversationActive()) { + ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "No active Conversation."); + return; + } Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { conversation.getMessageManager().addHostUnreadMessagesListener(listener); } + } catch (Exception e) { + ApptentiveLog.w("Error in Apptentive.addUnreadMessagesListener()", e); + MetricModule.sendError(e, null, null); } } @@ -783,13 +806,14 @@ public static void addUnreadMessagesListener(UnreadMessagesListener listener) { */ public static int getUnreadMessageCount() { try { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - return conversation.getMessageManager().getUnreadMessageCount(); - } + if (!ApptentiveInternal.isConversationActive()) { + ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "No active Conversation."); + return 0; } + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + return conversation.getMessageManager().getUnreadMessageCount(); } catch (Exception e) { + ApptentiveLog.w("Error in Apptentive.getUnreadMessageCount()", e); MetricModule.sendError(e, null, null); } return 0; @@ -802,12 +826,12 @@ public static int getUnreadMessageCount() { * @param text The message you wish to send. */ public static void sendAttachmentText(String text) { - if (!ApptentiveInternal.isConversationActive()) { - ApptentiveLog.i(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); - return; - } - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); try { + if (!ApptentiveInternal.isConversationActive()) { + ApptentiveLog.i(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); + return; + } + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); CompoundMessage message = new CompoundMessage(); message.setBody(text); message.setRead(true); @@ -819,7 +843,7 @@ public static void sendAttachmentText(String text) { mgr.sendMessage(message); } } catch (Exception e) { - ApptentiveLog.w("Error sending attachment text.", e); + ApptentiveLog.w("Error in Apptentive.sendAttachmentText(String)", e); MetricModule.sendError(e, null, null); } } @@ -832,16 +856,15 @@ public static void sendAttachmentText(String text) { * @param uri The URI of the local resource file. */ public static void sendAttachmentFile(String uri) { - if (!ApptentiveInternal.isConversationActive()) { - ApptentiveLog.i(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); - return; - } - if (TextUtils.isEmpty(uri)) { - return; - } - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - try { + if (!ApptentiveInternal.isConversationActive()) { + ApptentiveLog.i(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); + return; + } + if (TextUtils.isEmpty(uri)) { + return; + } + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); CompoundMessage message = new CompoundMessage(); // No body, just attachment message.setBody(null); @@ -883,9 +906,8 @@ public static void sendAttachmentFile(String uri) { if (mgr != null) { mgr.sendMessage(message); } - } catch (Exception e) { - ApptentiveLog.w("Error sending attachment file.", e); + ApptentiveLog.w("Error in Apptentive.sendAttachmentFile(String)", e); MetricModule.sendError(e, null, null); } } @@ -899,7 +921,11 @@ public static void sendAttachmentFile(String uri) { * @param mimeType The mime type of the file. */ public static void sendAttachmentFile(byte[] content, String mimeType) { - if (ApptentiveInternal.isApptentiveRegistered()) { + try { + if (!ApptentiveInternal.isConversationActive()) { + ApptentiveLog.i(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); + return; + } ByteArrayInputStream is = null; try { is = new ByteArrayInputStream(content); @@ -907,6 +933,9 @@ public static void sendAttachmentFile(byte[] content, String mimeType) { } finally { Util.ensureClosed(is); } + } catch (Exception e) { + ApptentiveLog.w("Error in Apptentive.sendAttachmentFile(byte[], String)", e); + MetricModule.sendError(e, null, null); } } @@ -919,16 +948,15 @@ public static void sendAttachmentFile(byte[] content, String mimeType) { * @param mimeType The mime type of the file. */ public static void sendAttachmentFile(InputStream is, String mimeType) { - if (!ApptentiveInternal.isConversationActive()) { - ApptentiveLog.i(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); - return; - } - if (is == null) { - return; - } - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - try { + if (!ApptentiveInternal.isConversationActive()) { + ApptentiveLog.i(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); + return; + } + if (is == null) { + return; + } + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); CompoundMessage message = new CompoundMessage(); // No body, just attachment message.setBody(null); @@ -954,7 +982,7 @@ public static void sendAttachmentFile(InputStream is, String mimeType) { message.setAssociatedFiles(attachmentStoredFiles); ApptentiveInternal.getInstance().getMessageManager().sendMessage(message); } catch (Exception e) { - ApptentiveLog.w("Error sending attachment file.", e); + ApptentiveLog.w("Error in Apptentive.sendAttachmentFile(InputStream, String)", e); MetricModule.sendError(e, null, null); } } @@ -1047,8 +1075,11 @@ public static synchronized boolean willShowInteraction(String event) { */ public static synchronized boolean canShowInteraction(String event) { try { - return EngagementModule.canShowInteraction("local", "app", event); + if (ApptentiveInternal.isConversationActive()) { + return EngagementModule.canShowInteraction("local", "app", event); + } } catch (Exception e) { + ApptentiveLog.w("Error in Apptentive.canShowInteraction()", e); MetricModule.sendError(e, null, null); } return false; @@ -1064,9 +1095,14 @@ public static synchronized boolean canShowInteraction(String event) { * to call when the survey is finished. */ public static void setOnSurveyFinishedListener(OnSurveyFinishedListener listener) { - ApptentiveInternal internal = ApptentiveInternal.getInstance(); - if (internal != null) { - internal.setOnSurveyFinishedListener(listener); + try { + ApptentiveInternal internal = ApptentiveInternal.getInstance(); + if (internal != null) { + internal.setOnSurveyFinishedListener(listener); + } + } catch (Exception e) { + ApptentiveLog.w("Error in Apptentive.setOnSurveyFinishedListener()", e); + MetricModule.sendError(e, null, null); } } @@ -1076,22 +1112,26 @@ public static void setOnSurveyFinishedListener(OnSurveyFinishedListener listener * Starts login process asynchronously. This call returns immediately. */ public static void login(String token, LoginCallback callback) { - if (token == null) { - if (callback != null) { - callback.onLoginFail("token is null"); + try { + if (token == null) { + if (callback != null) { + callback.onLoginFail("token is null"); + } + return; } - return; - } - - final ApptentiveInternal instance = ApptentiveInternal.getInstance(); - if (instance == null) { - ApptentiveLog.e("Unable to login: Apptentive instance is not properly initialized"); - if (callback != null) { - callback.onLoginFail("Apptentive instance is not properly initialized"); + final ApptentiveInternal instance = ApptentiveInternal.getInstance(); + if (instance == null) { + ApptentiveLog.e("Unable to login: Apptentive instance is not properly initialized"); + if (callback != null) { + callback.onLoginFail("Apptentive instance is not properly initialized"); + } + } else { + ApptentiveInternal.getInstance().login(token, callback); } - } else { - instance.login(token, callback); + } catch (Exception e) { + ApptentiveLog.w("Error in Apptentive.login()", e); + MetricModule.sendError(e, null, null); } } @@ -1121,7 +1161,8 @@ public static void logout() { instance.logout(); } } catch (Exception e) { - ApptentiveLog.e("Exception while logging out of conversation.", e); + ApptentiveLog.e("Exception in Apptentive.logout()", e); + MetricModule.sendError(e, null, null); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java index 2891f71cd..0965d9bf6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java @@ -47,10 +47,10 @@ public static synchronized boolean engageInternal(Context context, Interaction i } public static synchronized boolean engage(Context context, String vendor, String interaction, String interactionId, String eventName, String data, Map customData, ExtendedData... extendedData) { - if (!ApptentiveInternal.isApptentiveRegistered() || context == null) { - return false; - } try { + if (!ApptentiveInternal.isApptentiveRegistered() || context == null) { + return false; + } String eventLabel = generateEventLabel(vendor, interaction, eventName); ApptentiveLog.d("engage(%s)", eventLabel); @@ -63,6 +63,7 @@ public static synchronized boolean engage(Context context, String vendor, String return doEngage(context, eventLabel); } } catch (Exception e) { + ApptentiveLog.w("Error in engage()", e); MetricModule.sendError(e, null, null); } return false; From 4916aa6bc1f0360084e4058d64fb04e9d46332d7 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 14 Jun 2017 14:33:26 -0700 Subject: [PATCH 348/465] Turn on avatar fetching. --- .../view/holder/IncomingCompoundMessageHolder.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/IncomingCompoundMessageHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/IncomingCompoundMessageHolder.java index d22e1d365..a7c443ead 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/IncomingCompoundMessageHolder.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/IncomingCompoundMessageHolder.java @@ -39,8 +39,6 @@ public class IncomingCompoundMessageHolder extends MessageHolder { private TextView nameView; private ApptentiveImageGridView imageBandView; - private static final boolean loadAvatar = false; - public IncomingCompoundMessageHolder(View itemView) { super(itemView); root = itemView.findViewById(R.id.message_root); @@ -54,9 +52,7 @@ public IncomingCompoundMessageHolder(View itemView) { public void bindView(MessageCenterFragment fragment, final RecyclerView parent, final MessageCenterRecyclerViewAdapter adapter, final CompoundMessage message) { super.bindView(fragment, parent, message); imageBandView.setupUi(); - if (loadAvatar) { - ImageUtil.startDownloadAvatarTask(avatar, message.getSenderProfilePhoto()); - } + ImageUtil.startDownloadAvatarTask(avatar, message.getSenderProfilePhoto()); int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY); root.measure(widthMeasureSpec, 0); From 80d648d67f56506d6d6d54b8994a1a548746dc91 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 14 Jun 2017 15:42:01 -0700 Subject: [PATCH 349/465] Use nullable fields when serializing message. Null has special meaning. --- .../sdk/conversation/FileMessageStore.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index eda52c7f7..f1f252f53 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -26,7 +26,11 @@ import java.util.ArrayList; import java.util.List; +import static com.apptentive.android.sdk.util.Util.readNullableBoolean; +import static com.apptentive.android.sdk.util.Util.readNullableDouble; import static com.apptentive.android.sdk.util.Util.readNullableUTF; +import static com.apptentive.android.sdk.util.Util.writeNullableBoolean; +import static com.apptentive.android.sdk.util.Util.writeNullableDouble; import static com.apptentive.android.sdk.util.Util.writeNullableUTF; class FileMessageStore implements MessageStore { @@ -262,10 +266,10 @@ private MessageEntry findMessageEntry(String nonce) { private static class MessageEntry implements SerializableObject { String id; - double clientCreatedAt; + Double clientCreatedAt; String nonce; String state; - boolean isRead; + Boolean isRead; String json; MessageEntry() { @@ -273,20 +277,20 @@ private static class MessageEntry implements SerializableObject { MessageEntry(DataInput in) throws IOException { id = readNullableUTF(in); - clientCreatedAt = in.readDouble(); + clientCreatedAt = readNullableDouble(in); nonce = readNullableUTF(in); state = readNullableUTF(in); - isRead = in.readBoolean(); + isRead = readNullableBoolean(in); json = readNullableUTF(in); } @Override public void writeExternal(DataOutput out) throws IOException { writeNullableUTF(out, id); - out.writeDouble(clientCreatedAt); + writeNullableDouble(out, clientCreatedAt); writeNullableUTF(out, nonce); writeNullableUTF(out, state); - out.writeBoolean(isRead); + writeNullableBoolean(out, isRead); writeNullableUTF(out, json); } } From 0bc85ed975a0e276e41aadb59f14856af9a9a777 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 14 Jun 2017 15:47:50 -0700 Subject: [PATCH 350/465] Add nullable accessors. Fix network connection detection. --- .../com/apptentive/android/sdk/util/Util.java | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java index 839d192bf..311e28041 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java @@ -25,6 +25,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -92,7 +93,13 @@ public static void showSoftKeyboard(Activity activity, View target) { public static boolean isNetworkConnectionPresent() { ConnectivityManager cm = (ConnectivityManager) ApptentiveInternal.getInstance().getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); - return cm != null && cm.getActiveNetworkInfo() != null; + if (cm != null) { + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + if (activeNetwork != null) { + return activeNetwork.isConnectedOrConnecting(); + } + } + return false; } public static void ensureClosed(Closeable stream) { @@ -746,6 +753,31 @@ public static String readNullableUTF(DataInput in) throws IOException { return notNull ? in.readUTF() : null; } + public static void writeNullableBoolean(DataOutput out, Boolean value) throws IOException { + out.writeBoolean(value != null); + if (value != null) { + out.writeBoolean(value); + } + } + + public static Boolean readNullableBoolean(DataInput in) throws IOException { + boolean notNull = in.readBoolean(); + return notNull ? in.readBoolean() : null; + } + + public static void writeNullableDouble(DataOutput out, Double value) throws IOException { + out.writeBoolean(value != null); + if (value != null) { + out.writeDouble(value); + } + } + + public static Double readNullableDouble(DataInput in) throws IOException { + boolean notNull = in.readBoolean(); + return notNull ? in.readDouble() : null; + } + + public static boolean isMimeTypeImage(String mimeType) { if (TextUtils.isEmpty(mimeType)) { return false; From b8b4fc54793ea09ffd58edc19de712f6ca4cfb04 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 14 Jun 2017 15:48:03 -0700 Subject: [PATCH 351/465] Don't send HttpRequests if there is no network connection present. --- .../java/com/apptentive/android/sdk/network/HttpRequest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index cde7e646d..e9598b802 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -255,6 +255,11 @@ private void sendRequestSync() throws IOException { connection.setConnectTimeout((int) connectTimeout); connection.setReadTimeout((int) readTimeout); + if (!Util.isNetworkConnectionPresent()) { + ApptentiveLog.d("No network connection present. Cancelling request."); + cancel(); + } + if (isCancelled()) { return; } From 451a9da205e8439590947e134ca9003c993e5e52 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 14 Jun 2017 15:54:10 -0700 Subject: [PATCH 352/465] Better Message Polling logging. --- .../messagecenter/MessagePollingWorker.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java index f5d4f4052..12b1deb69 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -14,9 +14,8 @@ import java.util.concurrent.atomic.AtomicBoolean; -/** - * @author Sky Kelsey - */ +import static com.apptentive.android.sdk.ApptentiveLogTag.MESSAGES; + public class MessagePollingWorker { private MessagePollingThread sPollingThread; @@ -47,7 +46,8 @@ private MessagePollingThread createPollingThread() { Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable throwable) { - MetricModule.sendError(throwable, null, null); + ApptentiveLog.e(MESSAGES, "Error polling for messages.", throwable); + MetricModule.sendError(throwable, "Error polling for messages.", null); } }; newThread.setUncaughtExceptionHandler(handler); @@ -71,7 +71,7 @@ public MessagePollingThread() { public void run() { try { - ApptentiveLog.v("Started %s", toString()); + ApptentiveLog.v(MESSAGES, "Started %s", toString()); while (manager.appInForeground.get()) { MessagePollingThread thread = getAndSetMessagePollingThread(true, false); @@ -80,7 +80,7 @@ public void run() { } long pollingInterval = messageCenterInForeground.get() ? foregroundPollingInterval : backgroundPollingInterval; if (Apptentive.canShowMessageCenter()) { - ApptentiveLog.v("Checking server for new messages every %d seconds", pollingInterval / 1000); + ApptentiveLog.v(MESSAGES, "Checking server for new messages every %d seconds", pollingInterval / 1000); manager.fetchAndStoreMessages(messageCenterInForeground.get(), conf.isMessageCenterNotificationPopupEnabled()); } goToSleep(pollingInterval); @@ -88,7 +88,7 @@ public void run() { } finally { threadRunning.set(false); sPollingThread = null; - ApptentiveLog.v("Stopping MessagePollingThread."); + ApptentiveLog.v(MESSAGES, "Stopping MessagePollingThread."); } } } @@ -102,7 +102,7 @@ private void goToSleep(long millis) { } private void wakeUp() { - ApptentiveLog.v("Waking MessagePollingThread."); + ApptentiveLog.v(MESSAGES, "Waking MessagePollingThread."); MessagePollingThread thread = getAndSetMessagePollingThread(true, false); if (thread != null && thread.isAlive()) { thread.interrupt(); From de3c8b70ddbe4b7d93d491e7901450a66852f015 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 14 Jun 2017 15:54:51 -0700 Subject: [PATCH 353/465] Check for internet connection before fetching messages. --- .../android/sdk/module/messagecenter/MessageManager.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 7eb9e5720..ace91ee9a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -227,6 +227,10 @@ public void deleteAllMessages(Context context) { private List fetchMessages(String afterId) { ApptentiveLog.d("Fetching messages newer than: %s", (afterId == null) ? "0" : afterId); + if (!Util.isNetworkConnectionPresent()) { + ApptentiveLog.v("No internet present. Cancelling request."); + return null; + } ApptentiveHttpResponse response = ApptentiveClient.getMessages(null, afterId, null); List ret = new ArrayList<>(); From 9a6f381620daf3234d77fd0ede0848cc210bca3b Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 14 Jun 2017 16:00:30 -0700 Subject: [PATCH 354/465] Add VERY_VERBOSE log level in preparation for SDK development only logging. --- .../com/apptentive/android/sdk/ApptentiveLog.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java index 4e9b94a04..a0f540418 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java @@ -68,6 +68,19 @@ public static boolean canLog(Level level) { return logLevel.canLog(level); } + public static void vv(ApptentiveLogTag tag, String message, Object... args) { + if (tag.enabled) { + doLog(Level.VERBOSE, tag, null, message, args); + } + } + + public static void vv(String message, Object... args){ + doLog(Level.VERBOSE, null, null, message, args); + } + public static void vv(String message, Throwable throwable, Object... args){ + doLog(Level.VERBOSE, null, throwable, message, args); + } + public static void v(ApptentiveLogTag tag, String message, Object... args) { if (tag.enabled) { doLog(Level.VERBOSE, tag, null, message, args); @@ -141,6 +154,7 @@ public static void a(String message, Throwable throwable, Object... args){ } public enum Level { + VERY_VERBOSE(Log.VERBOSE), VERBOSE(Log.VERBOSE), DEBUG(Log.DEBUG), INFO(Log.INFO), From 0d72a6e0584317787d497f17f2571dc46fbadb80 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 14 Jun 2017 16:01:30 -0700 Subject: [PATCH 355/465] Move socket timeouts into Constants. Improve logging in network code. --- .../android/sdk/comm/ApptentiveClient.java | 9 ++-- .../android/sdk/network/HttpRequest.java | 42 +++++++++++-------- .../android/sdk/util/Constants.java | 2 + .../util/task/ApptentiveDownloaderTask.java | 9 ++-- 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index c8613beee..c863efebc 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -30,9 +30,6 @@ public class ApptentiveClient { private static final String USER_AGENT_STRING = "Apptentive/%s (Android)"; // Format with SDK version string. - public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 45000; - public static final int DEFAULT_HTTP_SOCKET_TIMEOUT = 45000; - // Active API private static final String ENDPOINT_MESSAGES = "/conversations/%s/messages?count=%s&after_id=%s&before_id=%s"; private static final String ENDPOINT_CONFIGURATION = "/conversations/%s/configuration"; @@ -124,8 +121,8 @@ private static ApptentiveHttpResponse performHttpRequest(String authToken, boole connection.setRequestProperty("User-Agent", getUserAgentString()); connection.setRequestProperty("Connection", "Keep-Alive"); - connection.setConnectTimeout(DEFAULT_HTTP_CONNECT_TIMEOUT); - connection.setReadTimeout(DEFAULT_HTTP_SOCKET_TIMEOUT); + connection.setConnectTimeout(Constants.DEFAULT_CONNECT_TIMEOUT_MILLIS); + connection.setReadTimeout(Constants.DEFAULT_READ_TIMEOUT_MILLIS); if (bearer) { connection.setRequestProperty("Authorization", "Bearer " + authToken); } else { @@ -137,6 +134,8 @@ private static ApptentiveHttpResponse performHttpRequest(String authToken, boole connection.setRequestProperty("APPTENTIVE-KEY", notNull(ApptentiveInternal.getInstance().getApptentiveKey())); connection.setRequestProperty("APPTENTIVE-SIGNATURE", notNull(ApptentiveInternal.getInstance().getApptentiveSignature())); + + ApptentiveLog.vv("Headers: ", connection.getRequestProperties()); switch (method) { case GET: connection.setRequestMethod("GET"); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index e9598b802..71a4ee6ce 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -1,6 +1,9 @@ package com.apptentive.android.sdk.network; +import android.util.Base64; + import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; @@ -19,6 +22,7 @@ import java.util.Set; import java.util.zip.GZIPInputStream; +import static com.apptentive.android.sdk.ApptentiveLog.Level.VERY_VERBOSE; import static com.apptentive.android.sdk.ApptentiveLogTag.*; import static com.apptentive.android.sdk.debug.Assert.*; @@ -27,16 +31,6 @@ */ public class HttpRequest { - /** - * Default connection timeout - */ - private static final long DEFAULT_CONNECT_TIMEOUT_MILLIS = 45 * 1000L; - - /** - * Default read timeout - */ - private static final long DEFAULT_READ_TIMEOUT_MILLIS = 45 * 1000L; - /** * Default retry policy (used if a custom one is not specified) */ @@ -90,12 +84,12 @@ public class HttpRequest { /** * Connection timeout in milliseconds */ - private long connectTimeout = DEFAULT_CONNECT_TIMEOUT_MILLIS; + private int connectTimeout = Constants.DEFAULT_CONNECT_TIMEOUT_MILLIS; /** * Read timeout in milliseconds */ - private long readTimeout = DEFAULT_READ_TIMEOUT_MILLIS; + private int readTimeout = Constants.DEFAULT_READ_TIMEOUT_MILLIS; /** * The status code from an HTTP response @@ -218,6 +212,8 @@ void dispatchSync(DispatchQueue networkQueue) { } catch (Exception e) { responseCode = -1; // indicates failure errorMessage = e.getMessage(); + ApptentiveLog.e(e, "Unable to perform request"); + ApptentiveLog.e("Cancelled? %b", isCancelled()); if (!isCancelled()) { ApptentiveLog.e(e, "Unable to perform request"); } @@ -247,13 +243,15 @@ private void sendRequestSync() throws IOException { try { URL url = new URL(urlString); ApptentiveLog.d(NETWORK, "Performing request: %s %s", method, url); - + if (ApptentiveLog.canLog(VERY_VERBOSE)) { + ApptentiveLog.vv(NETWORK, "%s", toString()); + } retrying = false; connection = openConnection(url); connection.setRequestMethod(method.toString()); - connection.setConnectTimeout((int) connectTimeout); - connection.setReadTimeout((int) readTimeout); + connection.setConnectTimeout(connectTimeout); + connection.setReadTimeout(readTimeout); if (!Util.isNetworkConnectionPresent()) { ApptentiveLog.d("No network connection present. Cancelling request."); @@ -445,6 +443,14 @@ public void setRequestProperty(String key, Object value) { public String toString() { try { + byte[] requestData = createRequestData(); + String requestString; + String contentType = requestProperties.get("Content-Type").toString(); + if (contentType.contains("application/octet-stream") || contentType.contains("multipart/encrypted")) { + requestString = "Base64 encoded binary request: " + Base64.encodeToString(requestData, Base64.NO_WRAP); + } else { + requestString = new String(requestData); + } return String.format( "\n" + "Request:\n" + @@ -458,7 +464,7 @@ public String toString() { /* Request */ method.name(), urlString, requestProperties, - new String(createRequestData()), + requestString, /* Response */ responseCode, responseData, @@ -481,11 +487,11 @@ public void setMethod(HttpRequestMethod method) { this.method = method; } - public void setConnectTimeout(long connectTimeout) { + public void setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; } - public void setReadTimeout(long readTimeout) { + public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index 87356cb16..b727992a7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -11,6 +11,8 @@ public class Constants { public static final int API_VERSION = 9; public static final String APPTENTIVE_SDK_VERSION = "4.0.0"; + public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 45000; + public static final int DEFAULT_READ_TIMEOUT_MILLIS = 45000; public static final int REQUEST_CODE_PHOTO_FROM_SYSTEM_PICKER = 10; public static final int REQUEST_CODE_CLOSE_COMPOSING_CONFIRMATION = 20; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java index 1d69c04ee..c07bec07f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 207, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -29,9 +29,6 @@ import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; -/** - * @author Barry Li - */ public class ApptentiveDownloaderTask extends AsyncTask { private static boolean FILE_DOWNLOAD_REDIRECTION_ENABLED = false; @@ -140,8 +137,8 @@ private ApptentiveHttpResponse downloadBitmap(String urlString, String destFileP connection.setRequestProperty("Cookie", cookies); } - connection.setConnectTimeout(ApptentiveClient.DEFAULT_HTTP_CONNECT_TIMEOUT); - connection.setReadTimeout(ApptentiveClient.DEFAULT_HTTP_SOCKET_TIMEOUT); + connection.setConnectTimeout(Constants.DEFAULT_CONNECT_TIMEOUT_MILLIS); + connection.setReadTimeout(Constants.DEFAULT_READ_TIMEOUT_MILLIS); connection.setRequestProperty("Accept-Encoding", "gzip"); connection.setRequestProperty("Accept", "application/json"); From 782ac9cf0564982051b66859369f09759f3d3fe3 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 15 Jun 2017 09:51:14 -0700 Subject: [PATCH 356/465] Rename upgrade method that was renamed incorrectly. --- .../android/sdk/storage/ApptentiveDatabaseHelper.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 1384a85a3..2b16e52f1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -232,15 +232,13 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { ApptentiveLog.d(DATABASE, "ApptentiveDatabase.onUpgrade(db, %d, %d)", oldVersion, newVersion); switch (oldVersion) { case 1: - upgradeVersion1to3(db); + upgradeVersion1to2(db); case 2: upgradeVersion2to3(db); } } - private void upgradeVersion1to3(SQLiteDatabase db) { - ApptentiveLog.i(DATABASE, "Upgrading Database from v1 to v2"); - db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); + private void upgradeVersion1to2(SQLiteDatabase db) { Cursor cursor = null; // Migrate legacy stored files to compound message associated files try { From 501c43f86edcfbc6bcd079305a7e12e702b4c431 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 15 Jun 2017 14:07:10 -0700 Subject: [PATCH 357/465] Add new log methods that consistently put any throwable object before the format string message. This is easier to read. --- .../apptentive/android/sdk/ApptentiveLog.java | 87 ++++++++++++++++--- 1 file changed, 73 insertions(+), 14 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java index a0f540418..f4436d57d 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java @@ -70,15 +70,19 @@ public static boolean canLog(Level level) { public static void vv(ApptentiveLogTag tag, String message, Object... args) { if (tag.enabled) { - doLog(Level.VERBOSE, tag, null, message, args); + doLog(Level.VERY_VERBOSE, tag, null, message, args); + } + } + public static void vv(ApptentiveLogTag tag, Throwable throwable, String message, Object... args){ + if (tag.enabled) { + doLog(Level.VERY_VERBOSE, tag, throwable, message, args); } } - public static void vv(String message, Object... args){ - doLog(Level.VERBOSE, null, null, message, args); + doLog(Level.VERY_VERBOSE, null, null, message, args); } - public static void vv(String message, Throwable throwable, Object... args){ - doLog(Level.VERBOSE, null, throwable, message, args); + public static void vv(Throwable throwable, String message, Object... args){ + doLog(Level.VERY_VERBOSE, null, throwable, message, args); } public static void v(ApptentiveLogTag tag, String message, Object... args) { @@ -86,11 +90,15 @@ public static void v(ApptentiveLogTag tag, String message, Object... args) { doLog(Level.VERBOSE, tag, null, message, args); } } - + public static void v(ApptentiveLogTag tag, Throwable throwable, String message, Object... args){ + if (tag.enabled) { + doLog(Level.VERBOSE, tag, throwable, message, args); + } + } public static void v(String message, Object... args){ doLog(Level.VERBOSE, null, null, message, args); } - public static void v(String message, Throwable throwable, Object... args){ + public static void v(Throwable throwable, String message, Object... args){ doLog(Level.VERBOSE, null, throwable, message, args); } @@ -99,11 +107,15 @@ public static void d(ApptentiveLogTag tag, String message, Object... args){ doLog(Level.DEBUG, tag, null, message, args); } } - + public static void d(ApptentiveLogTag tag, Throwable throwable, String message, Object... args){ + if (tag.enabled) { + doLog(Level.DEBUG, tag, throwable, message, args); + } + } public static void d(String message, Object... args){ doLog(Level.DEBUG, null, null, message, args); } - public static void d(String message, Throwable throwable, Object... args){ + public static void d(Throwable throwable, String message, Object... args){ doLog(Level.DEBUG, null, throwable, message, args); } @@ -112,10 +124,15 @@ public static void i(ApptentiveLogTag tag, String message, Object... args){ doLog(Level.INFO, tag, null, message, args); } } + public static void i(ApptentiveLogTag tag, Throwable throwable, String message, Object... args){ + if (tag.enabled) { + doLog(Level.INFO, tag, throwable, message, args); + } + } public static void i(String message, Object... args){ doLog(Level.INFO, null, null, message, args); } - public static void i(String message, Throwable throwable, Object... args){ + public static void i(Throwable throwable, String message, Object... args){ doLog(Level.INFO, null, throwable, message, args); } @@ -124,10 +141,15 @@ public static void w(ApptentiveLogTag tag, String message, Object... args){ doLog(Level.WARN, tag, null, message, args); } } + public static void w(ApptentiveLogTag tag, Throwable throwable, String message, Object... args){ + if (tag.enabled) { + doLog(Level.WARN, tag, throwable, message, args); + } + } public static void w(String message, Object... args){ doLog(Level.WARN, null, null, message, args); } - public static void w(String message, Throwable throwable, Object... args){ + public static void w(Throwable throwable, String message, Object... args){ doLog(Level.WARN, null, throwable, message, args); } @@ -136,22 +158,59 @@ public static void e(ApptentiveLogTag tag, String message, Object... args){ doLog(Level.ERROR, tag, null, message, args); } } + public static void e(ApptentiveLogTag tag, Throwable throwable, String message, Object... args){ + if (tag.enabled) { + doLog(Level.ERROR, tag, throwable, message, args); + } + } public static void e(String message, Object... args){ doLog(Level.ERROR, null, null, message, args); } - public static void e(String message, Throwable throwable, Object... args){ - doLog(Level.ERROR, null, throwable, message, args); - } public static void e(Throwable throwable, String message, Object... args){ doLog(Level.ERROR, null, throwable, message, args); } + public static void a(ApptentiveLogTag tag, String message, Object... args){ + if (tag.enabled) { + doLog(Level.ASSERT, tag, null, message, args); + } + } + public static void a(ApptentiveLogTag tag, Throwable throwable, String message, Object... args){ + if (tag.enabled) { + doLog(Level.ASSERT, tag, throwable, message, args); + } + } public static void a(String message, Object... args){ doLog(Level.ASSERT, null, null, message, args); } + public static void a(Throwable throwable, String message, Object... args){ + doLog(Level.ASSERT, null, throwable, message, args); + } + + //region TODO: Delete these: + public static void vv(String message, Throwable throwable, Object... args){ + doLog(Level.VERBOSE, null, throwable, message, args); + } + + public static void v(String message, Throwable throwable, Object... args){ + doLog(Level.VERBOSE, null, throwable, message, args); + } + public static void d(String message, Throwable throwable, Object... args){ + doLog(Level.DEBUG, null, throwable, message, args); + } + public static void i(String message, Throwable throwable, Object... args){ + doLog(Level.INFO, null, throwable, message, args); + } + public static void w(String message, Throwable throwable, Object... args){ + doLog(Level.WARN, null, throwable, message, args); + } + public static void e(String message, Throwable throwable, Object... args){ + doLog(Level.ERROR, null, throwable, message, args); + } public static void a(String message, Throwable throwable, Object... args){ doLog(Level.ASSERT, null, throwable, message, args); } + //endregion public enum Level { VERY_VERBOSE(Log.VERBOSE), From 34ae36199e388111dfb765421cd6e74a19d5eafc Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 16 Jun 2017 15:29:55 -0700 Subject: [PATCH 358/465] Migrate payloads to take into account the new format where we store the request body in a file pending sending. --- .../sdk/conversation/ConversationManager.java | 18 ++-- .../conversation/ConversationMetadata.java | 8 ++ .../ConversationMetadataItem.java | 13 +++ .../sdk/storage/ApptentiveDatabaseHelper.java | 83 ++++++++++++++++--- .../storage/legacy/LegacyPayloadFactory.java | 7 +- 5 files changed, 108 insertions(+), 21 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 3e3ed50ca..d642a470b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -39,6 +39,7 @@ import java.io.IOException; import java.lang.ref.WeakReference; +import static com.apptentive.android.sdk.ApptentiveLog.Level.VERY_VERBOSE; import static com.apptentive.android.sdk.ApptentiveLogTag.*; import static com.apptentive.android.sdk.ApptentiveNotifications.*; import static com.apptentive.android.sdk.conversation.ConversationState.*; @@ -65,7 +66,7 @@ public class ConversationManager { /** * A basic directory for storing conversation-related data. */ - private final File storageDir; + private final File apptentiveConversationsStorageDir; /** * Current state of conversation metadata. @@ -74,13 +75,13 @@ public class ConversationManager { private Conversation activeConversation; - public ConversationManager(Context context, File storageDir) { + public ConversationManager(Context context, File apptentiveConversationsStorageDir) { if (context == null) { throw new IllegalArgumentException("Context is null"); } this.contextRef = new WeakReference<>(context.getApplicationContext()); - this.storageDir = storageDir; + this.apptentiveConversationsStorageDir = apptentiveConversationsStorageDir; ApptentiveNotificationCenter.defaultCenter() .addObserver(NOTIFICATION_APP_ENTERED_FOREGROUND, new ApptentiveNotificationObserver() { @@ -169,8 +170,8 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali // no conversation available: create a new one ApptentiveLog.v(CONVERSATION, "Can't load conversation: creating anonymous conversation..."); - File dataFile = new File(storageDir, Util.generateRandomFilename()); - File messagesFile = new File(storageDir, Util.generateRandomFilename()); + File dataFile = new File(apptentiveConversationsStorageDir, "conversation-" + Util.generateRandomFilename()); + File messagesFile = new File(apptentiveConversationsStorageDir, "messages-" + Util.generateRandomFilename()); Conversation anonymousConversation = new Conversation(dataFile, messagesFile); // If there is a Legacy Conversation, migrate it into the new Conversation object. @@ -504,7 +505,7 @@ private void updateMetadataItems(Conversation conversation) { private ConversationMetadata resolveMetadata() { try { - File metaFile = new File(storageDir, CONVERSATION_METADATA_PATH); + File metaFile = new File(apptentiveConversationsStorageDir, CONVERSATION_METADATA_PATH); if (metaFile.exists()) { ApptentiveLog.v(CONVERSATION, "Loading meta file: " + metaFile); final ConversationMetadata metadata = ObjectSerialization.deserialize(metaFile, ConversationMetadata.class); @@ -523,8 +524,11 @@ private ConversationMetadata resolveMetadata() { private void saveMetadata() { try { + if (ApptentiveLog.canLog(VERY_VERBOSE)) { + ApptentiveLog.vv(CONVERSATION, "Saving metadata: ", conversationMetadata.toString()); + } long start = System.currentTimeMillis(); - File metaFile = new File(storageDir, CONVERSATION_METADATA_PATH); + File metaFile = new File(apptentiveConversationsStorageDir, CONVERSATION_METADATA_PATH); ObjectSerialization.serialize(metaFile, conversationMetadata); ApptentiveLog.v(CONVERSATION, "Saved metadata (took %d ms)", System.currentTimeMillis() - start); } catch (Exception e) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java index 192eabe9e..2de054a8f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java @@ -110,4 +110,12 @@ public interface Filter { } //endregion + + + @Override + public String toString() { + return "ConversationMetadata{" + + "items=" + items + + '}'; + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index e9e452696..267162f43 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -110,4 +110,17 @@ public String getUserId() { public String getJWT() { return JWT; } + + @Override + public String toString() { + return "ConversationMetadataItem{" + + "state=" + state + + ", conversationId='" + conversationId + '\'' + + ", dataFile=" + dataFile + + ", messagesFile=" + messagesFile + + ", encryptionKey='" + encryptionKey + '\'' + + ", userId='" + userId + '\'' + + ", JWT='" + JWT + '\'' + + '}'; + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 2b16e52f1..2e4034d3c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -56,7 +56,7 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { //region Payload SQL static final class PayloadEntry { - static final String TABLE_NAME = "pending_payload"; + static final String TABLE_NAME = "payload"; static final DatabaseColumn COLUMN_PRIMARY_KEY = new DatabaseColumn(0, "_id"); static final DatabaseColumn COLUMN_PAYLOAD_TYPE = new DatabaseColumn(1, "payloadType"); static final DatabaseColumn COLUMN_IDENTIFIER = new DatabaseColumn(2, "identifier"); @@ -70,12 +70,15 @@ static final class PayloadEntry { } static final class LegacyPayloadEntry { - static final String TABLE_NAME = "payload"; + static final String TABLE_NAME = "legacy_payload"; static final DatabaseColumn PAYLOAD_KEY_DB_ID = new DatabaseColumn(0, "_id"); static final DatabaseColumn PAYLOAD_KEY_BASE_TYPE = new DatabaseColumn(1, "base_type"); static final DatabaseColumn PAYLOAD_KEY_JSON = new DatabaseColumn(2, "json"); } + private static final String BACKUP_LEGACY_PAYLOAD_TABLE = String.format("ALTER TABLE %s RENAME TO %s;", PayloadEntry.TABLE_NAME, LegacyPayloadEntry.TABLE_NAME); + private static final String DELETE_LEGACY_PAYLOAD_TABLE = String.format("DROP TABLE %s;", LegacyPayloadEntry.TABLE_NAME); + private static final String TABLE_CREATE_PAYLOAD = "CREATE TABLE " + PayloadEntry.TABLE_NAME + " (" + @@ -116,10 +119,9 @@ static final class LegacyPayloadEntry { //endregion - //region Message SQL (region) + //region Message SQL private static final String TABLE_MESSAGE = "message"; - private static final String MESSAGE_KEY_DB_ID = "_id"; // 0 private static final String MESSAGE_KEY_ID = "id"; // 1 private static final String MESSAGE_KEY_CLIENT_CREATED_AT = "client_created_at"; // 2 @@ -370,20 +372,46 @@ private void upgradeVersion1to2(SQLiteDatabase db) { } } + /** + * 1. Rename payload table to legacy_payload + * 2. Create new payload table with new columns + * 2. select all payloads in temp_payload + * 3. load each into a the new payload object format + * 4. Save each into the new payload table + * 5. Drop temp_payload + * @param db + */ private void upgradeVersion2to3(SQLiteDatabase db) { ApptentiveLog.i(DATABASE, "Upgrading Database from v2 to v3"); Cursor cursor = null; try { + db.beginTransaction(); + + // 1. Rename existing "payload" table to "legacy_payload" + ApptentiveLog.vv(DATABASE, "\t1. Backing up \"payloads\" database to \"legacy_payloads\""); + db.execSQL(BACKUP_LEGACY_PAYLOAD_TABLE); + + // 2. Create new Payload table as "payload" + ApptentiveLog.vv(DATABASE, "\t2. Creating new \"payloads\" database."); + db.execSQL(TABLE_CREATE_PAYLOAD); + + // 3. Load legacy payloads + ApptentiveLog.vv(DATABASE, "\t3. Loading legacy payloads."); cursor = db.rawQuery(SQL_QUERY_PAYLOAD_LIST_LEGACY, null); List payloads = new ArrayList<>(cursor.getCount()); + ApptentiveLog.vv(DATABASE, "4. Save payloads into new table."); JsonPayload payload; while (cursor.moveToNext()) { PayloadType payloadType = PayloadType.parse(cursor.getString(1)); String json = cursor.getString(LegacyPayloadEntry.PAYLOAD_KEY_JSON.index); payload = LegacyPayloadFactory.createPayload(payloadType, json); + if (payload == null) { + ApptentiveLog.d(DATABASE, "Unable to construct payload of type %s. Continuing.", payloadType.name()); + continue; + } // the legacy payload format didn't store 'nonce' in the database so we need to extract if from json String nonce = payload.optString("nonce", null); @@ -392,15 +420,43 @@ private void upgradeVersion2to3(SQLiteDatabase db) { } payload.setNonce(nonce); - payloads.add(payload); - } + // 4. Save each payload in the new table. + ApptentiveLog.vv(DATABASE, "Payload of type %s:, %s", payload.getPayloadType().name(), payload); + ContentValues values = new ContentValues(); + values.put(PayloadEntry.COLUMN_IDENTIFIER.name, notNull(payload.getNonce())); + values.put(PayloadEntry.COLUMN_PAYLOAD_TYPE.name, notNull(payload.getPayloadType().name())); + values.put(PayloadEntry.COLUMN_CONTENT_TYPE.name, notNull(payload.getHttpRequestContentType())); + // The token is encrypted inside the payload body for Logged In Conversations. In that case, don't store it here. + if (!payload.hasEncryptionKey()) { + values.put(PayloadEntry.COLUMN_AUTH_TOKEN.name, payload.getToken()); // might be null + } + values.put(PayloadEntry.COLUMN_CONVERSATION_ID.name, payload.getConversationId()); // might be null + values.put(PayloadEntry.COLUMN_REQUEST_METHOD.name, payload.getHttpRequestMethod().name()); + values.put(PayloadEntry.COLUMN_PATH.name, payload.getHttpEndPoint( + StringUtils.isNullOrEmpty(payload.getConversationId()) ? "${conversationId}" : payload.getConversationId()) // if conversation id is missing we replace it with a place holder and update it later + ); - addPayload(payloads.toArray(new Payload[payloads.size()])); + File dest = getPayloadBodyFile(payload.getNonce()); + ApptentiveLog.v(DATABASE, "Saving payload body to: %s", dest); + Util.writeBytes(dest, payload.renderData()); + values.put(PayloadEntry.COLUMN_ENCRYPTED.name, payload.hasEncryptionKey() ? 1 : 0); + + db.insert(PayloadEntry.TABLE_NAME, null, values); + } + + // 5. Finally, delete the temporary legacy table + ApptentiveLog.vv(DATABASE, "\t5. Delete temporary \"legacy_payloads\" database."); + db.execSQL(DELETE_LEGACY_PAYLOAD_TABLE); + db.setTransactionSuccessful(); } catch (Exception e) { - ApptentiveLog.e(DATABASE, "upgradeVersion2to3 EXCEPTION: " + e.getMessage()); + ApptentiveLog.e(DATABASE, e, "Error in upgradeVersion2to3()"); } finally { ensureClosed(cursor); + if (db != null) { + db.endTransaction(); + ApptentiveLog.e(DATABASE, "Transaction ended."); + } } } @@ -413,7 +469,7 @@ private void upgradeVersion2to3(SQLiteDatabase db) { * a new message is added. */ void addPayload(Payload... payloads) { - SQLiteDatabase db; + SQLiteDatabase db = null; try { db = getWritableDatabase(); db.beginTransaction(); @@ -442,9 +498,12 @@ void addPayload(Payload... payloads) { db.insert(PayloadEntry.TABLE_NAME, null, values); } db.setTransactionSuccessful(); - db.endTransaction(); - } catch (Exception sqe) { - ApptentiveLog.e(DATABASE, "addPayload EXCEPTION: " + sqe.getMessage()); + } catch (Exception e) { + ApptentiveLog.e(DATABASE, e, "Error adding payload."); + } finally { + if (db != null) { + db.endTransaction(); + } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/LegacyPayloadFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/LegacyPayloadFactory.java index 88f7eb0e8..34b623932 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/LegacyPayloadFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/legacy/LegacyPayloadFactory.java @@ -32,9 +32,12 @@ public static JsonPayload createPayload(PayloadType type, String json) throws JS case device: return new DevicePayload(json); case sdk: - return new SdkPayload(json); + //return new SdkPayload(json); + // TODO: FIXME + return null; case app_release: - return new AppReleasePayload(json); + //return new AppReleasePayload(json); + return null; case person: return new PersonPayload(json); case survey: From e2e1327241964cb29a8f75ba6100be7ccddf99a1 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 16 Jun 2017 15:33:18 -0700 Subject: [PATCH 359/465] Fix Serialization: There were a couple places where an Object reference snuck in that were then set to non-Serializable objects such as JSONObject. Replace all objects we need to store that were back with JSONObject with something else, and provide bridge methods to go between native and JSONObject when needed. --- .../apptentive/android/sdk/Apptentive.java | 61 ++++--- .../sdk/conversation/Conversation.java | 8 +- .../android/sdk/migration/Migrator.java | 41 ++++- .../sdk/model/CommerceExtendedData.java | 149 ++++++++++++------ .../android/sdk/model/EventPayload.java | 2 +- .../android/sdk/model/ExtendedData.java | 27 ++-- .../sdk/model/LocationExtendedData.java | 43 +++-- .../android/sdk/model/TimeExtendedData.java | 22 +-- .../module/engagement/logic/ClauseParser.java | 4 +- .../android/sdk/storage/CustomData.java | 13 +- .../sdk/storage/IntegrationConfig.java | 2 +- .../sdk/storage/IntegrationConfigItem.java | 26 ++- 12 files changed, 268 insertions(+), 130 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 05cd5672f..50ed4d6a6 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -38,6 +38,7 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.io.Serializable; import java.util.ArrayList; import java.util.Map; @@ -1193,29 +1194,28 @@ public static void logout() { *
  • FF01
  • * */ - public static class Version extends JSONObject implements Comparable { + public static class Version implements Serializable, Comparable { + + private static final long serialVersionUID = 1L; + public static final String KEY_TYPE = "_type"; public static final String TYPE = "version"; + private String version; + public Version() { } - public Version(String json) throws JSONException { - super(json); + public Version(JSONObject json) throws JSONException { + this.version = json.optString(TYPE, null); } public Version(long version) { - super(); - setVersion(version); + this.version = Long.toString(version); } public void setVersion(String version) { - try { - put(KEY_TYPE, TYPE); - put(TYPE, version); - } catch (JSONException e) { - ApptentiveLog.e("Error creating Apptentive.Version.", e); - } + this.version = version; } public void setVersion(long version) { @@ -1223,7 +1223,17 @@ public void setVersion(long version) { } public String getVersion() { - return optString(TYPE, null); + return version; + } + + public void toJsonObject() { + JSONObject ret = new JSONObject(); + try { + ret.put(KEY_TYPE, TYPE); + ret.put(TYPE, version); + } catch (JSONException e) { + ApptentiveLog.e("Error creating Apptentive.Version.", e); + } } @Override @@ -1270,31 +1280,38 @@ public String toString() { } } - public static class DateTime extends JSONObject implements Comparable { + public static class DateTime implements Serializable, Comparable { public static final String KEY_TYPE = "_type"; public static final String TYPE = "datetime"; public static final String SEC = "sec"; - public DateTime(String json) throws JSONException { - super(json); + private String sec; + + public DateTime(JSONObject json) throws JSONException { + this.sec = json.optString(SEC); } public DateTime(double dateTime) { - super(); setDateTime(dateTime); } public void setDateTime(double dateTime) { + sec = String.valueOf(dateTime); + } + + public double getDateTime() { + return Double.valueOf(sec); + } + + public JSONObject toJSONObject() { + JSONObject ret = new JSONObject(); try { - put(KEY_TYPE, TYPE); - put(SEC, dateTime); + ret.put(KEY_TYPE, TYPE); + ret.put(SEC, sec); } catch (JSONException e) { ApptentiveLog.e("Error creating Apptentive.DateTime.", e); } - } - - public double getDateTime() { - return optDouble(SEC); + return ret; } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index ee1a6bab9..b485c8cdd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -235,8 +235,8 @@ else if (!response.isSuccessful()) { * if succeed. */ private synchronized void saveConversationData() throws SerializerException { - ApptentiveLog.d(CONVERSATION, "Saving %sconversation data...", hasState(LOGGED_IN) ? "encrypted " : ""); - ApptentiveLog.v(CONVERSATION, "EventData: %s", getEventData().toString()); // TODO: remove + ApptentiveLog.vv(CONVERSATION, "Saving %sconversation data...", hasState(LOGGED_IN) ? "encrypted " : ""); + ApptentiveLog.vv(CONVERSATION, "EventData: %s", getEventData().toString()); long start = System.currentTimeMillis(); @@ -250,7 +250,7 @@ private synchronized void saveConversationData() throws SerializerException { } serializer.serialize(conversationData); - ApptentiveLog.v(CONVERSATION, "Conversation data saved (took %d ms)", System.currentTimeMillis() - start); + ApptentiveLog.vv(CONVERSATION, "Conversation data saved (took %d ms)", System.currentTimeMillis() - start); } synchronized void loadConversationData() throws SerializerException { @@ -525,7 +525,7 @@ public void setPushIntegration(int pushProvider, String token) { ApptentiveLog.v(CONVERSATION, "Setting push provider: %d with token %s", pushProvider, token); IntegrationConfig integrationConfig = getDevice().getIntegrationConfig(); IntegrationConfigItem item = new IntegrationConfigItem(); - item.put(Apptentive.INTEGRATION_PUSH_TOKEN, token); + item.setToken(token); switch (pushProvider) { case Apptentive.PUSH_PROVIDER_APPTENTIVE: integrationConfig.setApptentive(item); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java index 0b146e069..c99e7fe19 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java @@ -9,6 +9,7 @@ import android.content.Context; import android.content.SharedPreferences; +import com.apptentive.android.sdk.Apptentive; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.migration.v4_0_0.CodePointStore; @@ -31,6 +32,7 @@ import org.json.JSONException; import org.json.JSONObject; +import java.io.Serializable; import java.util.Iterator; import java.util.Map; @@ -106,7 +108,12 @@ private void migrateDevice() { Iterator it = customDataOld.keys(); while (it.hasNext()) { String key = (String) it.next(); - customData.put(key, customDataOld.get(key)); + Object value = customDataOld.get(key); + if (value instanceof JSONObject) { + customData.put(key, jsonObjectToSerializableType((JSONObject) value)); + } else { + customData.put(key, (Serializable) value); + } } device.setCustomData(customData); } @@ -117,22 +124,18 @@ private void migrateDevice() { Iterator it = integrationConfigOld.keys(); while (it.hasNext()) { String key = (String) it.next(); - IntegrationConfigItem item = new IntegrationConfigItem(); + IntegrationConfigItem item = new IntegrationConfigItem(integrationConfigOld); switch (key) { case INTEGRATION_APPTENTIVE_PUSH: - item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); integrationConfig.setApptentive(item); break; case INTEGRATION_PARSE: - item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); integrationConfig.setParse(item); break; case INTEGRATION_URBAN_AIRSHIP: - item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); integrationConfig.setUrbanAirship(item); break; case INTEGRATION_AWS_SNS: - item.put(INTEGRATION_PUSH_TOKEN, integrationConfigOld.get(key)); integrationConfig.setAmazonAwsSns(item); break; } @@ -212,7 +215,12 @@ private void migratePerson() { Iterator it = customDataOld.keys(); while (it.hasNext()) { String key = (String) it.next(); - customData.put(key, customDataOld.get(key)); + Object value = customDataOld.get(key); + if (value instanceof JSONObject) { + customData.put(key, jsonObjectToSerializableType((JSONObject) value)); + } else { + customData.put(key, (Serializable) value); + } } person.setCustomData(customData); } @@ -263,4 +271,23 @@ private void migrateEventData() { ApptentiveLog.w("Error migrating Event Data.", e); } } + + /** + * Takes a legacy Apptentive Custom Data object base on JSON, and returns the modern serializable version + */ + private static Serializable jsonObjectToSerializableType(JSONObject input) { + String type = input.optString(Apptentive.Version.KEY_TYPE, null); + try { + if (type != null) { + if (type.equals(Apptentive.Version.TYPE)) { + return new Apptentive.Version(input); + } else if (type.equals(Apptentive.DateTime.TYPE)) { + return new Apptentive.DateTime(input); + } + } + } catch (JSONException e) { + ApptentiveLog.e(e, "Error migrating JSONObject."); + } + return null; + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CommerceExtendedData.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CommerceExtendedData.java index ea6f4f4d2..c03db170b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CommerceExtendedData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CommerceExtendedData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -10,9 +10,10 @@ import org.json.JSONException; import org.json.JSONObject; -/** - * @author Sky Kelsey - */ +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + public class CommerceExtendedData extends ExtendedData { private static final String KEY_ID = "id"; @@ -25,9 +26,17 @@ public class CommerceExtendedData extends ExtendedData { private static final int VERSION = 1; + private Object id; + private String affiliation; + private double revenue; + private double shipping; + private double tax; + private String currency; + private List items; @Override protected void init() { + items = new ArrayList<>(); setType(Type.commerce); setVersion(VERSION); } @@ -36,11 +45,7 @@ public CommerceExtendedData() { super(); } - public CommerceExtendedData(String json) throws JSONException { - super(json); - } - - public CommerceExtendedData(Object id, Object affiliation, Number revenue, Number shipping, Number tax, String currency) throws JSONException { + public CommerceExtendedData(Object id, String affiliation, double revenue, double shipping, double tax, String currency) throws JSONException { setId(id); setAffiliation(affiliation); setRevenue(revenue); @@ -49,33 +54,45 @@ public CommerceExtendedData(Object id, Object affiliation, Number revenue, Numbe setCurrency(currency); } + public CommerceExtendedData(String json) throws JSONException { + super(json); + JSONObject jsonObject = new JSONObject(json); + setId(jsonObject.opt(KEY_ID)); + setAffiliation(jsonObject.optString(KEY_AFFILIATION, null)); + setRevenue(jsonObject.optDouble(KEY_REVENUE, 0)); + setShipping(jsonObject.optDouble(KEY_SHIPPING, 0)); + setTax(jsonObject.optDouble(KEY_TAX, 0)); + setCurrency(jsonObject.optString(KEY_CURRENCY, null)); + setItems(jsonObject.optJSONArray(KEY_ITEMS)); + } + public CommerceExtendedData setId(Object id) throws JSONException { - put(KEY_ID, id); + this.id = id; return this; } - public CommerceExtendedData setAffiliation(Object affiliation) throws JSONException { - put(KEY_AFFILIATION, affiliation); + public CommerceExtendedData setAffiliation(String affiliation) throws JSONException { + this.affiliation = affiliation; return this; } - public CommerceExtendedData setRevenue(Number revenue) throws JSONException { - put(KEY_REVENUE, revenue); + public CommerceExtendedData setRevenue(double revenue) throws JSONException { + this.revenue = revenue; return this; } - public CommerceExtendedData setShipping(Number shipping) throws JSONException { - put(KEY_SHIPPING, shipping); + public CommerceExtendedData setShipping(double shipping) throws JSONException { + this.shipping = shipping; return this; } - public CommerceExtendedData setTax(Number tax) throws JSONException { - put(KEY_TAX, tax); + public CommerceExtendedData setTax(double tax) throws JSONException { + this.tax = tax; return this; } public CommerceExtendedData setCurrency(String currency) throws JSONException { - put(KEY_CURRENCY, currency); + this.currency = currency; return this; } @@ -87,18 +104,41 @@ public CommerceExtendedData setCurrency(String currency) throws JSONException { * @throws JSONException if item can't be added. */ public CommerceExtendedData addItem(Item item) throws JSONException { - JSONArray items; - if (isNull(KEY_ITEMS)) { - items = new JSONArray(); - put(KEY_ITEMS, items); - } else { - items = getJSONArray(KEY_ITEMS); + if (this.items == null) { + this.items = new ArrayList<>(); } - items.put(item); + items.add(item); return this; } - public static class Item extends JSONObject { + public CommerceExtendedData setItems(JSONArray items) throws JSONException { + if (items != null) { + for (int i = 0; i < items.length(); i++) { + Item item = (Item) items.get(i); + addItem(item); + } + } + return this; + } + + @Override + public JSONObject toJsonObject() throws JSONException { + JSONObject ret = super.toJsonObject(); + ret.put(KEY_ID, id); + ret.put(KEY_AFFILIATION, affiliation); + ret.put(KEY_REVENUE, revenue); + ret.put(KEY_SHIPPING, shipping); + ret.put(KEY_TAX, tax); + ret.put(KEY_CURRENCY, currency); + JSONArray itemsArray = new JSONArray(); + for (Item item : items) { + itemsArray.put(item.toJsonObject()); + } + ret.put(KEY_ITEMS, itemsArray); + return ret; + } + + public static class Item implements Serializable { private static final String KEY_ID = "id"; private static final String KEY_NAME = "name"; @@ -107,12 +147,24 @@ public static class Item extends JSONObject { private static final String KEY_QUANTITY = "quantity"; private static final String KEY_CURRENCY = "currency"; + private Object id; + private String name; + private String category; + private double price; + private double quantity; + private String currency; + public Item() { - super(); } public Item(String json) throws JSONException { - super(json); + JSONObject jsonObject = new JSONObject(json); + this.id = jsonObject.opt(KEY_ID); + this.name = jsonObject.optString(KEY_NAME, null); + this.category = jsonObject.optString(KEY_CATEGORY, null); + this.price = jsonObject.optDouble(KEY_PRICE, 0); + this.quantity = jsonObject.optDouble(KEY_QUANTITY, 0); + this.currency = jsonObject.optString(KEY_CURRENCY, null); } /** @@ -126,7 +178,7 @@ public Item(String json) throws JSONException { * @param currency The currency code for the currency used in this transaction. Ignored if null. * @throws JSONException if values cannot be set. */ - public Item(Object id, Object name, String category, Number price, Number quantity, String currency) throws JSONException { + public Item(Object id, String name, String category, double price, double quantity, String currency) throws JSONException { super(); if (id != null) { setId(id); @@ -137,45 +189,52 @@ public Item(Object id, Object name, String category, Number price, Number quanti if (category != null) { setCategory(category); } - if (price != null) { - setPrice(price); - } - if (quantity != null) { - setQuantity(quantity); - } + setPrice(price); + setQuantity(quantity); if (currency != null) { setCurrency(currency); } } public Item setId(Object id) throws JSONException { - put(KEY_ID, id); + this.id = id; return this; } - public Item setName(Object name) throws JSONException { - put(KEY_NAME, name); + public Item setName(String name) throws JSONException { + this.name = name; return this; } public Item setCategory(String category) throws JSONException { - put(KEY_CATEGORY, category); + this.category = category; return this; } - public Item setPrice(Number price) throws JSONException { - put(KEY_PRICE, price); + public Item setPrice(double price) throws JSONException { + this.price = price; return this; } - public Item setQuantity(Number quantity) throws JSONException { - put(KEY_QUANTITY, quantity); + public Item setQuantity(double quantity) throws JSONException { + this.quantity = quantity; return this; } public Item setCurrency(String currency) throws JSONException { - put(KEY_CURRENCY, currency); + this.currency = currency; return this; } + + public JSONObject toJsonObject() throws JSONException { + JSONObject ret = new JSONObject(); + ret.put(KEY_ID, id); + ret.put(KEY_NAME, name); + ret.put(KEY_CATEGORY, category); + ret.put(KEY_PRICE, price); + ret.put(KEY_QUANTITY, quantity); + ret.put(KEY_CURRENCY, currency); + return ret; + } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index 3e04818c1..c44ec684c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -74,7 +74,7 @@ public EventPayload(String label, String interactionId, String data, Map implements Saveable { +public class CustomData extends HashMap implements Saveable { private static final long serialVersionUID = 1L; @@ -36,21 +37,21 @@ public void notifyDataChanged() { //region Saving when modified @Override - public Object put(String key, Object value) { - Object ret = super.put(key, value); + public Serializable put(String key, Serializable value) { + Serializable ret = super.put(key, value); notifyDataChanged(); return ret; } @Override - public void putAll(Map m) { + public void putAll(Map m) { super.putAll(m); notifyDataChanged(); } @Override - public Object remove(Object key) { - Object ret = super.remove(key); + public Serializable remove(Object key) { + Serializable ret = super.remove(key); notifyDataChanged(); return ret; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java index 063c9cb00..4cdf73886 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java @@ -23,7 +23,7 @@ public class IntegrationConfig implements Saveable { private IntegrationConfigItem urbanAirship; private IntegrationConfigItem parse; - private DataChangedListener listener; + private transient DataChangedListener listener; //region Listeners diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java index 15627510b..de989b08c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java @@ -7,18 +7,38 @@ package com.apptentive.android.sdk.storage; import org.json.JSONException; +import org.json.JSONObject; +import java.io.Serializable; import java.util.HashMap; import java.util.Set; -public class IntegrationConfigItem extends HashMap { +public class IntegrationConfigItem implements Serializable { + + private static final long serialVersionUID = 1L; + + private static final String KEY_TOKEN = "token"; + + private HashMap contents = new HashMap<>(); + + public IntegrationConfigItem() { + } + + public IntegrationConfigItem(JSONObject old) { + String oldToken = old.optString(KEY_TOKEN, null); + setToken(oldToken); + } + + public void setToken(String token) { + contents.put(KEY_TOKEN, token); + } public com.apptentive.android.sdk.model.CustomData toJson() { try { com.apptentive.android.sdk.model.CustomData ret = new com.apptentive.android.sdk.model.CustomData(); - Set keys = keySet(); + Set keys = contents.keySet(); for (String key : keys) { - ret.put(key, get(key)); + ret.put(key, contents.get(key)); } } catch (JSONException e) { // This can't happen. From 72d70c4ec65ba40421bf16b4288cf85be68cec2a Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 16 Jun 2017 15:33:56 -0700 Subject: [PATCH 360/465] Fix token fetching and message fetching endpoints. The names had changed in the final API spec. --- .../com/apptentive/android/sdk/comm/ApptentiveClient.java | 2 +- .../com/apptentive/android/sdk/comm/ApptentiveHttpClient.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index c863efebc..5a4a47bd4 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -31,7 +31,7 @@ public class ApptentiveClient { private static final String USER_AGENT_STRING = "Apptentive/%s (Android)"; // Format with SDK version string. // Active API - private static final String ENDPOINT_MESSAGES = "/conversations/%s/messages?count=%s&after_id=%s&before_id=%s"; + private static final String ENDPOINT_MESSAGES = "/conversations/%s/messages?count=%s&starts_after=%s&before_id=%s"; private static final String ENDPOINT_CONFIGURATION = "/conversations/%s/configuration"; private static final String ENDPOINT_INTERACTIONS = "/conversations/%s/interactions"; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index de458097d..a4d807448 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -34,7 +34,7 @@ public class ApptentiveHttpClient implements PayloadRequestSender { // Active API private static final String ENDPOINT_CONVERSATION = "/conversation"; - private static final String ENDPOINT_LEGACY_CONVERSATION = "/conversation/login"; + private static final String ENDPOINT_LEGACY_CONVERSATION = "/conversation/token"; private static final String ENDPOINT_LOGIN = "/conversations/%s/session"; private final String apptentiveKey; @@ -77,7 +77,7 @@ public HttpJsonRequest getLegacyConversationId(String conversationToken, HttpReq throw new IllegalArgumentException("Conversation token is null or empty"); } - HttpJsonRequest request = createJsonRequest(ENDPOINT_LEGACY_CONVERSATION, new JSONObject(), HttpRequestMethod.POST); + HttpJsonRequest request = createJsonRequest(ENDPOINT_LEGACY_CONVERSATION, new JSONObject(), HttpRequestMethod.GET); request.setRequestProperty("Authorization", "OAuth " + conversationToken); request.addListener(listener); httpRequestManager.startRequest(request); From 758cc6d612e08726d32d72f468baf594b66b7ad6 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 16 Jun 2017 15:34:27 -0700 Subject: [PATCH 361/465] Move some constant paths to `Constants` --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 3 ++- .../main/java/com/apptentive/android/sdk/util/Constants.java | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 1b38af78e..aac3b9689 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -75,6 +75,7 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_WILL_LOGOUT; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_INTERACTIONS_SHOULD_DISMISS; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_ACTIVITY; +import static com.apptentive.android.sdk.util.Constants.CONVERSATIONS_DIR; /** * This class contains only internal methods. These methods should not be access directly by the host app. @@ -176,7 +177,7 @@ private ApptentiveInternal(Application application, String apptentiveKey, String globalSharedPrefs = application.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE); apptentiveHttpClient = new ApptentiveHttpClient(apptentiveKey, apptentiveSignature, getEndpointBase(globalSharedPrefs)); - conversationManager = new ConversationManager(appContext, Util.getInternalDir(appContext, "conversations", true)); + conversationManager = new ConversationManager(appContext, Util.getInternalDir(appContext, CONVERSATIONS_DIR, true)); appRelease = AppReleaseManager.generateCurrentAppRelease(application, this); taskManager = new ApptentiveTaskManager(appContext, apptentiveHttpClient); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index b727992a7..da04702ff 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -65,8 +65,9 @@ public class Constants { //endregion //region Database and File Storage - public static final String PAYLOAD_DATA_DIR = "\"payload-datas\""; - public static final String PAYLOAD_DATA_FILE_SUFFIX = ".payloads"; + public static final String CONVERSATIONS_DIR = "apptentive/conversations"; + public static final String PAYLOAD_DATA_DIR = "payloads"; + public static final String PAYLOAD_DATA_FILE_SUFFIX = ".data"; //endregion // region Keys used to access old data for migration From 06dd099d86c3c91200562eb5c7c2367de8fa6f90 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 19 Jun 2017 12:06:12 -0700 Subject: [PATCH 362/465] Get rid of separate JWT field in conversation. Use the same field for legacy OAuth token, as well as new JWT token. --- .../sdk/conversation/Conversation.java | 8 ------- .../sdk/conversation/ConversationManager.java | 7 +++---- .../ConversationMetadataItem.java | 21 +++++++++---------- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index b485c8cdd..dcbb8e8c9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -513,14 +513,6 @@ void setUserId(String userId) { this.userId = userId; } - String getJWT() { - return JWT; - } - - void setJWT(String JWT) { - this.JWT = JWT; - } - public void setPushIntegration(int pushProvider, String token) { ApptentiveLog.v(CONVERSATION, "Setting push provider: %d with token %s", pushProvider, token); IntegrationConfig integrationConfig = getDevice().getIntegrationConfig(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index d642a470b..21ceeb4d0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -258,9 +258,8 @@ public void onFinish(HttpJsonRequest request) { // set conversation data conversation.setState(ANONYMOUS); - conversation.setConversationToken(conversationToken); + conversation.setConversationToken(conversationJWT); conversation.setConversationId(conversationId); - conversation.setJWT(conversationJWT); // handle state change handleConversationStateChange(conversation); @@ -290,7 +289,7 @@ private Conversation loadConversation(ConversationMetadataItem item) throws Seri conversation.setEncryptionKey(item.getEncryptionKey()); // it's important to set encryption key before loading data conversation.setState(item.getState()); // set the state same as the item's state conversation.setUserId(item.getUserId()); - conversation.setJWT(item.getJWT()); + conversation.setConversationToken(item.getConversationToken()); conversation.loadConversationData(); conversation.checkInternalConsistency(); @@ -487,7 +486,7 @@ private void updateMetadataItems(Conversation conversation) { conversationMetadata.addItem(item); } item.state = conversation.getState(); - item.JWT = conversation.getJWT(); // TODO: can it be null for active conversations? + item.conversationToken = conversation.getConversationToken(); // TODO: can it be null for active conversations? // update encryption key (if necessary) if (conversation.hasState(LOGGED_IN)) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index 267162f43..024330a58 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -2,7 +2,6 @@ import com.apptentive.android.sdk.serialization.SerializableObject; import com.apptentive.android.sdk.util.StringUtils; -import com.apptentive.android.sdk.util.Util; import java.io.DataInput; import java.io.DataOutput; @@ -27,6 +26,11 @@ public class ConversationMetadataItem implements SerializableObject { */ final String conversationId; + /** + * The token for active conversations + */ + String conversationToken; + /** * Storage filename for conversation serialized data */ @@ -47,11 +51,6 @@ public class ConversationMetadataItem implements SerializableObject { */ String userId; - /** - * A JWT for active conversations - */ - String JWT; - public ConversationMetadataItem(String conversationId, File dataFile, File messagesFile) { if (StringUtils.isNullOrEmpty(conversationId)) { throw new IllegalArgumentException("Conversation id is null or empty"); @@ -72,23 +71,23 @@ public ConversationMetadataItem(String conversationId, File dataFile, File messa public ConversationMetadataItem(DataInput in) throws IOException { conversationId = in.readUTF(); + conversationToken = readNullableUTF(in); dataFile = new File(in.readUTF()); messagesFile = new File(in.readUTF()); state = ConversationState.valueOf(in.readByte()); encryptionKey = readNullableUTF(in); userId = readNullableUTF(in); - JWT = readNullableUTF(in); } @Override public void writeExternal(DataOutput out) throws IOException { out.writeUTF(conversationId); + writeNullableUTF(out, conversationToken); out.writeUTF(dataFile.getAbsolutePath()); out.writeUTF(messagesFile.getAbsolutePath()); out.writeByte(state.ordinal()); writeNullableUTF(out, encryptionKey); writeNullableUTF(out, userId); - writeNullableUTF(out, JWT); } public String getConversationId() { @@ -107,8 +106,8 @@ public String getUserId() { return userId; } - public String getJWT() { - return JWT; + public String getConversationToken() { + return conversationToken; } @Override @@ -116,11 +115,11 @@ public String toString() { return "ConversationMetadataItem{" + "state=" + state + ", conversationId='" + conversationId + '\'' + + ", conversationToken='" + conversationToken + '\'' + ", dataFile=" + dataFile + ", messagesFile=" + messagesFile + ", encryptionKey='" + encryptionKey + '\'' + ", userId='" + userId + '\'' + - ", JWT='" + JWT + '\'' + '}'; } } From 77374559c79322217c3b78a671b6573f345e58c6 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 19 Jun 2017 12:06:25 -0700 Subject: [PATCH 363/465] Remove unneeded logging. --- .../apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 2e4034d3c..db9bd21d4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -455,7 +455,6 @@ private void upgradeVersion2to3(SQLiteDatabase db) { ensureClosed(cursor); if (db != null) { db.endTransaction(); - ApptentiveLog.e(DATABASE, "Transaction ended."); } } } From 7a85a887a43de9420906880f5bfb5485a8ec2d3f Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 19 Jun 2017 12:06:44 -0700 Subject: [PATCH 364/465] Fix device object sending. --- .../apptentive/android/sdk/model/DevicePayload.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index d63b38e46..501a16ad7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -9,6 +9,7 @@ import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; +import org.json.JSONObject; public class DevicePayload extends JsonPayload { @@ -177,4 +178,14 @@ public void setUtcOffset(String utcOffset) { put(KEY_UTC_OFFSET, utcOffset); } + @Override + protected JSONObject marshallForSending() { + JSONObject jsonObject = new JSONObject(); + try { + jsonObject.put("device", super.marshallForSending()); + } catch (JSONException e) { + // This can't happen. + } + return jsonObject; + } } From fdc7a90e0ecc9b0ac29a7997f37b8498df4bc986 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 20 Jun 2017 16:15:30 -0700 Subject: [PATCH 365/465] Fix login to non-existing conversation so that it creates a conversation at that time. Fix conversation to perform whatever teardown it needs, delegating to nested objects as needed. Fix handling of encryption key so it is available when loading data file. --- .../sdk/comm/ApptentiveHttpClient.java | 14 ++++++---- .../sdk/conversation/Conversation.java | 20 ++++++-------- .../sdk/conversation/ConversationManager.java | 26 ++++++++++++------- .../module/messagecenter/MessageManager.java | 5 ++++ 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index a4d807448..f2bf99b06 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -35,7 +35,8 @@ public class ApptentiveHttpClient implements PayloadRequestSender { // Active API private static final String ENDPOINT_CONVERSATION = "/conversation"; private static final String ENDPOINT_LEGACY_CONVERSATION = "/conversation/token"; - private static final String ENDPOINT_LOGIN = "/conversations/%s/session"; + private static final String ENDPOINT_LOG_IN_TO_EXISTING_CONVERSATION = "/conversations/%s/session"; + private static final String ENDPOINT_LOG_IN_TO_NEW_CONVERSATION = "/conversations"; private final String apptentiveKey; private final String apptentiveSignature; @@ -85,9 +86,6 @@ public HttpJsonRequest getLegacyConversationId(String conversationToken, HttpReq } public HttpJsonRequest login(String conversationId, String token, HttpRequest.Listener listener) { - if (conversationId == null) { - throw new IllegalArgumentException("Conversation id is null"); - } if (token == null) { throw new IllegalArgumentException("Token is null"); } @@ -98,7 +96,13 @@ public HttpJsonRequest login(String conversationId, String token, HttpRequest.Li } catch (JSONException e) { // Can't happen } - String endPoint = StringUtils.format(ENDPOINT_LOGIN, conversationId); + String endPoint; + if (conversationId == null) { + endPoint = ENDPOINT_LOG_IN_TO_NEW_CONVERSATION; + + }else { + endPoint = StringUtils.format(ENDPOINT_LOG_IN_TO_EXISTING_CONVERSATION, conversationId); + } HttpJsonRequest request = createJsonRequest(endPoint, json, HttpRequestMethod.POST); request.addListener(listener); httpRequestManager.startRequest(request); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index dcbb8e8c9..fe1da71ab 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -45,8 +45,6 @@ import java.io.File; import static com.apptentive.android.sdk.debug.Assert.assertFail; -import static com.apptentive.android.sdk.debug.Assert.assertNotNull; -import static com.apptentive.android.sdk.debug.Assert.assertNull; import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; import static com.apptentive.android.sdk.ApptentiveLogTag.*; import static com.apptentive.android.sdk.conversation.ConversationState.*; @@ -140,6 +138,12 @@ public Conversation(File conversationDataFile, File conversationMessagesFile) { messageManager = new MessageManager(messageStore); // it's important to initialize message manager in a constructor since other SDK parts depend on it via Apptentive singleton } + public void teardown() { + if (messageManager != null) { + messageManager.teardown(); + } + } + //region Interactions /** @@ -241,11 +245,9 @@ private synchronized void saveConversationData() throws SerializerException { long start = System.currentTimeMillis(); FileSerializer serializer; - if (hasState(LOGGED_IN)) { - assertNotNull(encryptionKey, "Missing encryption key"); + if (!StringUtils.isNullOrEmpty(encryptionKey)) { serializer = new EncryptedFileSerializer(conversationDataFile, encryptionKey); } else { - assertNull(encryptionKey, "Encryption key should be null"); serializer = new FileSerializer(conversationDataFile); } @@ -257,11 +259,9 @@ synchronized void loadConversationData() throws SerializerException { long start = System.currentTimeMillis(); FileSerializer serializer; - if (hasState(LOGGED_IN)) { - assertNotNull(encryptionKey, "Missing encryption key"); + if (!StringUtils.isNullOrEmpty(encryptionKey)) { serializer = new EncryptedFileSerializer(conversationDataFile, encryptionKey); } else { - assertNull(encryptionKey, "Encryption key should be null"); serializer = new FileSerializer(conversationDataFile); } @@ -553,10 +553,6 @@ void checkInternalConsistency() throws IllegalStateException { } break; default: - if (!StringUtils.isNullOrEmpty(encryptionKey)) { - assertFail("Encryption key should be null"); - throw new IllegalStateException("Encryption key should be null"); - } break; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 21ceeb4d0..a6031e1a7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -576,7 +576,7 @@ private void requestLoggedInConversation(final String token, final LoginCallback userId = jwt.getPayload().getString("sub"); } catch (Exception e) { ApptentiveLog.e(e, "Error while extracting user id: Missing field \"sub\""); - callback.onLoginFail(e.getMessage()); + callback.onLoginFail("Error while extracting user id: Missing field \"sub\""); return; } @@ -593,8 +593,8 @@ public boolean accept(ConversationMetadataItem item) { }); if (conversationItem == null) { - ApptentiveLog.e("Unable to find an existing conversation with for user: '" + userId + "'"); - callback.onLoginFail("No previous conversation found"); + ApptentiveLog.e("No conversation found matching user: '%s'. Logging in as new user.", userId); + sendLoginRequest(null, userId, token, callback); return; } @@ -669,7 +669,8 @@ public void onFinish(HttpJsonRequest request) { try { final JSONObject responseObject = request.getResponseObject(); final String encryptionKey = responseObject.getString("encryption_key"); - handleLoginFinished(userId, token, encryptionKey); + final String incomingConversationId = responseObject.getString("id"); + handleLoginFinished(incomingConversationId, userId, token, encryptionKey); } catch (Exception e) { ApptentiveLog.e(e, "Exception while parsing login response"); handleLoginFailed("Internal error"); @@ -686,7 +687,7 @@ public void onFail(HttpJsonRequest request, String reason) { handleLoginFailed(reason); } - private void handleLoginFinished(final String userId, final String token, final String encryptionKey) { + private void handleLoginFinished(final String conversationId, final String userId, final String token, final String encryptionKey) { DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { @Override protected void execute() { @@ -704,16 +705,20 @@ public boolean accept(ConversationMetadataItem item) { } }); - if (conversationItem == null) { - handleLoginFailed("Unable to find an existing conversation with for user: '" + userId + "'"); - return; + if (conversationItem != null) { + conversationItem.encryptionKey = encryptionKey; + activeConversation = loadConversation(conversationItem); + } else { + ApptentiveLog.v(CONVERSATION, "Creating new logged in conversation..."); + File dataFile = new File(apptentiveConversationsStorageDir, "conversation-" + Util.generateRandomFilename()); + File messagesFile = new File(apptentiveConversationsStorageDir, "messages-" + Util.generateRandomFilename()); + activeConversation = new Conversation(dataFile, messagesFile); } - - activeConversation = loadConversation(conversationItem); } activeConversation.setEncryptionKey(encryptionKey); activeConversation.setConversationToken(token); + activeConversation.setConversationId(conversationId); activeConversation.setUserId(userId); activeConversation.setState(LOGGED_IN); handleConversationStateChange(activeConversation); @@ -760,6 +765,7 @@ private void doLogout() { ApptentiveLog.d("Ending active conversation."); // Post synchronously to ensure logout payload can be sent before destroying the logged in conversation. ApptentiveNotificationCenter.defaultCenter().postNotificationSync(NOTIFICATION_CONVERSATION_WILL_LOGOUT, ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, activeConversation)); + activeConversation.teardown(); activeConversation.setState(LOGGED_OUT); handleConversationStateChange(activeConversation); activeConversation = null; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index ace91ee9a..2d93882cb 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -231,6 +231,7 @@ private List fetchMessages(String afterId) { ApptentiveLog.v("No internet present. Cancelling request."); return null; } + // TODO: Use the new ApptentiveHttpClient for this. ApptentiveHttpResponse response = ApptentiveClient.getMessages(null, afterId, null); List ret = new ArrayList<>(); @@ -583,4 +584,8 @@ MessageCountDispatchTask setMessageCount(int messageCount) { } //endregion + + public void teardown() { + ApptentiveNotificationCenter.defaultCenter().removeObserver(this); + } } From 930ad85fa9bc12bbee8ef0d14e22968bca9c4714 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 20 Jun 2017 16:39:16 -0700 Subject: [PATCH 366/465] Fix VERY_VERBOSE log level so it doesn't interfere with Android log levels. --- .../apptentive/android/sdk/ApptentiveLog.java | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java index f4436d57d..1f937e112 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java @@ -51,13 +51,13 @@ private static void doLog(Level level, ApptentiveLogTag tag, Throwable throwable message = extra + " " + message; } - android.util.Log.println(level.getLevel(), TAG, message); + android.util.Log.println(level.getAndroidLevel(), TAG, message); if(throwable != null){ if(throwable.getMessage() != null){ - android.util.Log.println(level.getLevel(), TAG, throwable.getMessage()); + android.util.Log.println(level.getAndroidLevel(), TAG, throwable.getMessage()); } while(throwable != null) { - android.util.Log.println(level.getLevel(), TAG, android.util.Log.getStackTraceString(throwable)); + android.util.Log.println(level.getAndroidLevel(), TAG, android.util.Log.getStackTraceString(throwable)); throwable = throwable.getCause(); } } @@ -189,7 +189,7 @@ public static void a(Throwable throwable, String message, Object... args){ //region TODO: Delete these: public static void vv(String message, Throwable throwable, Object... args){ - doLog(Level.VERBOSE, null, throwable, message, args); + doLog(Level.VERY_VERBOSE, null, throwable, message, args); } public static void v(String message, Throwable throwable, Object... args){ @@ -213,19 +213,25 @@ public static void a(String message, Throwable throwable, Object... args){ //endregion public enum Level { - VERY_VERBOSE(Log.VERBOSE), - VERBOSE(Log.VERBOSE), - DEBUG(Log.DEBUG), - INFO(Log.INFO), - WARN(Log.WARN), - ERROR(Log.ERROR), - ASSERT(Log.ASSERT), - DEFAULT(Log.INFO); + VERY_VERBOSE(1, Log.VERBOSE), + VERBOSE(Log.VERBOSE, Log.VERBOSE), + DEBUG(Log.DEBUG, Log.DEBUG), + INFO(Log.INFO, Log.INFO), + WARN(Log.WARN, Log.WARN), + ERROR(Log.ERROR, Log.ERROR), + ASSERT(Log.ASSERT, Log.ASSERT), + DEFAULT(Log.INFO, Log.INFO); private int level; + private int androidLevel; - private Level(int level) { + private Level(int level, int androidLevel) { this.level = level; + this.androidLevel = androidLevel; + } + + public int getAndroidLevel() { + return androidLevel; } public int getLevel() { @@ -246,7 +252,7 @@ public static Level parse(String level) { * @return true if "level" can be logged. */ public boolean canLog(Level level) { - return level.getLevel() >= getLevel(); + return level.level >= this.level; } } } From a7459d2d9655dc2414c9c2a64cc616532a208182 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 20 Jun 2017 16:40:19 -0700 Subject: [PATCH 367/465] Log headers out in VERY_VERBOSE mode in legacy ApptentiveClient --- .../java/com/apptentive/android/sdk/comm/ApptentiveClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 5a4a47bd4..f6c1c9ca3 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -135,7 +135,7 @@ private static ApptentiveHttpResponse performHttpRequest(String authToken, boole connection.setRequestProperty("APPTENTIVE-SIGNATURE", notNull(ApptentiveInternal.getInstance().getApptentiveSignature())); - ApptentiveLog.vv("Headers: ", connection.getRequestProperties()); + ApptentiveLog.vv("Headers: %s", connection.getRequestProperties()); switch (method) { case GET: connection.setRequestMethod("GET"); From bdb9ad68dec16283324c8938270936f73fd22880 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 21 Jun 2017 09:44:12 -0700 Subject: [PATCH 368/465] Update MessageManager to only assert on missing response for successful responses. --- .../android/sdk/module/messagecenter/MessageManager.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 2d93882cb..b3c157bf6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -286,7 +286,6 @@ public void onSentMessage(String nonce, int responseCode, JSONObject responseJso final ApptentiveMessage apptentiveMessage = messageStore.findMessage(nonce); assertNotNull(apptentiveMessage, "Can't find a message with nonce: %s", nonce); - assertNotNull(responseJson, "Missing required responseJson."); if (apptentiveMessage == null) { return; // should not happen but we want to stay safe } @@ -313,6 +312,7 @@ public void onSentMessage(String nonce, int responseCode, JSONObject responseJso } if (isSuccessful) { + assertNotNull(responseJson, "Missing required responseJson."); // Don't store hidden messages once sent. Delete them. if (apptentiveMessage.isHidden()) { ((CompoundMessage) apptentiveMessage).deleteAssociatedFiles(); @@ -320,9 +320,7 @@ public void onSentMessage(String nonce, int responseCode, JSONObject responseJso return; } try { - // TODO: update the database with these values apptentiveMessage.setState(ApptentiveMessage.State.sent); - apptentiveMessage.setId(responseJson.getString(ApptentiveMessage.KEY_ID)); apptentiveMessage.setCreatedAt(responseJson.getDouble(ApptentiveMessage.KEY_CREATED_AT)); } catch (JSONException e) { From 302fbd8330288b6b858c0a67568657069ee59c72 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 21 Jun 2017 09:54:12 -0700 Subject: [PATCH 369/465] Remove bulk of content from InteractionManager and move it to Conversation. Most of it had already been moved. --- .../android/sdk/ApptentiveInternal.java | 1 - .../sdk/conversation/Conversation.java | 48 ++++++++++---- .../interaction/InteractionManager.java | 62 ------------------- 3 files changed, 37 insertions(+), 74 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index aac3b9689..9907624c6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -523,7 +523,6 @@ private boolean start() { // if (featureEverUsed) { // messageManager.init(); // } - activeConversation.setInteractionManager(new InteractionManager(activeConversation)); } apptentiveToolbarTheme = appContext.getResources().newTheme(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index fe1da71ab..f01d18dc9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -82,8 +82,10 @@ public class Conversation implements DataChangedListener, Destroyable { */ private final File conversationMessagesFile; - // TODO: remove this class - private InteractionManager interactionManager; + /** + * Internal flag to turn interaction polling on and off fir testing. + */ + private Boolean pollForInteractions; private ConversationState state = ConversationState.UNDEFINED; @@ -186,7 +188,6 @@ private boolean fetchInteractionsSync(String conversationId) { ApptentiveLog.v(CONVERSATION, "Fetching Interactions"); ApptentiveHttpResponse response = ApptentiveClient.getInteractions(conversationId); - // TODO: Move this to global config SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); boolean updateSuccessful = true; @@ -230,6 +231,39 @@ else if (!response.isSuccessful()) { return updateSuccessful; } + public boolean isPollForInteractions() { + if (pollForInteractions == null) { + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); + pollForInteractions = prefs.getBoolean(Constants.PREF_KEY_POLL_FOR_INTERACTIONS, true); + } + return pollForInteractions; + } + + public void setPollForInteractions(boolean pollForInteractions) { + this.pollForInteractions = pollForInteractions; + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); + prefs.edit().putBoolean(Constants.PREF_KEY_POLL_FOR_INTERACTIONS, pollForInteractions).apply(); + } + + /** + * Made public for testing. There is no other reason to use this method directly. + */ + public void storeInteractionManifest(String interactionManifest) { + try { + InteractionManifest payload = new InteractionManifest(interactionManifest); + Interactions interactions = payload.getInteractions(); + Targets targets = payload.getTargets(); + if (interactions != null && targets != null) { + setTargets(targets.toString()); + setInteractions(interactions.toString()); + } else { + ApptentiveLog.e("Unable to save InteractionManifest."); + } + } catch (JSONException e) { + ApptentiveLog.w("Invalid InteractionManifest received."); + } + } + //endregion //region Saving @@ -481,14 +515,6 @@ public MessageManager getMessageManager() { return messageManager; } - public InteractionManager getInteractionManager() { - return interactionManager; - } - - public void setInteractionManager(InteractionManager interactionManager) { - this.interactionManager = interactionManager; - } - synchronized File getConversationDataFile() { return conversationDataFile; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java index d050f1128..bfafdb1e0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/InteractionManager.java @@ -6,72 +6,10 @@ package com.apptentive.android.sdk.module.engagement.interaction; -import android.content.SharedPreferences; - -import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.conversation.Conversation; -import com.apptentive.android.sdk.module.engagement.interaction.model.InteractionManifest; -import com.apptentive.android.sdk.module.engagement.interaction.model.Interactions; -import com.apptentive.android.sdk.module.engagement.interaction.model.Targets; -import com.apptentive.android.sdk.util.Constants; - -import org.json.JSONException; - -import java.util.concurrent.atomic.AtomicBoolean; - -/** Determining wheather interaction should be fetched and performing the fetch */ public class InteractionManager { - private Boolean pollForInteractions; - // boolean to prevent multiple fetching threads - private AtomicBoolean isFetchPending = new AtomicBoolean(false); - - private Conversation conversation; - - public InteractionManager(Conversation conversation) { - this.conversation = conversation; - } - public interface InteractionUpdateListener { void onInteractionUpdated(boolean successful); } - /** - * Made public for testing. There is no other reason to use this method directly. - */ - public void storeInteractionManifest(String interactionManifest) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation == null) { - return; - } - try { - InteractionManifest payload = new InteractionManifest(interactionManifest); - Interactions interactions = payload.getInteractions(); - Targets targets = payload.getTargets(); - if (interactions != null && targets != null) { - conversation.setTargets(targets.toString()); - conversation.setInteractions(interactions.toString()); - } else { - ApptentiveLog.e("Unable to save interactionManifest."); - } - } catch (JSONException e) { - ApptentiveLog.w("Invalid InteractionManifest received."); - } - - } - - public boolean isPollForInteractions() { - if (pollForInteractions == null) { - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - pollForInteractions = prefs.getBoolean(Constants.PREF_KEY_POLL_FOR_INTERACTIONS, true); - } - return pollForInteractions; - } - - public void setPollForInteractions(boolean pollForInteractions) { - this.pollForInteractions = pollForInteractions; - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - prefs.edit().putBoolean(Constants.PREF_KEY_POLL_FOR_INTERACTIONS, pollForInteractions).apply(); - } } From 3e5f5ab44ed52af3736ea8204d7ca4a5cd8fa95a Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 21 Jun 2017 10:40:02 -0700 Subject: [PATCH 370/465] Add AuthenticationFailedListener, and plumbing to fire it when payload sending authentication fails for the current conversation. --- .../apptentive/android/sdk/Apptentive.java | 56 +++++++++++++++++++ .../android/sdk/ApptentiveInternal.java | 32 ++++++++++- .../android/sdk/ApptentiveNotifications.java | 7 +++ .../android/sdk/model/PayloadData.java | 14 ++++- .../android/sdk/network/HttpRequest.java | 30 ++++++++-- .../sdk/storage/ApptentiveDatabaseHelper.java | 2 +- .../android/sdk/storage/PayloadSender.java | 8 +++ .../sdk/storage/JsonPayloadSenderTest.java | 2 +- 8 files changed, 141 insertions(+), 10 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 50ed4d6a6..8cf3e7cbc 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -1167,6 +1167,62 @@ public static void logout() { } } + public static void addAuthenticationFailureListener(AuthenticationFailedListener listener) { + try { + if (!ApptentiveInternal.checkRegistered()) { + return; + } + ApptentiveInternal.getInstance().setAuthenticationFailureListener(listener); + } catch (Exception e) { + ApptentiveLog.w("Error in Apptentive.addUnreadMessagesListener()", e); + MetricModule.sendError(e, null, null); + } + } + + public interface AuthenticationFailedListener { + void onAuthenticationFailed(AuthenticationFailedReason reason); + } + + public enum AuthenticationFailedReason { + UNKNOWN, + INVALID_ALGORITHM, + MALFORMED_TOKEN, + INVALID_TOKEN, + MISSING_SUB_CLAIM, + MISMATCHED_SUB_CLAIM, + INVALID_SUB_CLAIM, + EXPIRED_TOKEN, + REVOKED_TOKEN, + MISSING_APP_KEY, + MISSING_APP_SIGNATURE, + INVALID_KEY_SIGNATURE_PAIR; + + private String error; + + public String message() { + return error; + } + + public static AuthenticationFailedReason parse(String errorType, String error) { + try { + AuthenticationFailedReason ret = AuthenticationFailedReason.valueOf(errorType); + ret.error = error; + return ret; + } catch (IllegalArgumentException e) { + ApptentiveLog.w("Error parsing unknown Apptentive.AuthenticationFailedReason: %s", errorType); + } + return UNKNOWN; + } + + @Override + public String toString() { + return "AuthenticationFailedReason{" + + "error='" + error + '\'' + + "errorType='" + name() + '\'' + + '}'; + } + } + //endregion /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 9907624c6..e933f514b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -72,9 +72,12 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_ACTIVITY_STARTED; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTERED_BACKGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTERED_FOREGROUND; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_AUTHENTICATION_FAILED; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_WILL_LOGOUT; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_INTERACTIONS_SHOULD_DISMISS; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_ACTIVITY; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_AUTHENTICATION_FAILED_REASON; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION_ID; import static com.apptentive.android.sdk.util.Constants.CONVERSATIONS_DIR; /** @@ -118,6 +121,8 @@ public class ApptentiveInternal implements ApptentiveNotificationObserver { private final LinkedBlockingQueue interactionUpdateListeners = new LinkedBlockingQueue(); + private WeakReference authenticationFailedListenerRef = null; + private final ExecutorService cachedExecutor; // Holds reference to the current foreground activity of the host app @@ -184,7 +189,9 @@ private ApptentiveInternal(Application application, String apptentiveKey, String cachedExecutor = Executors.newCachedThreadPool(); lifecycleCallbacks = new ApptentiveActivityLifecycleCallbacks(); - ApptentiveNotificationCenter.defaultCenter().addObserver(NOTIFICATION_CONVERSATION_WILL_LOGOUT, this); + ApptentiveNotificationCenter.defaultCenter() + .addObserver(NOTIFICATION_CONVERSATION_WILL_LOGOUT, this) + .addObserver(NOTIFICATION_AUTHENTICATION_FAILED, this); } public static boolean isApptentiveRegistered() { @@ -748,6 +755,23 @@ public void removeInteractionUpdateListener(InteractionManager.InteractionUpdate interactionUpdateListeners.remove(listener); } + public void setAuthenticationFailureListener(Apptentive.AuthenticationFailedListener listener) { + authenticationFailedListenerRef = new WeakReference<>(listener); + } + + public void notifyAuthenticationFailedListener(Apptentive.AuthenticationFailedReason reason, String conversationIdOfFailedRequest) { + if (isConversationActive()) { + String activeConversationId = getConversation().getConversationId(); + if (activeConversationId.equals(conversationIdOfFailedRequest)) { + Apptentive.AuthenticationFailedListener listener = authenticationFailedListenerRef.get(); + if (listener != null) { + listener.onAuthenticationFailed(reason); + } + return; + } + } + } + /** * Pass in a log level to override the default, which is {@link ApptentiveLog.Level#INFO} */ @@ -1025,6 +1049,10 @@ public static void dismissAllInteractions() { public void onReceiveNotification(ApptentiveNotification notification) { if (notification.hasName(NOTIFICATION_CONVERSATION_WILL_LOGOUT)) { getApptentiveTaskManager().addPayload(new LogoutPayload()); + } else if (notification.hasName(NOTIFICATION_AUTHENTICATION_FAILED)) { + String conversationIdOfFailedRequest = notification.getUserInfo(NOTIFICATION_KEY_CONVERSATION_ID, String.class); + Apptentive.AuthenticationFailedReason authenticationFailedReason = notification.getUserInfo(NOTIFICATION_KEY_AUTHENTICATION_FAILED_REASON, Apptentive.AuthenticationFailedReason.class); + notifyAuthenticationFailedListener(authenticationFailedReason, conversationIdOfFailedRequest); } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java index 206e0e729..9922c5ad6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -53,10 +53,17 @@ public class ApptentiveNotifications { */ public static final String NOTIFICATION_INTERACTIONS_SHOULD_DISMISS = "NOTIFICATION_INTERACTIONS_SHOULD_DISMISS"; + /** + * Sent when a request to the server fails with a 401, and external code needs to be notified. + */ + public static final String NOTIFICATION_AUTHENTICATION_FAILED = "NOTIFICATION_AUTHENTICATION_FAILED"; // { conversationId : String, authenticationFailedReason: AuthenticationFailedReason } + // keys public static final String NOTIFICATION_KEY_SUCCESSFUL = "successful"; public static final String NOTIFICATION_KEY_ACTIVITY = "activity"; public static final String NOTIFICATION_KEY_CONVERSATION = "conversation"; + public static final String NOTIFICATION_KEY_CONVERSATION_ID = "conversationId"; + public static final String NOTIFICATION_KEY_AUTHENTICATION_FAILED_REASON = "authenticationFailedReason";// type: AuthenticationFailedReason public static final String NOTIFICATION_KEY_PAYLOAD = "payload"; public static final String NOTIFICATION_KEY_RESPONSE_CODE = "responseCode"; public static final String NOTIFICATION_KEY_RESPONSE_DATA = "responseData"; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java index 4a07e88b9..176dfb7cf 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PayloadData.java @@ -12,6 +12,7 @@ public class PayloadData { private final PayloadType type; private final String nonce; + private final String conversationId; private final byte[] data; private final String authToken; private final String contentType; @@ -20,7 +21,7 @@ public class PayloadData { private final boolean encrypted; - public PayloadData(PayloadType type, String nonce, byte[] data, String authToken, String contentType, String httpRequestPath, HttpRequestMethod httpRequestMethod, boolean encrypted) { + public PayloadData(PayloadType type, String nonce, String conversationId, byte[] data, String authToken, String contentType, String httpRequestPath, HttpRequestMethod httpRequestMethod, boolean encrypted) { if (type == null) { throw new IllegalArgumentException("Payload type is null"); } @@ -29,6 +30,10 @@ public PayloadData(PayloadType type, String nonce, byte[] data, String authToken throw new IllegalArgumentException("Nonce is null"); } + if (conversationId == null) { + throw new IllegalArgumentException("Conversation ID is null"); + } + if (data == null) { throw new IllegalArgumentException("Data is null"); } @@ -47,6 +52,7 @@ public PayloadData(PayloadType type, String nonce, byte[] data, String authToken this.type = type; this.nonce = nonce; + this.conversationId = conversationId; this.data = data; this.authToken = authToken; this.contentType = contentType; @@ -59,7 +65,7 @@ public PayloadData(PayloadType type, String nonce, byte[] data, String authToken @Override public String toString() { - return StringUtils.format("type=%s nonce=%s authToken=%s httpRequestPath=%s", type, nonce, authToken, httpRequestPath); + return StringUtils.format("type=%s nonce=%s conversationId=%s authToken=%s httpRequestPath=%s", type, nonce, conversationId, authToken, httpRequestPath); } //endregion @@ -74,6 +80,10 @@ public String getNonce() { return nonce; } + public String getConversationId() { + return conversationId; + } + public byte[] getData() { return data; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 71a4ee6ce..75484b705 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -2,6 +2,7 @@ import android.util.Base64; +import com.apptentive.android.sdk.Apptentive; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.StringUtils; @@ -9,6 +10,9 @@ import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; +import org.json.JSONException; +import org.json.JSONObject; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -160,7 +164,7 @@ private void finishRequest() { try { listener.onFinish(this); } catch (Exception e) { - ApptentiveLog.e(e, "Exception in request finish listener"); + ApptentiveLog.e(e, "Exception in request onFinish() listener"); } } } else if (isCancelled()) { @@ -168,7 +172,7 @@ private void finishRequest() { try { listener.onCancel(this); } catch (Exception e) { - ApptentiveLog.e(e, "Exception in request finish listener"); + ApptentiveLog.e(e, "Exception in request onCancel() listener"); } } } else { @@ -176,7 +180,7 @@ private void finishRequest() { try { listener.onFail(this, errorMessage); } catch (Exception e) { - ApptentiveLog.e(e, "Exception in request finish listener"); + ApptentiveLog.e(e, "Exception in request onFail() listener"); } } } @@ -302,7 +306,7 @@ private void sendRequestSync() throws IOException { } else { errorMessage = StringUtils.format("Unexpected response code: %d (%s)", responseCode, connection.getResponseMessage()); responseData = readResponse(connection.getErrorStream(), gzipped); - ApptentiveLog.w(NETWORK, "Response data: %s", responseData); + ApptentiveLog.w(NETWORK, "Error response data: %s", responseData); } if (isCancelled()) { @@ -547,6 +551,24 @@ public int getResponseCode() { return responseCode; } + public boolean isAuthenticationFailure() { + return responseCode == 401; + } + + public Apptentive.AuthenticationFailedReason getAuthenticationFailedReason() { + if (responseData != null) { + try { + JSONObject errorObject = new JSONObject(responseData); + String error = errorObject.optString("error", null); + String errorType = errorObject.optString("error_type", null); + return Apptentive.AuthenticationFailedReason.parse(errorType, error); + } catch (JSONException e) { + ApptentiveLog.w(e, "Error parsing authentication failure object."); + } + } + return Apptentive.AuthenticationFailedReason.UNKNOWN; + } + public HttpRequest setRetryPolicy(HttpRequestRetryPolicy retryPolicy) { if (retryPolicy == null) { throw new IllegalArgumentException("Retry policy is null"); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index db9bd21d4..135f581bc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -574,7 +574,7 @@ public PayloadData getOldestUnsentPayload() { final String contentType = notNull(cursor.getString(PayloadEntry.COLUMN_CONTENT_TYPE.index)); final HttpRequestMethod httpRequestMethod = HttpRequestMethod.valueOf(notNull(cursor.getString(PayloadEntry.COLUMN_REQUEST_METHOD.index))); final boolean encrypted = cursor.getInt(PayloadEntry.COLUMN_ENCRYPTED.index) == 1; - return new PayloadData(payloadType, nonce, data, authToken, contentType, httpRequestPath, httpRequestMethod, encrypted); + return new PayloadData(payloadType, nonce, conversationId, data, authToken, contentType, httpRequestPath, httpRequestMethod, encrypted); } return null; } catch (Exception e) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 39e897ba9..5fb562795 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -10,11 +10,15 @@ import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.network.HttpRequestRetryPolicy; +import com.apptentive.android.sdk.notifications.ApptentiveNotificationCenter; import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONObject; import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_AUTHENTICATION_FAILED; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_AUTHENTICATION_FAILED_REASON; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION_ID; /** * Class responsible for a serial payload sending (one at a time) @@ -105,6 +109,7 @@ public void onFinish(HttpRequest request) { final JSONObject responseData = new JSONObject(request.getResponseData()); handleFinishSendingPayload(payload, false, null, request.getResponseCode(), responseData); } catch (Exception e) { + // TODO: Stop assuming the response is JSON. In fact, just send bytes back, and whatever part of the SDK needs it can try to convert it to the desired format. ApptentiveLog.e(PAYLOADS, "Exception while handling payload send response"); handleFinishSendingPayload(payload, false, null, -1, null); } @@ -117,6 +122,9 @@ public void onCancel(HttpRequest request) { @Override public void onFail(HttpRequest request, String reason) { + if (request.isAuthenticationFailure()) { + ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_AUTHENTICATION_FAILED, NOTIFICATION_KEY_CONVERSATION_ID, payload.getConversationId(), NOTIFICATION_KEY_AUTHENTICATION_FAILED_REASON, request.getAuthenticationFailedReason()); + } handleFinishSendingPayload(payload, false, reason, request.getResponseCode(), null); } }); diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java index c48c967c1..3b594ce6f 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java @@ -95,7 +95,7 @@ class MockPayload extends PayloadData { private ResponseHandler responseHandler; public MockPayload(String key, Object value) { - super(PayloadType.unknown, "nonce", new byte[0], "authToken", "contentType", "path", HttpRequestMethod.GET, false); // TODO: figure out a better type + super(PayloadType.unknown, "nonce", "conversationId", new byte[0], "authToken", "contentType", "path", HttpRequestMethod.GET, false); // TODO: figure out a better type json = StringUtils.format("{'%s':'%s'}", key, value); responseHandler = new DefaultResponseHandler(); From c51248b272494be91032232594c08b04bfbc9846 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 21 Jun 2017 11:02:04 -0700 Subject: [PATCH 371/465] Rename error getter. --- .../src/main/java/com/apptentive/android/sdk/Apptentive.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 8cf3e7cbc..0dcb68d77 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -1199,7 +1199,7 @@ public enum AuthenticationFailedReason { private String error; - public String message() { + public String error() { return error; } From 8a4162bc7cf6395e5bd6905054479418056fb8fe Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 21 Jun 2017 11:47:31 -0700 Subject: [PATCH 372/465] Fix test by removing dependency between MockHttpRequest and Android API. --- .../com/apptentive/android/sdk/network/HttpRequest.java | 6 +++++- .../com/apptentive/android/sdk/network/MockHttpRequest.java | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 75484b705..68bc28768 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -257,7 +257,7 @@ private void sendRequestSync() throws IOException { connection.setConnectTimeout(connectTimeout); connection.setReadTimeout(readTimeout); - if (!Util.isNetworkConnectionPresent()) { + if (!isNetworkConnectionPresent()) { ApptentiveLog.d("No network connection present. Cancelling request."); cancel(); } @@ -320,6 +320,10 @@ private void sendRequestSync() throws IOException { } } + protected boolean isNetworkConnectionPresent() { + return Util.isNetworkConnectionPresent(); + } + //region Retry private final DispatchTask retryDispatchTask = new DispatchTask() { diff --git a/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpRequest.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpRequest.java index d0d03b89c..dbca797e9 100644 --- a/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpRequest.java +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpRequest.java @@ -59,6 +59,11 @@ public MockHttpRequest setResponseData(String responseData) { return this; } + @Override + protected boolean isNetworkConnectionPresent() { + return true; + } + @Override public String toString() { return getName(); From cf3aff94fc8f81e169b5ec7f41bc23c35c4a86a4 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 21 Jun 2017 12:42:14 -0700 Subject: [PATCH 373/465] Supply Message Storage classes with Person ID when they need to be able to determine if messages are outgoing or incoming. --- .../storage/ApptentiveDatabaseHelperTest.java | 2 +- .../android/sdk/conversation/Conversation.java | 5 ++--- .../sdk/conversation/FileMessageStore.java | 16 ++++++++++++++-- .../module/messagecenter/MessageManager.java | 13 ++++++++++--- .../messagecenter/model/MessageFactory.java | 18 +++++++++--------- 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java index 39ae07744..f3598b50f 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java @@ -52,7 +52,7 @@ public void testMigration() throws Exception { new EventPayload("{\"nonce\":\"b9a91f27-87b4-4bd9-b9a0-5c605891824a\",\"client_created_at\":1.492737199856E9,\"client_created_at_utc_offset\":-25200,\"label\":\"com.apptentive#app#launch\"}"), new DevicePayload("{\"device\":\"bullhead\",\"integration_config\":{},\"locale_country_code\":\"US\",\"carrier\":\"\",\"uuid\":\"6c0b74d07c064421\",\"build_type\":\"user\",\"cpu\":\"arm64-v8a\",\"os_build\":\"3687331\",\"manufacturer\":\"LGE\",\"radio_version\":\"M8994F-2.6.36.2.20\",\"os_name\":\"Android\",\"build_id\":\"N4F26T\",\"utc_offset\":\"-28800\",\"bootloader_version\":\"BHZ11h\",\"board\":\"bullhead\",\"os_api_level\":\"25\",\"current_carrier\":\"AT&T\",\"network_type\":\"LTE\",\"locale_raw\":\"en_US\",\"brand\":\"google\",\"os_version\":\"7.1.1\",\"product\":\"bullhead\",\"model\":\"Nexus 5X\",\"locale_language_code\":\"en\",\"custom_data\":{}}"), new PersonPayload("{\"custom_data\":{}}"), - MessageFactory.fromJson("{\"nonce\":\"a68d606c-083a-4496-a5e0-f07bcdff52a4\",\"client_created_at\":1.492737257565E9,\"client_created_at_utc_offset\":-25200,\"type\":\"CompoundMessage\",\"body\":\"Test message\",\"text_only\":false}") + MessageFactory.fromJson("{\"nonce\":\"a68d606c-083a-4496-a5e0-f07bcdff52a4\",\"client_created_at\":1.492737257565E9,\"client_created_at_utc_offset\":-25200,\"type\":\"CompoundMessage\",\"body\":\"Test message\",\"text_only\":false}", null) }; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index f01d18dc9..f3f6d1dab 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -14,7 +14,6 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; -import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.InteractionManifest; import com.apptentive.android.sdk.module.engagement.interaction.model.Interactions; @@ -136,8 +135,8 @@ public Conversation(File conversationDataFile, File conversationMessagesFile) { conversationData = new ConversationData(); conversationData.setDataChangedListener(this); - FileMessageStore messageStore = new FileMessageStore(conversationMessagesFile); - messageManager = new MessageManager(messageStore); // it's important to initialize message manager in a constructor since other SDK parts depend on it via Apptentive singleton + FileMessageStore messageStore = new FileMessageStore(getPerson().getId(), conversationMessagesFile); + messageManager = new MessageManager(this, messageStore); // it's important to initialize message manager in a constructor since other SDK parts depend on it via Apptentive singleton } public void teardown() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index f1f252f53..d12f10f33 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -39,11 +39,23 @@ class FileMessageStore implements MessageStore { */ private static final byte VERSION = 1; + private final String currentPersonId; private final File file; private final List messageEntries; private boolean shouldFetchFromFile; + FileMessageStore(String currentPersonId, File file) { + this.currentPersonId = currentPersonId; + this.file = file; + this.messageEntries = new ArrayList<>(); // we need a random access + this.shouldFetchFromFile = true; // we would lazily read it from a file later + } + + /** + * Only use this constructor from a test where you don't care about knowing whether a message is outgoing or incoming. + */ FileMessageStore(File file) { + this.currentPersonId = null; this.file = file; this.messageEntries = new ArrayList<>(); // we need a random access this.shouldFetchFromFile = true; // we would lazily read it from a file later @@ -105,7 +117,7 @@ public synchronized List getAllMessages() throws Exception { List apptentiveMessages = new ArrayList<>(); for (MessageEntry entry : messageEntries) { - ApptentiveMessage apptentiveMessage = MessageFactory.fromJson(entry.json); + ApptentiveMessage apptentiveMessage = MessageFactory.fromJson(entry.json, currentPersonId); if (apptentiveMessage == null) { ApptentiveLog.e("Error parsing Record json from database: %s", entry.json); continue; @@ -170,7 +182,7 @@ public ApptentiveMessage findMessage(String nonce) { for (int i = 0; i < messageEntries.size(); ++i) { final MessageEntry messageEntry = messageEntries.get(i); if (StringUtils.equal(nonce, messageEntry.nonce)) { - return MessageFactory.fromJson(messageEntry.json); + return MessageFactory.fromJson(messageEntry.json, currentPersonId); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index b3c157bf6..3e8107750 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -16,6 +16,7 @@ import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.model.PayloadType; @@ -65,6 +66,8 @@ public class MessageManager implements Destroyable, ApptentiveNotificationObserv private static int TOAST_TYPE_UNREAD_MESSAGE = 1; + private final Conversation conversation; + private final MessageStore messageStore; private WeakReference currentForegroundApptentiveActivity; @@ -95,14 +98,18 @@ protected void execute(int messageCount) { } }; - public MessageManager(MessageStore messageStore) { + public MessageManager(Conversation conversation, MessageStore messageStore) { + if (conversation == null) { + throw new IllegalArgumentException("Conversation is null"); + } + if (messageStore == null) { throw new IllegalArgumentException("Message store is null"); } + this.conversation = conversation; this.messageStore = messageStore; this.pollingWorker = new MessagePollingWorker(this); - // conversation.setMessageCenterFeatureUsed(true); FIXME: figure out what to do with this call registerNotifications(); } @@ -259,7 +266,7 @@ private List parseMessagesString(String messageString) throws JSONArray items = root.getJSONArray("messages"); for (int i = 0; i < items.length(); i++) { String json = items.getJSONObject(i).toString(); - ApptentiveMessage apptentiveMessage = MessageFactory.fromJson(json); + ApptentiveMessage apptentiveMessage = MessageFactory.fromJson(json, conversation.getPerson().getId()); // Since these came back from the server, mark them saved before updating them in the DB. if (apptentiveMessage != null) { apptentiveMessage.setState(ApptentiveMessage.State.saved); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java index da7615e55..b251e2c10 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java @@ -1,14 +1,12 @@ /* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ package com.apptentive.android.sdk.module.messagecenter.model; -import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.ApptentiveLogTag; import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.util.StringUtils; @@ -17,7 +15,15 @@ import org.json.JSONObject; public class MessageFactory { + /** + * Only use this method if you don't need to know whether the resulting Message is outgoing or + * incoming. Use {@link #fromJson(String, String)} otherwise. + */ public static ApptentiveMessage fromJson(String json) { + return fromJson(json, null); + } + + public static ApptentiveMessage fromJson(String json, String personId) { try { // If KEY_TYPE is set to CompoundMessage or not set, treat them as CompoundMessage ApptentiveMessage.Type type = ApptentiveMessage.Type.CompoundMessage; @@ -41,12 +47,6 @@ public static ApptentiveMessage fromJson(String json) { } catch (JSONException e) { // Ignore, senderId would be null } - // TODO: Should we pass the person ID in when we ask this object if it's outgoing instead? - if (!ApptentiveInternal.isConversationActive()) { - ApptentiveLog.d(ApptentiveLogTag.MESSAGES, "Can't load message because no active conversation."); - return null; - } - String personId = ApptentiveInternal.getInstance().getConversation().getPerson().getId(); // If senderId is null or same as the locally stored id, construct message as outgoing return new CompoundMessage(json, (senderId == null || (personId != null && senderId.equals(personId)))); case unknown: From f39b6a400973ef995291c35109949b776fd9cd14 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 21 Jun 2017 12:48:23 -0700 Subject: [PATCH 374/465] Override network connection check in another mock request to fix tests. --- .../apptentive/android/sdk/network/MockHttpJsonRequest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java index 5869d0eb0..9d6cd51cf 100644 --- a/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java +++ b/apptentive/src/testCommon/java/com/apptentive/android/sdk/network/MockHttpJsonRequest.java @@ -42,4 +42,9 @@ public MockHttpJsonRequest setMockResponseData(String responseData) { connection.setMockResponseData(responseData); return this; } + + @Override + protected boolean isNetworkConnectionPresent() { + return true; + } } From cdb285e448e5c50f22739c4bae7d6090e2333c18 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 21 Jun 2017 12:53:40 -0700 Subject: [PATCH 375/465] Fix encryptor test. --- .../android/sdk/storage/EncryptedPayloadSenderTest.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java index cf8b390f2..b4d68c571 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java @@ -10,7 +10,6 @@ import com.apptentive.android.sdk.encryption.Encryptor; import com.apptentive.android.sdk.model.EventPayload; -import org.json.JSONObject; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -25,24 +24,20 @@ public class EncryptedPayloadSenderTest extends TestCaseBase { @Test public void testEncryptedPayload() throws Exception { -/* final EventPayload original = new EventPayload(EVENT_LABEL, "trigger"); original.setToken(AUTH_TOKEN); original.setEncryptionKey(ENCRYPTION_KEY); - byte[] cipherText = original.getData(file); + byte[] cipherText = original.renderData(); Encryptor encryptor = new Encryptor(ENCRYPTION_KEY); try { byte[] plainText = encryptor.decrypt(cipherText); - JSONObject resultJson = new JSONObject(new String(plainText)); - EventPayload result = new EventPayload(resultJson.getJSONObject("payload").toString()); + EventPayload result = new EventPayload(new String(plainText)); assertEquals(result.getEventLabel(), EVENT_LABEL); } catch (Exception e) { fail(e.getMessage()); } -*/ - throw new Exception("FIXME"); } } \ No newline at end of file From e7d9f4003b553deea7d7565fe6a67b07aa97cc37 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 21 Jun 2017 12:56:36 -0700 Subject: [PATCH 376/465] Fix typo --- .../android/sdk/util/task/ApptentiveDownloaderTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java index c07bec07f..19cfa37b2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 207, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ From fd11e2e3ae513773436ff7c23082b2d3691bda4d Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 21 Jun 2017 16:29:07 -0700 Subject: [PATCH 377/465] Add Asserts for safety. --- .../com/apptentive/android/sdk/conversation/Conversation.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index f3f6d1dab..b373665dc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -14,6 +14,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; +import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.InteractionManifest; import com.apptentive.android.sdk.module.engagement.interaction.model.Interactions; @@ -279,8 +280,10 @@ private synchronized void saveConversationData() throws SerializerException { FileSerializer serializer; if (!StringUtils.isNullOrEmpty(encryptionKey)) { + Assert.assertFalse(hasState(ANONYMOUS, ANONYMOUS_PENDING)); serializer = new EncryptedFileSerializer(conversationDataFile, encryptionKey); } else { + Assert.assertTrue(hasState(ANONYMOUS, ANONYMOUS_PENDING)); serializer = new FileSerializer(conversationDataFile); } From e638a6c00008ee96a299f38e6bf31321bacd2f0c Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 21 Jun 2017 17:39:01 -0700 Subject: [PATCH 378/465] Tweak public API names for authentication failure listeners. Add and clean up Javadoc for public API. --- .../apptentive/android/sdk/Apptentive.java | 92 ++++++++++++++++++- 1 file changed, 87 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 0dcb68d77..05c45188f 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -1110,7 +1110,13 @@ public static void setOnSurveyFinishedListener(OnSurveyFinishedListener listener //region Login/Logout /** - * Starts login process asynchronously. This call returns immediately. + * Starts login process asynchronously. This call returns immediately. Using this method requires + * you to implement JWT generation on your server. Please read about it in Apptentive's Android + * Integration Reference Guide. + * + * @param token A JWT signed by your server using the secret from your app's Apptentive settings. + * @param callback A LoginCallback, which will be called asynchronously when the login succeeds + * or fails. */ public static void login(String token, LoginCallback callback) { try { @@ -1137,7 +1143,7 @@ public static void login(String token, LoginCallback callback) { } /** - * Callback interface for an async login process. + * Callback interface login(). */ public interface LoginCallback { /** @@ -1146,7 +1152,8 @@ public interface LoginCallback { void onLoginFinish(); /** - * Called when a login attempt has failed. + * Called when a login attempt has failed. May be called synchronously, for example, if your JWT + * is missing the "sub" claim. * * @param errorMessage failure cause message */ @@ -1167,34 +1174,109 @@ public static void logout() { } } - public static void addAuthenticationFailureListener(AuthenticationFailedListener listener) { + /** + * Registers your listener with Apptentive. This listener is stored with a WeakReference, which + * means that you must store a static reference to the listener as long as you want it to live. + * One possible way to do this is to implement this listener with your Application class, or store + * one on your Application. + * + * This listener will alert you to authentication failures, so that you can either recover from + * expired or revoked JWTs, or fix your authentication implementation. + * @param listener A listener that will be called when there is an authentication failure other + * for the current logged in conversation. If the failure is for another + * conversation, or there is no active conversation, the listener is not called. + */ + public static void setAuthenticationFailureListener(AuthenticationFailedListener listener) { try { if (!ApptentiveInternal.checkRegistered()) { return; } ApptentiveInternal.getInstance().setAuthenticationFailureListener(listener); } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.addUnreadMessagesListener()", e); + ApptentiveLog.w("Error in Apptentive.setUnreadMessagesListener()", e); + MetricModule.sendError(e, null, null); + } + } + + public static void clearAuthenticationFailureListener() { + try { + if (!ApptentiveInternal.checkRegistered()) { + return; + } + ApptentiveInternal.getInstance().setAuthenticationFailureListener(null); + } catch (Exception e) { + ApptentiveLog.w("Error in Apptentive.clearUnreadMessagesListener()", e); MetricModule.sendError(e, null, null); } } + /** + * A Listener you can register globally for the app, that will be called when requests other than + * login fail for the active conversation. This includes failure to send queued data to + * Apptentive, and failure to fetch app configuration from Apptentive. + */ public interface AuthenticationFailedListener { void onAuthenticationFailed(AuthenticationFailedReason reason); } + /** + * A list of error codes you will encounter when a JWT failure for logged in conversations occurs. + */ public enum AuthenticationFailedReason { + /** + * This should not happen. + */ UNKNOWN, + /** + * Currently only the HS512 signature algorithm is supported. + */ INVALID_ALGORITHM, + /** + * The JWT structure is constructed improperly (missing a part, etc.) + */ MALFORMED_TOKEN, + /** + * The token is not signed properly, or can't be decoded. + */ INVALID_TOKEN, + /** + * There is no "sub" property in the JWT claims. The "sub" is required, and should be an + * immutable, unique id for your user. + */ MISSING_SUB_CLAIM, + /** + * The JWT "sub" claim does not match the one previously registered to the internal Apptentive + * conversation. Internal use only. + */ MISMATCHED_SUB_CLAIM, + /** + * Internal use only. + */ INVALID_SUB_CLAIM, + /** + * The expiration "exp" claim is expired. The "exp" claim is a UNIX timestamp in milliseconds. + * The JWT will receive this authentication failure when the "exp" time has elapsed. + */ EXPIRED_TOKEN, + /** + * The JWT has been revoked. This happens after a successful logout. In such cases, you will + * need a new JWT to login. + */ REVOKED_TOKEN, + /** + * The Apptentive Key field was not specified during registration. You can get this from your app's Apptentive + * settings. + */ MISSING_APP_KEY, + /** + * The Apptentive Signature field was not specified during registration. You can get this from your app's Apptentive + * settings. + */ MISSING_APP_SIGNATURE, + /** + * The Apptentive Key and Apptentive Signature fields do not match. Make sure you got them from + * the same app's Apptentive settings page. + */ INVALID_KEY_SIGNATURE_PAIR; private String error; From 38061f1a716fa3eff2f3407c4b326508d80575b2 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 21 Jun 2017 21:14:18 -0700 Subject: [PATCH 379/465] Use existing Destroyable.destroy() instead of the teardown() I introduced. --- .../apptentive/android/sdk/conversation/Conversation.java | 6 ------ .../android/sdk/conversation/ConversationManager.java | 2 +- .../android/sdk/module/messagecenter/MessageManager.java | 4 ---- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index b373665dc..064bddf27 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -140,12 +140,6 @@ public Conversation(File conversationDataFile, File conversationMessagesFile) { messageManager = new MessageManager(this, messageStore); // it's important to initialize message manager in a constructor since other SDK parts depend on it via Apptentive singleton } - public void teardown() { - if (messageManager != null) { - messageManager.teardown(); - } - } - //region Interactions /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index a6031e1a7..f3a4e227b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -765,7 +765,7 @@ private void doLogout() { ApptentiveLog.d("Ending active conversation."); // Post synchronously to ensure logout payload can be sent before destroying the logged in conversation. ApptentiveNotificationCenter.defaultCenter().postNotificationSync(NOTIFICATION_CONVERSATION_WILL_LOGOUT, ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, activeConversation)); - activeConversation.teardown(); + activeConversation.destroy(); activeConversation.setState(LOGGED_OUT); handleConversationStateChange(activeConversation); activeConversation = null; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 3e8107750..ea0129b5a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -589,8 +589,4 @@ MessageCountDispatchTask setMessageCount(int messageCount) { } //endregion - - public void teardown() { - ApptentiveNotificationCenter.defaultCenter().removeObserver(this); - } } From 27e3c5b710b91d4c5e96216f4ca9de3b9b0ae2f5 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 23 Jun 2017 16:21:39 -0700 Subject: [PATCH 380/465] =?UTF-8?q?Added=20=E2=80=98login=E2=80=99=20and?= =?UTF-8?q?=20=E2=80=98logout=E2=80=99=20events?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/sdk/ApptentiveInternal.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index e933f514b..6caeee7b0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -1028,11 +1028,29 @@ private String getEndpointBase(SharedPreferences prefs) { //region Login/Logout - void login(String token, LoginCallback callback) { - conversationManager.login(token, callback); + void login(String token, final LoginCallback callback) { + LoginCallback wrapperCallback = new LoginCallback() { + @Override + public void onLoginFinish() { + Apptentive.engage(getApplicationContext(), "login"); + if (callback != null) { + callback.onLoginFinish(); + } + } + + @Override + public void onLoginFail(String errorMessage) { + if (callback != null) { + callback.onLoginFail(errorMessage); + } + } + }; + + conversationManager.login(token, wrapperCallback); } void logout() { + Apptentive.engage(getApplicationContext(), "logout"); conversationManager.logout(); } From 514c1ed1d6af006a20b0835966e6dd4580226c38 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 26 Jun 2017 18:08:42 -0700 Subject: [PATCH 381/465] Fixed engaging internal events --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 6caeee7b0..40e984e02 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -1032,7 +1032,7 @@ void login(String token, final LoginCallback callback) { LoginCallback wrapperCallback = new LoginCallback() { @Override public void onLoginFinish() { - Apptentive.engage(getApplicationContext(), "login"); + EngagementModule.engageInternal(getApplicationContext(), "login"); if (callback != null) { callback.onLoginFinish(); } @@ -1050,7 +1050,7 @@ public void onLoginFail(String errorMessage) { } void logout() { - Apptentive.engage(getApplicationContext(), "logout"); + EngagementModule.engageInternal(getApplicationContext(), "logout"); conversationManager.logout(); } From df997c41c4080c67d7b97e7103c50ffab193753e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 27 Jun 2017 13:15:37 -0700 Subject: [PATCH 382/465] Decouple interaction fetching from global active conversation --- .../android/sdk/comm/ApptentiveClient.java | 12 ++++++++---- .../android/sdk/conversation/Conversation.java | 6 +++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index f6c1c9ca3..bb5b33e93 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -85,12 +85,16 @@ public static ApptentiveHttpResponse getMessages(Integer count, String afterId, return performHttpRequest(conversationToken, true, uri, Method.GET, null); } - public static ApptentiveHttpResponse getInteractions(String conversationId) { - if (conversationId == null) { - throw new IllegalArgumentException("Conversation id is null"); + public static ApptentiveHttpResponse getInteractions(String conversationToken, String conversationId) { + if (StringUtils.isNullOrEmpty(conversationToken)) { + throw new IllegalArgumentException("Conversation token is null or empty"); + } + + if (StringUtils.isNullOrEmpty(conversationId)) { + throw new IllegalArgumentException("Conversation id is null or empty"); } final String endPoint = StringUtils.format(ENDPOINT_INTERACTIONS, conversationId); - return performHttpRequest(ApptentiveInternal.getInstance().getConversation().getConversationToken(), true, endPoint, Method.GET, null); + return performHttpRequest(conversationToken, true, endPoint, Method.GET, null); } /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 064bddf27..22a3ae8a9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -95,7 +95,7 @@ public class Conversation implements DataChangedListener, Destroyable { private final DispatchTask fetchInteractionsTask = new DispatchTask() { @Override protected void execute() { - final boolean updateSuccessful = fetchInteractionsSync(getConversationId()); + final boolean updateSuccessful = fetchInteractionsSync(); dispatchDebugEvent(EVT_CONVERSATION_FETCH_INTERACTIONS, updateSuccessful); // Update pending state on UI thread after finishing the task @@ -178,9 +178,9 @@ boolean fetchInteractions(Context context) { /** * Fetches interaction synchronously. Returns true if succeed. */ - private boolean fetchInteractionsSync(String conversationId) { + private boolean fetchInteractionsSync() { ApptentiveLog.v(CONVERSATION, "Fetching Interactions"); - ApptentiveHttpResponse response = ApptentiveClient.getInteractions(conversationId); + ApptentiveHttpResponse response = ApptentiveClient.getInteractions(getConversationToken(), getConversationId()); SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); boolean updateSuccessful = true; From c18f7755d448ff9e4b8c6f60e546e0b04f8e629f Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 27 Jun 2017 13:35:53 -0700 Subject: [PATCH 383/465] One step towards decoupling EngagementModule from ApptentiveInternal --- .../java/com/apptentive/android/sdk/Apptentive.java | 4 ++-- .../apptentive/android/sdk/ApptentiveInternal.java | 8 +++++++- .../sdk/module/engagement/EngagementModule.java | 12 ++++++++---- .../sdk/module/messagecenter/MessageManager.java | 4 ++++ .../module/messagecenter/MessagePollingWorker.java | 9 +++++++-- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 05c45188f..01dc802de 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -745,7 +745,7 @@ public static boolean canShowMessageCenter() { ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "No active Conversation."); return false; } - return ApptentiveInternal.getInstance().canShowMessageCenterInternal(); + return ApptentiveInternal.getInstance().canShowMessageCenterInternal(ApptentiveInternal.getInstance().getConversation()); } catch (Exception e) { ApptentiveLog.w("Error in Apptentive.canShowMessageCenter()", e); MetricModule.sendError(e, null, null); @@ -1077,7 +1077,7 @@ public static synchronized boolean willShowInteraction(String event) { public static synchronized boolean canShowInteraction(String event) { try { if (ApptentiveInternal.isConversationActive()) { - return EngagementModule.canShowInteraction("local", "app", event); + return EngagementModule.canShowInteraction(ApptentiveInternal.getInstance().getConversation(), "app", event, "local"); } } catch (Exception e) { ApptentiveLog.w("Error in Apptentive.canShowInteraction()", e); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 40e984e02..a6e166ff6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -959,8 +959,14 @@ public void showMessageCenterFallback(Context context) { EngagementModule.launchMessageCenterErrorActivity(context); } + // FIXME: remove this method public boolean canShowMessageCenterInternal() { - return EngagementModule.canShowInteraction("com.apptentive", "app", MessageCenterInteraction.DEFAULT_INTERNAL_EVENT_NAME); + Conversation conversation = getConversation(); + return conversation != null && canShowMessageCenterInternal(conversation); + } + + public boolean canShowMessageCenterInternal(Conversation conversation) { + return EngagementModule.canShowInteraction(conversation, "app", MessageCenterInteraction.DEFAULT_INTERNAL_EVENT_NAME, "com.apptentive"); } public Map getAndClearCustomData() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java index 0965d9bf6..03cb58922 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java @@ -121,13 +121,17 @@ public static void launchMessageCenterErrorActivity(Context context) { } } - public static boolean canShowInteraction(String vendor, String interaction, String eventName) { + public static boolean canShowInteraction(Conversation conversation, String interaction, String eventName, String vendor) { String eventLabel = generateEventLabel(vendor, interaction, eventName); - return canShowInteraction(eventLabel); + return canShowInteraction(conversation, eventLabel); } - private static boolean canShowInteraction(String eventLabel) { - Interaction interaction = ApptentiveInternal.getInstance().getConversation().getApplicableInteraction(eventLabel); + private static boolean canShowInteraction(Conversation conversation, String eventLabel) { + if (conversation == null) { + throw new IllegalArgumentException("Conversation is null"); + } + + Interaction interaction = conversation.getApplicableInteraction(eventLabel); return interaction != null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index ea0129b5a..10f413d92 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -550,6 +550,10 @@ private void appWentToBackground() { pollingWorker.appWentToBackground(); } + Conversation getConversation() { + return conversation; + } + //region Message Dispatch Task private abstract static class MessageDispatchTask extends DispatchTask { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java index 12b1deb69..8583e95d1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java @@ -7,8 +7,9 @@ package com.apptentive.android.sdk.module.messagecenter; -import com.apptentive.android.sdk.Apptentive; +import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.model.Configuration; import com.apptentive.android.sdk.module.metric.MetricModule; @@ -79,7 +80,7 @@ public void run() { return; } long pollingInterval = messageCenterInForeground.get() ? foregroundPollingInterval : backgroundPollingInterval; - if (Apptentive.canShowMessageCenter()) { + if (ApptentiveInternal.getInstance().canShowMessageCenterInternal(getConversation())) { ApptentiveLog.v(MESSAGES, "Checking server for new messages every %d seconds", pollingInterval / 1000); manager.fetchAndStoreMessages(messageCenterInForeground.get(), conf.isMessageCenterNotificationPopupEnabled()); } @@ -145,4 +146,8 @@ private void startPolling() { wakeUp(); } } + + private Conversation getConversation() { + return manager.getConversation(); + } } From 4db9fba89499ed302aa0443f8c5190bf79336e65 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 27 Jun 2017 13:50:00 -0700 Subject: [PATCH 384/465] Fixed accessing the active conversation from a background thread --- .../com/apptentive/android/sdk/Apptentive.java | 18 ++++++++++++++++-- .../sdk/conversation/ConversationManager.java | 4 ++-- .../apptentive/android/sdk/debug/Assert.java | 7 +++---- .../sdk/util/threading/DispatchQueue.java | 7 +++++++ 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 01dc802de..c8fc6415b 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -19,6 +19,7 @@ import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.model.CommerceExtendedData; +import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.model.ExtendedData; import com.apptentive.android.sdk.model.LocationExtendedData; import com.apptentive.android.sdk.model.StoredFile; @@ -26,12 +27,13 @@ import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.messagecenter.MessageManager; import com.apptentive.android.sdk.module.messagecenter.UnreadMessagesListener; -import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.module.metric.MetricModule; import com.apptentive.android.sdk.module.rating.IRatingProvider; import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; +import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchTask; import org.json.JSONException; import org.json.JSONObject; @@ -374,8 +376,20 @@ public static void removeCustomPersonData(String key) { *
    The GCM Registration ID, which you can access like this.
    * */ - public static void setPushNotificationIntegration(int pushProvider, String token) { + public static void setPushNotificationIntegration(final int pushProvider, final String token) { + // we only access the active conversation on the main thread to avoid concurrency issues + if (!DispatchQueue.isMainQueue()) { + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + setPushNotificationIntegration(pushProvider, token); + } + }); + return; + } + if (!ApptentiveInternal.isApptentiveRegistered()) { + ApptentiveLog.w("Unable to set push notification integration: Apptentive instance is not initialized"); return; } // Store the push stuff globally diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index f3a4e227b..66deb0822 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -551,7 +551,7 @@ public void onLoginFail(String errorMessage) { public void login(final String token, final LoginCallback callback) { // we only deal with an active conversation on the main thread - if (Looper.getMainLooper() == Looper.myLooper()) { + if (DispatchQueue.isMainQueue()) { requestLoggedInConversation(token, callback != null ? callback : NULL_LOGIN_CALLBACK); // avoid constant null-pointer checking } else { DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { @@ -746,7 +746,7 @@ protected void execute() { public void logout() { // we only deal with an active conversation on the main thread - if (Looper.myLooper() != Looper.getMainLooper()) { + if (!DispatchQueue.isMainQueue()) { DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { @Override protected void execute() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java index aba736bc2..d3f041409 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -1,9 +1,8 @@ package com.apptentive.android.sdk.debug; -import android.os.Looper; - import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; +import com.apptentive.android.sdk.util.threading.DispatchQueue; /** * A set of assertion methods useful for writing 'runtime' tests. These methods can be used directly: @@ -162,7 +161,7 @@ public static void assertNotEquals(Object first, Object second) { * Asserts that code executes on the main thread. */ public static void assertMainThread() { - if (imp != null && Looper.myLooper() != Looper.getMainLooper()) { + if (imp != null && !DispatchQueue.isMainQueue()) { imp.assertFailed(StringUtils.format("Expected 'main' thread but was '%s'", Thread.currentThread().getName())); } } @@ -171,7 +170,7 @@ public static void assertMainThread() { * Asserts that code executes on the main thread. */ public static void assertBackgroundThread() { - if (imp != null && Looper.myLooper() == Looper.getMainLooper()) { + if (imp != null && DispatchQueue.isMainQueue()) { imp.assertFailed("Expected background thread but was 'main'"); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java index afcd55694..55f083a0e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/threading/DispatchQueue.java @@ -87,6 +87,13 @@ public static DispatchQueue mainQueue() { return Holder.MAIN_QUEUE; } + /** + * Returns true if code is executing on the main queue + */ + public static boolean isMainQueue() { + return Looper.getMainLooper() == Looper.myLooper(); // FIXME: make it configurable for Unit testing + } + /** * A global dispatch concurrent queue */ From 3ee035660489b6153fe7c94b6426c53bf7159b7d Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 27 Jun 2017 14:06:14 -0700 Subject: [PATCH 385/465] Decoupled message fetching request from ApptentiveInternal --- .../java/com/apptentive/android/sdk/comm/ApptentiveClient.java | 3 +-- .../android/sdk/module/messagecenter/MessageManager.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index bb5b33e93..20b2b85c1 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -65,8 +65,7 @@ public static ApptentiveHttpResponse getAppConfiguration() { * * @return An ApptentiveHttpResponse object with the HTTP response code, reason, and content. */ - public static ApptentiveHttpResponse getMessages(Integer count, String afterId, String beforeId) { - final Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + public static ApptentiveHttpResponse getMessages(Conversation conversation, String afterId, String beforeId, Integer count) { if (conversation == null) { throw new IllegalStateException("Conversation is null"); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 10f413d92..9834a9c14 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -239,7 +239,7 @@ private List fetchMessages(String afterId) { return null; } // TODO: Use the new ApptentiveHttpClient for this. - ApptentiveHttpResponse response = ApptentiveClient.getMessages(null, afterId, null); + ApptentiveHttpResponse response = ApptentiveClient.getMessages(conversation, afterId, null, null); List ret = new ArrayList<>(); if (!response.isSuccessful()) { From 1907d4f4f9246270912668001158550563acbd63 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 27 Jun 2017 14:22:45 -0700 Subject: [PATCH 386/465] Refactoring --- .../com/apptentive/android/sdk/ApptentiveInternal.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index a6e166ff6..307fd30ad 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -551,7 +551,10 @@ private boolean start() { // Used for application theme inheritance if the theme is an AppCompat theme. setApplicationDefaultTheme(ai.theme); - checkSendVersionChanges(); + Conversation conversation = getConversation(); + if (conversation != null) { + checkSendVersionChanges(conversation); + } defaultAppDisplayName = packageManager.getApplicationLabel(packageManager.getApplicationInfo(packageInfo.packageName, 0)).toString(); @@ -621,8 +624,7 @@ private boolean start() { return bRet; } - private void checkSendVersionChanges() { - final Conversation conversation = getConversation(); + private void checkSendVersionChanges(Conversation conversation) { if (conversation == null) { ApptentiveLog.e("Can't check session data changes: session data is not initialized"); return; From 39cec4104698d5bc8b9df5290c1aedd5480fb22a Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 27 Jun 2017 14:27:19 -0700 Subject: [PATCH 387/465] Remove unnecessary data blob from payloads, since we store data in a file now. --- .../android/sdk/storage/ApptentiveDatabaseHelper.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 135f581bc..9b9c5cfe3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -65,8 +65,7 @@ static final class PayloadEntry { static final DatabaseColumn COLUMN_CONVERSATION_ID = new DatabaseColumn(5, "conversationId"); static final DatabaseColumn COLUMN_REQUEST_METHOD = new DatabaseColumn(6, "requestMethod"); static final DatabaseColumn COLUMN_PATH = new DatabaseColumn(7, "path"); - static final DatabaseColumn COLUMN_DATA = new DatabaseColumn(8, "data"); - static final DatabaseColumn COLUMN_ENCRYPTED = new DatabaseColumn(9, "encrypted"); + static final DatabaseColumn COLUMN_ENCRYPTED = new DatabaseColumn(8, "encrypted"); } static final class LegacyPayloadEntry { @@ -90,7 +89,6 @@ static final class LegacyPayloadEntry { PayloadEntry.COLUMN_CONVERSATION_ID + " TEXT," + PayloadEntry.COLUMN_REQUEST_METHOD + " TEXT," + PayloadEntry.COLUMN_PATH + " TEXT," + - PayloadEntry.COLUMN_DATA + " BLOB," + PayloadEntry.COLUMN_ENCRYPTED + " INTEGER" + ");"; From 3d0ada1ac87558eb0315299b215d7f216a071f49 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 27 Jun 2017 14:30:16 -0700 Subject: [PATCH 388/465] Use constant in place of bare numbers for encrypted flag. --- .../android/sdk/storage/ApptentiveDatabaseHelper.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 9b9c5cfe3..20e2b2bfe 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -438,7 +438,7 @@ private void upgradeVersion2to3(SQLiteDatabase db) { ApptentiveLog.v(DATABASE, "Saving payload body to: %s", dest); Util.writeBytes(dest, payload.renderData()); - values.put(PayloadEntry.COLUMN_ENCRYPTED.name, payload.hasEncryptionKey() ? 1 : 0); + values.put(PayloadEntry.COLUMN_ENCRYPTED.name, payload.hasEncryptionKey() ? TRUE : FALSE); db.insert(PayloadEntry.TABLE_NAME, null, values); } @@ -490,7 +490,7 @@ void addPayload(Payload... payloads) { ApptentiveLog.v(DATABASE, "Saving payload body to: %s", dest); Util.writeBytes(dest, payload.renderData()); - values.put(PayloadEntry.COLUMN_ENCRYPTED.name, payload.hasEncryptionKey() ? 1 : 0); + values.put(PayloadEntry.COLUMN_ENCRYPTED.name, payload.hasEncryptionKey() ? TRUE : FALSE); db.insert(PayloadEntry.TABLE_NAME, null, values); } @@ -571,7 +571,7 @@ public PayloadData getOldestUnsentPayload() { byte[] data = Util.readBytes(file); final String contentType = notNull(cursor.getString(PayloadEntry.COLUMN_CONTENT_TYPE.index)); final HttpRequestMethod httpRequestMethod = HttpRequestMethod.valueOf(notNull(cursor.getString(PayloadEntry.COLUMN_REQUEST_METHOD.index))); - final boolean encrypted = cursor.getInt(PayloadEntry.COLUMN_ENCRYPTED.index) == 1; + final boolean encrypted = cursor.getInt(PayloadEntry.COLUMN_ENCRYPTED.index) == TRUE; return new PayloadData(payloadType, nonce, conversationId, data, authToken, contentType, httpRequestPath, httpRequestMethod, encrypted); } return null; From 60aa4150c59468ceef2eba212d26b73f8edd7fe0 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 27 Jun 2017 14:39:18 -0700 Subject: [PATCH 389/465] Clean up lint warnings in database code, remove dead code, improve comments. --- .../sdk/storage/ApptentiveDatabaseHelper.java | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 20e2b2bfe..330ad99d3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -68,7 +68,7 @@ static final class PayloadEntry { static final DatabaseColumn COLUMN_ENCRYPTED = new DatabaseColumn(8, "encrypted"); } - static final class LegacyPayloadEntry { + private static final class LegacyPayloadEntry { static final String TABLE_NAME = "legacy_payload"; static final DatabaseColumn PAYLOAD_KEY_DB_ID = new DatabaseColumn(0, "_id"); static final DatabaseColumn PAYLOAD_KEY_BASE_TYPE = new DatabaseColumn(1, "base_type"); @@ -117,7 +117,7 @@ static final class LegacyPayloadEntry { //endregion - //region Message SQL + //region Message SQL (Deprecated: Used for migration only) private static final String TABLE_MESSAGE = "message"; private static final String MESSAGE_KEY_DB_ID = "_id"; // 0 @@ -140,15 +140,12 @@ static final class LegacyPayloadEntry { MESSAGE_KEY_JSON + " TEXT" + ");"; - private static final String QUERY_MESSAGE_GET_BY_NONCE = "SELECT * FROM " + TABLE_MESSAGE + " WHERE " + MESSAGE_KEY_NONCE + " = ?"; // Coalesce returns the second arg if the first is null. This forces the entries with null IDs to be ordered last in the list until they do have IDs because they were sent and retrieved from the server. private static final String QUERY_MESSAGE_GET_ALL_IN_ORDER = "SELECT * FROM " + TABLE_MESSAGE + " ORDER BY COALESCE(" + MESSAGE_KEY_ID + ", 'z') ASC"; - private static final String QUERY_MESSAGE_GET_LAST_ID = "SELECT " + MESSAGE_KEY_ID + " FROM " + TABLE_MESSAGE + " WHERE " + MESSAGE_KEY_STATE + " = '" + ApptentiveMessage.State.saved + "' AND " + MESSAGE_KEY_ID + " NOTNULL ORDER BY " + MESSAGE_KEY_ID + " DESC LIMIT 1"; - private static final String QUERY_MESSAGE_UNREAD = "SELECT " + MESSAGE_KEY_ID + " FROM " + TABLE_MESSAGE + " WHERE " + MESSAGE_KEY_READ + " = " + FALSE + " AND " + MESSAGE_KEY_ID + " NOTNULL"; //endregion - //region File SQL (legacy) + //region File SQL (Deprecated: Used for migration only) private static final String TABLE_FILESTORE = "file_store"; private static final String FILESTORE_KEY_ID = "id"; // 0 @@ -217,7 +214,7 @@ public void onCreate(SQLiteDatabase db) { ApptentiveLog.d(DATABASE, "ApptentiveDatabase.onCreate(db)"); db.execSQL(TABLE_CREATE_PAYLOAD); - // TODO: remove legacy tables + // Leave legacy tables in place for now. db.execSQL(TABLE_CREATE_MESSAGE); db.execSQL(TABLE_CREATE_FILESTORE); db.execSQL(TABLE_CREATE_COMPOUND_FILESTORE); @@ -377,7 +374,6 @@ private void upgradeVersion1to2(SQLiteDatabase db) { * 3. load each into a the new payload object format * 4. Save each into the new payload table * 5. Drop temp_payload - * @param db */ private void upgradeVersion2to3(SQLiteDatabase db) { ApptentiveLog.i(DATABASE, "Upgrading Database from v2 to v3"); @@ -397,7 +393,6 @@ private void upgradeVersion2to3(SQLiteDatabase db) { // 3. Load legacy payloads ApptentiveLog.vv(DATABASE, "\t3. Loading legacy payloads."); cursor = db.rawQuery(SQL_QUERY_PAYLOAD_LIST_LEGACY, null); - List payloads = new ArrayList<>(cursor.getCount()); ApptentiveLog.vv(DATABASE, "4. Save payloads into new table."); JsonPayload payload; @@ -526,7 +521,7 @@ void deletePayload(String payloadIdentifier) { ApptentiveLog.v(DATABASE, "Deleted payload \"%s\" data file successfully? %b", payloadIdentifier, dest.delete()); } - public void deleteAllPayloads() { + void deleteAllPayloads() { // FIXME: Delete files too. SQLiteDatabase db; try { @@ -537,7 +532,7 @@ public void deleteAllPayloads() { } } - public PayloadData getOldestUnsentPayload() { + PayloadData getOldestUnsentPayload() { SQLiteDatabase db; Cursor cursor = null; @@ -587,7 +582,7 @@ private String updatePayloadRequestPath(String path, String conversationId) { return path.replace("${conversationId}", conversationId); } - public void updateIncompletePayloads(String conversationId, String authToken) { + void updateIncompletePayloads(String conversationId, String authToken) { if (StringUtils.isNullOrEmpty(conversationId)) { throw new IllegalArgumentException("Conversation id is null or empty"); } @@ -683,9 +678,8 @@ boolean addCompoundMessageFiles(List associatedFiles) { db.endTransaction(); } catch (SQLException sqe) { ApptentiveLog.e(DATABASE, "addCompoundMessageFiles EXCEPTION: " + sqe.getMessage()); - } finally { - return ret != -1; } + return ret != -1; } //endregion @@ -706,11 +700,11 @@ private void ensureClosed(Cursor cursor) { } } - public void reset(Context context) { - /** - * The following ONLY be used during development and testing. It will delete the database, including all saved - * payloads, messages, and files. - */ + /** + * The following shall ONLY be used during development and testing. It will delete the database, + * including all saved payloads, messages, and files. + */ + void reset(Context context) { context.deleteDatabase(DATABASE_NAME); } @@ -720,7 +714,7 @@ public void reset(Context context) { private static final class DatabaseColumn { public final String name; - public final int index; + final int index; DatabaseColumn(int index, String name) { this.index = index; From 37e9b750dc342637447f5f991c0aab78fc4b09cc Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 27 Jun 2017 16:12:40 -0700 Subject: [PATCH 390/465] Fix outgoing message flag, which is used to determine how a message is decorated. 1. Stop trying to compute this based on the sender ID. Instead, the server now supplies a flag, so use it. 2. Stop padding the conversation and personID around where we no longer need them. 3. Update some comments and print more data in very verbose mode. --- .../conversation/FileMessageStoreTest.java | 2 +- .../storage/ApptentiveDatabaseHelperTest.java | 2 +- .../sdk/conversation/Conversation.java | 12 +++--- .../sdk/conversation/ConversationManager.java | 4 +- .../sdk/conversation/FileMessageStore.java | 37 ++++++++++++------- .../android/sdk/model/ApptentiveMessage.java | 15 +++++--- .../android/sdk/model/CompoundMessage.java | 24 ++++-------- .../module/messagecenter/MessageManager.java | 19 ++++------ .../messagecenter/model/MessageFactory.java | 23 +----------- 9 files changed, 60 insertions(+), 78 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java index b2096ad1c..f833e83ee 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/conversation/FileMessageStoreTest.java @@ -256,7 +256,7 @@ private ApptentiveMessage createMessage(String nonce, State state, boolean read, JSONObject object = new JSONObject(); object.put("nonce", nonce); object.put("client_created_at", clientCreatedAt); - CompoundMessage message = new CompoundMessage(object.toString(), true); + CompoundMessage message = new CompoundMessage(object.toString()); message.setId(id); message.setState(state); message.setRead(read); diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java index f3598b50f..39ae07744 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelperTest.java @@ -52,7 +52,7 @@ public void testMigration() throws Exception { new EventPayload("{\"nonce\":\"b9a91f27-87b4-4bd9-b9a0-5c605891824a\",\"client_created_at\":1.492737199856E9,\"client_created_at_utc_offset\":-25200,\"label\":\"com.apptentive#app#launch\"}"), new DevicePayload("{\"device\":\"bullhead\",\"integration_config\":{},\"locale_country_code\":\"US\",\"carrier\":\"\",\"uuid\":\"6c0b74d07c064421\",\"build_type\":\"user\",\"cpu\":\"arm64-v8a\",\"os_build\":\"3687331\",\"manufacturer\":\"LGE\",\"radio_version\":\"M8994F-2.6.36.2.20\",\"os_name\":\"Android\",\"build_id\":\"N4F26T\",\"utc_offset\":\"-28800\",\"bootloader_version\":\"BHZ11h\",\"board\":\"bullhead\",\"os_api_level\":\"25\",\"current_carrier\":\"AT&T\",\"network_type\":\"LTE\",\"locale_raw\":\"en_US\",\"brand\":\"google\",\"os_version\":\"7.1.1\",\"product\":\"bullhead\",\"model\":\"Nexus 5X\",\"locale_language_code\":\"en\",\"custom_data\":{}}"), new PersonPayload("{\"custom_data\":{}}"), - MessageFactory.fromJson("{\"nonce\":\"a68d606c-083a-4496-a5e0-f07bcdff52a4\",\"client_created_at\":1.492737257565E9,\"client_created_at_utc_offset\":-25200,\"type\":\"CompoundMessage\",\"body\":\"Test message\",\"text_only\":false}", null) + MessageFactory.fromJson("{\"nonce\":\"a68d606c-083a-4496-a5e0-f07bcdff52a4\",\"client_created_at\":1.492737257565E9,\"client_created_at_utc_offset\":-25200,\"type\":\"CompoundMessage\",\"body\":\"Test message\",\"text_only\":false}") }; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 064bddf27..2a905fa03 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -136,8 +136,8 @@ public Conversation(File conversationDataFile, File conversationMessagesFile) { conversationData = new ConversationData(); conversationData.setDataChangedListener(this); - FileMessageStore messageStore = new FileMessageStore(getPerson().getId(), conversationMessagesFile); - messageManager = new MessageManager(this, messageStore); // it's important to initialize message manager in a constructor since other SDK parts depend on it via Apptentive singleton + FileMessageStore messageStore = new FileMessageStore(conversationMessagesFile); + messageManager = new MessageManager(messageStore); // it's important to initialize message manager in a constructor since other SDK parts depend on it via Apptentive singleton } //region Interactions @@ -267,9 +267,11 @@ public void storeInteractionManifest(String interactionManifest) { * if succeed. */ private synchronized void saveConversationData() throws SerializerException { - ApptentiveLog.vv(CONVERSATION, "Saving %sconversation data...", hasState(LOGGED_IN) ? "encrypted " : ""); - ApptentiveLog.vv(CONVERSATION, "EventData: %s", getEventData().toString()); - + if (ApptentiveLog.canLog(ApptentiveLog.Level.VERY_VERBOSE)) { + ApptentiveLog.vv(CONVERSATION, "Saving %sconversation data...", hasState(LOGGED_IN) ? "encrypted " : ""); + ApptentiveLog.vv(CONVERSATION, "EventData: %s", getEventData().toString()); + ApptentiveLog.vv(CONVERSATION, "Messages: %s", messageManager.getMessageStore().toString()); + } long start = System.currentTimeMillis(); FileSerializer serializer; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index f3a4e227b..9a985cc1e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -162,13 +162,13 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali return conversation; } - // seems like we only have 'logged-out' conversations + // Check for only LOGGED_OUT Conversations if (conversationMetadata.hasItems()) { ApptentiveLog.v(CONVERSATION, "Can't load conversation: only 'logged-out' conversations available"); return null; } - // no conversation available: create a new one + // No conversation exists: Create a new one ApptentiveLog.v(CONVERSATION, "Can't load conversation: creating anonymous conversation..."); File dataFile = new File(apptentiveConversationsStorageDir, "conversation-" + Util.generateRandomFilename()); File messagesFile = new File(apptentiveConversationsStorageDir, "messages-" + Util.generateRandomFilename()); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index d12f10f33..2ab04c866 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -39,23 +39,11 @@ class FileMessageStore implements MessageStore { */ private static final byte VERSION = 1; - private final String currentPersonId; private final File file; private final List messageEntries; private boolean shouldFetchFromFile; - FileMessageStore(String currentPersonId, File file) { - this.currentPersonId = currentPersonId; - this.file = file; - this.messageEntries = new ArrayList<>(); // we need a random access - this.shouldFetchFromFile = true; // we would lazily read it from a file later - } - - /** - * Only use this constructor from a test where you don't care about knowing whether a message is outgoing or incoming. - */ FileMessageStore(File file) { - this.currentPersonId = null; this.file = file; this.messageEntries = new ArrayList<>(); // we need a random access this.shouldFetchFromFile = true; // we would lazily read it from a file later @@ -117,7 +105,7 @@ public synchronized List getAllMessages() throws Exception { List apptentiveMessages = new ArrayList<>(); for (MessageEntry entry : messageEntries) { - ApptentiveMessage apptentiveMessage = MessageFactory.fromJson(entry.json, currentPersonId); + ApptentiveMessage apptentiveMessage = MessageFactory.fromJson(entry.json); if (apptentiveMessage == null) { ApptentiveLog.e("Error parsing Record json from database: %s", entry.json); continue; @@ -182,7 +170,7 @@ public ApptentiveMessage findMessage(String nonce) { for (int i = 0; i < messageEntries.size(); ++i) { final MessageEntry messageEntry = messageEntries.get(i); if (StringUtils.equal(nonce, messageEntry.nonce)) { - return MessageFactory.fromJson(messageEntry.json, currentPersonId); + return MessageFactory.fromJson(messageEntry.json); } } @@ -305,7 +293,28 @@ public void writeExternal(DataOutput out) throws IOException { writeNullableBoolean(out, isRead); writeNullableUTF(out, json); } + + @Override + public String toString() { + return "MessageEntry{" + + "id='" + id + '\'' + + ", clientCreatedAt=" + clientCreatedAt + + ", nonce='" + nonce + '\'' + + ", state='" + state + '\'' + + ", isRead=" + isRead + + ", json='" + json + '\'' + + '}'; + } } //endregion + + @Override + public String toString() { + return "FileMessageStore{" + + "file=" + file + + ", messageEntries=" + messageEntries + + ", shouldFetchFromFile=" + shouldFetchFromFile + + '}'; + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java index e55289be1..984a32710 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -20,10 +20,14 @@ public abstract class ApptentiveMessage extends ConversationItem implements Mess public static final String KEY_CREATED_AT = "created_at"; public static final String KEY_TYPE = "type"; public static final String KEY_HIDDEN = "hidden"; + /** inbound here means inbound to the server. When this is true, the message is outgoing */ + public static final String KEY_INBOUND = "inbound"; public static final String KEY_CUSTOM_DATA = "custom_data"; public static final String KEY_AUTOMATED = "automated"; public static final String KEY_SENDER = "sender"; public static final String KEY_SENDER_ID = "id"; + private static final String KEY_SENDER_NAME = "name"; + private static final String KEY_SENDER_PROFILE_PHOTO = "profile_photo"; // State and Read are not stored in JSON, only in DB. private State state = State.unknown; @@ -32,8 +36,6 @@ public abstract class ApptentiveMessage extends ConversationItem implements Mess // datestamp is only stored in memory, due to how we selectively apply date labeling in the view. private String datestamp; - private static final String KEY_SENDER_NAME = "name"; - private static final String KEY_SENDER_PROFILE_PHOTO = "profile_photo"; protected ApptentiveMessage() { @@ -87,6 +89,11 @@ public void setHidden(boolean hidden) { put(KEY_HIDDEN, hidden); } + public boolean isOutgoingMessage() { + // Default is true because this field is only set from the server. + return getBoolean(KEY_INBOUND, true); + } + public void setCustomData(Map customData) { if (customData == null || customData.size() == 0) { if (!isNull(KEY_CUSTOM_DATA)) { @@ -209,8 +216,6 @@ public boolean clearDatestamp() { } } - public abstract boolean isOutgoingMessage(); - public boolean isAutomatedMessage() { return getAutomated(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java index 074ac453d..73067c9ab 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java @@ -42,8 +42,6 @@ public class CompoundMessage extends ApptentiveMessage implements MessageCenterU private boolean hasNoAttachments = true; - private boolean isOutgoing = true; - private final String boundary; /* For incoming message, this array stores attachment Urls @@ -56,20 +54,18 @@ public class CompoundMessage extends ApptentiveMessage implements MessageCenterU public CompoundMessage() { super(); boundary = UUID.randomUUID().toString(); - isOutgoing = true; } - /* Constructing compound message when JSON is received from incoming, or repopulated from database - * - * @param json The JSON string of the message - * @param bOutgoing true if the message is originated from local + /** + * Construct a CompoundMessage when JSON is fetched from server, or repopulated from database. + * + * @param json The message JSON */ - public CompoundMessage(String json, boolean bOutgoing) throws JSONException { + public CompoundMessage(String json) throws JSONException { super(json); boundary = UUID.randomUUID().toString(); parseAttachmentsArray(json); hasNoAttachments = getTextOnly(); - isOutgoing = bOutgoing; } //region Http-request @@ -124,7 +120,6 @@ public void setTextOnly(boolean bVal) { put(KEY_TEXT_ONLY, bVal); } - private List attachedFiles; public boolean setAssociatedImages(List attachedImages) { @@ -223,7 +218,7 @@ public void deleteAssociatedFiles() { @Override public boolean isLastSent() { - return (isOutgoingMessage()) ? isLast : false; + return (isOutgoingMessage()) && isLast; } @Override @@ -231,11 +226,6 @@ public void setLastSent(boolean bVal) { isLast = bVal; } - @Override - public boolean isOutgoingMessage() { - return isOutgoing; - } - public List getRemoteAttachments() { return remoteAttachmentStoredFiles; } @@ -274,7 +264,7 @@ private boolean parseAttachmentsArray(String messageString) throws JSONException public int getListItemType() { if (isAutomatedMessage()) { return MESSAGE_AUTO; - } else if (isOutgoing) { + } else if (isOutgoingMessage()) { return MESSAGE_OUTGOING; } else { return MESSAGE_INCOMING; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index ea0129b5a..ebce93563 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -16,7 +16,6 @@ import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; -import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.model.PayloadType; @@ -66,8 +65,6 @@ public class MessageManager implements Destroyable, ApptentiveNotificationObserv private static int TOAST_TYPE_UNREAD_MESSAGE = 1; - private final Conversation conversation; - private final MessageStore messageStore; private WeakReference currentForegroundApptentiveActivity; @@ -98,16 +95,11 @@ protected void execute(int messageCount) { } }; - public MessageManager(Conversation conversation, MessageStore messageStore) { - if (conversation == null) { - throw new IllegalArgumentException("Conversation is null"); - } - + public MessageManager(MessageStore messageStore) { if (messageStore == null) { throw new IllegalArgumentException("Message store is null"); } - this.conversation = conversation; this.messageStore = messageStore; this.pollingWorker = new MessagePollingWorker(this); @@ -266,7 +258,7 @@ private List parseMessagesString(String messageString) throws JSONArray items = root.getJSONArray("messages"); for (int i = 0; i < items.length(); i++) { String json = items.getJSONObject(i).toString(); - ApptentiveMessage apptentiveMessage = MessageFactory.fromJson(json, conversation.getPerson().getId()); + ApptentiveMessage apptentiveMessage = MessageFactory.fromJson(json); // Since these came back from the server, mark them saved before updating them in the DB. if (apptentiveMessage != null) { apptentiveMessage.setState(ApptentiveMessage.State.saved); @@ -587,6 +579,9 @@ MessageCountDispatchTask setMessageCount(int messageCount) { return this; } } - //endregion + + public MessageStore getMessageStore() { + return messageStore; + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java index b251e2c10..76bd0d143 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java @@ -15,15 +15,8 @@ import org.json.JSONObject; public class MessageFactory { - /** - * Only use this method if you don't need to know whether the resulting Message is outgoing or - * incoming. Use {@link #fromJson(String, String)} otherwise. - */ - public static ApptentiveMessage fromJson(String json) { - return fromJson(json, null); - } - public static ApptentiveMessage fromJson(String json, String personId) { + public static ApptentiveMessage fromJson(String json) { try { // If KEY_TYPE is set to CompoundMessage or not set, treat them as CompoundMessage ApptentiveMessage.Type type = ApptentiveMessage.Type.CompoundMessage; @@ -36,19 +29,7 @@ public static ApptentiveMessage fromJson(String json, String personId) { } switch (type) { case CompoundMessage: - String senderId = null; - try { - if (!root.isNull(ApptentiveMessage.KEY_SENDER)) { - JSONObject sender = root.getJSONObject(ApptentiveMessage.KEY_SENDER); - if (!sender.isNull((ApptentiveMessage.KEY_SENDER_ID))) { - senderId = sender.getString(ApptentiveMessage.KEY_SENDER_ID); - } - } - } catch (JSONException e) { - // Ignore, senderId would be null - } - // If senderId is null or same as the locally stored id, construct message as outgoing - return new CompoundMessage(json, (senderId == null || (personId != null && senderId.equals(personId)))); + return new CompoundMessage(json); case unknown: break; default: From 7feedbb69673e21bcf1429f4c5e3f608c594a4c8 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 27 Jun 2017 16:34:52 -0700 Subject: [PATCH 391/465] One more step towards decoupling EngagementModule from ApptentiveInternal --- .../apptentive/android/sdk/Apptentive.java | 25 ++++++- .../android/sdk/ApptentiveInternal.java | 39 +++++++++-- .../android/sdk/ApptentiveViewActivity.java | 2 +- .../module/engagement/EngagementModule.java | 65 ++++++++++--------- .../interaction/fragment/AboutFragment.java | 11 ++-- .../fragment/ApptentiveBaseFragment.java | 22 ++++++- .../fragment/EnjoymentDialogFragment.java | 9 ++- .../fragment/MessageCenterErrorFragment.java | 7 +- .../fragment/MessageCenterFragment.java | 39 ++++++----- .../fragment/NavigateToLinkFragment.java | 8 +-- .../interaction/fragment/NoteFragment.java | 6 +- .../fragment/RatingDialogFragment.java | 9 ++- .../interaction/fragment/SurveyFragment.java | 12 ++-- .../fragment/UpgradeMessageFragment.java | 3 +- .../MessageCenterRecyclerViewAdapter.java | 7 +- 15 files changed, 160 insertions(+), 104 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index c8fc6415b..c9aba5559 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -31,6 +31,7 @@ import com.apptentive.android.sdk.module.rating.IRatingProvider; import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.threading.DispatchQueue; import com.apptentive.android.sdk.util.threading.DispatchTask; @@ -1019,7 +1020,7 @@ public static void sendAttachmentFile(InputStream is, String mimeType) { * @return true if the an interaction was shown, else false. */ public static synchronized boolean engage(Context context, String event) { - return EngagementModule.engage(context, "local", "app", null, event, null, null, (ExtendedData[]) null); + return engage(context, event, null, (ExtendedData[]) null); } /** @@ -1039,7 +1040,7 @@ public static synchronized boolean engage(Context context, String event) { * @return true if the an interaction was shown, else false. */ public static synchronized boolean engage(Context context, String event, Map customData) { - return EngagementModule.engage(context, "local", "app", null, event, null, customData, (ExtendedData[]) null); + return engage(context, event, customData, (ExtendedData[]) null); } /** @@ -1063,7 +1064,25 @@ public static synchronized boolean engage(Context context, String event, Map customData, ExtendedData... extendedData) { - return EngagementModule.engage(context, "local", "app", null, event, null, customData, extendedData); + if (StringUtils.isNullOrEmpty(event)) { + ApptentiveLog.e("Unable to engage event: name is null or empty"); // TODO: throw an IllegalArgumentException instead? + return false; + } + if (context == null) { + ApptentiveLog.e("Unable to engage '%s' event: context is null", event); // TODO: throw an IllegalArgumentException instead? + return false; + } + if (!ApptentiveInternal.isApptentiveRegistered()) { + ApptentiveLog.e("Unable to engage '%s' event: Apptentive SDK is not initialized", event); + return false; + } + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation == null) { + ApptentiveLog.e("Unable to engage '%s' event: no active conversation", event); + return false; + } + + return EngagementModule.engage(context, conversation, "local", "app", null, event, null, customData, extendedData); } /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 307fd30ad..e3a1f9af8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -33,9 +33,11 @@ import com.apptentive.android.sdk.lifecycle.ApptentiveActivityLifecycleCallbacks; import com.apptentive.android.sdk.model.Configuration; import com.apptentive.android.sdk.model.EventPayload; +import com.apptentive.android.sdk.model.ExtendedData; import com.apptentive.android.sdk.model.LogoutPayload; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; +import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; import com.apptentive.android.sdk.module.messagecenter.MessageManager; import com.apptentive.android.sdk.module.metric.MetricModule; @@ -78,6 +80,8 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_ACTIVITY; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_AUTHENTICATION_FAILED_REASON; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION_ID; +import static com.apptentive.android.sdk.debug.Assert.assertMainThread; +import static com.apptentive.android.sdk.debug.Assert.assertNotNull; import static com.apptentive.android.sdk.util.Constants.CONVERSATIONS_DIR; /** @@ -409,11 +413,11 @@ public ApptentiveHttpClient getApptentiveHttpClient() { } public void onAppLaunch(final Context appContext) { - EngagementModule.engageInternal(appContext, EventPayload.EventLabel.app__launch.getLabelName()); + engageInternal(appContext, EventPayload.EventLabel.app__launch.getLabelName()); } public void onAppExit(final Context appContext) { - EngagementModule.engageInternal(appContext, EventPayload.EventLabel.app__exit.getLabelName()); + engageInternal(appContext, EventPayload.EventLabel.app__exit.getLabelName()); } public void onActivityStarted(Activity activity) { @@ -947,7 +951,7 @@ public boolean showMessageCenterInternal(Context context, Map cu } } this.customData = customData; - interactionShown = EngagementModule.engageInternal(context, MessageCenterInteraction.DEFAULT_INTERNAL_EVENT_NAME); + interactionShown = engageInternal(context, MessageCenterInteraction.DEFAULT_INTERNAL_EVENT_NAME); if (!interactionShown) { this.customData = null; } @@ -1040,7 +1044,7 @@ void login(String token, final LoginCallback callback) { LoginCallback wrapperCallback = new LoginCallback() { @Override public void onLoginFinish() { - EngagementModule.engageInternal(getApplicationContext(), "login"); + engageInternal(getApplicationContext(), "login"); if (callback != null) { callback.onLoginFinish(); } @@ -1058,7 +1062,7 @@ public void onLoginFail(String errorMessage) { } void logout() { - EngagementModule.engageInternal(getApplicationContext(), "logout"); + engageInternal(getApplicationContext(), "logout"); conversationManager.logout(); } @@ -1081,4 +1085,29 @@ public void onReceiveNotification(ApptentiveNotification notification) { notifyAuthenticationFailedListener(authenticationFailedReason, conversationIdOfFailedRequest); } } + + //region Engagement + + public static boolean engage(Context context, String vendor, String interaction, String interactionId, String eventName, String data, Map customData, ExtendedData[] extendedData) { + assertMainThread(); + Conversation conversation = getInstance().getConversation(); + assertNotNull(conversation, "Attempted to engage '%s' event without an active conversation"); + return conversation != null && EngagementModule.engage(context, conversation, vendor, interaction, interactionId, eventName, data, customData, extendedData); + } + + public static boolean engageInternal(Context context, String eventName) { + assertMainThread(); + Conversation conversation = getInstance().getConversation(); + assertNotNull(conversation, "Attempted to engage '%s' internal event without an active conversation"); + return conversation != null && EngagementModule.engageInternal(context, conversation, eventName); + } + + public static boolean engageInternal(Context context, Interaction interaction, String eventName, String data) { + assertMainThread(); + Conversation conversation = getInstance().getConversation(); + assertNotNull(conversation, "Attempted to engage '%s' internal event without an active conversation"); + return conversation != null && EngagementModule.engageInternal(context, conversation, interaction, eventName, data); + } + + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index ab1145950..eed1b04ce 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -99,7 +99,7 @@ protected void onCreate(Bundle savedInstanceState) { if (fragmentType == Constants.FragmentTypes.ENGAGE_INTERNAL_EVENT) { String eventName = getIntent().getStringExtra(Constants.FragmentConfigKeys.EXTRA); if (eventName != null) { - EngagementModule.engageInternal(this, eventName); + ApptentiveInternal.engageInternal(this, eventName); } } finish(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java index 03cb58922..702d3a435 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java @@ -13,13 +13,14 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.ApptentiveViewActivity; -import com.apptentive.android.sdk.model.EventPayload; +import com.apptentive.android.sdk.conversation.Conversation; +import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.model.EventManager; +import com.apptentive.android.sdk.model.EventPayload; import com.apptentive.android.sdk.model.ExtendedData; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; import com.apptentive.android.sdk.module.metric.MetricModule; -import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -30,38 +31,45 @@ */ public class EngagementModule { - public static synchronized boolean engageInternal(Context context, String eventName) { - return engage(context, "com.apptentive", "app", null, eventName, null, null, (ExtendedData[]) null); + public static synchronized boolean engageInternal(Context context, Conversation conversation, String eventName) { + return engage(context, conversation, "com.apptentive", "app", null, eventName, null, null, (ExtendedData[]) null); } - public static synchronized boolean engageInternal(Context context, String eventName, String data) { - return engage(context, "com.apptentive", "app", null, eventName, data, null, (ExtendedData[]) null); + public static synchronized boolean engageInternal(Context context, Conversation conversation, String eventName, String data) { + return engage(context, conversation, "com.apptentive", "app", null, eventName, data, null, (ExtendedData[]) null); } - public static synchronized boolean engageInternal(Context context, Interaction interaction, String eventName) { - return engage(context, "com.apptentive", interaction.getType().name(), interaction.getId(), eventName, null, null, (ExtendedData[]) null); + public static synchronized boolean engageInternal(Context context, Conversation conversation, Interaction interaction, String eventName) { + return engage(context, conversation, "com.apptentive", interaction.getType().name(), interaction.getId(), eventName, null, null, (ExtendedData[]) null); } - public static synchronized boolean engageInternal(Context context, Interaction interaction, String eventName, String data) { - return engage(context, "com.apptentive", interaction.getType().name(), interaction.getId(), eventName, data, null, (ExtendedData[]) null); + public static synchronized boolean engageInternal(Context context, Conversation conversation, Interaction interaction, String eventName, String data) { + return engage(context, conversation, "com.apptentive", interaction.getType().name(), interaction.getId(), eventName, data, null, (ExtendedData[]) null); } - public static synchronized boolean engage(Context context, String vendor, String interaction, String interactionId, String eventName, String data, Map customData, ExtendedData... extendedData) { + public static synchronized boolean engage(Context context, Conversation conversation, String vendor, String interaction, String interactionId, String eventName, String data, Map customData, ExtendedData... extendedData) { + if (context == null) { + throw new IllegalArgumentException("Context is null"); + } + + if (conversation == null) { + throw new IllegalArgumentException("Conversation is null"); + } + + Assert.assertTrue(ApptentiveInternal.isApptentiveRegistered()); + if (!ApptentiveInternal.isApptentiveRegistered()) { + return false; + } + try { - if (!ApptentiveInternal.isApptentiveRegistered() || context == null) { - return false; - } String eventLabel = generateEventLabel(vendor, interaction, eventName); ApptentiveLog.d("engage(%s)", eventLabel); - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); - int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); - conversation.getEventData().storeEventForCurrentAppVersion(Util.currentTimeSeconds(), versionCode, versionName, eventLabel); - EventManager.sendEvent(new EventPayload(eventLabel, interactionId, data, customData, extendedData)); - return doEngage(context, eventLabel); - } + String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); + int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); + conversation.getEventData().storeEventForCurrentAppVersion(Util.currentTimeSeconds(), versionCode, versionName, eventLabel); + EventManager.sendEvent(new EventPayload(eventLabel, interactionId, data, customData, extendedData)); + return doEngage(conversation, context, eventLabel); } catch (Exception e) { ApptentiveLog.w("Error in engage()", e); MetricModule.sendError(e, null, null); @@ -69,15 +77,12 @@ public static synchronized boolean engage(Context context, String vendor, String return false; } - public static boolean doEngage(Context context, String eventLabel) { - Interaction interaction = ApptentiveInternal.getInstance().getConversation().getApplicableInteraction(eventLabel); + private static boolean doEngage(Conversation conversation, Context context, String eventLabel) { + Interaction interaction = conversation.getApplicableInteraction(eventLabel); if (interaction != null) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); - int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); - conversation.getEventData().storeInteractionForCurrentAppVersion(Util.currentTimeSeconds(), versionCode, versionName, interaction.getId()); - } + String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); + int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); + conversation.getEventData().storeInteractionForCurrentAppVersion(Util.currentTimeSeconds(), versionCode, versionName, interaction.getId()); launchInteraction(context, interaction); return true; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/AboutFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/AboutFragment.java index e50f25810..67cda3029 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/AboutFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/AboutFragment.java @@ -17,7 +17,6 @@ import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.model.ExtendedData; -import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -64,7 +63,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa close.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - EngagementModule.engage(getActivity(), "com.apptentive", INTERACTION_NAME, null, EVENT_NAME_CLOSE, null, null, (ExtendedData[]) null); + engage("com.apptentive", INTERACTION_NAME, null, EVENT_NAME_CLOSE, null, null, (ExtendedData[]) null); transit(); } }); @@ -96,17 +95,17 @@ public void onClick(View view) { public boolean onFragmentExit(ApptentiveViewExitType exitType) { if (exitType.equals(ApptentiveViewExitType.BACK_BUTTON)) { - EngagementModule.engage(getActivity(), "com.apptentive", INTERACTION_NAME, null, EVENT_NAME_CANCEL, null, null, (ExtendedData[]) null); + engage("com.apptentive", INTERACTION_NAME, null, EVENT_NAME_CANCEL, null, null, (ExtendedData[]) null); } else if (exitType.equals(ApptentiveViewExitType.NOTIFICATION)) { - EngagementModule.engage(getActivity(), "com.apptentive", INTERACTION_NAME, null, EVENT_NAME_CANCEL, exitTypeToDataJson(exitType), null, (ExtendedData[]) null); + engage("com.apptentive", INTERACTION_NAME, null, EVENT_NAME_CANCEL, exitTypeToDataJson(exitType), null, (ExtendedData[]) null); } else { - EngagementModule.engage(getActivity(), "com.apptentive", INTERACTION_NAME, null, EVENT_NAME_CLOSE, exitTypeToDataJson(exitType), null, (ExtendedData[]) null); + engage("com.apptentive", INTERACTION_NAME, null, EVENT_NAME_CLOSE, exitTypeToDataJson(exitType), null, (ExtendedData[]) null); } return false; } @Override protected void sendLaunchEvent(Activity activity) { - EngagementModule.engage(getActivity(), "com.apptentive", INTERACTION_NAME, null, EVENT_NAME_LAUNCH, null, null, (ExtendedData[]) null); + engage("com.apptentive", INTERACTION_NAME, null, EVENT_NAME_LAUNCH, null, null, (ExtendedData[]) null); } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java index 0b15d3000..fb63b1823 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java @@ -10,7 +10,6 @@ import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; - import android.content.res.Resources; import android.graphics.PorterDuff; import android.graphics.drawable.ColorDrawable; @@ -36,7 +35,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.engagement.EngagementModule; +import com.apptentive.android.sdk.model.ExtendedData; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.util.Constants; @@ -47,6 +46,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Map; public abstract class ApptentiveBaseFragment extends DialogFragment implements InteractionManager.InteractionUpdateListener { @@ -248,7 +248,7 @@ public void onCreate(Bundle savedInstanceState) { */ protected void sendLaunchEvent(Activity activity) { if (interaction != null) { - EngagementModule.engageInternal(activity, interaction, EVENT_NAME_LAUNCH); + engageInternal(EVENT_NAME_LAUNCH); } } @@ -514,4 +514,20 @@ public static void removeFragment(FragmentManager fragmentManager, Fragment frag fragmentManager.beginTransaction().remove(fragment).commit(); } + //region Helpers + + public boolean engage(String vendor, String interaction, String interactionId, String eventName, String data, Map customData, ExtendedData... extendedData) { + return ApptentiveInternal.engage(getActivity(), vendor, interaction, interactionId, eventName, data, customData, extendedData); + } + + public boolean engageInternal(String eventName) { + return engageInternal(eventName, null); + } + + public boolean engageInternal(String eventName, String data) { + return ApptentiveInternal.engageInternal(getActivity(), interaction, eventName, data); + } + + //endregion + } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/EnjoymentDialogFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/EnjoymentDialogFragment.java index 32c76c373..44ce2e375 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/EnjoymentDialogFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/EnjoymentDialogFragment.java @@ -16,7 +16,6 @@ import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.EnjoymentDialogInteraction; public class EnjoymentDialogFragment extends ApptentiveBaseFragment implements View.OnClickListener { @@ -73,7 +72,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa @Override public boolean onFragmentExit(ApptentiveViewExitType exitType) { - EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_CANCEL, exitTypeToDataJson(exitType)); + engageInternal(CODE_POINT_CANCEL, exitTypeToDataJson(exitType)); return false; } @@ -81,11 +80,11 @@ public boolean onFragmentExit(ApptentiveViewExitType exitType) { public void onClick(View v) { int id = v.getId(); if (id == R.id.yes) { - EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_YES); + engageInternal(CODE_POINT_YES); } else if (id == R.id.no) { - EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_NO); + engageInternal(CODE_POINT_NO); } else if (id == R.id.dismiss) { - EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_DISMISS); + engageInternal(CODE_POINT_DISMISS); } transit(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterErrorFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterErrorFragment.java index 88e8b502a..60f246793 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterErrorFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterErrorFragment.java @@ -21,7 +21,6 @@ import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.model.ExtendedData; -import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -45,9 +44,9 @@ public static MessageCenterErrorFragment newInstance(Bundle bundle) { @Override protected void sendLaunchEvent(Activity activity) { if (wasLastAttemptServerError(getContext()) || Util.isNetworkConnectionPresent()) { - EngagementModule.engage(getActivity(), "com.apptentive", "MessageCenter", null, EVENT_NAME_NO_INTERACTION_ATTEMPTING, null, null, (ExtendedData[]) null); + engage("com.apptentive", "MessageCenter", null, EVENT_NAME_NO_INTERACTION_ATTEMPTING, null, null, (ExtendedData[]) null); } else { - EngagementModule.engage(getActivity(), "com.apptentive", "MessageCenter", null, EVENT_NAME_NO_INTERACTION_NO_INTERNET, null, null, (ExtendedData[]) null); + engage("com.apptentive", "MessageCenter", null, EVENT_NAME_NO_INTERACTION_NO_INTERNET, null, null, (ExtendedData[]) null); } } @@ -84,7 +83,7 @@ private boolean wasLastAttemptServerError(Context context) { public boolean onFragmentExit(ApptentiveViewExitType exitType) { - EngagementModule.engage(getActivity(), "com.apptentive", "MessageCenter", null, EVENT_NAME_NO_INTERACTION_CLOSE, exitTypeToDataJson(exitType), null, (ExtendedData[]) null); + engage("com.apptentive", "MessageCenter", null, EVENT_NAME_NO_INTERACTION_CLOSE, exitTypeToDataJson(exitType), null, (ExtendedData[]) null); return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index 503b6bd86..f7eeb57a2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -43,12 +43,11 @@ import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.conversation.Conversation; -import com.apptentive.android.sdk.module.engagement.EngagementModule; +import com.apptentive.android.sdk.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; import com.apptentive.android.sdk.module.messagecenter.MessageManager; import com.apptentive.android.sdk.module.messagecenter.OnListviewItemActionListener; -import com.apptentive.android.sdk.model.ApptentiveMessage; -import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.module.messagecenter.model.ContextMessage; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterStatus; @@ -284,7 +283,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } } - EngagementModule.engageInternal(getActivity(), interaction, MessageCenterInteraction.EVENT_NAME_ATTACH); + engageInternal(MessageCenterInteraction.EVENT_NAME_ATTACH); String originalPath = Util.getRealFilePathFromUri(hostingActivity, uri); if (originalPath != null) { @@ -328,7 +327,7 @@ public void onResume() { * abandoned the image picker without picking anything */ if (imagePickerStillOpen) { - EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_ATTACHMENT_CANCEL); + engageInternal(MessageCenterInteraction.EVENT_NAME_ATTACHMENT_CANCEL); imagePickerStillOpen = false; } } @@ -477,7 +476,7 @@ public boolean onMenuItemClick(MenuItem menuItem) { } catch (JSONException e) { // } - EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString()); + engageInternal(MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString()); boolean whoCardDisplayedBefore = wasWhoCardAsPreviouslyDisplayed(); forceShowKeyboard = true; @@ -515,11 +514,11 @@ public boolean onFragmentExit(ApptentiveViewExitType exitType) { } cleanup(); if (exitType.equals(ApptentiveViewExitType.BACK_BUTTON)) { - EngagementModule.engageInternal(hostingActivity, interaction, MessageCenterInteraction.EVENT_NAME_CANCEL); + engageInternal(MessageCenterInteraction.EVENT_NAME_CANCEL); } else if (exitType.equals(ApptentiveViewExitType.NOTIFICATION)) { - EngagementModule.engageInternal(hostingActivity, interaction, MessageCenterInteraction.EVENT_NAME_CANCEL, exitTypeToDataJson(exitType)); + engageInternal(MessageCenterInteraction.EVENT_NAME_CANCEL, exitTypeToDataJson(exitType)); } else { - EngagementModule.engageInternal(hostingActivity, interaction, MessageCenterInteraction.EVENT_NAME_CLOSE, exitTypeToDataJson(exitType)); + engageInternal(MessageCenterInteraction.EVENT_NAME_CLOSE, exitTypeToDataJson(exitType)); } } return false; @@ -572,7 +571,7 @@ private boolean checkAddWhoCardIfRequired() { } catch (JSONException e) { // } - EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString()); + engageInternal(MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString()); return true; } return false; @@ -678,7 +677,7 @@ public void setAttachmentsInComposer(final List images) { } public void removeImageFromComposer(final int position) { - EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_ATTACHMENT_DELETE); + engageInternal(MessageCenterInteraction.EVENT_NAME_ATTACHMENT_DELETE); messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_REMOVE_ATTACHMENT, position, 0)); messagingActionHandler.sendEmptyMessageDelayed(MSG_SCROLL_TO_BOTTOM, DEFAULT_DELAYMILLIS); } @@ -848,7 +847,7 @@ public void onCancelComposing() { } catch (JSONException e) { // } - EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_COMPOSE_CLOSE, data.toString()); + engageInternal(MessageCenterInteraction.EVENT_NAME_COMPOSE_CLOSE, data.toString()); messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_REMOVE_COMPOSER)); if (messageCenterRecyclerViewAdapter != null) { addExpectationStatusIfNeeded(); @@ -898,7 +897,7 @@ public void onSubmitWhoCard(String buttonLabel) { } catch (JSONException e) { // } - EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_PROFILE_SUBMIT, data.toString()); + engageInternal(MessageCenterInteraction.EVENT_NAME_PROFILE_SUBMIT, data.toString()); setWhoCardAsPreviouslyDisplayed(); cleanupWhoCard(); @@ -920,7 +919,7 @@ public void onCloseWhoCard(String buttonLabel) { } catch (JSONException e) { // } - EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_PROFILE_CLOSE, data.toString()); + engageInternal(MessageCenterInteraction.EVENT_NAME_PROFILE_CLOSE, data.toString()); setWhoCardAsPreviouslyDisplayed(); cleanupWhoCard(); @@ -1299,7 +1298,7 @@ public void handleMessage(Message msg) { break; } case MSG_MESSAGE_ADD_COMPOSING: { - EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_COMPOSE_OPEN); + fragment.engageInternal(MessageCenterInteraction.EVENT_NAME_COMPOSE_OPEN); fragment.listItems.add(fragment.interaction.getComposer()); fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1); fragment.messageCenterRecyclerView.setSelection(fragment.listItems.size() - 1); @@ -1375,7 +1374,7 @@ public void handleMessage(Message msg) { } catch (JSONException e) { // } - EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString()); + fragment.engageInternal(MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString()); fragment.forceShowKeyboard = true; fragment.addWhoCard(true); } @@ -1455,7 +1454,7 @@ public void handleMessage(Message msg) { if (createdTime != null && createdTime > Double.MIN_VALUE) { MessageCenterStatus status = fragment.interaction.getRegularStatus(); if (status != null) { - EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_STATUS); + fragment.engageInternal(MessageCenterInteraction.EVENT_NAME_STATUS); // Add expectation status message if the last is a sent listItems.add(status); fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(listItems.size() - 1); @@ -1493,13 +1492,13 @@ public void handleMessage(Message msg) { MessageCenterStatus status = null; if (reason == MessageManager.SEND_PAUSE_REASON_NETWORK) { status = fragment.interaction.getErrorStatusNetwork(); - EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_MESSAGE_NETWORK_ERROR); + fragment.engageInternal(MessageCenterInteraction.EVENT_NAME_MESSAGE_NETWORK_ERROR); } else if (reason == MessageManager.SEND_PAUSE_REASON_SERVER) { status = fragment.interaction.getErrorStatusServer(); - EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_MESSAGE_HTTP_ERROR); + fragment.engageInternal(MessageCenterInteraction.EVENT_NAME_MESSAGE_HTTP_ERROR); } if (status != null) { - EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_STATUS); + fragment.engageInternal(MessageCenterInteraction.EVENT_NAME_STATUS); fragment.listItems.add(status); fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NavigateToLinkFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NavigateToLinkFragment.java index 3c61903e9..7a98401cb 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NavigateToLinkFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NavigateToLinkFragment.java @@ -7,20 +7,16 @@ package com.apptentive.android.sdk.module.engagement.interaction.fragment; import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; -import android.content.ActivityNotFoundException; - import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.apptentive.android.sdk.ApptentiveLog; - - import com.apptentive.android.sdk.ApptentiveViewExitType; -import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.NavigateToLinkInteraction; import com.apptentive.android.sdk.util.Util; @@ -76,7 +72,7 @@ public void onCreate(Bundle savedInstanceState) { } catch (JSONException e) { ApptentiveLog.e("Error creating Event data object.", e); } - EngagementModule.engageInternal(getActivity(), interaction, NavigateToLinkInteraction.EVENT_NAME_NAVIGATE, data.toString()); + engageInternal(NavigateToLinkInteraction.EVENT_NAME_NAVIGATE, data.toString()); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java index e8ac0c98d..b4a8f3e47 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java @@ -111,7 +111,7 @@ public void onClick(View view) { } catch (JSONException e) { ApptentiveLog.e("Error creating Event data object.", e); } - EngagementModule.engageInternal(getActivity(), interaction, TextModalInteraction.EVENT_NAME_DISMISS, data.toString()); + engageInternal(TextModalInteraction.EVENT_NAME_DISMISS, data.toString()); transit(); } }); @@ -157,7 +157,7 @@ public void onClick(View view) { ApptentiveLog.e("Error creating Event data object.", e); } - EngagementModule.engageInternal(getActivity(), interaction, TextModalInteraction.EVENT_NAME_INTERACTION, data.toString()); + engageInternal(TextModalInteraction.EVENT_NAME_INTERACTION, data.toString()); if (invokedInteraction != null) { EngagementModule.launchInteraction(getActivity(), invokedInteraction); } @@ -177,7 +177,7 @@ public void onClick(View view) { @Override public boolean onFragmentExit(ApptentiveViewExitType exitType) { - EngagementModule.engageInternal(getActivity(), interaction, TextModalInteraction.EVENT_NAME_CANCEL, exitTypeToDataJson(exitType)); + engageInternal(TextModalInteraction.EVENT_NAME_CANCEL, exitTypeToDataJson(exitType)); return false; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/RatingDialogFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/RatingDialogFragment.java index 0db7eec52..03e610cd3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/RatingDialogFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/RatingDialogFragment.java @@ -15,7 +15,6 @@ import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.RatingDialogInteraction; public class RatingDialogFragment extends ApptentiveBaseFragment { @@ -54,7 +53,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa rateButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_RATE); + engageInternal(CODE_POINT_RATE); transit(); } }); @@ -68,7 +67,7 @@ public void onClick(View view) { remindButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_REMIND); + engageInternal(CODE_POINT_REMIND); transit(); } }); @@ -82,7 +81,7 @@ public void onClick(View view) { declineButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_DECLINE); + engageInternal(CODE_POINT_DECLINE); transit(); } }); @@ -91,7 +90,7 @@ public void onClick(View view) { @Override public boolean onFragmentExit(ApptentiveViewExitType exitType) { - EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_CANCEL, exitTypeToDataJson(exitType)); + engageInternal(CODE_POINT_CANCEL, exitTypeToDataJson(exitType)); return false; } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java index 462d8d7ce..bdd05a722 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java @@ -26,12 +26,10 @@ import android.widget.Toast; import com.apptentive.android.sdk.ApptentiveInternal; - import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.model.SurveyResponsePayload; -import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.SurveyInteraction; import com.apptentive.android.sdk.module.engagement.interaction.model.survey.MultichoiceQuestion; import com.apptentive.android.sdk.module.engagement.interaction.model.survey.MultiselectQuestion; @@ -120,7 +118,7 @@ public void onClick(View view) { } getActivity().finish(); - EngagementModule.engageInternal(getActivity(), interaction, EVENT_SUBMIT); + engageInternal(EVENT_SUBMIT); ApptentiveInternal.getInstance().getApptentiveTaskManager().addPayload(new SurveyResponsePayload(interaction, answers)); ApptentiveLog.d("Survey Submitted."); @@ -257,7 +255,7 @@ void sendMetricForQuestion(Activity activity, String questionId) { } catch (JSONException e) { // Never happens. } - EngagementModule.engageInternal(activity, interaction, EVENT_QUESTION_RESPONSE, answerData.toString()); + engageInternal(EVENT_QUESTION_RESPONSE, answerData.toString()); } private void callListener(boolean completed) { @@ -270,11 +268,11 @@ private void callListener(boolean completed) { @Override public boolean onFragmentExit(ApptentiveViewExitType exitType) { if (exitType.equals(ApptentiveViewExitType.BACK_BUTTON)) { - EngagementModule.engageInternal(getActivity(), interaction, EVENT_CANCEL); + engageInternal(EVENT_CANCEL); } else if (exitType.equals(ApptentiveViewExitType.NOTIFICATION)) { - EngagementModule.engageInternal(getActivity(), interaction, EVENT_CANCEL, exitTypeToDataJson(exitType)); + engageInternal(EVENT_CANCEL, exitTypeToDataJson(exitType)); } else { - EngagementModule.engageInternal(getActivity(), interaction, EVENT_CLOSE, exitTypeToDataJson(exitType)); + engageInternal(EVENT_CLOSE, exitTypeToDataJson(exitType)); } return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/UpgradeMessageFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/UpgradeMessageFragment.java index 07a9ebf64..0d129212a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/UpgradeMessageFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/UpgradeMessageFragment.java @@ -23,7 +23,6 @@ import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.model.Configuration; -import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.UpgradeMessageInteraction; public class UpgradeMessageFragment extends ApptentiveBaseFragment { @@ -66,7 +65,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, @Override public boolean onFragmentExit(ApptentiveViewExitType exitType) { - EngagementModule.engageInternal(getActivity(), interaction, CODE_POINT_DISMISS, exitTypeToDataJson(exitType)); + engageInternal(CODE_POINT_DISMISS, exitTypeToDataJson(exitType)); return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java index 4a87fc888..7bd0f7b4f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java @@ -15,15 +15,14 @@ import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.engagement.EngagementModule; +import com.apptentive.android.sdk.model.ApptentiveMessage; +import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.module.engagement.interaction.fragment.MessageCenterFragment; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; import com.apptentive.android.sdk.module.messagecenter.MessageManager; import com.apptentive.android.sdk.module.messagecenter.OnListviewItemActionListener; -import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.Composer; -import com.apptentive.android.sdk.model.CompoundMessage; import com.apptentive.android.sdk.module.messagecenter.model.ContextMessage; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterGreeting; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem; @@ -252,7 +251,7 @@ protected Void doInBackground(ApptentiveMessage... messages) { } catch (JSONException e) { // } - EngagementModule.engageInternal(fragment.getContext(), interaction, MessageCenterInteraction.EVENT_NAME_READ, data.toString()); + fragment.engageInternal(MessageCenterInteraction.EVENT_NAME_READ, data.toString()); MessageManager mgr = ApptentiveInternal.getInstance().getMessageManager(); if (mgr != null) { From 7ae0e6cf6c7ac3c82f079c97e8c7849999468953 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 27 Jun 2017 17:21:02 -0700 Subject: [PATCH 392/465] Change how we detect missing network connection. Throw an internal exception in that case. When we catch it, log without stacktrace, since it's a common error. Then, make sure we don't delete payloads that fail to send, or payloads that failed due to a server error. That way they will be sent when the network is back up and the server is fixed. --- .../android/sdk/network/HttpRequest.java | 22 ++++++++++++++++--- .../sdk/storage/ApptentiveTaskManager.java | 7 ++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 68bc28768..080e15949 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + package com.apptentive.android.sdk.network; import android.util.Base64; @@ -213,10 +219,14 @@ void dispatchSync(DispatchQueue networkQueue) { try { sendRequestSync(); + } catch (NetworkUnavailableException e) { + responseCode = -1; // indicates failure + errorMessage = e.getMessage(); + ApptentiveLog.w(e.getMessage()); + ApptentiveLog.w("Cancelled? %b", isCancelled()); } catch (Exception e) { responseCode = -1; // indicates failure errorMessage = e.getMessage(); - ApptentiveLog.e(e, "Unable to perform request"); ApptentiveLog.e("Cancelled? %b", isCancelled()); if (!isCancelled()) { ApptentiveLog.e(e, "Unable to perform request"); @@ -258,8 +268,8 @@ private void sendRequestSync() throws IOException { connection.setReadTimeout(readTimeout); if (!isNetworkConnectionPresent()) { - ApptentiveLog.d("No network connection present. Cancelling request."); - cancel(); + ApptentiveLog.d("No network connection present. Request will fail."); + throw new NetworkUnavailableException("The network is not currently active."); } if (isCancelled()) { @@ -617,4 +627,10 @@ public void onFail(T request, String reason) { } //endregion + + private class NetworkUnavailableException extends IOException { + NetworkUnavailableException(String message) { + super(message); + } + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 5af12c7b0..4fe128ee6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -203,11 +203,18 @@ public void onFinishSending(PayloadSender sender, PayloadData payload, boolean c if (appInBackground) { ApptentiveLog.v(PAYLOADS, "The app went to the background so we won't remove the payload from the queue"); return; + } else if (responseCode == -1) { + ApptentiveLog.v(PAYLOADS, "Payload failed to send due to a connection error."); + return; + } else if (responseCode > 500) { + ApptentiveLog.v(PAYLOADS, "Payload failed to send due to a server error."); + return; } } else { ApptentiveLog.v(PAYLOADS, "Payload was successfully sent: %s", payload); } + // Only let the payload be deleted if it was successfully sent, or got an unrecoverable client error. deletePayload(payload.getNonce()); } From 5ca622c2868005857f6a7beab00eb57f0c5f5a85 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 27 Jun 2017 17:32:21 -0700 Subject: [PATCH 393/465] Logout event emitted even when no active conversation. ANDROID-998 --- .../com/apptentive/android/sdk/ApptentiveInternal.java | 1 - .../android/sdk/conversation/ConversationManager.java | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 40e984e02..31242b768 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -1050,7 +1050,6 @@ public void onLoginFail(String errorMessage) { } void logout() { - EngagementModule.engageInternal(getApplicationContext(), "logout"); conversationManager.logout(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 9a985cc1e..0ff384545 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2017, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + package com.apptentive.android.sdk.conversation; import android.content.Context; @@ -12,6 +18,7 @@ import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.conversation.ConversationMetadata.Filter; import com.apptentive.android.sdk.model.ConversationTokenRequest; +import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.notifications.ApptentiveNotification; @@ -763,6 +770,7 @@ private void doLogout() { switch (activeConversation.getState()) { case LOGGED_IN: ApptentiveLog.d("Ending active conversation."); + EngagementModule.engageInternal(getContext(), "logout"); // Post synchronously to ensure logout payload can be sent before destroying the logged in conversation. ApptentiveNotificationCenter.defaultCenter().postNotificationSync(NOTIFICATION_CONVERSATION_WILL_LOGOUT, ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, activeConversation)); activeConversation.destroy(); From 66c484f8436b9509470294930ae536d9719e460c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 10:48:35 -0700 Subject: [PATCH 394/465] Payload sending refactoring --- .../sdk/storage/ApptentiveDatabaseHelper.java | 40 +++++++++---------- .../sdk/storage/ApptentiveTaskManager.java | 22 +++++----- .../android/sdk/storage/PayloadStore.java | 4 +- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 330ad99d3..460bbbf42 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -460,35 +460,33 @@ private void upgradeVersion2to3(SQLiteDatabase db) { * If an item with the same nonce as an item passed in already exists, it is overwritten by the item. Otherwise * a new message is added. */ - void addPayload(Payload... payloads) { + void addPayload(Payload payload) { SQLiteDatabase db = null; try { db = getWritableDatabase(); db.beginTransaction(); - for (Payload payload : payloads) { - ContentValues values = new ContentValues(); - values.put(PayloadEntry.COLUMN_IDENTIFIER.name, notNull(payload.getNonce())); - values.put(PayloadEntry.COLUMN_PAYLOAD_TYPE.name, notNull(payload.getPayloadType().name())); - values.put(PayloadEntry.COLUMN_CONTENT_TYPE.name, notNull(payload.getHttpRequestContentType())); - // The token is encrypted inside the payload body for Logged In Conversations. In that case, don't store it here. - if (!payload.hasEncryptionKey()) { - values.put(PayloadEntry.COLUMN_AUTH_TOKEN.name, payload.getToken()); // might be null - } - values.put(PayloadEntry.COLUMN_CONVERSATION_ID.name, payload.getConversationId()); // might be null - values.put(PayloadEntry.COLUMN_REQUEST_METHOD.name, payload.getHttpRequestMethod().name()); - values.put(PayloadEntry.COLUMN_PATH.name, payload.getHttpEndPoint( - StringUtils.isNullOrEmpty(payload.getConversationId()) ? "${conversationId}" : payload.getConversationId()) // if conversation id is missing we replace it with a place holder and update it later - ); + ContentValues values = new ContentValues(); + values.put(PayloadEntry.COLUMN_IDENTIFIER.name, notNull(payload.getNonce())); + values.put(PayloadEntry.COLUMN_PAYLOAD_TYPE.name, notNull(payload.getPayloadType().name())); + values.put(PayloadEntry.COLUMN_CONTENT_TYPE.name, notNull(payload.getHttpRequestContentType())); + // The token is encrypted inside the payload body for Logged In Conversations. In that case, don't store it here. + if (!payload.hasEncryptionKey()) { + values.put(PayloadEntry.COLUMN_AUTH_TOKEN.name, payload.getToken()); // might be null + } + values.put(PayloadEntry.COLUMN_CONVERSATION_ID.name, payload.getConversationId()); // might be null + values.put(PayloadEntry.COLUMN_REQUEST_METHOD.name, payload.getHttpRequestMethod().name()); + values.put(PayloadEntry.COLUMN_PATH.name, payload.getHttpEndPoint( + StringUtils.isNullOrEmpty(payload.getConversationId()) ? "${conversationId}" : payload.getConversationId()) // if conversation id is missing we replace it with a place holder and update it later + ); - File dest = getPayloadBodyFile(payload.getNonce()); - ApptentiveLog.v(DATABASE, "Saving payload body to: %s", dest); - Util.writeBytes(dest, payload.renderData()); + File dest = getPayloadBodyFile(payload.getNonce()); + ApptentiveLog.v(DATABASE, "Saving payload body to: %s", dest); + Util.writeBytes(dest, payload.renderData()); - values.put(PayloadEntry.COLUMN_ENCRYPTED.name, payload.hasEncryptionKey() ? TRUE : FALSE); + values.put(PayloadEntry.COLUMN_ENCRYPTED.name, payload.hasEncryptionKey() ? TRUE : FALSE); - db.insert(PayloadEntry.TABLE_NAME, null, values); - } + db.insert(PayloadEntry.TABLE_NAME, null, values); db.setTransactionSuccessful(); } catch (Exception e) { ApptentiveLog.e(DATABASE, e, "Error adding payload."); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 4fe128ee6..e51743f38 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -103,24 +103,22 @@ public boolean shouldRetryRequest(int responseCode, int retryAttempt) { * If an item with the same nonce as an item passed in already exists, it is overwritten by the item. Otherwise * a new message is added. */ - public void addPayload(final Payload... payloads) { + public void addPayload(final Payload payload) { // Provide each payload with the information it will need to send itself to the server securely. - for (Payload payload : payloads) { - if (currentConversationId != null) { - payload.setConversationId(currentConversationId); - } - if (currentConversationToken != null) { - payload.setToken(currentConversationToken); - } - if (conversationEncryptionKey != null) { - payload.setEncryptionKey(conversationEncryptionKey); - } + if (currentConversationId != null) { + payload.setConversationId(currentConversationId); + } + if (currentConversationToken != null) { + payload.setToken(currentConversationToken); + } + if (conversationEncryptionKey != null) { + payload.setEncryptionKey(conversationEncryptionKey); } singleThreadExecutor.execute(new Runnable() { @Override public void run() { - dbHelper.addPayload(payloads); + dbHelper.addPayload(payload); sendNextPayloadSync(); } }); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java index 8795ea140..26836dbcd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java @@ -2,11 +2,9 @@ import com.apptentive.android.sdk.model.Payload; -import java.util.concurrent.Future; - public interface PayloadStore { - void addPayload(Payload... payloads); + void addPayload(Payload payloads); void deletePayload(String payloadIdentifier); From 2049d8c1de67e9eb92d5d1a6663ae222f424419e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 11:49:24 -0700 Subject: [PATCH 395/465] Remove async notifications --- .../sdk/conversation/ConversationManager.java | 4 +-- .../ApptentiveNotificationCenter.java | 35 ++----------------- .../ApptentiveNotificationCenterTest.java | 32 +---------------- 3 files changed, 5 insertions(+), 66 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index e47b4847a..a23d69ffd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -420,7 +420,7 @@ private void handleConversationStateChange(Conversation conversation) { "conversation_identifier", conversation.getConversationId()); ApptentiveNotificationCenter.defaultCenter() - .postNotificationSync(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, + .postNotification(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE, ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, conversation)); if (conversation.hasActiveState()) { @@ -770,7 +770,7 @@ private void doLogout() { ApptentiveLog.d("Ending active conversation."); ApptentiveInternal.engageInternal(getContext(), "logout"); // Post synchronously to ensure logout payload can be sent before destroying the logged in conversation. - ApptentiveNotificationCenter.defaultCenter().postNotificationSync(NOTIFICATION_CONVERSATION_WILL_LOGOUT, ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, activeConversation)); + ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_CONVERSATION_WILL_LOGOUT, ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, activeConversation)); activeConversation.destroy(); activeConversation.setState(LOGGED_OUT); handleConversationStateChange(activeConversation); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java index 09cf2026d..3f49b2cf7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenter.java @@ -8,8 +8,6 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.util.ObjectUtils; -import com.apptentive.android.sdk.util.threading.DispatchQueue; -import com.apptentive.android.sdk.util.threading.DispatchTask; import java.util.Collections; import java.util.HashMap; @@ -34,18 +32,8 @@ public class ApptentiveNotificationCenter { */ private final Map observerListLookup; - /** - * Dispatch queue for posting notifications. - */ - private final DispatchQueue notificationQueue; - - ApptentiveNotificationCenter(DispatchQueue notificationQueue) { - if (notificationQueue == null) { - throw new IllegalArgumentException("Notification queue is not defined"); - } - + ApptentiveNotificationCenter() { this.observerListLookup = new HashMap<>(); - this.notificationQueue = notificationQueue; } //region Observers @@ -109,25 +97,6 @@ public synchronized void postNotification(final String name, Object... args) { * Creates a notification with a given name and user info and posts it to the receiver. */ public synchronized void postNotification(final String name, final Map userInfo) { - notificationQueue.dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - postNotificationSync(name, userInfo); - } - }); - } - - /** - * Creates a notification with a given name and information and posts it to the receiver synchronously. - */ - public synchronized void postNotificationSync(String name) { - postNotificationSync(name, EMPTY_USER_INFO); - } - - /** - * Creates a notification with a given name and information and posts it to the receiver synchronously. - */ - public synchronized void postNotificationSync(String name, Map userInfo) { final ApptentiveNotification notification = new ApptentiveNotification(name, userInfo); ApptentiveLog.v(NOTIFICATIONS, "Post notification: %s", notification); @@ -177,7 +146,7 @@ public static ApptentiveNotificationCenter defaultCenter() { * Thread-safe initialization trick */ private static class Holder { - static final ApptentiveNotificationCenter INSTANCE = new ApptentiveNotificationCenter(DispatchQueue.mainQueue()); + static final ApptentiveNotificationCenter INSTANCE = new ApptentiveNotificationCenter(); } //endregion diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java index ca712b385..ff770ee2c 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/notifications/ApptentiveNotificationCenterTest.java @@ -9,7 +9,6 @@ import com.apptentive.android.sdk.TestCaseBase; import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; -import com.apptentive.android.sdk.util.threading.MockDispatchQueue; import org.junit.After; import org.junit.Before; @@ -22,7 +21,7 @@ public class ApptentiveNotificationCenterTest extends TestCaseBase { @Before public void setUp() { super.setUp(); - notificationCenter = new ApptentiveNotificationCenter(new MockDispatchQueue(true)); + notificationCenter = new ApptentiveNotificationCenter(); } @After @@ -101,35 +100,6 @@ public void testPostNotifications() { assertResult(); } - @Test - public void testPostNotificationsSync() { - final Observer observer1 = new Observer("observer1"); - final Observer observer3 = new Observer("observer3"); - final Observer observer2 = new Observer("observer2") { - @Override - public void onReceiveNotification(ApptentiveNotification notification) { - super.onReceiveNotification(notification); - notificationCenter.removeObserver(observer3); - } - }; - notificationCenter.addObserver("notification", observer1); - notificationCenter.addObserver("notification", observer2); - notificationCenter.addObserver("notification", observer3); - notificationCenter.postNotificationSync("notification", ObjectUtils.toMap("key", "value")); - - assertResult( - "observer1: notification {'key':'value'}", - "observer2: notification {'key':'value'}", - "observer3: notification {'key':'value'}" - ); - - notificationCenter.postNotificationSync("notification", ObjectUtils.toMap("key", "value")); - assertResult( - "observer1: notification {'key':'value'}", - "observer2: notification {'key':'value'}" - ); - } - private class Observer implements ApptentiveNotificationObserver { private final String name; From b138ce277c13d967b5a059d53613445f4ef5f627 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 13:32:38 -0700 Subject: [PATCH 396/465] Decoupled ApptentiveTaskManager from active conversation --- .../android/sdk/ApptentiveInternal.java | 6 ++- .../sdk/conversation/Conversation.java | 24 +++++++++++ .../android/sdk/model/EventManager.java | 25 ------------ .../apptentive/android/sdk/model/Payload.java | 10 +++++ .../module/engagement/EngagementModule.java | 3 +- .../fragment/ApptentiveBaseFragment.java | 5 +++ .../interaction/fragment/SurveyFragment.java | 2 +- .../module/messagecenter/MessageManager.java | 2 +- .../sdk/module/metric/MetricModule.java | 17 ++++++-- .../sdk/storage/ApptentiveDatabaseHelper.java | 35 ++++++++++++++-- .../sdk/storage/ApptentiveTaskManager.java | 40 +++++-------------- .../android/sdk/storage/PayloadStore.java | 2 +- 12 files changed, 101 insertions(+), 70 deletions(-) delete mode 100644 apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 30130e21e..826d4187d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -79,6 +79,7 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_INTERACTIONS_SHOULD_DISMISS; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_ACTIVITY; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_AUTHENTICATION_FAILED_REASON; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION_ID; import static com.apptentive.android.sdk.debug.Assert.assertMainThread; import static com.apptentive.android.sdk.debug.Assert.assertNotNull; @@ -677,7 +678,7 @@ private void checkSendVersionChanges(Conversation conversation) { } if (appReleaseChanged || sdkChanged) { - taskManager.addPayload(AppReleaseManager.getPayload(sdk, appRelease)); + conversation.addPayload(AppReleaseManager.getPayload(sdk, appRelease)); invalidateCaches(); } } @@ -1077,7 +1078,8 @@ public static void dismissAllInteractions() { @Override public void onReceiveNotification(ApptentiveNotification notification) { if (notification.hasName(NOTIFICATION_CONVERSATION_WILL_LOGOUT)) { - getApptentiveTaskManager().addPayload(new LogoutPayload()); + Conversation conversation = notification.getRequiredUserInfo(NOTIFICATION_KEY_CONVERSATION, Conversation.class); + conversation.addPayload(new LogoutPayload()); } else if (notification.hasName(NOTIFICATION_AUTHENTICATION_FAILED)) { String conversationIdOfFailedRequest = notification.getUserInfo(NOTIFICATION_KEY_CONVERSATION_ID, String.class); Apptentive.AuthenticationFailedReason authenticationFailedReason = notification.getUserInfo(NOTIFICATION_KEY_AUTHENTICATION_FAILED_REASON, Apptentive.AuthenticationFailedReason.class); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 090679db8..b741c4280 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -15,6 +15,7 @@ import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.debug.Assert; +import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.InteractionManifest; import com.apptentive.android.sdk.module.engagement.interaction.model.Interactions; @@ -43,6 +44,7 @@ import org.json.JSONException; import java.io.File; +import java.util.UUID; import static com.apptentive.android.sdk.debug.Assert.assertFail; import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; @@ -52,6 +54,9 @@ public class Conversation implements DataChangedListener, Destroyable { + /** Conversation 'local' identifier */ + private final String localIdentifier; + /** * Conversation data for this class to manage */ @@ -130,6 +135,7 @@ public Conversation(File conversationDataFile, File conversationMessagesFile) { throw new IllegalArgumentException("Messages file is null"); } + this.localIdentifier = UUID.randomUUID().toString(); this.conversationDataFile = conversationDataFile; this.conversationMessagesFile = conversationMessagesFile; @@ -140,6 +146,20 @@ public Conversation(File conversationDataFile, File conversationMessagesFile) { messageManager = new MessageManager(this, messageStore); // it's important to initialize message manager in a constructor since other SDK parts depend on it via Apptentive singleton } + //region Payloads + + public void addPayload(Payload payload) { + payload.setLocalConversationIdentifier(getLocalIdentifier()); + payload.setConversationId(getConversationId()); + payload.setToken(getConversationToken()); + payload.setEncryptionKey(getEncryptionKey()); + + // FIXME: don't use singleton here + ApptentiveInternal.getInstance().getApptentiveTaskManager().addPayload(payload); + } + + //endregion + //region Interactions /** @@ -330,6 +350,10 @@ public void destroy() { //region Getters & Setters + public String getLocalIdentifier() { + return localIdentifier; + } + public ConversationState getState() { return state; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java deleted file mode 100644 index a114298d7..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventManager.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2014, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.model; - -import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.storage.EventStore; - -import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; -import static com.apptentive.android.sdk.debug.TesterEvent.*; - -public class EventManager { - - private static EventStore getEventStore() { - return ApptentiveInternal.getInstance().getApptentiveTaskManager(); - } - - public static void sendEvent(EventPayload event) { - dispatchDebugEvent(EVT_APPTENTIVE_EVENT, EVT_APPTENTIVE_EVENT_KEY_EVENT_LABEL, event.getEventLabel()); - getEventStore().addPayload(event); - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index d637ccfd4..aca0c0dd4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -30,6 +30,8 @@ public abstract class Payload { */ protected String token; + private String localConversationIdentifier; + private List attachments; // TODO: Figure out attachment handling protected Payload(PayloadType type) { @@ -110,5 +112,13 @@ public void setAttachments(List attachments) { this.attachments = attachments; } + public String getLocalConversationIdentifier() { + return localConversationIdentifier; + } + + public void setLocalConversationIdentifier(String localConversationIdentifier) { + this.localConversationIdentifier = localConversationIdentifier; + } + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java index 702d3a435..c8b03ef63 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java @@ -15,7 +15,6 @@ import com.apptentive.android.sdk.ApptentiveViewActivity; import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.debug.Assert; -import com.apptentive.android.sdk.model.EventManager; import com.apptentive.android.sdk.model.EventPayload; import com.apptentive.android.sdk.model.ExtendedData; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; @@ -68,7 +67,7 @@ public static synchronized boolean engage(Context context, Conversation conversa String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); conversation.getEventData().storeEventForCurrentAppVersion(Util.currentTimeSeconds(), versionCode, versionName, eventLabel); - EventManager.sendEvent(new EventPayload(eventLabel, interactionId, data, customData, extendedData)); + conversation.addPayload(new EventPayload(eventLabel, interactionId, data, customData, extendedData)); return doEngage(conversation, context, eventLabel); } catch (Exception e) { ApptentiveLog.w("Error in engage()", e); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java index fb63b1823..e551c8b10 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java @@ -35,6 +35,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.model.ExtendedData; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; @@ -528,6 +529,10 @@ public boolean engageInternal(String eventName, String data) { return ApptentiveInternal.engageInternal(getActivity(), interaction, eventName, data); } + protected Conversation getConversation() { + return ApptentiveInternal.getInstance().getConversation(); // FIXME: don't use singleton + } + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java index bdd05a722..856de2f85 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/SurveyFragment.java @@ -120,7 +120,7 @@ public void onClick(View view) { engageInternal(EVENT_SUBMIT); - ApptentiveInternal.getInstance().getApptentiveTaskManager().addPayload(new SurveyResponsePayload(interaction, answers)); + getConversation().addPayload(new SurveyResponsePayload(interaction, answers)); ApptentiveLog.d("Survey Submitted."); callListener(true); } else { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 84be0cb7d..1745fc673 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -220,7 +220,7 @@ public List getMessageCenterListItems() { public void sendMessage(ApptentiveMessage apptentiveMessage) { messageStore.addOrUpdateMessages(apptentiveMessage); - ApptentiveInternal.getInstance().getApptentiveTaskManager().addPayload(apptentiveMessage); + conversation.addPayload(apptentiveMessage); } /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java index 8664bdbbd..f9604d3fe 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java @@ -6,11 +6,13 @@ package com.apptentive.android.sdk.module.metric; +import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.model.Configuration; import com.apptentive.android.sdk.model.EventPayload; -import com.apptentive.android.sdk.model.EventManager; import com.apptentive.android.sdk.util.Util; + import org.json.JSONObject; import java.util.Map; @@ -36,7 +38,7 @@ public static void sendMetric(EventPayload.EventLabel type, String trigger, Map< ApptentiveLog.v("Sending Metric: %s, trigger: %s, data: %s", type.getLabelName(), trigger, data != null ? data.toString() : "null"); EventPayload event = new EventPayload(type.getLabelName(), trigger); event.putData(data); - EventManager.sendEvent(event); + sendEvent(event); } } @@ -68,7 +70,7 @@ public static void sendError(Throwable throwable, String description, String ext if (config.isMetricsEnabled()) { ApptentiveLog.v("Sending Error Metric: %s, data: %s", type.getLabelName(), data.toString()); EventPayload event = new EventPayload(type.getLabelName(), data); - EventManager.sendEvent(event); + sendEvent(event); } } catch (Exception e) { // Since this is the last place in Apptentive code we can catch exceptions, we must catch all other Exceptions to @@ -76,4 +78,13 @@ public static void sendError(Throwable throwable, String description, String ext ApptentiveLog.w("Error creating Error Metric. Nothing we can do but log this.", e); } } + + private static void sendEvent(EventPayload event) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.addPayload(event); + } else { + ApptentiveLog.w("Unable to send event '%s': no active conversation"); + } + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 460bbbf42..e29f3130b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -66,6 +66,7 @@ static final class PayloadEntry { static final DatabaseColumn COLUMN_REQUEST_METHOD = new DatabaseColumn(6, "requestMethod"); static final DatabaseColumn COLUMN_PATH = new DatabaseColumn(7, "path"); static final DatabaseColumn COLUMN_ENCRYPTED = new DatabaseColumn(8, "encrypted"); + static final DatabaseColumn COLUMN_LOCAL_CONVERSATION_ID = new DatabaseColumn(9, "localConversationId"); } private static final class LegacyPayloadEntry { @@ -89,7 +90,8 @@ private static final class LegacyPayloadEntry { PayloadEntry.COLUMN_CONVERSATION_ID + " TEXT," + PayloadEntry.COLUMN_REQUEST_METHOD + " TEXT," + PayloadEntry.COLUMN_PATH + " TEXT," + - PayloadEntry.COLUMN_ENCRYPTED + " INTEGER" + + PayloadEntry.COLUMN_ENCRYPTED + " INTEGER," + + PayloadEntry.COLUMN_LOCAL_CONVERSATION_ID + " TEXT" + ");"; private static final String SQL_QUERY_PAYLOAD_LIST_LEGACY = @@ -105,6 +107,13 @@ private static final class LegacyPayloadEntry { "UPDATE " + PayloadEntry.TABLE_NAME + " SET " + PayloadEntry.COLUMN_AUTH_TOKEN + " = ?, " + PayloadEntry.COLUMN_CONVERSATION_ID + " = ? " + + "WHERE " + + PayloadEntry.COLUMN_LOCAL_CONVERSATION_ID + " = ? AND" + + PayloadEntry.COLUMN_AUTH_TOKEN + " IS NULL AND " + + PayloadEntry.COLUMN_CONVERSATION_ID + " IS NULL"; + + private static final String SQL_QUERY_REMOVE_INCOMPLETE_PAYLOADS = + "DELETE FROM " + PayloadEntry.TABLE_NAME + " " + "WHERE " + PayloadEntry.COLUMN_AUTH_TOKEN + " IS NULL OR " + PayloadEntry.COLUMN_CONVERSATION_ID + " IS NULL"; @@ -485,6 +494,7 @@ void addPayload(Payload payload) { Util.writeBytes(dest, payload.renderData()); values.put(PayloadEntry.COLUMN_ENCRYPTED.name, payload.hasEncryptionKey() ? TRUE : FALSE); + values.put(PayloadEntry.COLUMN_LOCAL_CONVERSATION_ID.name, notNull(payload.getLocalConversationIdentifier())); db.insert(PayloadEntry.TABLE_NAME, null, values); db.setTransactionSuccessful(); @@ -580,7 +590,7 @@ private String updatePayloadRequestPath(String path, String conversationId) { return path.replace("${conversationId}", conversationId); } - void updateIncompletePayloads(String conversationId, String authToken) { + void updateIncompletePayloads(String conversationId, String authToken, String localConversationId) { if (StringUtils.isNullOrEmpty(conversationId)) { throw new IllegalArgumentException("Conversation id is null or empty"); } @@ -590,8 +600,8 @@ void updateIncompletePayloads(String conversationId, String authToken) { Cursor cursor = null; try { SQLiteDatabase db = getWritableDatabase(); - cursor = db.rawQuery(SQL_QUERY_UPDATE_INCOMPLETE_PAYLOADS, new String[]{ - authToken, conversationId + cursor = db.rawQuery(SQL_QUERY_UPDATE_INCOMPLETE_PAYLOADS, new String[] { + authToken, conversationId, localConversationId }); cursor.moveToFirst(); // we need to move a cursor in order to update database ApptentiveLog.v(DATABASE, "Updated missing conversation ids"); @@ -600,6 +610,23 @@ void updateIncompletePayloads(String conversationId, String authToken) { } finally { ensureClosed(cursor); } + + // remove incomplete payloads which don't belong to an active conversation + removeCorruptedPayloads(); + } + + private void removeCorruptedPayloads() { + Cursor cursor = null; + try { + SQLiteDatabase db = getWritableDatabase(); + cursor = db.rawQuery(SQL_QUERY_REMOVE_INCOMPLETE_PAYLOADS, null); + cursor.moveToFirst(); // we need to move a cursor in order to update database + ApptentiveLog.v(DATABASE, "Removed incomplete payloads"); + } catch (SQLException e) { + ApptentiveLog.e(e, "Exception while removing incomplete payloads"); + } finally { + ensureClosed(cursor); + } } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index e51743f38..188d230ba 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -11,7 +11,6 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.conversation.Conversation; -import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.model.StoredFile; @@ -46,6 +45,7 @@ import static com.apptentive.android.sdk.conversation.ConversationState.UNDEFINED; import static com.apptentive.android.sdk.debug.Assert.assertNotEquals; import static com.apptentive.android.sdk.debug.Assert.assertNotNull; +import static com.apptentive.android.sdk.debug.Assert.notNull; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; @@ -54,11 +54,6 @@ public class ApptentiveTaskManager implements PayloadStore, EventStore, Apptenti private final ApptentiveDatabaseHelper dbHelper; private final ThreadPoolExecutor singleThreadExecutor; // TODO: replace with a private concurrent dispatch queue - // Set when receiving an ApptentiveNotification - private String currentConversationId; - private String currentConversationToken; - private String conversationEncryptionKey; - private final PayloadSender payloadSender; private boolean appInBackground; @@ -80,7 +75,7 @@ public ApptentiveTaskManager(Context context, ApptentiveHttpClient apptentiveHtt // If no new task arrives in 30 seconds, the worker thread terminates; otherwise it will be reused singleThreadExecutor.allowCoreThreadTimeOut(true); - + // Create payload sender object with a custom 'retry' policy payloadSender = new PayloadSender(apptentiveHttpClient, new HttpRequestRetryPolicyDefault() { @Override @@ -104,17 +99,6 @@ public boolean shouldRetryRequest(int responseCode, int retryAttempt) { * a new message is added. */ public void addPayload(final Payload payload) { - - // Provide each payload with the information it will need to send itself to the server securely. - if (currentConversationId != null) { - payload.setConversationId(currentConversationId); - } - if (currentConversationToken != null) { - payload.setToken(currentConversationToken); - } - if (conversationEncryptionKey != null) { - payload.setEncryptionKey(conversationEncryptionKey); - } singleThreadExecutor.execute(new Runnable() { @Override public void run() { @@ -266,18 +250,15 @@ private void sendNextPayloadSync() { @Override public void onReceiveNotification(ApptentiveNotification notification) { if (notification.hasName(NOTIFICATION_CONVERSATION_STATE_DID_CHANGE)) { - Conversation conversation = notification.getUserInfo(NOTIFICATION_KEY_CONVERSATION, Conversation.class); + final Conversation conversation = notification.getUserInfo(NOTIFICATION_KEY_CONVERSATION, Conversation.class); assertNotNull(conversation); // sanity check assertNotEquals(conversation.getState(), UNDEFINED); if (conversation.hasActiveState()) { - assertNotNull(conversation.getConversationId()); - currentConversationId = conversation.getConversationId(); - Assert.assertNotNull(currentConversationId); - - currentConversationToken = conversation.getConversationToken(); - conversationEncryptionKey = conversation.getEncryptionKey(); - Assert.assertNotNull(currentConversationToken); - ApptentiveLog.d("Conversation %s state changed to %s.", currentConversationId, conversation.getState()); + final String conversationId = notNull(conversation.getConversationId()); + final String conversationToken = notNull(conversation.getConversationToken()); + final String conversationLocalIdentifier = notNull(conversation.getLocalIdentifier()); + + ApptentiveLog.d("Conversation %s state changed to %s.", conversationId, conversation.getState()); // when the Conversation ID comes back from the server, we need to update // the payloads that may have already been enqueued so // that they each have the Conversation ID. @@ -285,14 +266,11 @@ public void onReceiveNotification(ApptentiveNotification notification) { singleThreadExecutor.execute(new Runnable() { @Override public void run() { - dbHelper.updateIncompletePayloads(currentConversationId, currentConversationToken); + dbHelper.updateIncompletePayloads(conversationId, conversationToken, conversationLocalIdentifier); sendNextPayloadSync(); // after we've updated payloads - we need to send them } }); } - - } else { - currentConversationId = null; } } else if (notification.hasName(NOTIFICATION_APP_ENTERED_FOREGROUND)) { appInBackground = false; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java index 26836dbcd..c77343230 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadStore.java @@ -2,7 +2,7 @@ import com.apptentive.android.sdk.model.Payload; -public interface PayloadStore { +interface PayloadStore { void addPayload(Payload payloads); From dcd2f4f222082dcb82b03f6fe9de8b08333a64fc Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 13:59:44 -0700 Subject: [PATCH 397/465] Stop message polling when conversation is destroyed --- .../module/messagecenter/MessageManager.java | 1 + .../messagecenter/MessagePollingWorker.java | 30 ++++++++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 1745fc673..14cc2c5b0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -401,6 +401,7 @@ public void onReceiveNotification(ApptentiveNotification notification) { @Override public void destroy() { ApptentiveNotificationCenter.defaultCenter().removeObserver(this); + pollingWorker.destroy(); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java index 8583e95d1..5043435d4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java @@ -12,14 +12,15 @@ import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.model.Configuration; import com.apptentive.android.sdk.module.metric.MetricModule; +import com.apptentive.android.sdk.util.Destroyable; import java.util.concurrent.atomic.AtomicBoolean; import static com.apptentive.android.sdk.ApptentiveLogTag.MESSAGES; -public class MessagePollingWorker { +public class MessagePollingWorker implements Destroyable { - private MessagePollingThread sPollingThread; + private MessagePollingThread pollingThread; // The following booleans will be accessed by both ui thread and worker thread public AtomicBoolean messageCenterInForeground = new AtomicBoolean(false); @@ -35,11 +36,11 @@ public MessagePollingWorker(MessageManager manager) { public synchronized MessagePollingThread getAndSetMessagePollingThread(boolean expect, boolean create) { if (expect && create) { - sPollingThread = createPollingThread(); + pollingThread = createPollingThread(); } else if (!expect) { - sPollingThread = null; + pollingThread = null; } - return sPollingThread; + return pollingThread; } private MessagePollingThread createPollingThread() { @@ -57,6 +58,12 @@ public void uncaughtException(Thread thread, Throwable throwable) { return newThread; } + @Override + public void destroy() { + if (pollingThread != null) { + pollingThread.interrupt(); + } + } private class MessagePollingThread extends Thread { @@ -74,7 +81,7 @@ public void run() { try { ApptentiveLog.v(MESSAGES, "Started %s", toString()); - while (manager.appInForeground.get()) { + while (manager.appInForeground.get() && !Thread.currentThread().isInterrupted()) { MessagePollingThread thread = getAndSetMessagePollingThread(true, false); if (thread != null && thread != MessagePollingThread.this) { return; @@ -84,21 +91,24 @@ public void run() { ApptentiveLog.v(MESSAGES, "Checking server for new messages every %d seconds", pollingInterval / 1000); manager.fetchAndStoreMessages(messageCenterInForeground.get(), conf.isMessageCenterNotificationPopupEnabled()); } - goToSleep(pollingInterval); + if (!goToSleep(pollingInterval)) { + break; + } } } finally { threadRunning.set(false); - sPollingThread = null; + pollingThread = null; ApptentiveLog.v(MESSAGES, "Stopping MessagePollingThread."); } } } - private void goToSleep(long millis) { + private boolean goToSleep(long millis) { try { Thread.sleep(millis); + return true; } catch (InterruptedException e) { - // This is normal and happens whenever we wake the thread with an interrupt. + return false; } } From d63a14aa2313635cb35c304240d967ebba1bf9ce Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 14:00:19 -0700 Subject: [PATCH 398/465] Fixed log output --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 826d4187d..2d60fef62 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -1092,21 +1092,21 @@ public void onReceiveNotification(ApptentiveNotification notification) { public static boolean engage(Context context, String vendor, String interaction, String interactionId, String eventName, String data, Map customData, ExtendedData[] extendedData) { assertMainThread(); Conversation conversation = getInstance().getConversation(); - assertNotNull(conversation, "Attempted to engage '%s' event without an active conversation"); + assertNotNull(conversation, "Attempted to engage '%s' event without an active conversation", eventName); return conversation != null && EngagementModule.engage(context, conversation, vendor, interaction, interactionId, eventName, data, customData, extendedData); } public static boolean engageInternal(Context context, String eventName) { assertMainThread(); Conversation conversation = getInstance().getConversation(); - assertNotNull(conversation, "Attempted to engage '%s' internal event without an active conversation"); + assertNotNull(conversation, "Attempted to engage '%s' internal event without an active conversation", eventName); return conversation != null && EngagementModule.engageInternal(context, conversation, eventName); } public static boolean engageInternal(Context context, Interaction interaction, String eventName, String data) { assertMainThread(); Conversation conversation = getInstance().getConversation(); - assertNotNull(conversation, "Attempted to engage '%s' internal event without an active conversation"); + assertNotNull(conversation, "Attempted to engage '%s' internal event without an active conversation", eventName); return conversation != null && EngagementModule.engageInternal(context, conversation, interaction, eventName, data); } From ce9dc5d4e014e5f8cf14efe2ea5f7d4c0420343f Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 14:28:32 -0700 Subject: [PATCH 399/465] Fixed raw SQL statement --- .../android/sdk/storage/ApptentiveDatabaseHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index e29f3130b..642520928 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -108,7 +108,7 @@ private static final class LegacyPayloadEntry { PayloadEntry.COLUMN_AUTH_TOKEN + " = ?, " + PayloadEntry.COLUMN_CONVERSATION_ID + " = ? " + "WHERE " + - PayloadEntry.COLUMN_LOCAL_CONVERSATION_ID + " = ? AND" + + PayloadEntry.COLUMN_LOCAL_CONVERSATION_ID + " = ? AND " + PayloadEntry.COLUMN_AUTH_TOKEN + " IS NULL AND " + PayloadEntry.COLUMN_CONVERSATION_ID + " IS NULL"; From d8f0a42f83bbadd000737fedec80be64177ff03b Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 14:29:09 -0700 Subject: [PATCH 400/465] Fixed empty json responses for payloads --- .../com/apptentive/android/sdk/storage/PayloadSender.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 5fb562795..93e185120 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -106,11 +106,12 @@ private synchronized void sendPayloadRequest(final PayloadData payload) { @Override public void onFinish(HttpRequest request) { try { - final JSONObject responseData = new JSONObject(request.getResponseData()); + String json = StringUtils.isNullOrEmpty(request.getResponseData()) ? "{}" : request.getResponseData(); + final JSONObject responseData = new JSONObject(json); handleFinishSendingPayload(payload, false, null, request.getResponseCode(), responseData); } catch (Exception e) { // TODO: Stop assuming the response is JSON. In fact, just send bytes back, and whatever part of the SDK needs it can try to convert it to the desired format. - ApptentiveLog.e(PAYLOADS, "Exception while handling payload send response"); + ApptentiveLog.e(PAYLOADS, e, "Exception while handling payload send response"); handleFinishSendingPayload(payload, false, null, -1, null); } } From fa774be0dd3be013dba39d26b77c651fda90e049 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 14:30:53 -0700 Subject: [PATCH 401/465] More logging and assertions --- .../android/sdk/conversation/ConversationManager.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index a23d69ffd..24a63360c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -92,6 +92,7 @@ public ConversationManager(Context context, File apptentiveConversationsStorageD .addObserver(NOTIFICATION_APP_ENTERED_FOREGROUND, new ApptentiveNotificationObserver() { @Override public void onReceiveNotification(ApptentiveNotification notification) { + assertMainThread(); if (activeConversation != null && activeConversation.hasActiveState()) { ApptentiveLog.v(CONVERSATION, "App entered foreground notification received. Trying to fetch interactions..."); final Context context = getContext(); @@ -117,6 +118,8 @@ public boolean loadActiveConversation(Context context) { } try { + assertMainThread(); + // resolving metadata conversationMetadata = resolveMetadata(); @@ -585,6 +588,8 @@ private void requestLoggedInConversation(final String token, final LoginCallback return; } + assertMainThread(); + // Check if there is an active conversation if (activeConversation == null) { ApptentiveLog.d(CONVERSATION, "No active conversation. Performing login..."); @@ -598,7 +603,7 @@ public boolean accept(ConversationMetadataItem item) { }); if (conversationItem == null) { - ApptentiveLog.e("No conversation found matching user: '%s'. Logging in as new user.", userId); + ApptentiveLog.w("No conversation found matching user: '%s'. Logging in as new user.", userId); sendLoginRequest(null, userId, token, callback); return; } @@ -624,6 +629,7 @@ public boolean accept(ConversationMetadataItem item) { fetchRequest.addListener(new HttpRequest.Listener() { @Override public void onFinish(HttpRequest request) { + assertMainThread(); assertTrue(activeConversation != null && activeConversation.hasState(ANONYMOUS), "Active conversation is missing or in a wrong state: %s", activeConversation); if (activeConversation != null && activeConversation.hasState(ANONYMOUS)) { @@ -698,6 +704,7 @@ private void handleLoginFinished(final String conversationId, final String userI protected void execute() { assertFalse(isNullOrEmpty(encryptionKey),"Login finished with missing encryption key."); assertFalse(isNullOrEmpty(token), "Login finished with missing token."); + assertMainThread(); try { // if we were previously logged out we might end up with no active conversation @@ -764,6 +771,7 @@ protected void execute() { } private void doLogout() { + assertMainThread(); if (activeConversation != null) { switch (activeConversation.getState()) { case LOGGED_IN: @@ -791,6 +799,7 @@ private void doLogout() { //region Getters/Setters public Conversation getActiveConversation() { + assertMainThread(); return activeConversation; } From 5b3045b03e5ee9c668a296a67df1bb83b5611238 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 14:45:07 -0700 Subject: [PATCH 402/465] Fixed engaging events with no active conversation --- .../main/java/com/apptentive/android/sdk/Apptentive.java | 8 ++++---- .../com/apptentive/android/sdk/ApptentiveInternal.java | 8 ++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index c9aba5559..66f55558a 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -844,7 +844,7 @@ public static int getUnreadMessageCount() { public static void sendAttachmentText(String text) { try { if (!ApptentiveInternal.isConversationActive()) { - ApptentiveLog.i(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); + ApptentiveLog.w(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); return; } Conversation conversation = ApptentiveInternal.getInstance().getConversation(); @@ -874,7 +874,7 @@ public static void sendAttachmentText(String text) { public static void sendAttachmentFile(String uri) { try { if (!ApptentiveInternal.isConversationActive()) { - ApptentiveLog.i(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); + ApptentiveLog.w(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); return; } if (TextUtils.isEmpty(uri)) { @@ -966,7 +966,7 @@ public static void sendAttachmentFile(byte[] content, String mimeType) { public static void sendAttachmentFile(InputStream is, String mimeType) { try { if (!ApptentiveInternal.isConversationActive()) { - ApptentiveLog.i(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); + ApptentiveLog.w(ApptentiveLogTag.MESSAGES, "Can't send attachment: No active Conversation."); return; } if (is == null) { @@ -1078,7 +1078,7 @@ public static synchronized boolean engage(Context context, String event, Map Date: Wed, 28 Jun 2017 14:48:50 -0700 Subject: [PATCH 403/465] Refactoring --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 5ef789d23..477fa14db 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -414,13 +414,13 @@ public ApptentiveHttpClient getApptentiveHttpClient() { } public void onAppLaunch(final Context appContext) { - if (getConversation() != null) { + if (isConversationActive()) { engageInternal(appContext, EventPayload.EventLabel.app__launch.getLabelName()); } } public void onAppExit(final Context appContext) { - if (getConversation() != null) { + if (isConversationActive()) { engageInternal(appContext, EventPayload.EventLabel.app__exit.getLabelName()); } } @@ -897,7 +897,7 @@ static PendingIntent generatePendingIntentFromApptentivePushData(String apptenti // do we have a conversation right now? if (conversation == null) { - ApptentiveLog.i("Can't generate pending intent from Apptentive push data: no active conversation"); + ApptentiveLog.w("Can't generate pending intent from Apptentive push data: no active conversation"); return null; } From 490c9a50bbc7686e9838c63701a8bc4f0549a40a Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 14:49:46 -0700 Subject: [PATCH 404/465] Removed unused code --- .../android/sdk/ApptentiveInternal.java | 26 ------------------- .../android/sdk/comm/ApptentiveClient.java | 20 -------------- 2 files changed, 46 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 477fa14db..7a757f732 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -25,9 +25,7 @@ import android.text.TextUtils; import com.apptentive.android.sdk.Apptentive.LoginCallback; -import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpClient; -import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.conversation.ConversationManager; import com.apptentive.android.sdk.lifecycle.ApptentiveActivityLifecycleCallbacks; @@ -700,30 +698,6 @@ private void invalidateCaches() { config.save(); } - /** - * Fetches the global app configuration from the server and stores the keys into our SharedPreferences. - */ - private void fetchAppConfiguration() { // FIXME: remove unused method - ApptentiveLog.i("Fetching new Configuration task started."); - ApptentiveHttpResponse response = ApptentiveClient.getAppConfiguration(); - try { - Map headers = response.getHeaders(); - if (headers != null) { - String cacheControl = headers.get("Cache-Control"); - Integer cacheSeconds = Util.parseCacheControlHeader(cacheControl); - if (cacheSeconds == null) { - cacheSeconds = Constants.CONFIG_DEFAULT_APP_CONFIG_EXPIRATION_DURATION_SECONDS; - } - ApptentiveLog.d("Caching configuration for %d seconds.", cacheSeconds); - Configuration config = new Configuration(response.getContent()); - config.setConfigurationCacheExpirationMillis(System.currentTimeMillis() + cacheSeconds * 1000); - config.save(); - } - } catch (JSONException e) { - ApptentiveLog.e("Error parsing app configuration from server.", e); - } - } - public IRatingProvider getRatingProvider() { if (ratingProvider == null) { ratingProvider = new GooglePlayRatingProvider(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 20b2b85c1..90929a537 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -40,26 +40,6 @@ public class ApptentiveClient { // private static final String ENDPOINT_RECORDS = ENDPOINT_BASE + "/records"; // private static final String ENDPOINT_SURVEYS_FETCH = ENDPOINT_BASE + "/surveys"; - public static ApptentiveHttpResponse getAppConfiguration() { - final Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation == null) { - throw new IllegalStateException("Conversation is null"); - } - - final String conversationId = conversation.getConversationId(); - if (conversationId == null) { - throw new IllegalStateException("Conversation id is null"); - } - - final String conversationToken = conversation.getConversationToken(); - if (conversationToken == null) { - throw new IllegalStateException("Conversation token is null"); - } - - final String endPoint = StringUtils.format(ENDPOINT_CONFIGURATION, conversationId); - return performHttpRequest(conversationToken, true, endPoint, Method.GET, null); - } - /** * Gets all messages since the message specified by GUID was sent. * From 6b2783df561487de75a90612233e783c79ffa4f6 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 15:09:56 -0700 Subject: [PATCH 405/465] Capture active conversation in UI fragments --- .../android/sdk/ApptentiveInternal.java | 20 +------------------ .../android/sdk/ApptentiveViewActivity.java | 10 +++++++++- .../sdk/conversation/ConversationManager.java | 3 ++- .../fragment/ApptentiveBaseFragment.java | 18 ++++++++++++++--- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 7a757f732..21d80c4cc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -31,11 +31,9 @@ import com.apptentive.android.sdk.lifecycle.ApptentiveActivityLifecycleCallbacks; import com.apptentive.android.sdk.model.Configuration; import com.apptentive.android.sdk.model.EventPayload; -import com.apptentive.android.sdk.model.ExtendedData; import com.apptentive.android.sdk.model.LogoutPayload; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; -import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; import com.apptentive.android.sdk.module.messagecenter.MessageManager; import com.apptentive.android.sdk.module.metric.MetricModule; @@ -79,7 +77,6 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_AUTHENTICATION_FAILED_REASON; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION_ID; -import static com.apptentive.android.sdk.debug.Assert.assertMainThread; import static com.apptentive.android.sdk.debug.Assert.assertNotNull; import static com.apptentive.android.sdk.util.Constants.CONVERSATIONS_DIR; @@ -1067,26 +1064,11 @@ public void onReceiveNotification(ApptentiveNotification notification) { //region Engagement - public static boolean engage(Context context, String vendor, String interaction, String interactionId, String eventName, String data, Map customData, ExtendedData[] extendedData) { - assertMainThread(); - Conversation conversation = getInstance().getConversation(); - assertNotNull(conversation, "Attempted to engage '%s' event without an active conversation", eventName); - return conversation != null && EngagementModule.engage(context, conversation, vendor, interaction, interactionId, eventName, data, customData, extendedData); - } - - public static boolean engageInternal(Context context, String eventName) { - assertMainThread(); + private static boolean engageInternal(Context context, String eventName) { Conversation conversation = getInstance().getConversation(); assertNotNull(conversation, "Attempted to engage '%s' internal event without an active conversation", eventName); return conversation != null && EngagementModule.engageInternal(context, conversation, eventName); } - public static boolean engageInternal(Context context, Interaction interaction, String eventName, String data) { - assertMainThread(); - Conversation conversation = getInstance().getConversation(); - assertNotNull(conversation, "Attempted to engage '%s' internal event without an active conversation", eventName); - return conversation != null && EngagementModule.engageInternal(context, conversation, interaction, eventName, data); - } - //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index eed1b04ce..d2db12107 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -41,6 +41,7 @@ import com.apptentive.android.sdk.util.Util; import static com.apptentive.android.sdk.ApptentiveNotifications.*; +import static com.apptentive.android.sdk.debug.Assert.notNull; public class ApptentiveViewActivity extends ApptentiveBaseActivity implements ApptentiveBaseFragment.OnFragmentTransitionListener { @@ -61,6 +62,12 @@ public class ApptentiveViewActivity extends ApptentiveBaseActivity implements Ap protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + Conversation conversation = notNull(ApptentiveInternal.getInstance().getConversation()); + if (conversation == null) { + finish(); + return; + } + Bundle bundle = FragmentFactory.addDisplayModeToFragmentBundle(getIntent().getExtras()); boolean isInteractionModal = bundle.getBoolean(Constants.FragmentConfigKeys.MODAL); @@ -99,7 +106,7 @@ protected void onCreate(Bundle savedInstanceState) { if (fragmentType == Constants.FragmentTypes.ENGAGE_INTERNAL_EVENT) { String eventName = getIntent().getStringExtra(Constants.FragmentConfigKeys.EXTRA); if (eventName != null) { - ApptentiveInternal.engageInternal(this, eventName); + EngagementModule.engageInternal(this, conversation, eventName); } } finish(); @@ -146,6 +153,7 @@ protected void onCreate(Bundle savedInstanceState) { //current_tab = extra.getInt(SELECTED_TAB_EXTRA_KEY, 0); current_tab = 0; + newFragment.setConversation(conversation); addFragmentToAdapter(newFragment, newFragment.getTitle()); // Get the ViewPager and set it's PagerAdapter so that it can display items diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 24a63360c..d47039cd7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -17,6 +17,7 @@ import com.apptentive.android.sdk.conversation.ConversationMetadata.Filter; import com.apptentive.android.sdk.migration.Migrator; import com.apptentive.android.sdk.model.ConversationTokenRequest; +import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.network.HttpJsonRequest; import com.apptentive.android.sdk.network.HttpRequest; import com.apptentive.android.sdk.notifications.ApptentiveNotification; @@ -776,7 +777,7 @@ private void doLogout() { switch (activeConversation.getState()) { case LOGGED_IN: ApptentiveLog.d("Ending active conversation."); - ApptentiveInternal.engageInternal(getContext(), "logout"); + EngagementModule.engageInternal(getContext(), activeConversation, "logout"); // Post synchronously to ensure logout payload can be sent before destroying the logged in conversation. ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_CONVERSATION_WILL_LOGOUT, ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, activeConversation)); activeConversation.destroy(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java index e551c8b10..910db5de2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java @@ -37,6 +37,7 @@ import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.model.ExtendedData; +import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.util.Constants; @@ -49,6 +50,8 @@ import java.util.List; import java.util.Map; +import static com.apptentive.android.sdk.debug.Assert.assertNotNull; + public abstract class ApptentiveBaseFragment extends DialogFragment implements InteractionManager.InteractionUpdateListener { protected static final String EVENT_NAME_LAUNCH = "launch"; @@ -73,6 +76,7 @@ public abstract class ApptentiveBaseFragment extends Dial protected boolean hasLaunched; protected String sectionTitle; + private Conversation conversation; private OnFragmentTransitionListener onTransitionListener; public interface OnFragmentTransitionListener { @@ -518,7 +522,9 @@ public static void removeFragment(FragmentManager fragmentManager, Fragment frag //region Helpers public boolean engage(String vendor, String interaction, String interactionId, String eventName, String data, Map customData, ExtendedData... extendedData) { - return ApptentiveInternal.engage(getActivity(), vendor, interaction, interactionId, eventName, data, customData, extendedData); + Conversation conversation = getConversation(); + assertNotNull(conversation, "Attempted to engage '%s' event without an active conversation", eventName); + return conversation != null && EngagementModule.engage(getActivity(), conversation, vendor, interaction, interactionId, eventName, data, customData, extendedData); } public boolean engageInternal(String eventName) { @@ -526,11 +532,17 @@ public boolean engageInternal(String eventName) { } public boolean engageInternal(String eventName, String data) { - return ApptentiveInternal.engageInternal(getActivity(), interaction, eventName, data); + Conversation conversation = getConversation(); + assertNotNull(conversation, "Attempted to engage '%s' event without an active conversation", eventName); + return conversation != null && EngagementModule.engageInternal(getActivity(), conversation, interaction, eventName, data); } protected Conversation getConversation() { - return ApptentiveInternal.getInstance().getConversation(); // FIXME: don't use singleton + return conversation; + } + + public void setConversation(Conversation conversation) { + this.conversation = conversation; } //endregion From 218fc628e4f162612e09d6a9b4ced8220c04bf2e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 15:12:25 -0700 Subject: [PATCH 406/465] Refactoring --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 21d80c4cc..c1279ab39 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -1064,8 +1064,8 @@ public void onReceiveNotification(ApptentiveNotification notification) { //region Engagement - private static boolean engageInternal(Context context, String eventName) { - Conversation conversation = getInstance().getConversation(); + private boolean engageInternal(Context context, String eventName) { + Conversation conversation = getConversation(); assertNotNull(conversation, "Attempted to engage '%s' internal event without an active conversation", eventName); return conversation != null && EngagementModule.engageInternal(context, conversation, eventName); } From 8630c0b3c6324077a48b39440daf64ecbb0f1cf8 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 15:17:47 -0700 Subject: [PATCH 407/465] Decoupled UI-fragments from ApptentiveInternal singleton --- .../fragment/MessageCenterFragment.java | 14 +++++++------- .../interaction/fragment/NoteFragment.java | 6 ++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index f7eeb57a2..f66b15297 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -433,7 +433,7 @@ else if (pendingWhoCardName != null || pendingWhoCardEmail != null || pendingWho // Retrieve any saved attachments final SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + Conversation conversation = getConversation(); if (conversation != null && conversation.getMessageCenterPendingAttachments() != null) { String pendingAttachmentsString = conversation.getMessageCenterPendingAttachments(); JSONArray savedAttachmentsJsonArray = null; @@ -746,7 +746,7 @@ public void onComposingViewCreated(MessageComposerHolder composer, final EditTex this.composer = composer; this.composerEditText = composerEditText; - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + Conversation conversation = getConversation(); // Restore composing text editing state, such as cursor position, after rotation if (composingViewSavedState != null) { if (this.composerEditText != null) { @@ -873,7 +873,7 @@ public void onFinishComposing() { compoundMessage.setCustomData(ApptentiveInternal.getInstance().getAndClearCustomData()); compoundMessage.setAssociatedImages(new ArrayList(pendingAttachments)); - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + Conversation conversation = getConversation(); if (conversation != null && conversation.hasActiveState()) { compoundMessage.setSenderId(conversation.getPerson().getId()); } @@ -999,7 +999,7 @@ public void onAttachImage() { } private void setWhoCardAsPreviouslyDisplayed() { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + Conversation conversation = getConversation(); if (conversation == null) { return; } @@ -1007,7 +1007,7 @@ private void setWhoCardAsPreviouslyDisplayed() { } private boolean wasWhoCardAsPreviouslyDisplayed() { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + Conversation conversation = getConversation(); if (conversation == null) { return false; } @@ -1024,7 +1024,7 @@ public void savePendingComposingMessage() { Editable content = getPendingComposingContent(); SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); SharedPreferences.Editor editor = prefs.edit(); - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + Conversation conversation = getConversation(); if (conversation == null) { return; } @@ -1051,7 +1051,7 @@ public void savePendingComposingMessage() { * will clear the pending composing message previously saved in shared preference */ public void clearPendingComposingMessage() { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + Conversation conversation = getConversation(); if (conversation != null) { conversation.setMessageCenterPendingMessage(null); conversation.setMessageCenterPendingAttachments(null); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java index b4a8f3e47..6fd825168 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java @@ -14,11 +14,10 @@ import android.widget.LinearLayout; import android.widget.TextView; -import com.apptentive.android.sdk.ApptentiveInternal; - import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.ApptentiveViewExitType; import com.apptentive.android.sdk.R; +import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.Interactions; @@ -27,7 +26,6 @@ import com.apptentive.android.sdk.module.engagement.interaction.model.common.Action; import com.apptentive.android.sdk.module.engagement.interaction.model.common.Actions; import com.apptentive.android.sdk.module.engagement.interaction.model.common.LaunchInteractionAction; -import com.apptentive.android.sdk.conversation.Conversation; import org.json.JSONException; import org.json.JSONObject; @@ -133,7 +131,7 @@ public void onClick(View view) { Interaction invokedInteraction = null; if (interactionIdToLaunch != null) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + Conversation conversation = getConversation(); if (conversation != null) { String interactionsString = conversation.getInteractions(); if (interactionsString != null) { From a20c36d82e605b26fe3d78077d3332fafbb1041c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 15:22:12 -0700 Subject: [PATCH 408/465] =?UTF-8?q?Added=20a=20few=20FIXME=E2=80=99s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sdk/module/engagement/logic/FieldManager.java | 2 +- .../sdk/util/image/ApptentiveAttachmentLoader.java | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java index 88c9fb5e9..b1c53a1ea 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java @@ -31,7 +31,7 @@ public static Comparable getValue(String query) { } public static Object doGetValue(String query) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); // FIXME: get rid of singleton if (conversation == null) { return null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java index 6f107ec20..3f2c925df 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java @@ -6,12 +6,6 @@ package com.apptentive.android.sdk.util.image; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.concurrent.RejectedExecutionException; - import android.annotation.SuppressLint; import android.graphics.Bitmap; import android.os.AsyncTask; @@ -27,6 +21,12 @@ import com.apptentive.android.sdk.util.task.ApptentiveDownloaderTask; import com.apptentive.android.sdk.util.task.ApptentiveDrawableLoaderTask; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.RejectedExecutionException; + public class ApptentiveAttachmentLoader { public static final int DRAWABLE_DOWNLOAD_TAG = R.id.apptentive_drawable_downloader; @@ -217,7 +217,7 @@ public void doDownload() { try { ApptentiveLog.v("ApptentiveAttachmentLoader doDownload: " + uri); // Conversation token is needed if the download url is a redirect link from an Apptentive endpoint - String conversationToken = ApptentiveInternal.getInstance().getConversation().getConversationToken(); + String conversationToken = ApptentiveInternal.getInstance().getConversation().getConversationToken(); // FIXME: get rid of singleton if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { mDrawableDownloaderTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uri, diskCacheFilePath, conversationToken); } else { From 0cd62bb44f67fbaf441b9c37a2106ca3b51981fc Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 17:03:07 -0700 Subject: [PATCH 409/465] More log statements --- .../java/com/apptentive/android/sdk/Apptentive.java | 10 +++++----- .../android/sdk/conversation/ConversationManager.java | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 66f55558a..02cf0edb0 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -737,7 +737,7 @@ public static boolean showMessageCenter(Context context) { public static boolean showMessageCenter(Context context, Map customData) { try { if (!ApptentiveInternal.isConversationActive()) { - ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "No active Conversation."); + ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "Unable to show message center: no active conversation."); return false; } return ApptentiveInternal.getInstance().showMessageCenterInternal(context, customData); @@ -757,7 +757,7 @@ public static boolean showMessageCenter(Context context, Map cus public static boolean canShowMessageCenter() { try { if (!ApptentiveInternal.isConversationActive()) { - ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "No active Conversation."); + ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "Unable to show message center: no active conversation."); return false; } return ApptentiveInternal.getInstance().canShowMessageCenterInternal(ApptentiveInternal.getInstance().getConversation()); @@ -782,7 +782,7 @@ public static boolean canShowMessageCenter() { public static void setUnreadMessagesListener(UnreadMessagesListener listener) { try { if (!ApptentiveInternal.isConversationActive()) { - ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "No active Conversation."); + ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "Unable to set unread messages listener: no active conversation."); return; } ApptentiveInternal.getInstance().getMessageManager().setHostUnreadMessagesListener(listener); @@ -802,7 +802,7 @@ public static void setUnreadMessagesListener(UnreadMessagesListener listener) { public static void addUnreadMessagesListener(UnreadMessagesListener listener) { try { if (!ApptentiveInternal.isConversationActive()) { - ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "No active Conversation."); + ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "Unable to add unread messages listener: no active conversation."); return; } Conversation conversation = ApptentiveInternal.getInstance().getConversation(); @@ -823,7 +823,7 @@ public static void addUnreadMessagesListener(UnreadMessagesListener listener) { public static int getUnreadMessageCount() { try { if (!ApptentiveInternal.isConversationActive()) { - ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "No active Conversation."); + ApptentiveLog.v(ApptentiveLogTag.MESSAGES, "Unable to get unread message count: no active conversation."); return 0; } Conversation conversation = ApptentiveInternal.getInstance().getConversation(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index d47039cd7..4099e2e3d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -122,9 +122,11 @@ public boolean loadActiveConversation(Context context) { assertMainThread(); // resolving metadata + ApptentiveLog.vv(CONVERSATION, "Resolving metadata..."); conversationMetadata = resolveMetadata(); // attempt to load existing conversation + ApptentiveLog.vv(CONVERSATION, "Loading active conversation..."); activeConversation = loadActiveConversationGuarded(); if (activeConversation != null) { @@ -203,6 +205,7 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali anonymousConversation.setState(LEGACY_PENDING); anonymousConversation.setConversationToken(legacyConversationToken); + ApptentiveLog.v("Fetching legacy conversation..."); fetchLegacyConversation(anonymousConversation) // remove legacy key when request is finished .addListener(new HttpRequest.Adapter() { From 1b71369a485ed5c5c596b63f91dddbc06d7e552e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 28 Jun 2017 17:19:23 -0700 Subject: [PATCH 410/465] =?UTF-8?q?Moved=20Conversation=E2=80=99s=20local?= =?UTF-8?q?=20identifier=20to=20conversation=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/sdk/conversation/Conversation.java | 10 +++------- .../android/sdk/conversation/ConversationData.java | 9 ++++++++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index b741c4280..9a0a6e11a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -44,9 +44,9 @@ import org.json.JSONException; import java.io.File; -import java.util.UUID; import static com.apptentive.android.sdk.debug.Assert.assertFail; +import static com.apptentive.android.sdk.debug.Assert.notNull; import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; import static com.apptentive.android.sdk.ApptentiveLogTag.*; import static com.apptentive.android.sdk.conversation.ConversationState.*; @@ -54,9 +54,6 @@ public class Conversation implements DataChangedListener, Destroyable { - /** Conversation 'local' identifier */ - private final String localIdentifier; - /** * Conversation data for this class to manage */ @@ -135,7 +132,6 @@ public Conversation(File conversationDataFile, File conversationMessagesFile) { throw new IllegalArgumentException("Messages file is null"); } - this.localIdentifier = UUID.randomUUID().toString(); this.conversationDataFile = conversationDataFile; this.conversationMessagesFile = conversationMessagesFile; @@ -149,7 +145,7 @@ public Conversation(File conversationDataFile, File conversationMessagesFile) { //region Payloads public void addPayload(Payload payload) { - payload.setLocalConversationIdentifier(getLocalIdentifier()); + payload.setLocalConversationIdentifier(notNull(getLocalIdentifier())); payload.setConversationId(getConversationId()); payload.setToken(getConversationToken()); payload.setEncryptionKey(getEncryptionKey()); @@ -351,7 +347,7 @@ public void destroy() { //region Getters & Setters public String getLocalIdentifier() { - return localIdentifier; + return conversationData.getLocalIdentifier(); } public ConversationState getState() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java index 6745ba320..dd11dbf5d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java @@ -17,10 +17,12 @@ import com.apptentive.android.sdk.storage.VersionHistory; import com.apptentive.android.sdk.util.StringUtils; +import java.util.UUID; + public class ConversationData implements Saveable, DataChangedListener { private static final long serialVersionUID = 1L; - + private String localIdentifier; private String conversationToken; private String conversationId; private Device device; @@ -41,6 +43,7 @@ public class ConversationData implements Saveable, DataChangedListener { private double interactionExpiration; public ConversationData() { + this.localIdentifier = UUID.randomUUID().toString(); this.device = new Device(); this.person = new Person(); this.sdk = new Sdk(); @@ -77,6 +80,10 @@ public void onDataChanged() { //region Getters & Setters + public String getLocalIdentifier() { + return localIdentifier; + } + public String getConversationToken() { return conversationToken; } From 25d3936074cdffea11f4e425e36e2e7ccd31b495 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 29 Jun 2017 11:54:00 -0700 Subject: [PATCH 411/465] 1. Update travis config to upload snapshot builds, and send slack notifications. 2. Update build tools to 25.0.3. 3. Use the new Android Maven repo for resolving support library dependencies. --- .travis.yml | 26 ++++++++++++------------- apptentive/build.gradle | 16 ++++++--------- build.gradle | 11 ++++++++++- samples/apptentive-example/build.gradle | 2 +- tests/test-app/build.gradle | 6 +++--- 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/.travis.yml b/.travis.yml index 704cee48a..7fea4a2f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,43 +1,41 @@ language: android - +env: + global: + - SNAPSHOT_REPOSITORY_USERNAME=travis + - SNAPSHOT_REPOSITORY_URL=http://54.183.158.246:8081/artifactory/apptentive-snapshots + - secure: pc2twMw60say0ASdXiJiRAD6tx9Qy82DIMw83qPijB2wyVHLpLbjptqBFyTYy4+JYthZ8xcB5Yretiv//AQS4wdDPsJNwOKUGXamm8IBx+1wnhG/R/ROz67Ibj4XWHIX24GaKN/MD8tCN9VPdeNEL1jysSEVxqqsvOGBsxitAyI= jdk: - oraclejdk8 - before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ - cache: directories: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ - $HOME/.android/build-cache - android: - components: - tools - tools - platform-tools - - build-tools-25.0.2 + - build-tools-25.0.3 - android-24 - android-25 - - extra-google-google_play_services - extra-google-m2repository - - extra-android-m2repository - addon-google_apis-google-25 - - sys-img-armeabi-v7a-android-19 - install: true - -# Emulator Management: Create, Start and Wait before_script: - echo no | android create avd --force -n test -t android-19 --abi armeabi-v7a - emulator -avd test -no-audio -no-window & - android-wait-for-emulator - adb shell input keyevent 82 & - script: - - ./gradlew :apptentive:test -i \ No newline at end of file + - ./gradlew :apptentive:test -i +after_script: + - ./gradlew :apptentive:uploadArchives +notifications: + slack: + secure: HejMl0EUociwGu+5djx95snbS+m/Yw8DseQKCSqeyWvMQLrAy8bi9oa89mZvXnvjqSVY3kKRZgJncEkQdIe9c7xwgNA9QYLkc7UVbXqga291HMoNnWaIMewD2ervbzM4aBQAHnkDr+GsXgb7+1YdOktIn8dA7jdIuB90ar4So9U= diff --git a/apptentive/build.gradle b/apptentive/build.gradle index 6aad3a927..80d4293c7 100644 --- a/apptentive/build.gradle +++ b/apptentive/build.gradle @@ -2,10 +2,6 @@ init() apply plugin: 'com.android.library' -repositories { - jcenter() -} - dependencies { compile 'com.android.support:support-v4:24.2.1' compile 'com.android.support:appcompat-v7:24.2.1' @@ -25,13 +21,13 @@ dependencies { android { compileSdkVersion 25 - buildToolsVersion '25.0.2' + buildToolsVersion '25.0.3' defaultConfig { minSdkVersion 14 targetSdkVersion 25 // BUILD_NUMBER is provided by Jenkins. Default to 1 in dev builds. - versionCode System.getenv("BUILD_NUMBER") as Integer ?: 1 + versionCode System.getenv("BUILD_NUMBER") as Integer ?: System.getenv("TRAVIS_BUILD_NUMBER") as Integer ?: 1 versionName project.version consumerProguardFiles 'consumer-proguard-rules.pro' testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -72,14 +68,14 @@ def getReleaseRepositoryUrl() { } def getSnapshotRepositoryUrl() { - return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL : "" + return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL : System.getenv("SNAPSHOT_REPOSITORY_URL") } def getRepositoryUsername() { if (isReleaseBuild()) { return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" } else { - return hasProperty('SNAPSHOT_REPOSITORY_USERNAME') ? SNAPSHOT_REPOSITORY_USERNAME : "" + return hasProperty('SNAPSHOT_REPOSITORY_USERNAME') ? SNAPSHOT_REPOSITORY_USERNAME : System.getenv("SNAPSHOT_REPOSITORY_USERNAME") } } @@ -87,7 +83,7 @@ def getRepositoryPassword() { if (isReleaseBuild()) { return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" } else { - return hasProperty('SNAPSHOT_REPOSITORY_PASSWORD') ? SNAPSHOT_REPOSITORY_PASSWORD : "" + return hasProperty('SNAPSHOT_REPOSITORY_PASSWORD') ? SNAPSHOT_REPOSITORY_PASSWORD : System.getenv("SNAPSHOT_REPOSITORY_PASSWORD") } } @@ -162,7 +158,7 @@ task androidJavadocs(type: Javadoc) { classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) } -androidJavadocs << { +androidJavadocs.doLast { println "Javadocs written to: " + destinationDir } diff --git a/build.gradle b/build.gradle index 4d72013e5..458f18515 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,15 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.android.tools.build:gradle:2.3.2' + } +} + +allprojects { + repositories { + jcenter() + maven { + url "https://maven.google.com" + } } } \ No newline at end of file diff --git a/samples/apptentive-example/build.gradle b/samples/apptentive-example/build.gradle index dc277ef7c..0461b7ee2 100644 --- a/samples/apptentive-example/build.gradle +++ b/samples/apptentive-example/build.gradle @@ -20,7 +20,7 @@ dependencies { android { compileSdkVersion 25 - buildToolsVersion '25.0.2' + buildToolsVersion '25.0.3' defaultConfig { minSdkVersion 14 diff --git a/tests/test-app/build.gradle b/tests/test-app/build.gradle index 4e1a8545f..ac5d1cc79 100644 --- a/tests/test-app/build.gradle +++ b/tests/test-app/build.gradle @@ -5,13 +5,13 @@ repositories { } android { - compileSdkVersion 24 + compileSdkVersion 25 - buildToolsVersion '25.0.2' + buildToolsVersion '25.0.3' defaultConfig { minSdkVersion 14 - targetSdkVersion 24 + targetSdkVersion 25 versionCode 4 versionName "2.0" } From a94e486c9f1faaaba5e441e0720574beb900d713 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 29 Jun 2017 15:46:34 -0700 Subject: [PATCH 412/465] Wrap a public API call with try..catch block --- .../apptentive/android/sdk/Apptentive.java | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 02cf0edb0..d488987ac 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -1064,25 +1064,30 @@ public static synchronized boolean engage(Context context, String event, Map customData, ExtendedData... extendedData) { - if (StringUtils.isNullOrEmpty(event)) { - ApptentiveLog.e("Unable to engage event: name is null or empty"); // TODO: throw an IllegalArgumentException instead? - return false; - } - if (context == null) { - ApptentiveLog.e("Unable to engage '%s' event: context is null", event); // TODO: throw an IllegalArgumentException instead? - return false; - } - if (!ApptentiveInternal.isApptentiveRegistered()) { - ApptentiveLog.e("Unable to engage '%s' event: Apptentive SDK is not initialized", event); - return false; - } - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation == null) { - ApptentiveLog.w("Unable to engage '%s' event: no active conversation", event); + try { + if (StringUtils.isNullOrEmpty(event)) { + ApptentiveLog.e("Unable to engage event: name is null or empty"); // TODO: throw an IllegalArgumentException instead? + return false; + } + if (context == null) { + ApptentiveLog.e("Unable to engage '%s' event: context is null", event); // TODO: throw an IllegalArgumentException instead? + return false; + } + if (!ApptentiveInternal.isApptentiveRegistered()) { + ApptentiveLog.e("Unable to engage '%s' event: Apptentive SDK is not initialized", event); + return false; + } + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation == null) { + ApptentiveLog.w("Unable to engage '%s' event: no active conversation", event); + return false; + } + + return EngagementModule.engage(context, conversation, "local", "app", null, event, null, customData, extendedData); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while engaging '%s' event", event); return false; } - - return EngagementModule.engage(context, conversation, "local", "app", null, event, null, customData, extendedData); } /** From 844733305d0035b41ffc5834522f65cb2b6b9326 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 29 Jun 2017 16:46:43 -0700 Subject: [PATCH 413/465] Upgrade to Gradle plugin 2.3.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 458f18515..cb6149a15 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:2.3.3' } } From 71346afba80e5e063233a45cecb5747519c4cfc6 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 29 Jun 2017 17:30:32 -0700 Subject: [PATCH 414/465] Send an internal Apptentive notification when interactions are fetched. Can be used by tests to know when they can reliably run interactions. --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 2 ++ .../com/apptentive/android/sdk/ApptentiveNotifications.java | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index c1279ab39..9df3af1bd 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -72,6 +72,7 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_APP_ENTERED_FOREGROUND; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_AUTHENTICATION_FAILED; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_CONVERSATION_WILL_LOGOUT; +import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_INTERACTIONS_FETCHED; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_INTERACTIONS_SHOULD_DISMISS; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_ACTIVITY; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_AUTHENTICATION_FAILED_REASON; @@ -963,6 +964,7 @@ public void resetSdkState() { } public void notifyInteractionUpdated(boolean successful) { + ApptentiveNotificationCenter.defaultCenter().postNotification(NOTIFICATION_INTERACTIONS_FETCHED); Iterator it = interactionUpdateListeners.iterator(); while (it.hasNext()) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java index 9922c5ad6..8a5e315df 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveNotifications.java @@ -58,6 +58,11 @@ public class ApptentiveNotifications { */ public static final String NOTIFICATION_AUTHENTICATION_FAILED = "NOTIFICATION_AUTHENTICATION_FAILED"; // { conversationId : String, authenticationFailedReason: AuthenticationFailedReason } + /** + * Sent when interactions are fetched for any conversation. Used right now so espresso tests know when they can run. + */ + public static final String NOTIFICATION_INTERACTIONS_FETCHED = "NOTIFICATION_INTERACTIONS_FETCHED"; + // keys public static final String NOTIFICATION_KEY_SUCCESSFUL = "successful"; public static final String NOTIFICATION_KEY_ACTIVITY = "activity"; From d02c9bb49f6f32c9161e7e3f66157eb4f9637c98 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Fri, 30 Jun 2017 11:52:03 -0700 Subject: [PATCH 415/465] Update travis to only upload SNAPSHOT on commits to `develop` branch. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7fea4a2f2..75477fc9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ before_script: script: - ./gradlew :apptentive:test -i after_script: - - ./gradlew :apptentive:uploadArchives + - if [ $TRAVIS_BRANCH = 'develop' ]; ./gradlew :apptentive:uploadArchives; fi notifications: slack: secure: HejMl0EUociwGu+5djx95snbS+m/Yw8DseQKCSqeyWvMQLrAy8bi9oa89mZvXnvjqSVY3kKRZgJncEkQdIe9c7xwgNA9QYLkc7UVbXqga291HMoNnWaIMewD2ervbzM4aBQAHnkDr+GsXgb7+1YdOktIn8dA7jdIuB90ar4So9U= From 3329431c86c5643c7c6f3bb9e4546ae0a3749438 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Sat, 1 Jul 2017 15:48:04 -0700 Subject: [PATCH 416/465] Update SDK to remove old style internl logging methods in favor of new style. --- .../apptentive/android/sdk/Apptentive.java | 34 +++++++++---------- .../android/sdk/ApptentiveInternal.java | 4 +-- .../apptentive/android/sdk/ApptentiveLog.java | 25 -------------- .../android/sdk/ApptentiveViewActivity.java | 4 +-- .../android/sdk/comm/ApptentiveClient.java | 10 +++--- .../android/sdk/migration/Migrator.java | 12 +++---- .../migration/v4_0_0/VersionHistoryStore.java | 4 +-- .../android/sdk/model/ApptentiveMessage.java | 4 +-- .../android/sdk/model/Configuration.java | 4 +-- .../android/sdk/model/EventPayload.java | 6 ++-- .../sdk/model/SurveyResponsePayload.java | 2 +- .../module/engagement/EngagementModule.java | 2 +- .../fragment/ApptentiveBaseFragment.java | 4 +-- .../fragment/MessageCenterFragment.java | 4 +-- .../fragment/NavigateToLinkFragment.java | 4 +-- .../interaction/fragment/NoteFragment.java | 4 +-- .../fragment/UpgradeMessageFragment.java | 2 +- .../interaction/model/Interaction.java | 2 +- .../model/InteractionCriteria.java | 4 +-- .../model/InteractionManifest.java | 4 +-- .../interaction/model/Interactions.java | 2 +- .../interaction/model/common/Actions.java | 2 +- .../survey/MultichoiceSurveyQuestionView.java | 2 +- .../view/survey/SurveyQuestionChoice.java | 2 +- .../module/engagement/logic/FieldManager.java | 2 +- .../module/messagecenter/MessageManager.java | 8 ++--- .../messagecenter/model/MessageFactory.java | 2 +- .../view/ApptentiveAvatarView.java | 6 ++-- .../sdk/module/metric/MetricModule.java | 2 +- .../sdk/storage/ApptentiveDatabaseHelper.java | 2 +- .../android/sdk/util/JsonDiffer.java | 6 ++-- .../com/apptentive/android/sdk/util/Util.java | 20 +++++------ .../android/sdk/util/image/ImageUtil.java | 6 ++-- .../util/task/ApptentiveDownloaderTask.java | 12 +++---- .../sdk/view/ApptentiveAlertDialog.java | 2 +- 35 files changed, 95 insertions(+), 120 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index d488987ac..1966bac7d 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -742,7 +742,7 @@ public static boolean showMessageCenter(Context context, Map cus } return ApptentiveInternal.getInstance().showMessageCenterInternal(context, customData); } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.showMessageCenter()", e); + ApptentiveLog.w(e, "Error in Apptentive.showMessageCenter()"); MetricModule.sendError(e, null, null); } return false; @@ -762,7 +762,7 @@ public static boolean canShowMessageCenter() { } return ApptentiveInternal.getInstance().canShowMessageCenterInternal(ApptentiveInternal.getInstance().getConversation()); } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.canShowMessageCenter()", e); + ApptentiveLog.w(e, "Error in Apptentive.canShowMessageCenter()"); MetricModule.sendError(e, null, null); } return false; @@ -787,7 +787,7 @@ public static void setUnreadMessagesListener(UnreadMessagesListener listener) { } ApptentiveInternal.getInstance().getMessageManager().setHostUnreadMessagesListener(listener); } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.setUnreadMessagesListener()", e); + ApptentiveLog.w(e, "Error in Apptentive.setUnreadMessagesListener()"); MetricModule.sendError(e, null, null); } } @@ -810,7 +810,7 @@ public static void addUnreadMessagesListener(UnreadMessagesListener listener) { conversation.getMessageManager().addHostUnreadMessagesListener(listener); } } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.addUnreadMessagesListener()", e); + ApptentiveLog.w(e, "Error in Apptentive.addUnreadMessagesListener()"); MetricModule.sendError(e, null, null); } } @@ -829,7 +829,7 @@ public static int getUnreadMessageCount() { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); return conversation.getMessageManager().getUnreadMessageCount(); } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.getUnreadMessageCount()", e); + ApptentiveLog.w(e, "Error in Apptentive.getUnreadMessageCount()"); MetricModule.sendError(e, null, null); } return 0; @@ -859,7 +859,7 @@ public static void sendAttachmentText(String text) { mgr.sendMessage(message); } } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.sendAttachmentText(String)", e); + ApptentiveLog.w(e, "Error in Apptentive.sendAttachmentText(String)"); MetricModule.sendError(e, null, null); } } @@ -923,7 +923,7 @@ public static void sendAttachmentFile(String uri) { mgr.sendMessage(message); } } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.sendAttachmentFile(String)", e); + ApptentiveLog.w(e, "Error in Apptentive.sendAttachmentFile(String)"); MetricModule.sendError(e, null, null); } } @@ -950,7 +950,7 @@ public static void sendAttachmentFile(byte[] content, String mimeType) { Util.ensureClosed(is); } } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.sendAttachmentFile(byte[], String)", e); + ApptentiveLog.w(e, "Error in Apptentive.sendAttachmentFile(byte[], String)"); MetricModule.sendError(e, null, null); } } @@ -998,7 +998,7 @@ public static void sendAttachmentFile(InputStream is, String mimeType) { message.setAssociatedFiles(attachmentStoredFiles); ApptentiveInternal.getInstance().getMessageManager().sendMessage(message); } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.sendAttachmentFile(InputStream, String)", e); + ApptentiveLog.w(e, "Error in Apptentive.sendAttachmentFile(InputStream, String)"); MetricModule.sendError(e, null, null); } } @@ -1118,7 +1118,7 @@ public static synchronized boolean canShowInteraction(String event) { return EngagementModule.canShowInteraction(ApptentiveInternal.getInstance().getConversation(), "app", event, "local"); } } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.canShowInteraction()", e); + ApptentiveLog.w(e, "Error in Apptentive.canShowInteraction()"); MetricModule.sendError(e, null, null); } return false; @@ -1140,7 +1140,7 @@ public static void setOnSurveyFinishedListener(OnSurveyFinishedListener listener internal.setOnSurveyFinishedListener(listener); } } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.setOnSurveyFinishedListener()", e); + ApptentiveLog.w(e, "Error in Apptentive.setOnSurveyFinishedListener()"); MetricModule.sendError(e, null, null); } } @@ -1175,7 +1175,7 @@ public static void login(String token, LoginCallback callback) { ApptentiveInternal.getInstance().login(token, callback); } } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.login()", e); + ApptentiveLog.w(e, "Error in Apptentive.login()"); MetricModule.sendError(e, null, null); } } @@ -1207,7 +1207,7 @@ public static void logout() { instance.logout(); } } catch (Exception e) { - ApptentiveLog.e("Exception in Apptentive.logout()", e); + ApptentiveLog.e(e, "Exception in Apptentive.logout()"); MetricModule.sendError(e, null, null); } } @@ -1231,7 +1231,7 @@ public static void setAuthenticationFailureListener(AuthenticationFailedListener } ApptentiveInternal.getInstance().setAuthenticationFailureListener(listener); } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.setUnreadMessagesListener()", e); + ApptentiveLog.w(e, "Error in Apptentive.setUnreadMessagesListener()"); MetricModule.sendError(e, null, null); } } @@ -1243,7 +1243,7 @@ public static void clearAuthenticationFailureListener() { } ApptentiveInternal.getInstance().setAuthenticationFailureListener(null); } catch (Exception e) { - ApptentiveLog.w("Error in Apptentive.clearUnreadMessagesListener()", e); + ApptentiveLog.w(e, "Error in Apptentive.clearUnreadMessagesListener()"); MetricModule.sendError(e, null, null); } } @@ -1408,7 +1408,7 @@ public void toJsonObject() { ret.put(KEY_TYPE, TYPE); ret.put(TYPE, version); } catch (JSONException e) { - ApptentiveLog.e("Error creating Apptentive.Version.", e); + ApptentiveLog.e(e, "Error creating Apptentive.Version."); } } @@ -1485,7 +1485,7 @@ public JSONObject toJSONObject() { ret.put(KEY_TYPE, TYPE); ret.put(SEC, sec); } catch (JSONException e) { - ApptentiveLog.e("Error creating Apptentive.DateTime.", e); + ApptentiveLog.e(e, "Error creating Apptentive.DateTime."); } return ret; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 9df3af1bd..2b2b99cff 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -575,7 +575,7 @@ private boolean start() { } } catch (Exception e) { - ApptentiveLog.e("Unexpected error while reading application or package info.", e); + ApptentiveLog.e(e, "Unexpected error while reading application or package info."); bRet = false; } @@ -898,7 +898,7 @@ static PendingIntent generatePendingIntentFromApptentivePushData(String apptenti ApptentiveLog.w("Unknown Apptentive push notification action: \"%s\"", action.name()); } } catch (JSONException e) { - ApptentiveLog.e("Error parsing JSON from push notification.", e); + ApptentiveLog.e(e, "Error parsing JSON from push notification."); MetricModule.sendError(e, "Parsing Apptentive Push", apptentivePushData); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java index 1f937e112..76ad854e0 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveLog.java @@ -187,31 +187,6 @@ public static void a(Throwable throwable, String message, Object... args){ doLog(Level.ASSERT, null, throwable, message, args); } - //region TODO: Delete these: - public static void vv(String message, Throwable throwable, Object... args){ - doLog(Level.VERY_VERBOSE, null, throwable, message, args); - } - - public static void v(String message, Throwable throwable, Object... args){ - doLog(Level.VERBOSE, null, throwable, message, args); - } - public static void d(String message, Throwable throwable, Object... args){ - doLog(Level.DEBUG, null, throwable, message, args); - } - public static void i(String message, Throwable throwable, Object... args){ - doLog(Level.INFO, null, throwable, message, args); - } - public static void w(String message, Throwable throwable, Object... args){ - doLog(Level.WARN, null, throwable, message, args); - } - public static void e(String message, Throwable throwable, Object... args){ - doLog(Level.ERROR, null, throwable, message, args); - } - public static void a(String message, Throwable throwable, Object... args){ - doLog(Level.ASSERT, null, throwable, message, args); - } - //endregion - public enum Level { VERY_VERBOSE(1, Log.VERBOSE), VERBOSE(Log.VERBOSE, Log.VERBOSE), diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index d2db12107..f66f7ddd7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -115,7 +115,7 @@ protected void onCreate(Bundle savedInstanceState) { } } catch (Exception e) { - ApptentiveLog.e("Error creating ApptentiveViewActivity.", e); + ApptentiveLog.e(e, "Error creating ApptentiveViewActivity."); MetricModule.sendError(e, null, null); } @@ -298,7 +298,7 @@ private void applyApptentiveTheme(boolean isModalInteraction) { setTaskDescription(taskDes); } } catch (Exception e) { - ApptentiveLog.e("Error apply Apptentive Theme.", e); + ApptentiveLog.e(e, "Error apply Apptentive Theme."); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 90929a537..ae156ef60 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -157,19 +157,19 @@ private static ApptentiveHttpResponse performHttpRequest(String authToken, boole ApptentiveLog.w("Response: %s", ret.getContent()); } } catch (IllegalArgumentException e) { - ApptentiveLog.w("Error communicating with server.", e); + ApptentiveLog.w(e, "Error communicating with server."); } catch (SocketTimeoutException e) { - ApptentiveLog.w("Timeout communicating with server.", e); + ApptentiveLog.w(e, "Timeout communicating with server."); } catch (final MalformedURLException e) { - ApptentiveLog.w("MalformedUrlException", e); + ApptentiveLog.w(e, "MalformedUrlException"); } catch (final IOException e) { - ApptentiveLog.w("IOException", e); + ApptentiveLog.w(e, "IOException"); // Read the error response. try { ret.setContent(getErrorResponse(connection, ret.isZipped())); ApptentiveLog.w("Response: " + ret.getContent()); } catch (IOException ex) { - ApptentiveLog.w("Can't read error stream.", ex); + ApptentiveLog.w(ex, "Can't read error stream."); } } return ret; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java index c99e7fe19..7f4efc3c8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/Migrator.java @@ -145,7 +145,7 @@ private void migrateDevice() { conversation.setDevice(device); } } catch (Exception e) { - ApptentiveLog.e("Error migrating Device.", e); + ApptentiveLog.e(e, "Error migrating Device."); } } @@ -164,7 +164,7 @@ private void migrateSdk() { sdk.setAuthorEmail(sdkOld.getAuthorEmail()); conversation.setSdk(sdk); } catch (Exception e) { - ApptentiveLog.e("Error migrating Sdk.", e); + ApptentiveLog.e(e, "Error migrating Sdk."); } } } @@ -186,7 +186,7 @@ private void migrateAppRelease() { appRelease.setVersionName(appReleaseOld.getVersionName()); conversation.setAppRelease(appRelease); } catch (Exception e) { - ApptentiveLog.e("Error migrating AppRelease.", e); + ApptentiveLog.e(e, "Error migrating AppRelease."); } } } @@ -226,7 +226,7 @@ private void migratePerson() { } conversation.setPerson(person); } catch (Exception e) { - ApptentiveLog.e("Error migrating Person.", e); + ApptentiveLog.e(e, "Error migrating Person."); } } } @@ -250,7 +250,7 @@ private void migrateVersionHistory() { } } } catch (Exception e) { - ApptentiveLog.w("Error migrating VersionHistory entries V2 to V3.", e); + ApptentiveLog.w(e, "Error migrating VersionHistory entries V2 to V3."); } } @@ -268,7 +268,7 @@ private void migrateEventData() { eventData.setInteractions(migratedInteractions); } } catch (Exception e) { - ApptentiveLog.w("Error migrating Event Data.", e); + ApptentiveLog.w(e, "Error migrating Event Data."); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/VersionHistoryStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/VersionHistoryStore.java index b15ccfb87..285940586 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/VersionHistoryStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/migration/v4_0_0/VersionHistoryStore.java @@ -55,7 +55,7 @@ private static void ensureLoaded() { versionHistoryEntries.add(entry); } } catch (Exception e) { - ApptentiveLog.w("Error loading VersionHistoryStore.", e); + ApptentiveLog.w(e, "Error loading VersionHistoryStore."); } } } @@ -87,7 +87,7 @@ public static synchronized void updateVersionHistory(Integer newVersionCode, Str save(); } } catch (Exception e) { - ApptentiveLog.w("Error updating VersionHistoryStore.", e); + ApptentiveLog.w(e, "Error updating VersionHistoryStore."); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java index 984a32710..876e46336 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ApptentiveMessage.java @@ -108,7 +108,7 @@ public void setCustomData(Map customData) { } put(KEY_CUSTOM_DATA, customDataJson); } catch (JSONException e) { - ApptentiveLog.e("Exception setting ApptentiveMessage's %s field.", e, KEY_CUSTOM_DATA); + ApptentiveLog.e(e, "Exception setting ApptentiveMessage's %s field.", KEY_CUSTOM_DATA); } } @@ -143,7 +143,7 @@ public void setSenderId(String senderId) { } sender.put(KEY_SENDER_ID, senderId); } catch (JSONException e) { - ApptentiveLog.e("Exception setting ApptentiveMessage's %s field.", e, KEY_SENDER_ID); + ApptentiveLog.e(e, "Exception setting ApptentiveMessage's %s field.", KEY_SENDER_ID); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Configuration.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Configuration.java index 77dc26f4e..b5edb2f07 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Configuration.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Configuration.java @@ -62,7 +62,7 @@ public static Configuration load(SharedPreferences prefs) { return new Configuration(json); } } catch (JSONException e) { - ApptentiveLog.e("Error loading Configuration from SharedPreferences.", e); + ApptentiveLog.e(e, "Error loading Configuration from SharedPreferences."); } return new Configuration(); } @@ -167,7 +167,7 @@ public boolean isHideBranding(Context context) { Bundle metaData = ai.metaData; return metaData.getBoolean(Constants.MANIFEST_KEY_INITIALLY_HIDE_BRANDING, Constants.CONFIG_DEFAULT_HIDE_BRANDING); } catch (Exception e) { - ApptentiveLog.w("Unexpected error while reading %s manifest setting.", e, Constants.MANIFEST_KEY_INITIALLY_HIDE_BRANDING); + ApptentiveLog.w(e, "Unexpected error while reading %s manifest setting.", Constants.MANIFEST_KEY_INITIALLY_HIDE_BRANDING); } return Constants.CONFIG_DEFAULT_HIDE_BRANDING; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index c44ec684c..93c3340e2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -51,7 +51,7 @@ public EventPayload(String label, Map data) { put(KEY_DATA, dataObject); } } catch (JSONException e) { - ApptentiveLog.e("Unable to construct Event.", e); + ApptentiveLog.e(e, "Unable to construct Event."); } } @@ -79,7 +79,7 @@ public EventPayload(String label, String interactionId, String data, Map data) { dataObject.put(key, data.get(key)); } } catch (JSONException e) { - ApptentiveLog.e("Unable to add data to Event.", e); + ApptentiveLog.e(e, "Unable to add data to Event."); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index 9cdb68e71..e5865cae0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -33,7 +33,7 @@ public SurveyResponsePayload(SurveyInteraction definition, Map a put(KEY_SURVEY_ANSWERS, answersJson); } catch (JSONException e) { - ApptentiveLog.e("Unable to construct survey payload.", e); + ApptentiveLog.e(e, "Unable to construct survey payload."); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java index c8b03ef63..f60256990 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/EngagementModule.java @@ -70,7 +70,7 @@ public static synchronized boolean engage(Context context, Conversation conversa conversation.addPayload(new EventPayload(eventLabel, interactionId, data, customData, extendedData)); return doEngage(conversation, context, eventLabel); } catch (Exception e) { - ApptentiveLog.w("Error in engage()", e); + ApptentiveLog.w(e, "Error in engage()"); MetricModule.sendError(e, null, null); } return false; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java index 910db5de2..6c8aa1b54 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java @@ -154,7 +154,7 @@ public void onAttach(Context context) { updateHosts(getFragmentManager(), (FragmentHostCallback) hostField.get(getFragmentManager())); } catch (Exception e) { - ApptentiveLog.w(e.getMessage(), e); + ApptentiveLog.w(e, e.getMessage()); } } else { //If the child fragment manager has not been retained yet @@ -179,7 +179,7 @@ private void updateHosts(FragmentManager fragmentManager, FragmentHostCallback c mHostField.setAccessible(true); mHostField.set(fragment, currentHost); } catch (Exception e) { - ApptentiveLog.w(e.getMessage(), e); + ApptentiveLog.w(e, e.getMessage()); } if (fragment.getChildFragmentManager() != null) { updateHosts(fragment.getChildFragmentManager(), currentHost); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index f66b15297..c98d98aa4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -693,7 +693,7 @@ public void openNonImageAttachment(final ImageItem image) { ApptentiveLog.d("Cannot open file attachment"); } } catch (Exception e) { - ApptentiveLog.e("Error loading attachment", e); + ApptentiveLog.e(e, "Error loading attachment"); } } @@ -717,7 +717,7 @@ public void showAttachmentDialog(final ImageItem image) { dialog.show(ft, DIALOG_IMAGE_PREVIEW); } catch (Exception e) { - ApptentiveLog.e("Error loading attachment preview.", e); + ApptentiveLog.e(e, "Error loading attachment preview."); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NavigateToLinkFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NavigateToLinkFragment.java index 7a98401cb..8525f7707 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NavigateToLinkFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NavigateToLinkFragment.java @@ -62,7 +62,7 @@ public void onCreate(Bundle savedInstanceState) { success = true; } } catch (ActivityNotFoundException e) { - ApptentiveLog.w("NavigateToLink Error: ", e); + ApptentiveLog.w(e, "NavigateToLink Error: "); } finally { JSONObject data = new JSONObject(); try { @@ -70,7 +70,7 @@ public void onCreate(Bundle savedInstanceState) { data.put(NavigateToLinkInteraction.KEY_TARGET, interaction.getTarget().lowercaseName()); data.put(NavigateToLinkInteraction.EVENT_KEY_SUCCESS, success); } catch (JSONException e) { - ApptentiveLog.e("Error creating Event data object.", e); + ApptentiveLog.e(e, "Error creating Event data object."); } engageInternal(NavigateToLinkInteraction.EVENT_NAME_NAVIGATE, data.toString()); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java index 6fd825168..876c2ca28 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/NoteFragment.java @@ -107,7 +107,7 @@ public void onClick(View view) { data.put(Action.KEY_LABEL, buttonAction.getLabel()); data.put(TextModalInteraction.EVENT_KEY_ACTION_POSITION, position); } catch (JSONException e) { - ApptentiveLog.e("Error creating Event data object.", e); + ApptentiveLog.e(e, "Error creating Event data object."); } engageInternal(TextModalInteraction.EVENT_NAME_DISMISS, data.toString()); transit(); @@ -152,7 +152,7 @@ public void onClick(View view) { data.put(TextModalInteraction.EVENT_KEY_ACTION_POSITION, position); data.put(TextModalInteraction.EVENT_KEY_INVOKED_INTERACTION_ID, invokedInteraction == null ? JSONObject.NULL : invokedInteraction.getId()); } catch (JSONException e) { - ApptentiveLog.e("Error creating Event data object.", e); + ApptentiveLog.e(e, "Error creating Event data object."); } engageInternal(TextModalInteraction.EVENT_NAME_INTERACTION, data.toString()); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/UpgradeMessageFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/UpgradeMessageFragment.java index 0d129212a..67d060fb3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/UpgradeMessageFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/UpgradeMessageFragment.java @@ -76,7 +76,7 @@ private Drawable getIconDrawableResourceId() { PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); return ContextCompat.getDrawable(getContext(), pi.applicationInfo.icon); } catch (Exception e) { - ApptentiveLog.e("Error loading app icon.", e); + ApptentiveLog.e(e, "Error loading app icon."); } return null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/Interaction.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/Interaction.java index 32f87842e..d5658f929 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/Interaction.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/Interaction.java @@ -143,7 +143,7 @@ public static Interaction parseInteraction(String interactionString) { break; } } catch (JSONException e) { - ApptentiveLog.w("Error parsing Interaction", e); + ApptentiveLog.w(e, "Error parsing Interaction"); // Ignore } return null; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionCriteria.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionCriteria.java index edfc8d954..e04914ff8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionCriteria.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionCriteria.java @@ -34,9 +34,9 @@ public boolean isMet() { ApptentiveLog.i("- => %b", ret); return ret; } catch (JSONException e) { - ApptentiveLog.w("Error parsing and running InteractionCriteria predicate logic.", e); + ApptentiveLog.w(e, "Error parsing and running InteractionCriteria predicate logic."); } catch (Exception e) { - ApptentiveLog.w("Error parsing and running InteractionCriteria predicate logic.", e); + ApptentiveLog.w(e, "Error parsing and running InteractionCriteria predicate logic."); } return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionManifest.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionManifest.java index 50f733a3a..ba7fe5f68 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionManifest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/InteractionManifest.java @@ -42,7 +42,7 @@ public Interactions getInteractions() { } } } catch (JSONException e) { - ApptentiveLog.w("Unable to load Interactions from InteractionManifest.", e); + ApptentiveLog.w(e, "Unable to load Interactions from InteractionManifest."); } return null; } @@ -54,7 +54,7 @@ public Targets getTargets() { return new Targets(targets.toString()); } } catch (JSONException e) { - ApptentiveLog.w("Unable to load Targets from InteractionManifest.", e); + ApptentiveLog.w(e, "Unable to load Targets from InteractionManifest."); } return null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/Interactions.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/Interactions.java index b6568a131..1ca5ae471 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/Interactions.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/Interactions.java @@ -36,7 +36,7 @@ public Interaction getInteraction(String id) { return Interaction.Factory.parseInteraction(getJSONObject(id).toString()); } } catch (JSONException e) { - ApptentiveLog.w("Exception parsing interactions array.", e); + ApptentiveLog.w(e, "Exception parsing interactions array."); } return null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/common/Actions.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/common/Actions.java index 39bf8100f..84bbcfe6d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/common/Actions.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/common/Actions.java @@ -32,7 +32,7 @@ public List getAsList() { } } } catch (JSONException e) { - ApptentiveLog.w("Exception parsing interactions array.", e); + ApptentiveLog.w(e, "Exception parsing interactions array."); } return ret; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/view/survey/MultichoiceSurveyQuestionView.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/view/survey/MultichoiceSurveyQuestionView.java index 0b7cf6c15..43236b6d4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/view/survey/MultichoiceSurveyQuestionView.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/view/survey/MultichoiceSurveyQuestionView.java @@ -153,7 +153,7 @@ public Object getAnswer() { } } } catch (Exception e) { - ApptentiveLog.e("Error getting survey answer.", e); + ApptentiveLog.e(e, "Error getting survey answer."); } return null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/view/survey/SurveyQuestionChoice.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/view/survey/SurveyQuestionChoice.java index 688581847..eaf22e381 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/view/survey/SurveyQuestionChoice.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/view/survey/SurveyQuestionChoice.java @@ -143,7 +143,7 @@ public Object getAnswer() { } return answer; } catch (Exception e) { - ApptentiveLog.e("Error producing survey answer.", e); + ApptentiveLog.e(e, "Error producing survey answer."); } return null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java index b1c53a1ea..977da3e91 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java @@ -324,7 +324,7 @@ public static QueryPart parse(String name) { try { return QueryPart.valueOf(name); } catch (IllegalArgumentException e) { - ApptentiveLog.d(String.format("Unrecognized QueryPart: \"%s\". Defaulting to \"unknown\"", name), e); + ApptentiveLog.d(e, "Unrecognized QueryPart: \"%s\". Defaulting to \"unknown\"", name); } } return other; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 14cc2c5b0..0a876e870 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -129,7 +129,7 @@ protected void execute() { DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { @Override protected void execute() { - ApptentiveLog.w("Unhandled Exception thrown from fetching new message task", e); + ApptentiveLog.w(e, "Unhandled Exception thrown from fetching new message task"); MetricModule.sendError(e, null, null); } }); @@ -248,9 +248,9 @@ private List fetchMessages(String afterId) { try { ret = parseMessagesString(response.getContent()); } catch (JSONException e) { - ApptentiveLog.e("Error parsing messages JSON.", e); + ApptentiveLog.e(e, "Error parsing messages JSON."); } catch (Exception e) { - ApptentiveLog.e("Unexpected error parsing messages JSON.", e); + ApptentiveLog.e(e, "Unexpected error parsing messages JSON."); } return ret; } @@ -331,7 +331,7 @@ public void onSentMessage(String nonce, int responseCode, JSONObject responseJso apptentiveMessage.setId(responseJson.getString(ApptentiveMessage.KEY_ID)); apptentiveMessage.setCreatedAt(responseJson.getDouble(ApptentiveMessage.KEY_CREATED_AT)); } catch (JSONException e) { - ApptentiveLog.e("Error parsing sent apptentiveMessage response.", e); + ApptentiveLog.e(e, "Error parsing sent apptentiveMessage response."); } messageStore.updateMessage(apptentiveMessage); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java index 76bd0d143..8d998f47d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java @@ -36,7 +36,7 @@ public static ApptentiveMessage fromJson(String json) { break; } } catch (JSONException e) { - ApptentiveLog.v("Error parsing json as Message: %s", e, json); + ApptentiveLog.v(e, "Error parsing json as Message: %s", json); } catch (IllegalArgumentException e) { // Exception treated as unknown type } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/ApptentiveAvatarView.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/ApptentiveAvatarView.java index 3ce7abe20..1238399fb 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/ApptentiveAvatarView.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/ApptentiveAvatarView.java @@ -150,7 +150,7 @@ private Bitmap getBitmapFromDrawable(Drawable d) { d.draw(canvas); return b; } catch (OutOfMemoryError e) { - ApptentiveLog.w("Error creating bitmap.", e); + ApptentiveLog.w(e, "Error creating bitmap."); return null; } } @@ -222,7 +222,7 @@ public void run() { URL url = new URL(urlString); bitmap = BitmapFactory.decodeStream(url.openStream()); } catch (IOException e) { - ApptentiveLog.d("Error opening avatar from URL: \"%s\"", e, urlString); + ApptentiveLog.d(e, "Error opening avatar from URL: \"%s\"", urlString); } if (bitmap != null) { final Bitmap finalBitmap = bitmap; @@ -237,7 +237,7 @@ public void run() { Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable throwable) { - ApptentiveLog.w("UncaughtException in AvatarView.", throwable); + ApptentiveLog.w(throwable, "UncaughtException in AvatarView."); MetricModule.sendError(throwable, null, null); } }; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java index f9604d3fe..9ef27bdf5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java @@ -75,7 +75,7 @@ public static void sendError(Throwable throwable, String description, String ext } catch (Exception e) { // Since this is the last place in Apptentive code we can catch exceptions, we must catch all other Exceptions to // prevent the app from crashing. - ApptentiveLog.w("Error creating Error Metric. Nothing we can do but log this.", e); + ApptentiveLog.w(e, "Error creating Error Metric. Nothing we can do but log this."); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 642520928..88ef338fb 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -579,7 +579,7 @@ PayloadData getOldestUnsentPayload() { } return null; } catch (Exception e) { - ApptentiveLog.e("Error getting oldest unsent payload.", e); + ApptentiveLog.e(e, "Error getting oldest unsent payload."); return null; } finally { ensureClosed(cursor); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/JsonDiffer.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/JsonDiffer.java index 23dce5d9e..e18b77b50 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/JsonDiffer.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/JsonDiffer.java @@ -59,7 +59,7 @@ public static JSONObject getDiff(JSONObject original, JSONObject updated) { // Do nothing. } } catch (JSONException e) { - ApptentiveLog.w("Error diffing object with key %s", e, key); + ApptentiveLog.w(e, "Error diffing object with key %s", key); } finally { it.remove(); } @@ -105,7 +105,7 @@ public static boolean areObjectsEqual(Object left, Object right) { return false; } } catch (JSONException e) { - ApptentiveLog.w("Error comparing JSONObjects", e); + ApptentiveLog.w(e, "Error comparing JSONObjects"); return false; } } @@ -123,7 +123,7 @@ public static boolean areObjectsEqual(Object left, Object right) { } } } catch (JSONException e) { - ApptentiveLog.e("", e); + ApptentiveLog.e(e, ""); return false; } return true; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java index 311e28041..b8af91880 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java @@ -145,7 +145,7 @@ public static Integer parseCacheControlHeader(String cacheControlHeader) { Integer ret = Integer.parseInt(expiration); return ret; } catch (NumberFormatException e) { - ApptentiveLog.e("Error parsing cache expiration as number: %s", e, expiration); + ApptentiveLog.e(e, "Error parsing cache expiration as number: %s", expiration); } } } @@ -208,7 +208,7 @@ public static String getAppVersionName(Context appContext) { PackageInfo packageInfo = packageManager.getPackageInfo(appContext.getPackageName(), 0); return packageInfo.versionName; } catch (PackageManager.NameNotFoundException e) { - ApptentiveLog.e("Error getting app version name.", e); + ApptentiveLog.e(e, "Error getting app version name."); } return null; } @@ -219,7 +219,7 @@ public static int getAppVersionCode(Context appContext) { PackageInfo packageInfo = packageManager.getPackageInfo(appContext.getPackageName(), 0); return packageInfo.versionCode; } catch (PackageManager.NameNotFoundException e) { - ApptentiveLog.e("Error getting app version code.", e); + ApptentiveLog.e(e, "Error getting app version code."); } return -1; } @@ -279,7 +279,7 @@ public static Integer getMajorOsVersion() { return Integer.parseInt(parts[0]); } } catch (Exception e) { - ApptentiveLog.w("Error getting major OS version", e); + ApptentiveLog.w(e, "Error getting major OS version"); } return -1; } @@ -630,7 +630,7 @@ && hasPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { context.startActivity(intent); return true; } catch (ActivityNotFoundException e) { - ApptentiveLog.e("Activity not found to open attachment: ", e); + ApptentiveLog.e(e, "Activity not found to open attachment: "); } } } else { @@ -920,9 +920,9 @@ public static void replaceDefaultFont(Context context, String fontFilePath) { staticField.setAccessible(true); staticField.set(null, newMap); } catch (NoSuchFieldException e) { - ApptentiveLog.e("Exception replacing system font", e); + ApptentiveLog.e(e, "Exception replacing system font"); } catch (IllegalAccessException e) { - ApptentiveLog.e("Exception replacing system font", e); + ApptentiveLog.e(e, "Exception replacing system font"); } } } else { @@ -940,9 +940,9 @@ public static void replaceDefaultFont(Context context, String fontFilePath) { staticField.setAccessible(true); staticField.set(null, newTypeface); } catch (NoSuchFieldException e) { - ApptentiveLog.e("Exception replacing system font", e); + ApptentiveLog.e(e, "Exception replacing system font"); } catch (IllegalAccessException e) { - ApptentiveLog.e("Exception replacing system font", e); + ApptentiveLog.e(e, "Exception replacing system font"); } } } @@ -1000,7 +1000,7 @@ public static String getManifestMetadataString(Context context, String key) { return Util.trim(metaData.getString(key)); } } catch (Exception e) { - ApptentiveLog.e("Unexpected error while reading application or package info.", e); + ApptentiveLog.e(e, "Unexpected error while reading application or package info."); } return null; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageUtil.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageUtil.java index af34a388c..f72fb7c14 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageUtil.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageUtil.java @@ -289,10 +289,10 @@ public static boolean createScaledDownImageCacheFile(String sourcePath, String c smaller.recycle(); System.gc(); } catch (FileNotFoundException e) { - ApptentiveLog.e("File not found while storing image.", e); + ApptentiveLog.e(e, "File not found while storing image."); return false; } catch (Exception e) { - ApptentiveLog.a("Error storing image.", e); + ApptentiveLog.a(e, "Error storing image."); return false; } finally { Util.ensureClosed(cos); @@ -322,7 +322,7 @@ public static boolean appendScaledDownImageToStream(String sourcePath, OutputStr smaller.recycle(); return true; } catch (Exception e) { - ApptentiveLog.a("Error storing image.", e); + ApptentiveLog.a(e, "Error storing image."); return false; } finally { Util.ensureClosed(cos); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java index 19cfa37b2..d3466674f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java @@ -66,7 +66,7 @@ protected ApptentiveHttpResponse doInBackground(Object... params) { try { finished = downloadBitmap((String) params[0], (String) params[1], (String) params[2]); } catch (Exception e) { - ApptentiveLog.d("Error downloading bitmap", e); + ApptentiveLog.d(e, "Error downloading bitmap"); } return finished; } @@ -224,18 +224,18 @@ private ApptentiveHttpResponse downloadBitmap(String urlString, String destFileP } } } catch (IllegalArgumentException e) { - ApptentiveLog.w("Error communicating with server.", e); + ApptentiveLog.w(e, "Error communicating with server."); } catch (SocketTimeoutException e) { - ApptentiveLog.w("Timeout communicating with server.", e); + ApptentiveLog.w(e, "Timeout communicating with server."); } catch (final MalformedURLException e) { - ApptentiveLog.w("ClientProtocolException", e); + ApptentiveLog.w(e, "ClientProtocolException"); } catch (final IOException e) { - ApptentiveLog.w("ClientProtocolException", e); + ApptentiveLog.w(e, "ClientProtocolException"); // Read the error response. try { ret.setContent(ApptentiveClient.getErrorResponse(connection, ret.isZipped())); } catch (IOException ex) { - ApptentiveLog.w("Can't read error stream.", ex); + ApptentiveLog.w(ex, "Can't read error stream."); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveAlertDialog.java b/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveAlertDialog.java index 477337df4..8764909a9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveAlertDialog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveAlertDialog.java @@ -93,7 +93,7 @@ public void onClick(View v) { }); } } catch (Exception e) { - ApptentiveLog.e("Error:", e); + ApptentiveLog.e(e, "Error:"); } AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); if (view != null) { From ca5b97679b3dfc6c7b388e3e9d3d49612f8cd122 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Sat, 1 Jul 2017 15:48:21 -0700 Subject: [PATCH 417/465] Update test-app to remove old style internal logging methods in favor of new style. --- .../android/sdk/tests/ApptentiveTestCaseBase.java | 4 ++-- .../sdk/tests/model/DateTimeComparisonTest.java | 2 +- .../apptentive/android/sdk/tests/model/EventTests.java | 2 +- .../android/sdk/tests/model/VersionComparisonTest.java | 2 +- .../tests/module/engagement/CriteriaParsingTest.java | 2 +- .../android/sdk/tests/module/engagement/EventTest.java | 2 +- .../sdk/tests/module/engagement/InteractionTest.java | 6 +++--- .../tests/module/engagement/SurveyInteractionTest.java | 2 +- .../criteria/CodePointAndInteractionStoreTest.java | 10 +++++----- .../tests/module/engagement/criteria/CornerCases.java | 4 ++-- .../module/engagement/criteria/DefaultValues.java | 2 +- .../module/engagement/criteria/OperatorTests.java | 2 +- .../engagement/criteria/WhitespaceTrimmingTest.java | 2 +- 13 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/ApptentiveTestCaseBase.java b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/ApptentiveTestCaseBase.java index f12d52e95..4c2631fbd 100644 --- a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/ApptentiveTestCaseBase.java +++ b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/ApptentiveTestCaseBase.java @@ -69,7 +69,7 @@ public static String loadRawTextResourceAsString(Context context, int resourceId } return builder.toString(); } catch (IOException e) { - ApptentiveLog.e("Error reading from raw resource with ID \"%d\"", e, resourceId); + ApptentiveLog.e(e, "Error reading from raw resource with ID \"%d\"", resourceId); } finally { Util.ensureClosed(reader); } @@ -94,7 +94,7 @@ public static String loadTextAssetAsString(Context context, String path) { } return builder.toString(); } catch (IOException e) { - ApptentiveLog.e("Error reading from file \"%s\"", e, path); + ApptentiveLog.e(e, "Error reading from file \"%s\"", path); } finally { Util.ensureClosed(reader); } diff --git a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/model/DateTimeComparisonTest.java b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/model/DateTimeComparisonTest.java index 84ba866b9..97842581d 100644 --- a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/model/DateTimeComparisonTest.java +++ b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/model/DateTimeComparisonTest.java @@ -59,7 +59,7 @@ public void dateTimeComparison() { assertEquals(String.format("Comparison of [\"%s\" %s \"%s\"] failed", left, operator, right), expected, actual); } } catch (JSONException e) { - ApptentiveLog.e("Error loading experiment results.", e); + ApptentiveLog.e(e, "Error loading experiment results."); } } } diff --git a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/model/EventTests.java b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/model/EventTests.java index 056da83a7..9a8298c4f 100644 --- a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/model/EventTests.java +++ b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/model/EventTests.java @@ -59,7 +59,7 @@ public void extendedDataEvents() { CommerceExtendedData.Item item = new CommerceExtendedData.Item(22222222, "Item Name", "Category", 20, 5.0d, "USD"); commerce.addItem(item); } catch (JSONException e) { - ApptentiveLog.e("Error: ", e); + ApptentiveLog.e(e, "Error: "); } assertNotNull(commerce); diff --git a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/model/VersionComparisonTest.java b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/model/VersionComparisonTest.java index 109aa2fa1..5cac5b71f 100644 --- a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/model/VersionComparisonTest.java +++ b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/model/VersionComparisonTest.java @@ -76,7 +76,7 @@ public void basicVersionComparison() { assertEquals(String.format("Comparison of [\"%s\" %s \"%s\"] failed", left, operator, right), expected, actual); } } catch (JSONException e) { - ApptentiveLog.e("Error loading experiment results.", e); + ApptentiveLog.e(e, "Error loading experiment results."); } } } diff --git a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/CriteriaParsingTest.java b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/CriteriaParsingTest.java index eb80e98b4..24137d3ce 100644 --- a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/CriteriaParsingTest.java +++ b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/CriteriaParsingTest.java @@ -35,7 +35,7 @@ public void predicateParsing() throws JSONException { Clause criteria = ClauseParser.parse(json); assertNotNull("Criteria was null, but it shouldn't be.", criteria); } catch (JSONException e) { - ApptentiveLog.e("Error parsing test JSON.", e); + ApptentiveLog.e(e, "Error parsing test JSON."); assertNotNull(e); } } diff --git a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/EventTest.java b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/EventTest.java index d3ee2f828..f2a4a0210 100644 --- a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/EventTest.java +++ b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/EventTest.java @@ -49,7 +49,7 @@ public void eventLabelCreation() { assertTrue(result.equals(expected)); } } catch (IOException e) { - ApptentiveLog.e("Error reading asset.", e); + ApptentiveLog.e(e, "Error reading asset."); throw new RuntimeException(e); } finally { Util.ensureClosed(reader); diff --git a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/InteractionTest.java b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/InteractionTest.java index f3a9342ba..d9fb57af6 100644 --- a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/InteractionTest.java +++ b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/InteractionTest.java @@ -169,7 +169,7 @@ public void criteriaApplicationVersionCode() { InteractionCriteria criteria = new InteractionCriteria(json); assertTrue(criteria.isMet()); } catch (Exception e) { - ApptentiveLog.e("Error running test.", e); + ApptentiveLog.e(e, "Error running test."); assertNull(e); } } @@ -187,7 +187,7 @@ public void criteriaApplicationVersionName() { InteractionCriteria criteria = new InteractionCriteria(json); assertTrue(criteria.isMet()); } catch (Exception e) { - ApptentiveLog.e("Error parsing test JSON.", e); + ApptentiveLog.e(e, "Error parsing test JSON."); assertNull(e); } } @@ -204,7 +204,7 @@ public void criteriaApplicationDebug() { InteractionCriteria criteria = new InteractionCriteria(json); assertTrue(criteria.isMet()); } catch (Exception e) { - ApptentiveLog.e("Error parsing test JSON.", e); + ApptentiveLog.e(e, "Error parsing test JSON."); assertNull(e); } } diff --git a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/SurveyInteractionTest.java b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/SurveyInteractionTest.java index 3642f7236..11d8a348d 100644 --- a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/SurveyInteractionTest.java +++ b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/SurveyInteractionTest.java @@ -32,7 +32,7 @@ public void surveyParsing() { try { survey = new SurveyInteraction(json); } catch (Exception e) { - ApptentiveLog.e("Error loading survey.", e); + ApptentiveLog.e(e, "Error loading survey."); } assertNotNull(survey); } diff --git a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/CodePointAndInteractionStoreTest.java b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/CodePointAndInteractionStoreTest.java index 1f20ae712..af90d927f 100644 --- a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/CodePointAndInteractionStoreTest.java +++ b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/CodePointAndInteractionStoreTest.java @@ -133,7 +133,7 @@ public void codePointInvokesTotal() { codePointStore.storeCodePointForCurrentAppVersion("test.code.point"); assertFalse(criteria.isMet()); } catch (JSONException e) { - ApptentiveLog.e("Error parsing test JSON.", e); + ApptentiveLog.e(e, "Error parsing test JSON."); assertNull(e); } ApptentiveLog.e("Finished test."); @@ -258,7 +258,7 @@ public void codePointInvokesVersionCode() { assertFalse(criteria.isMet()); } catch (JSONException e) { - ApptentiveLog.e("Error parsing test JSON.", e); + ApptentiveLog.e(e, "Error parsing test JSON."); assertNull(e); } ApptentiveLog.e("Finished test."); @@ -383,7 +383,7 @@ public void codePointInvokesVersionName() { assertFalse(criteria.isMet()); } catch (JSONException e) { - ApptentiveLog.e("Error parsing test JSON.", e); + ApptentiveLog.e(e, "Error parsing test JSON."); assertNull(e); } ApptentiveLog.e("Finished test."); @@ -461,7 +461,7 @@ public void codePointLastInvokedAt() { assertTrue(criteria.isMet()); } catch (JSONException e) { - ApptentiveLog.e("Error parsing test JSON.", e); + ApptentiveLog.e(e, "Error parsing test JSON."); assertNull(e); } ApptentiveLog.e("Finished test."); @@ -577,7 +577,7 @@ public void interactionInvokesTotal() { assertFalse(criteria.isMet()); } catch (JSONException e) { - ApptentiveLog.e("Error parsing test JSON.", e); + ApptentiveLog.e(e, "Error parsing test JSON."); assertNull(e); } ApptentiveLog.e("Finished test."); diff --git a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/CornerCases.java b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/CornerCases.java index 2c1d201a6..a46aa474d 100644 --- a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/CornerCases.java +++ b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/CornerCases.java @@ -42,7 +42,7 @@ public void ornerCasesThatShouldBeTrue() throws JSONException { boolean result = criteria.evaluate(); assertTrue(result); } catch (JSONException e) { - ApptentiveLog.e("Error parsing test JSON.", e); + ApptentiveLog.e(e, "Error parsing test JSON."); assertNull(e); } ApptentiveLog.e("Finished test."); @@ -62,7 +62,7 @@ public void cornerCasesThatShouldBeFalse() throws JSONException { boolean result = criteria.evaluate(); assertTrue(result); } catch (JSONException e) { - ApptentiveLog.e("Error parsing test JSON.", e); + ApptentiveLog.e(e, "Error parsing test JSON."); assertNull(e); } ApptentiveLog.e("Finished test."); diff --git a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/DefaultValues.java b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/DefaultValues.java index 9068ee2cc..5af29b1bd 100644 --- a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/DefaultValues.java +++ b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/DefaultValues.java @@ -39,7 +39,7 @@ public void defaultValues() throws JSONException { boolean result = criteria.evaluate(); assertTrue(result); } catch (JSONException e) { - ApptentiveLog.e("Error parsing test JSON.", e); + ApptentiveLog.e(e, "Error parsing test JSON."); assertNull(e); } } diff --git a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/OperatorTests.java b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/OperatorTests.java index 680d625d3..04905ebd7 100644 --- a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/OperatorTests.java +++ b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/OperatorTests.java @@ -115,7 +115,7 @@ private void doTest(String testFile) { InteractionCriteria criteria = new InteractionCriteria(json); assertTrue(criteria.isMet()); } catch (JSONException e) { - ApptentiveLog.e("Error parsing test JSON.", e); + ApptentiveLog.e(e, "Error parsing test JSON."); assertNull(e); } } diff --git a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/WhitespaceTrimmingTest.java b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/WhitespaceTrimmingTest.java index b159dbb1c..8cc70a6a9 100644 --- a/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/WhitespaceTrimmingTest.java +++ b/tests/test-app/src/androidTest/java/com/apptentive/android/sdk/tests/module/engagement/criteria/WhitespaceTrimmingTest.java @@ -42,7 +42,7 @@ private void doTest(String testFile) { InteractionCriteria criteria = new InteractionCriteria(json); assertTrue(criteria.isMet()); } catch (JSONException e) { - ApptentiveLog.e("Error parsing test JSON.", e); + ApptentiveLog.e(e, "Error parsing test JSON."); assertNull(e); } } From d5e63ebbecfd16895ae6c88cae7f46084c638239 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 5 Jul 2017 11:56:03 -0700 Subject: [PATCH 418/465] Fixed sending survey responses --- .../android/sdk/model/SurveyResponsePayload.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index e5865cae0..2c6797050 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -18,6 +18,8 @@ public class SurveyResponsePayload extends ConversationItem { + private static final String KEY_RESPONSE = "response"; + private static final String KEY_SURVEY_ID = "id"; private static final String KEY_SURVEY_ANSWERS = "answers"; @@ -41,11 +43,22 @@ public SurveyResponsePayload(String json) throws JSONException { super(PayloadType.survey, json); } + @Override + protected JSONObject marshallForSending() { + JSONObject object = new JSONObject(); + try { + object.put(KEY_RESPONSE, super.marshallForSending()); + } catch (JSONException e) { + ApptentiveLog.e(e, "Error creating survey response object"); + } + return object; + } + //region Http-request @Override public String getHttpEndPoint(String conversationId) { - return StringUtils.format("/conversations/%s/surveys/%s/respond", conversationId, getId()); + return StringUtils.format("/conversations/%s/surveys/%s/responses", conversationId, getId()); } @Override From fa83c95906aabb863dcdcb42e5c061acfed74117 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 5 Jul 2017 19:39:17 -0700 Subject: [PATCH 419/465] Better logging --- .../sdk/conversation/ConversationManager.java | 12 +++++++++--- .../apptentive/android/sdk/model/JsonPayload.java | 6 ++---- .../android/sdk/storage/ApptentiveTaskManager.java | 1 + .../android/sdk/storage/PayloadSender.java | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 4099e2e3d..e65dbeac3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -585,10 +585,16 @@ private void requestLoggedInConversation(final String token, final LoginCallback final String userId; try { final Jwt jwt = Jwt.decode(token); - userId = jwt.getPayload().getString("sub"); + userId = jwt.getPayload().optString("sub"); + if (StringUtils.isNullOrEmpty(userId)) { + ApptentiveLog.e("Error while extracting user id: Missing field \"sub\""); + callback.onLoginFail("Error while extracting user id: Missing field \"sub\""); + return; + } + } catch (Exception e) { - ApptentiveLog.e(e, "Error while extracting user id: Missing field \"sub\""); - callback.onLoginFail("Error while extracting user id: Missing field \"sub\""); + ApptentiveLog.e(e, "Exception while extracting user id"); + callback.onLoginFail("Exception while extracting user id"); return; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index e69b08025..e6af2081c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -9,6 +9,7 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.encryption.Encryptor; import com.apptentive.android.sdk.network.HttpRequestMethod; +import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; import org.json.JSONObject; @@ -39,7 +40,6 @@ public JsonPayload(PayloadType type, String json) throws JSONException { @Override public byte[] renderData() { if (encryptionKey != null) { - ApptentiveLog.v(PAYLOADS, "Getting data for encrypted payload."); byte[] bytes = marshallForSending().toString().getBytes(); Encryptor encryptor = new Encryptor(encryptionKey); try { @@ -49,7 +49,6 @@ public byte[] renderData() { } return null; } else { - ApptentiveLog.v(PAYLOADS, "Getting data for plaintext payload."); return marshallForSending().toString().getBytes(); } } @@ -148,10 +147,9 @@ protected boolean isNull(String key) { // TODO: rename to containsKey @Override public String toString() { - return jsonObject.toString(); + return StringUtils.format("%s %s", getClass().getSimpleName(), jsonObject); } - //endregion //region Getters/Setters diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 188d230ba..89b68ec2e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -99,6 +99,7 @@ public boolean shouldRetryRequest(int responseCode, int retryAttempt) { * a new message is added. */ public void addPayload(final Payload payload) { + ApptentiveLog.v(PAYLOADS, "Adding payload: %s", payload); singleThreadExecutor.execute(new Runnable() { @Override public void run() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index 93e185120..d7c24920d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -99,7 +99,7 @@ synchronized boolean sendPayload(final PayloadData payload) { * @param payload */ private synchronized void sendPayloadRequest(final PayloadData payload) { - ApptentiveLog.v(PAYLOADS, "Sending payload: %s", payload.getClass().getName()); + ApptentiveLog.v(PAYLOADS, "Sending payload: %s", payload); // create request object final HttpRequest payloadRequest = requestSender.sendPayload(payload, new HttpRequest.Listener() { From 4422f04f3b82f6a85c3eb6ba019ea93693f6fbd4 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 6 Jul 2017 11:11:16 -0700 Subject: [PATCH 420/465] Fixed storing messages --- .../apptentive/android/sdk/conversation/FileMessageStore.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java index 2ab04c866..c9f542555 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/FileMessageStore.java @@ -64,7 +64,7 @@ public synchronized void addOrUpdateMessages(ApptentiveMessage... apptentiveMess if (apptentiveMessage.isRead()) { // A apptentiveMessage can't be unread after being read. existing.isRead = true; } - existing.json = apptentiveMessage.toString(); + existing.json = apptentiveMessage.getJsonObject().toString(); } else { // Insert MessageEntry entry = new MessageEntry(); @@ -73,7 +73,7 @@ public synchronized void addOrUpdateMessages(ApptentiveMessage... apptentiveMess entry.nonce = apptentiveMessage.getNonce(); entry.state = apptentiveMessage.getState().name(); entry.isRead = apptentiveMessage.isRead(); - entry.json = apptentiveMessage.toString(); + entry.json = apptentiveMessage.getJsonObject().toString(); messageEntries.add(entry); } } From d92b3a1e7de9810435070791b15aaf016feb768d Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 6 Jul 2017 13:45:19 -0700 Subject: [PATCH 421/465] Better logging + fixed handling payloads with unknown type --- .../android/sdk/module/metric/MetricModule.java | 2 +- .../android/sdk/storage/ApptentiveDatabaseHelper.java | 10 ++++++++-- .../android/sdk/storage/ApptentiveTaskManager.java | 1 - 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java index 9ef27bdf5..29ddf1bbc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/metric/MetricModule.java @@ -84,7 +84,7 @@ private static void sendEvent(EventPayload event) { if (conversation != null) { conversation.addPayload(event); } else { - ApptentiveLog.w("Unable to send event '%s': no active conversation"); + ApptentiveLog.w("Unable to send event '%s': no active conversation", event); } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 88ef338fb..81cb99ce4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -37,6 +37,8 @@ import java.util.UUID; import static com.apptentive.android.sdk.ApptentiveLogTag.DATABASE; +import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; +import static com.apptentive.android.sdk.debug.Assert.assertFalse; import static com.apptentive.android.sdk.debug.Assert.notNull; import static com.apptentive.android.sdk.util.Constants.PAYLOAD_DATA_FILE_SUFFIX; @@ -550,18 +552,22 @@ PayloadData getOldestUnsentPayload() { while(cursor.moveToNext()) { final String conversationId = cursor.getString(PayloadEntry.COLUMN_CONVERSATION_ID.index); if (conversationId == null) { + ApptentiveLog.d(PAYLOADS, "Oldest unsent payload is missing a conversation id"); return null; } final String authToken = cursor.getString(PayloadEntry.COLUMN_AUTH_TOKEN.index); + final String nonce = notNull(cursor.getString(PayloadEntry.COLUMN_IDENTIFIER.index)); final PayloadType payloadType = PayloadType.parse(cursor.getString(PayloadEntry.COLUMN_PAYLOAD_TYPE.index)); + assertFalse(PayloadType.unknown.equals(payloadType), "Oldest unsent payload has unknown type"); + if (PayloadType.unknown.equals(payloadType)) { - return null; + deletePayload(nonce); + continue; } final String httpRequestPath = updatePayloadRequestPath(cursor.getString(PayloadEntry.COLUMN_PATH.index), conversationId); - final String nonce = notNull(cursor.getString(PayloadEntry.COLUMN_IDENTIFIER.index)); // TODO: We need a migration for existing payload bodies to put them into files. diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 89b68ec2e..45137f244 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -233,7 +233,6 @@ private void sendNextPayloadSync() { } if (payload == null) { - ApptentiveLog.v(PAYLOADS, "Can't send the next payload: no unsent payloads found"); return; } From 76a16819494b4b8b9c1d98ff8083776488eb89a2 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 7 Jul 2017 08:11:02 -0700 Subject: [PATCH 422/465] Added more detailed logging --- .../sdk/conversation/ConversationManager.java | 45 +++++++++++ .../android/sdk/network/HttpRequest.java | 29 ++++++- .../sdk/storage/ApptentiveDatabaseHelper.java | 76 +++++++++++++++++++ .../android/sdk/util/StringUtils.java | 40 ++++++++++ 4 files changed, 189 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index e65dbeac3..da328ee1d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -16,6 +16,7 @@ import com.apptentive.android.sdk.comm.ApptentiveHttpClient; import com.apptentive.android.sdk.conversation.ConversationMetadata.Filter; import com.apptentive.android.sdk.migration.Migrator; +import com.apptentive.android.sdk.model.ConversationItem; import com.apptentive.android.sdk.model.ConversationTokenRequest; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.network.HttpJsonRequest; @@ -44,6 +45,7 @@ import java.io.File; import java.io.IOException; import java.lang.ref.WeakReference; +import java.util.List; import static com.apptentive.android.sdk.ApptentiveLog.Level.VERY_VERBOSE; import static com.apptentive.android.sdk.ApptentiveLogTag.*; @@ -124,6 +126,9 @@ public boolean loadActiveConversation(Context context) { // resolving metadata ApptentiveLog.vv(CONVERSATION, "Resolving metadata..."); conversationMetadata = resolveMetadata(); + if (ApptentiveLog.canLog(VERY_VERBOSE)) { + printMetadata(conversationMetadata, "Loaded Metadata"); + } // attempt to load existing conversation ApptentiveLog.vv(CONVERSATION, "Loading active conversation..."); @@ -443,6 +448,9 @@ private void handleConversationStateChange(Conversation conversation) { } updateMetadataItems(conversation); + if (ApptentiveLog.canLog(VERY_VERBOSE)) { + printMetadata(conversationMetadata, "Updated Metadata"); + } } } @@ -806,6 +814,43 @@ private void doLogout() { //endregion + //region Debug + + private void printMetadata(ConversationMetadata metadata, String title) { + List items = metadata.getItems(); + if (items.isEmpty()) { + ApptentiveLog.vv(CONVERSATION, "%s (%d item(s))", title, items.size()); + return; + } + + Object[][] rows = new Object[1 + items.size()][]; + rows[0] = new Object[] { + "state", + "conversationId", + "userId", + "dataFile", + "messagesFile", + "conversationToken", + "encryptionKey" + }; + int index = 1; + for (ConversationMetadataItem item : items) { + rows[index++] = new Object[] { + item.state, + item.conversationId, + item.userId, + item.dataFile, + item.messagesFile, + item.conversationToken, + item.encryptionKey + }; + } + + ApptentiveLog.vv(CONVERSATION, "%s (%d item(s))\n%s", title, items.size(), StringUtils.table(rows)); + } + + //endregion + //region Getters/Setters public Conversation getActiveConversation() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 080e15949..edfbcd182 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -149,6 +149,9 @@ public class HttpRequest { */ private DispatchQueue callbackQueue; + /** Optional injector for debugging purposes */ + private Injector injector; + public HttpRequest(String urlString) { if (urlString == null || urlString.length() == 0) { throw new IllegalArgumentException("Invalid URL string '" + urlString + "'"); @@ -253,8 +256,12 @@ protected void execute() { } } - private void sendRequestSync() throws IOException { + private void sendRequestSync() throws Exception { try { + if (injector != null) { + injector.onBeforeSend(this); + } + URL url = new URL(urlString); ApptentiveLog.d(NETWORK, "Performing request: %s %s", method, url); if (ApptentiveLog.canLog(VERY_VERBOSE)) { @@ -323,6 +330,10 @@ private void sendRequestSync() throws IOException { return; } + if (injector != null) { + injector.onAfterSend(this); + } + // optionally handle response data (should be overridden in a sub class) handleResponse(responseData); } finally { @@ -596,6 +607,10 @@ protected void setResponseCode(int code) { responseCode = code; } + public void setInjector(Injector injector) { + this.injector = injector; + } + //endregion //region Listener @@ -628,6 +643,18 @@ public void onFail(T request, String reason) { //endregion + //region Debug + + public static class Injector { + public void onBeforeSend(HttpRequest request) throws Exception { + } + + public void onAfterSend(HttpRequest request) throws Exception { + } + } + + //endregion + private class NetworkUnavailableException extends IOException { NetworkUnavailableException(String message) { super(message); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 81cb99ce4..53e82d067 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -507,6 +507,10 @@ void addPayload(Payload payload) { db.endTransaction(); } } + + if (ApptentiveLog.canLog(ApptentiveLog.Level.VERY_VERBOSE)) { + printPayloadTable("Added payload"); + } } void deletePayload(String payloadIdentifier) { @@ -529,6 +533,10 @@ void deletePayload(String payloadIdentifier) { // Then delete the data file File dest = getPayloadBodyFile(payloadIdentifier); ApptentiveLog.v(DATABASE, "Deleted payload \"%s\" data file successfully? %b", payloadIdentifier, dest.delete()); + + if (ApptentiveLog.canLog(ApptentiveLog.Level.VERY_VERBOSE)) { + printPayloadTable("Deleted payload"); + } } void deleteAllPayloads() { @@ -543,12 +551,18 @@ void deleteAllPayloads() { } PayloadData getOldestUnsentPayload() { + if (ApptentiveLog.canLog(ApptentiveLog.Level.VERY_VERBOSE)) { + printPayloadTable("getOldestUnsentPayload"); + } SQLiteDatabase db; Cursor cursor = null; try { db = getWritableDatabase(); cursor = db.rawQuery(SQL_QUERY_PAYLOAD_GET_IN_SEND_ORDER, null); + int count = cursor.getCount(); + ApptentiveLog.v(PAYLOADS, "Unsent payloads count: %d", count); + while(cursor.moveToNext()) { final String conversationId = cursor.getString(PayloadEntry.COLUMN_CONVERSATION_ID.index); if (conversationId == null) { @@ -597,6 +611,10 @@ private String updatePayloadRequestPath(String path, String conversationId) { } void updateIncompletePayloads(String conversationId, String authToken, String localConversationId) { + if (ApptentiveLog.canLog(ApptentiveLog.Level.VERY_VERBOSE)) { + printPayloadTable("updateIncompletePayloads BEFORE"); + } + if (StringUtils.isNullOrEmpty(conversationId)) { throw new IllegalArgumentException("Conversation id is null or empty"); } @@ -619,6 +637,10 @@ void updateIncompletePayloads(String conversationId, String authToken, String lo // remove incomplete payloads which don't belong to an active conversation removeCorruptedPayloads(); + + if (ApptentiveLog.canLog(ApptentiveLog.Level.VERY_VERBOSE)) { + printPayloadTable("updateIncompletePayloads AFTER"); + } } private void removeCorruptedPayloads() { @@ -759,4 +781,58 @@ public String toString() { } //endregion + + //region Debug + + private void printPayloadTable(String title) { + SQLiteDatabase db; + Cursor cursor = null; + try { + db = getWritableDatabase(); + cursor = db.rawQuery(SQL_QUERY_PAYLOAD_GET_IN_SEND_ORDER, null); + int payloadCount = cursor.getCount(); + if (payloadCount == 0) { + ApptentiveLog.vv(PAYLOADS, "%s (%d payload(s))", title, payloadCount); + return; + } + + Object[][] rows = new Object[1 + payloadCount][]; + rows[0] = new Object[] { + PayloadEntry.COLUMN_PRIMARY_KEY, + PayloadEntry.COLUMN_PAYLOAD_TYPE, + PayloadEntry.COLUMN_IDENTIFIER, + PayloadEntry.COLUMN_CONTENT_TYPE, + PayloadEntry.COLUMN_CONVERSATION_ID, + PayloadEntry.COLUMN_REQUEST_METHOD, + PayloadEntry.COLUMN_PATH, + PayloadEntry.COLUMN_ENCRYPTED, + PayloadEntry.COLUMN_LOCAL_CONVERSATION_ID, + PayloadEntry.COLUMN_AUTH_TOKEN + }; + + int index = 1; + while(cursor.moveToNext()) { + + rows[index++] = new Object[] { + cursor.getInt(PayloadEntry.COLUMN_PRIMARY_KEY.index), + cursor.getString(PayloadEntry.COLUMN_PAYLOAD_TYPE.index), + cursor.getString(PayloadEntry.COLUMN_IDENTIFIER.index), + cursor.getString(PayloadEntry.COLUMN_CONTENT_TYPE.index), + cursor.getString(PayloadEntry.COLUMN_CONVERSATION_ID.index), + cursor.getString(PayloadEntry.COLUMN_REQUEST_METHOD.index), + cursor.getString(PayloadEntry.COLUMN_PATH.index), + cursor.getInt(PayloadEntry.COLUMN_ENCRYPTED.index), + cursor.getString(PayloadEntry.COLUMN_LOCAL_CONVERSATION_ID.index), + cursor.getString(PayloadEntry.COLUMN_AUTH_TOKEN.index) + }; + } + ApptentiveLog.vv(PAYLOADS, "%s (%d payload(s)):\n%s", title, payloadCount, StringUtils.table(rows)); + } catch (Exception ignored) { + ignored.printStackTrace(); + } finally { + ensureClosed(cursor); + } + } + + //endregion } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java index 50da5be8a..4724d2419 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/StringUtils.java @@ -190,4 +190,44 @@ public static byte[] hexToBytes(String hex) { } return ret; } + + public static String table(Object[][] rows) { + return table(rows, null); + } + + public static String table(Object[][] rows, String title) { + int[] columnSizes = new int[rows[0].length]; + for (Object[] row : rows) { + for (int i = 0; i < row.length; ++i) { + columnSizes[i] = Math.max(columnSizes[i], toString(row[i]).length()); + } + } + + StringBuilder line = new StringBuilder(); + int totalSize = 0; + for (int i = 0; i < columnSizes.length; ++i) { + totalSize += columnSizes[i]; + } + totalSize += columnSizes.length > 0 ? (columnSizes.length - 1) * " | ".length() : 0; + while (totalSize-- > 0) { + line.append('-'); + } + + StringBuilder result = new StringBuilder(line); + + for (Object[] row : rows) { + result.append("\n"); + + for (int i = 0; i < row.length; ++i) { + if (i > 0) { + result.append(" | "); + } + + result.append(String.format("%-" + columnSizes[i] + "s", row[i])); + } + } + + result.append("\n").append(line); + return result.toString(); + } } From dcc5c4991c3f4a52c5f23746cacd3d11301c3765 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 10 Jul 2017 22:18:31 -0700 Subject: [PATCH 423/465] Fixed assertion statement and race condition --- .../apptentive/android/sdk/conversation/Conversation.java | 4 ++-- .../android/sdk/conversation/ConversationManager.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 9a0a6e11a..f3371345a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -292,10 +292,10 @@ private synchronized void saveConversationData() throws SerializerException { FileSerializer serializer; if (!StringUtils.isNullOrEmpty(encryptionKey)) { - Assert.assertFalse(hasState(ANONYMOUS, ANONYMOUS_PENDING)); + Assert.assertFalse(hasState(ANONYMOUS, ANONYMOUS_PENDING, LEGACY_PENDING)); serializer = new EncryptedFileSerializer(conversationDataFile, encryptionKey); } else { - Assert.assertTrue(hasState(ANONYMOUS, ANONYMOUS_PENDING)); + Assert.assertTrue(hasState(ANONYMOUS, ANONYMOUS_PENDING, LEGACY_PENDING), "Unexpected conversation state: %s", getState()); serializer = new FileSerializer(conversationDataFile); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index da328ee1d..288cfc7d5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -204,12 +204,12 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali lastSeenVersion.setVersion(lastSeenVersionString); if (lastSeenVersionString != null && lastSeenVersion.compareTo(version4) < 0) { - Migrator migrator = new Migrator(getContext(), prefs, anonymousConversation); - migrator.migrate(); - anonymousConversation.setState(LEGACY_PENDING); anonymousConversation.setConversationToken(legacyConversationToken); + Migrator migrator = new Migrator(getContext(), prefs, anonymousConversation); + migrator.migrate(); + ApptentiveLog.v("Fetching legacy conversation..."); fetchLegacyConversation(anonymousConversation) // remove legacy key when request is finished From ae7c93a6c74efc50788c769ac90f0e919bbf3211 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 11 Jul 2017 21:15:36 -0700 Subject: [PATCH 424/465] Fixed message polling worker --- .../sdk/conversation/ConversationManager.java | 1 + .../module/messagecenter/MessageManager.java | 11 ++ .../messagecenter/MessagePollingWorker.java | 109 ++++++------------ 3 files changed, 45 insertions(+), 76 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 288cfc7d5..9950e8625 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -437,6 +437,7 @@ private void handleConversationStateChange(Conversation conversation) { if (conversation.hasActiveState()) { conversation.fetchInteractions(getContext()); + conversation.getMessageManager().startPollingMessages(); // Update conversation with push configuration changes that happened while it wasn't active. SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 0a876e870..8224fb3e7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -406,6 +406,17 @@ public void destroy() { //endregion + //region Polling + + public void startPollingMessages() { + pollingWorker.startPolling(); + } + + public void stopPollingMessages() { + pollingWorker.stopPolling(); + } + + //endregion // Listeners public interface AfterSendMessageListener { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java index 5043435d4..7302af62d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java @@ -11,67 +11,36 @@ import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.conversation.Conversation; import com.apptentive.android.sdk.model.Configuration; -import com.apptentive.android.sdk.module.metric.MetricModule; import com.apptentive.android.sdk.util.Destroyable; import java.util.concurrent.atomic.AtomicBoolean; import static com.apptentive.android.sdk.ApptentiveLogTag.MESSAGES; -public class MessagePollingWorker implements Destroyable { +class MessagePollingWorker implements Destroyable { private MessagePollingThread pollingThread; - // The following booleans will be accessed by both ui thread and worker thread - public AtomicBoolean messageCenterInForeground = new AtomicBoolean(false); - private AtomicBoolean threadRunning = new AtomicBoolean(false); + private final MessageManager manager; + final AtomicBoolean messageCenterInForeground = new AtomicBoolean(); - private MessageManager manager; - - public MessagePollingWorker(MessageManager manager) { + MessagePollingWorker(MessageManager manager) { this.manager = manager; } - // A synchronized getter/setter to the static instance of thread object - public synchronized MessagePollingThread getAndSetMessagePollingThread(boolean expect, - boolean create) { - if (expect && create) { - pollingThread = createPollingThread(); - } else if (!expect) { - pollingThread = null; - } - return pollingThread; - } - - private MessagePollingThread createPollingThread() { - MessagePollingThread newThread = new MessagePollingThread(); - Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread thread, Throwable throwable) { - ApptentiveLog.e(MESSAGES, "Error polling for messages.", throwable); - MetricModule.sendError(throwable, "Error polling for messages.", null); - } - }; - newThread.setUncaughtExceptionHandler(handler); - newThread.setName("Apptentive-MessagePollingWorker"); - newThread.start(); - return newThread; - } - @Override public void destroy() { - if (pollingThread != null) { - pollingThread.interrupt(); - } + stopPolling(); } private class MessagePollingThread extends Thread { - private long backgroundPollingInterval = -1; - private long foregroundPollingInterval = -1; - private Configuration conf; + private final long backgroundPollingInterval; + private final long foregroundPollingInterval; + private final Configuration conf; public MessagePollingThread() { + super("Message Polling Thread"); conf = Configuration.load(); backgroundPollingInterval = conf.getMessageCenterBgPoll() * 1000; foregroundPollingInterval = conf.getMessageCenterFgPoll() * 1000; @@ -79,26 +48,22 @@ public MessagePollingThread() { public void run() { try { - ApptentiveLog.v(MESSAGES, "Started %s", toString()); + ApptentiveLog.v(MESSAGES, "Started polling messages"); - while (manager.appInForeground.get() && !Thread.currentThread().isInterrupted()) { - MessagePollingThread thread = getAndSetMessagePollingThread(true, false); - if (thread != null && thread != MessagePollingThread.this) { - return; - } + while (!Thread.currentThread().isInterrupted()) { long pollingInterval = messageCenterInForeground.get() ? foregroundPollingInterval : backgroundPollingInterval; if (ApptentiveInternal.getInstance().canShowMessageCenterInternal(getConversation())) { - ApptentiveLog.v(MESSAGES, "Checking server for new messages every %d seconds", pollingInterval / 1000); + ApptentiveLog.v(MESSAGES, "Checking server for new messages..."); manager.fetchAndStoreMessages(messageCenterInForeground.get(), conf.isMessageCenterNotificationPopupEnabled()); } + + ApptentiveLog.v(MESSAGES, "Polling messages in %d sec", pollingInterval / 1000); if (!goToSleep(pollingInterval)) { break; } } } finally { - threadRunning.set(false); - pollingThread = null; - ApptentiveLog.v(MESSAGES, "Stopping MessagePollingThread."); + ApptentiveLog.v(MESSAGES, "Stopped polling messages"); } } } @@ -112,23 +77,13 @@ private boolean goToSleep(long millis) { } } - private void wakeUp() { - ApptentiveLog.v(MESSAGES, "Waking MessagePollingThread."); - MessagePollingThread thread = getAndSetMessagePollingThread(true, false); - if (thread != null && thread.isAlive()) { - thread.interrupt(); - } - } - // Called from main UI thread to create a new worker thread - public void appWentToForeground() { + void appWentToForeground() { startPolling(); } - - public void appWentToBackground() { - // When app goes to background, polling thread will be waken up, and finish and terminate - wakeUp(); + void appWentToBackground() { + stopPolling(); } /** @@ -136,24 +91,26 @@ public void appWentToBackground() { * from the foreground, let the polling interval timeout naturally, at which point the polling interval will become * the background polling interval. * - * @param bInForeground true if the worker should be in foreground polling mode, else false. + * @param foreground true if the worker should be in foreground polling mode, else false. */ - public void setMessageCenterInForeground(boolean bInForeground) { - if (!messageCenterInForeground.getAndSet(bInForeground) && bInForeground) { - /* bInForeground is "true" && messageCenterInForeground was false - */ + public void setMessageCenterInForeground(boolean foreground) { + messageCenterInForeground.set(foreground); + if (foreground) { startPolling(); } } - private void startPolling() { - if (threadRunning.compareAndSet(false, true)) { - // If polling thread isn't running (either terminated or hasn't been created it), create a new one and run - getAndSetMessagePollingThread(true, true); - } else { - // If polling thread has been created but in sleep, wake it up, then continue the while loop and proceed - // with fetching with shorter interval - wakeUp(); + synchronized void startPolling() { + stopPolling(); + + pollingThread = new MessagePollingThread(); + pollingThread.start(); + } + + synchronized void stopPolling() { + if (pollingThread != null) { + pollingThread.interrupt(); + pollingThread = null; } } From 6cb8e83cfd34d9db9e17acfac782a085bea7cf49 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 11 Jul 2017 21:42:58 -0700 Subject: [PATCH 425/465] Fixed storing conversation token --- .../android/sdk/conversation/ConversationManager.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 9950e8625..264dcd0e9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -495,9 +495,10 @@ private void updateMetadataItems(Conversation conversation) { } } - // delete all existing encryption keys + // delete sensitive information for (ConversationMetadataItem item : conversationMetadata) { item.encryptionKey = null; + item.conversationToken = null; } // update the state of the corresponding item @@ -507,7 +508,9 @@ private void updateMetadataItems(Conversation conversation) { conversationMetadata.addItem(item); } item.state = conversation.getState(); - item.conversationToken = conversation.getConversationToken(); // TODO: can it be null for active conversations? + if (conversation.hasActiveState()) { + item.conversationToken = conversation.getConversationToken(); + } // update encryption key (if necessary) if (conversation.hasState(LOGGED_IN)) { @@ -737,6 +740,7 @@ public boolean accept(ConversationMetadataItem item) { }); if (conversationItem != null) { + conversationItem.conversationToken = token; conversationItem.encryptionKey = encryptionKey; activeConversation = loadConversation(conversationItem); } else { From 598aadfab84cc15968bd44ad980d891793293da6 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 11 Jul 2017 22:12:29 -0700 Subject: [PATCH 426/465] Dismiss all interactions when user logs out --- .../apptentive/android/sdk/conversation/ConversationManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 264dcd0e9..246c855ec 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -806,6 +806,7 @@ private void doLogout() { activeConversation.setState(LOGGED_OUT); handleConversationStateChange(activeConversation); activeConversation = null; + ApptentiveInternal.dismissAllInteractions(); break; default: ApptentiveLog.w(CONVERSATION, "Attempted to logout() from Conversation, but the Active Conversation was not in LOGGED_IN state."); From ff9cdb71c6cc5a003f4ab11990c5069c4c9a142c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 12 Jul 2017 12:29:07 -0700 Subject: [PATCH 427/465] Fixed sending person diffs --- .../apptentive/android/sdk/Apptentive.java | 10 ++++- .../sdk/conversation/Conversation.java | 25 +++++++++++ .../android/sdk/model/PersonPayload.java | 13 ++++++ .../android/sdk/storage/CustomData.java | 5 ++- .../android/sdk/storage/Person.java | 23 ++++++++++ .../android/sdk/storage/PersonManager.java | 42 +++++++++++++------ 6 files changed, 102 insertions(+), 16 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 1966bac7d..94f49d262 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -30,6 +30,7 @@ import com.apptentive.android.sdk.module.metric.MetricModule; import com.apptentive.android.sdk.module.rating.IRatingProvider; import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; +import com.apptentive.android.sdk.storage.Person; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; @@ -83,8 +84,8 @@ public static void setPersonEmail(String email) { if (ApptentiveInternal.isApptentiveRegistered()) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { - // FIXME: Make sure Person object diff is sent. conversation.getPerson().setEmail(email); + conversation.schedulePersonUpdate(); } } } @@ -118,8 +119,8 @@ public static void setPersonName(String name) { if (ApptentiveInternal.isApptentiveRegistered()) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { - // FIXME: Make sure Person object diff is sent. conversation.getPerson().setName(name); + conversation.schedulePersonUpdate(); } } } @@ -242,6 +243,7 @@ public static void addCustomPersonData(String key, String value) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { conversation.getPerson().getCustomData().put(key, value); + conversation.schedulePersonUpdate(); } } } @@ -276,6 +278,7 @@ public static void addCustomPersonData(String key, Boolean value) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { conversation.getPerson().getCustomData().put(key, value); + conversation.schedulePersonUpdate(); } } } @@ -285,6 +288,7 @@ private static void addCustomPersonData(String key, Version version) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { conversation.getPerson().getCustomData().put(key, version); + conversation.schedulePersonUpdate(); } } } @@ -294,6 +298,7 @@ private static void addCustomPersonData(String key, DateTime dateTime) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { conversation.getPerson().getCustomData().remove(key); + conversation.schedulePersonUpdate(); } } } @@ -308,6 +313,7 @@ public static void removeCustomPersonData(String key) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { conversation.getPerson().getCustomData().remove(key); + conversation.schedulePersonUpdate(); } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index f3371345a..d2657abd7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -16,6 +16,7 @@ import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.debug.Assert; import com.apptentive.android.sdk.model.Payload; +import com.apptentive.android.sdk.model.PersonPayload; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; import com.apptentive.android.sdk.module.engagement.interaction.model.InteractionManifest; import com.apptentive.android.sdk.module.engagement.interaction.model.Interactions; @@ -30,6 +31,7 @@ import com.apptentive.android.sdk.storage.IntegrationConfig; import com.apptentive.android.sdk.storage.IntegrationConfigItem; import com.apptentive.android.sdk.storage.Person; +import com.apptentive.android.sdk.storage.PersonManager; import com.apptentive.android.sdk.storage.Sdk; import com.apptentive.android.sdk.storage.SerializerException; import com.apptentive.android.sdk.storage.VersionHistory; @@ -46,6 +48,7 @@ import java.io.File; import static com.apptentive.android.sdk.debug.Assert.assertFail; +import static com.apptentive.android.sdk.debug.Assert.assertNotNull; import static com.apptentive.android.sdk.debug.Assert.notNull; import static com.apptentive.android.sdk.debug.Tester.dispatchDebugEvent; import static com.apptentive.android.sdk.ApptentiveLogTag.*; @@ -344,6 +347,28 @@ public void destroy() { //endregion + //region Diffs & Updates + + private final DispatchTask personUpdateTask = new DispatchTask() { + @Override + protected void execute() { + Person lastSentPerson = getLastSentPerson(); + Person currentPerson = getPerson(); + assertNotNull(currentPerson, "Current person object is null"); + PersonPayload personPayload = PersonManager.getDiffPayload(lastSentPerson, currentPerson); + if (personPayload != null) { + addPayload(personPayload); + setLastSentPerson(currentPerson != null ? currentPerson.clone() : null); + } + } + }; + + public void schedulePersonUpdate() { + DispatchQueue.mainQueue().dispatchAsyncOnce(personUpdateTask); + } + + //endregion + //region Getters & Setters public String getLocalIdentifier() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index cd28b11d7..b379984f3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -6,10 +6,12 @@ package com.apptentive.android.sdk.model; +import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; +import org.json.JSONObject; public class PersonPayload extends JsonPayload { @@ -37,6 +39,17 @@ public PersonPayload(String json) throws JSONException { //region Http-request + @Override + protected JSONObject marshallForSending() { + JSONObject object = new JSONObject(); + try { + object.put(KEY, super.marshallForSending()); + } catch (JSONException e) { + ApptentiveLog.e(e, "Error creating person object"); + } + return object; + } + @Override public String getHttpEndPoint(String conversationId) { return StringUtils.format("/conversations/%s/person", conversationId); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java index abae4e027..e49c4e0ff 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/CustomData.java @@ -6,6 +6,8 @@ package com.apptentive.android.sdk.storage; +import com.apptentive.android.sdk.ApptentiveLog; + import org.json.JSONException; import java.io.Serializable; @@ -70,8 +72,9 @@ public com.apptentive.android.sdk.model.CustomData toJson() { for (String key : keys) { ret.put(key, get(key)); } + return ret; } catch (JSONException e) { - // This can't happen. + ApptentiveLog.e(e, "Exception while creating custom data"); } return null; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java index 9efe02d66..25b9febe2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Person.java @@ -173,4 +173,27 @@ public void setCustomData(CustomData customData) { } //endregion + + //region Clone + + public Person clone() { + Person person = new Person(); + person.id = id; + person.email = email; + person.name = name; + person.facebookId = facebookId; + person.phoneNumber = phoneNumber; + person.street = street; + person.city = city; + person.zip = zip; + person.country = country; + person.birthday = birthday; + if (customData != null) { + person.customData.putAll(customData); + } + person.listener = listener; + return person; + } + + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java index 12813c437..127f85140 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PersonManager.java @@ -10,58 +10,74 @@ public class PersonManager { - public static PersonPayload getDiffPayload(com.apptentive.android.sdk.storage.Person oldPerson, com.apptentive.android.sdk.storage.Person newPerson) { + public static PersonPayload getDiffPayload(Person oldPerson, Person newPerson) { if (newPerson == null) { return null; } PersonPayload ret = new PersonPayload(); + boolean changed = false; - if (oldPerson == null || !oldPerson.getId().equals(newPerson.getId())) { + if (oldPerson == null || !equal(oldPerson.getId(), newPerson.getId())) { ret.setId(newPerson.getId()); + changed = true; } - if (oldPerson == null || !oldPerson.getEmail().equals(newPerson.getEmail())) { + if (oldPerson == null || !equal(oldPerson.getEmail(), newPerson.getEmail())) { ret.setEmail(newPerson.getEmail()); + changed = true; } - if (oldPerson == null || !oldPerson.getName().equals(newPerson.getName())) { + if (oldPerson == null || !equal(oldPerson.getName(), newPerson.getName())) { ret.setName(newPerson.getName()); + changed = true; } - if (oldPerson == null || !oldPerson.getFacebookId().equals(newPerson.getFacebookId())) { + if (oldPerson == null || !equal(oldPerson.getFacebookId(), newPerson.getFacebookId())) { ret.setFacebookId(newPerson.getFacebookId()); + changed = true; } - if (oldPerson == null || !oldPerson.getPhoneNumber().equals(newPerson.getPhoneNumber())) { + if (oldPerson == null || !equal(oldPerson.getPhoneNumber(), newPerson.getPhoneNumber())) { ret.setPhoneNumber(newPerson.getPhoneNumber()); + changed = true; } - if (oldPerson == null || !oldPerson.getStreet().equals(newPerson.getStreet())) { + if (oldPerson == null || !equal(oldPerson.getStreet(), newPerson.getStreet())) { ret.setStreet(newPerson.getStreet()); + changed = true; } - if (oldPerson == null || !oldPerson.getCity().equals(newPerson.getCity())) { + if (oldPerson == null || !equal(oldPerson.getCity(), newPerson.getCity())) { ret.setCity(newPerson.getCity()); + changed = true; } - if (oldPerson == null || !oldPerson.getZip().equals(newPerson.getZip())) { + if (oldPerson == null || !equal(oldPerson.getZip(), newPerson.getZip())) { ret.setZip(newPerson.getZip()); + changed = true; } - if (oldPerson == null || !oldPerson.getCountry().equals(newPerson.getCountry())) { + if (oldPerson == null || !equal(oldPerson.getCountry(), newPerson.getCountry())) { ret.setCountry(newPerson.getCountry()); + changed = true; } - if (oldPerson == null || !oldPerson.getBirthday().equals(newPerson.getBirthday())) { + if (oldPerson == null || !equal(oldPerson.getBirthday(), newPerson.getBirthday())) { ret.setBirthday(newPerson.getBirthday()); + changed = true; } - if (oldPerson == null || !oldPerson.getCustomData().equals(newPerson.getCustomData())) { + if (oldPerson == null || !equal(oldPerson.getCustomData(), newPerson.getCustomData())) { CustomData customData = newPerson.getCustomData(); ret.setCustomData(customData != null ? customData.toJson() : null); + changed = true; } - return ret; + return changed ? ret : null; + } + + private static boolean equal(Object a, Object b) { + return a == null && b == null || a != null && b != null && a.equals(b); } } From 7ce0c41617701f7b83e7885acffc249590edbf09 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 12 Jul 2017 13:44:14 -0700 Subject: [PATCH 428/465] Fixed sending device diffs --- .../apptentive/android/sdk/Apptentive.java | 2 + .../sdk/conversation/Conversation.java | 20 +++++ .../sdk/conversation/ConversationManager.java | 2 +- .../android/sdk/storage/Device.java | 35 +++++++- .../android/sdk/storage/DeviceManager.java | 83 ++++++++++++------- 5 files changed, 112 insertions(+), 30 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 94f49d262..f51be67b6 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -157,6 +157,7 @@ public static void addCustomDeviceData(String key, String value) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { conversation.getDevice().getCustomData().put(key, value); + conversation.scheduleDeviceUpdate(); } } } @@ -223,6 +224,7 @@ public static void removeCustomDeviceData(String key) { Conversation conversation = ApptentiveInternal.getInstance().getConversation(); if (conversation != null) { conversation.getDevice().getCustomData().remove(key); + conversation.scheduleDeviceUpdate(); } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index d2657abd7..a2588f5f1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -15,6 +15,7 @@ import com.apptentive.android.sdk.comm.ApptentiveClient; import com.apptentive.android.sdk.comm.ApptentiveHttpResponse; import com.apptentive.android.sdk.debug.Assert; +import com.apptentive.android.sdk.model.DevicePayload; import com.apptentive.android.sdk.model.Payload; import com.apptentive.android.sdk.model.PersonPayload; import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; @@ -25,6 +26,7 @@ import com.apptentive.android.sdk.storage.AppRelease; import com.apptentive.android.sdk.storage.DataChangedListener; import com.apptentive.android.sdk.storage.Device; +import com.apptentive.android.sdk.storage.DeviceManager; import com.apptentive.android.sdk.storage.EncryptedFileSerializer; import com.apptentive.android.sdk.storage.EventData; import com.apptentive.android.sdk.storage.FileSerializer; @@ -363,10 +365,28 @@ protected void execute() { } }; + private final DispatchTask deviceUpdateTask = new DispatchTask() { + @Override + protected void execute() { + Device lastSentDevice = getLastSentDevice(); + Device currentDevice = getDevice(); + assertNotNull(currentDevice, "Current device object is null"); + DevicePayload devicePayload = DeviceManager.getDiffPayload(lastSentDevice, currentDevice); + if (devicePayload != null) { + addPayload(devicePayload); + setLastSentDevice(currentDevice != null ? currentDevice.clone() : null); + } + } + }; + public void schedulePersonUpdate() { DispatchQueue.mainQueue().dispatchAsyncOnce(personUpdateTask); } + public void scheduleDeviceUpdate() { + DispatchQueue.mainQueue().dispatchAsyncOnce(deviceUpdateTask); + } + //endregion //region Getters & Setters diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 246c855ec..f3c984da1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -383,7 +383,7 @@ public void onFinish(HttpJsonRequest request) { conversation.setConversationToken(conversationToken); conversation.setConversationId(conversationId); conversation.setDevice(device); - conversation.setLastSentDevice(device); + conversation.setLastSentDevice(device.clone()); conversation.setAppRelease(appRelease); conversation.setSdk(sdk); conversation.setLastSeenSdkVersion(sdk.getVersion()); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java index bf4cd9ff1..c4d04e3a2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java @@ -63,6 +63,39 @@ public void notifyDataChanged() { public void onDataChanged() { notifyDataChanged(); } + + public Device clone() { + Device clone = new Device(); + clone.uuid = uuid; + clone.osName = osName; + clone.osVersion = osVersion; + clone.osBuild = osBuild; + clone.osApiLevel = osApiLevel; + clone.manufacturer = manufacturer; + clone.model = model; + clone.board = board; + clone.product = product; + clone.brand = brand; + clone.cpu = cpu; + clone.device = device; + clone.carrier = carrier; + clone.currentCarrier = currentCarrier; + clone.networkType = networkType; + clone.buildType = buildType; + clone.buildId = buildId; + clone.bootloaderVersion = bootloaderVersion; + clone.radioVersion = radioVersion; + if (customData != null) { + clone.customData.putAll(customData); + } + clone.localeCountryCode = localeCountryCode; + clone.localeLanguageCode = localeLanguageCode; + clone.localeRaw = localeRaw; + clone.utcOffset = utcOffset; + clone.integrationConfig = integrationConfig; // TODO: make a deep clone + clone.listener = listener; + return clone; + } //region Getters & Setters @@ -339,6 +372,6 @@ public void setIntegrationConfig(IntegrationConfig integrationConfig) { notifyDataChanged(); } -//endregion + //endregion } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java index 82f876b56..5d40e5390 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/DeviceManager.java @@ -64,117 +64,144 @@ public static Device generateNewDevice(Context context) { return device; } - public static void onSentDeviceInfo() { - } - public static DevicePayload getDiffPayload(com.apptentive.android.sdk.storage.Device oldDevice, com.apptentive.android.sdk.storage.Device newDevice) { if (newDevice == null) { return null; } DevicePayload ret = new DevicePayload(); + boolean changed = false; - if (oldDevice == null || !oldDevice.getUuid().equals(newDevice.getUuid())) { + if (oldDevice == null || !equal(oldDevice.getUuid(), newDevice.getUuid())) { ret.setUuid(newDevice.getUuid()); + changed = true; } - if (oldDevice == null || !oldDevice.getOsName().equals(newDevice.getOsName())) { + if (oldDevice == null || !equal(oldDevice.getOsName(), newDevice.getOsName())) { ret.setOsName(newDevice.getOsName()); + changed = true; } - if (oldDevice == null || !oldDevice.getOsVersion().equals(newDevice.getOsVersion())) { + if (oldDevice == null || !equal(oldDevice.getOsVersion(), newDevice.getOsVersion())) { ret.setOsVersion(newDevice.getOsVersion()); + changed = true; } - if (oldDevice == null || !oldDevice.getOsBuild().equals(newDevice.getOsBuild())) { + if (oldDevice == null || !equal(oldDevice.getOsBuild(), newDevice.getOsBuild())) { ret.setOsBuild(newDevice.getOsBuild()); + changed = true; } if (oldDevice == null || oldDevice.getOsApiLevel() != newDevice.getOsApiLevel()) { ret.setOsApiLevel(String.valueOf(newDevice.getOsApiLevel())); + changed = true; } - if (oldDevice == null || !oldDevice.getManufacturer().equals(newDevice.getManufacturer())) { + if (oldDevice == null || !equal(oldDevice.getManufacturer(), newDevice.getManufacturer())) { ret.setManufacturer(newDevice.getManufacturer()); + changed = true; } - if (oldDevice == null || !oldDevice.getModel().equals(newDevice.getModel())) { + if (oldDevice == null || !equal(oldDevice.getModel(), newDevice.getModel())) { ret.setModel(newDevice.getModel()); + changed = true; } - if (oldDevice == null || !oldDevice.getBoard().equals(newDevice.getBoard())) { + if (oldDevice == null || !equal(oldDevice.getBoard(), newDevice.getBoard())) { ret.setBoard(newDevice.getBoard()); + changed = true; } - if (oldDevice == null || !oldDevice.getProduct().equals(newDevice.getProduct())) { + if (oldDevice == null || !equal(oldDevice.getProduct(), newDevice.getProduct())) { ret.setProduct(newDevice.getProduct()); + changed = true; } - if (oldDevice == null || !oldDevice.getBrand().equals(newDevice.getBrand())) { + if (oldDevice == null || !equal(oldDevice.getBrand(), newDevice.getBrand())) { ret.setBrand(newDevice.getBrand()); + changed = true; } - if (oldDevice == null || !oldDevice.getCpu().equals(newDevice.getCpu())) { + if (oldDevice == null || !equal(oldDevice.getCpu(), newDevice.getCpu())) { ret.setCpu(newDevice.getCpu()); + changed = true; } - if (oldDevice == null || !oldDevice.getDevice().equals(newDevice.getDevice())) { + if (oldDevice == null || !equal(oldDevice.getDevice(), newDevice.getDevice())) { ret.setDevice(newDevice.getDevice()); + changed = true; } - if (oldDevice == null || !oldDevice.getCarrier().equals(newDevice.getCarrier())) { + if (oldDevice == null || !equal(oldDevice.getCarrier(), newDevice.getCarrier())) { ret.setCarrier(newDevice.getCarrier()); + changed = true; } - if (oldDevice == null || !oldDevice.getCurrentCarrier().equals(newDevice.getCurrentCarrier())) { + if (oldDevice == null || !equal(oldDevice.getCurrentCarrier(), newDevice.getCurrentCarrier())) { ret.setCurrentCarrier(newDevice.getCurrentCarrier()); + changed = true; } - if (oldDevice == null || !oldDevice.getNetworkType().equals(newDevice.getNetworkType())) { + if (oldDevice == null || !equal(oldDevice.getNetworkType(), newDevice.getNetworkType())) { ret.setNetworkType(newDevice.getNetworkType()); + changed = true; } - if (oldDevice == null || !oldDevice.getBuildType().equals(newDevice.getBuildType())) { + if (oldDevice == null || !equal(oldDevice.getBuildType(), newDevice.getBuildType())) { ret.setBuildType(newDevice.getBuildType()); + changed = true; } - if (oldDevice == null || !oldDevice.getBuildId().equals(newDevice.getBuildId())) { + if (oldDevice == null || !equal(oldDevice.getBuildId(), newDevice.getBuildId())) { ret.setBuildId(newDevice.getBuildId()); + changed = true; } - if (oldDevice == null || !oldDevice.getBootloaderVersion().equals(newDevice.getBootloaderVersion())) { + if (oldDevice == null || !equal(oldDevice.getBootloaderVersion(), newDevice.getBootloaderVersion())) { ret.setBootloaderVersion(newDevice.getBootloaderVersion()); + changed = true; } - if (oldDevice == null || !oldDevice.getRadioVersion().equals(newDevice.getRadioVersion())) { + if (oldDevice == null || !equal(oldDevice.getRadioVersion(), newDevice.getRadioVersion())) { ret.setRadioVersion(newDevice.getRadioVersion()); + changed = true; } - if (oldDevice == null || !oldDevice.getCustomData().equals(newDevice.getCustomData())) { + if (oldDevice == null || !equal(oldDevice.getCustomData(), newDevice.getCustomData())) { CustomData customData = newDevice.getCustomData(); ret.setCustomData(customData != null ? customData.toJson() : null); + changed = true; } - if (oldDevice == null || !oldDevice.getLocaleCountryCode().equals(newDevice.getLocaleCountryCode())) { + if (oldDevice == null || !equal(oldDevice.getLocaleCountryCode(), newDevice.getLocaleCountryCode())) { ret.setLocaleCountryCode(newDevice.getLocaleCountryCode()); + changed = true; } - if (oldDevice == null || !oldDevice.getLocaleLanguageCode().equals(newDevice.getLocaleLanguageCode())) { + if (oldDevice == null || !equal(oldDevice.getLocaleLanguageCode(), newDevice.getLocaleLanguageCode())) { ret.setLocaleLanguageCode(newDevice.getLocaleLanguageCode()); + changed = true; } - if (oldDevice == null || !oldDevice.getLocaleRaw().equals(newDevice.getLocaleRaw())) { + if (oldDevice == null || !equal(oldDevice.getLocaleRaw(), newDevice.getLocaleRaw())) { ret.setLocaleRaw(newDevice.getLocaleRaw()); + changed = true; } - if (oldDevice == null || !oldDevice.getUtcOffset().equals(newDevice.getUtcOffset())) { + if (oldDevice == null || !equal(oldDevice.getUtcOffset(), newDevice.getUtcOffset())) { ret.setUtcOffset(newDevice.getUtcOffset()); + changed = true; } - if (oldDevice == null || !oldDevice.getIntegrationConfig().equals(newDevice.getIntegrationConfig())) { + if (oldDevice == null || !equal(oldDevice.getIntegrationConfig(), newDevice.getIntegrationConfig())) { IntegrationConfig integrationConfig = newDevice.getIntegrationConfig(); ret.setIntegrationConfig(integrationConfig != null ? integrationConfig.toJson() : null); + changed = true; } - return ret; + return changed ? ret : null; + } + + private static boolean equal(Object a, Object b) { + return a == null && b == null || a != null && b != null && a.equals(b); } } From 5e10b7fbf8f1ce3564e2c207713ce1ba0d194ce1 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 12 Jul 2017 14:41:05 -0700 Subject: [PATCH 429/465] Fixed conversation request payload format --- .../sdk/conversation/ConversationManager.java | 3 +- .../sdk/model/ConversationTokenRequest.java | 35 +++++++++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index f3c984da1..8c5f1d0d4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -350,8 +350,7 @@ private HttpRequest fetchConversationToken(final Conversation conversation) { final AppRelease appRelease = ApptentiveInternal.getInstance().getAppRelease(); conversationTokenRequest.setDevice(DeviceManager.getDiffPayload(null, device)); - conversationTokenRequest.setSdk(SdkManager.getPayload(sdk)); - conversationTokenRequest.setAppRelease(AppReleaseManager.getPayload(appRelease)); + conversationTokenRequest.setSdkAndAppRelease(SdkManager.getPayload(sdk), AppReleaseManager.getPayload(appRelease)); HttpRequest request = getHttpClient() .getConversationToken(conversationTokenRequest, new HttpRequest.Listener() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java index 86d65ca01..630813e77 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java @@ -7,10 +7,13 @@ package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.util.StringUtils; import org.json.JSONException; import org.json.JSONObject; +import java.util.Iterator; + public class ConversationTokenRequest extends JSONObject { public ConversationTokenRequest() { } @@ -39,11 +42,37 @@ public void setPerson(PersonPayload person) { } } - public void setAppRelease(AppReleasePayload appRelease) { + public void setSdkAndAppRelease(SdkPayload sdkPayload, AppReleasePayload appReleasePayload) { + JSONObject combinedJson = new JSONObject(); + + if (sdkPayload != null) { + Iterator keys = sdkPayload.getJsonObject().keys(); + while (keys.hasNext()) { + String key = keys.next(); + try { + combinedJson.put("sdk_" + key, sdkPayload.getJsonObject().opt(key)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + } + if (appReleasePayload != null) { + Iterator keys = appReleasePayload.getJsonObject().keys(); + while (keys.hasNext()) { + String key = keys.next(); + try { + combinedJson.put(key, appReleasePayload.getJsonObject().opt(key)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + try { - put(AppReleasePayload.KEY, appRelease == null ? null : appRelease.getJsonObject()); + put("app_release", combinedJson); } catch (JSONException e) { - ApptentiveLog.e("Error adding %s to ConversationTokenRequest", AppReleasePayload.KEY); + e.printStackTrace(); } } } From b6fc46e6967303d7726ea84e4bd4e6d19e14aa8d Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 12 Jul 2017 19:47:58 -0700 Subject: [PATCH 430/465] Fixed handling legacy pending conversations --- .../sdk/conversation/ConversationData.java | 4 --- .../sdk/conversation/ConversationManager.java | 16 +++++++----- .../conversation/ConversationMetadata.java | 3 ++- .../ConversationMetadataItem.java | 8 ++---- .../android/sdk/storage/EventData.java | 26 +++++++++---------- 5 files changed, 27 insertions(+), 30 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java index dd11dbf5d..85449a37c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationData.java @@ -89,10 +89,6 @@ public String getConversationToken() { } public void setConversationToken(String conversationToken) { - if (conversationToken == null) { - throw new IllegalArgumentException("Conversation token is null"); - } - if (!StringUtils.equal(this.conversationToken, conversationToken)) { this.conversationToken = conversationToken; notifyDataChanged(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 8c5f1d0d4..24d3ae8ae 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -173,11 +173,21 @@ private Conversation loadActiveConversationGuarded() throws IOException, Seriali // check if we have a 'pending' anonymous conversation item = conversationMetadata.findItem(ANONYMOUS_PENDING); if (item != null) { + ApptentiveLog.v(CONVERSATION, "Loading anonymous pending conversation..."); final Conversation conversation = loadConversation(item); fetchConversationToken(conversation); return conversation; } + // check if we have a 'legacy pending' conversation + item = conversationMetadata.findItem(LEGACY_PENDING); + if (item != null) { + ApptentiveLog.v(CONVERSATION, "Loading legacy pending conversation..."); + final Conversation conversation = loadConversation(item); + fetchLegacyConversation(conversation); + return conversation; + } + // Check for only LOGGED_OUT Conversations if (conversationMetadata.hasItems()) { ApptentiveLog.v(CONVERSATION, "Can't load conversation: only 'logged-out' conversations available"); @@ -479,12 +489,6 @@ public boolean accept(ConversationMetadataItem item) { } private void updateMetadataItems(Conversation conversation) { - - if (conversation.hasState(ANONYMOUS_PENDING, LEGACY_PENDING)) { - ApptentiveLog.v(CONVERSATION, "Skipping updating metadata since conversation is %s", conversation.getState()); - return; - } - // if the conversation is 'logged-in' we should not have any other 'logged-in' items in metadata if (conversation.hasState(LOGGED_IN)) { for (ConversationMetadataItem item : conversationMetadata) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java index 2de054a8f..5b56f1b9f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java @@ -1,6 +1,7 @@ package com.apptentive.android.sdk.conversation; import com.apptentive.android.sdk.serialization.SerializableObject; +import com.apptentive.android.sdk.util.StringUtils; import java.io.DataInput; import java.io.DataOutput; @@ -66,7 +67,7 @@ ConversationMetadataItem findItem(final Conversation conversation) { return findItem(new Filter() { @Override public boolean accept(ConversationMetadataItem item) { - return item.conversationId.equals(conversation.getConversationId()); + return StringUtils.equal(item.conversationId, conversation.getConversationId()); } }); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index 024330a58..a472768ff 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -52,10 +52,6 @@ public class ConversationMetadataItem implements SerializableObject { String userId; public ConversationMetadataItem(String conversationId, File dataFile, File messagesFile) { - if (StringUtils.isNullOrEmpty(conversationId)) { - throw new IllegalArgumentException("Conversation id is null or empty"); - } - if (dataFile == null) { throw new IllegalArgumentException("Data file is null"); } @@ -70,7 +66,7 @@ public ConversationMetadataItem(String conversationId, File dataFile, File messa } public ConversationMetadataItem(DataInput in) throws IOException { - conversationId = in.readUTF(); + conversationId = readNullableUTF(in); conversationToken = readNullableUTF(in); dataFile = new File(in.readUTF()); messagesFile = new File(in.readUTF()); @@ -81,7 +77,7 @@ public ConversationMetadataItem(DataInput in) throws IOException { @Override public void writeExternal(DataOutput out) throws IOException { - out.writeUTF(conversationId); + writeNullableUTF(out, conversationId); writeNullableUTF(out, conversationToken); out.writeUTF(dataFile.getAbsolutePath()); out.writeUTF(messagesFile.getAbsolutePath()); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java index bd1f09c3c..77d12a9f0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/EventData.java @@ -16,8 +16,8 @@ public class EventData implements Saveable { private static final long serialVersionUID = 1L; - private Map events; - private Map interactions; + private Map events; // we need a synchronized access to the map to avoid concurrent modification exceptions + private Map interactions; // we need a synchronized access to the map to avoid concurrent modification exceptions public EventData() { events = new HashMap(); @@ -64,7 +64,7 @@ public synchronized void storeInteractionForCurrentAppVersion(double timestamp, notifyDataChanged(); } - public Long getEventCountTotal(String eventLabel) { + public synchronized Long getEventCountTotal(String eventLabel) { EventRecord eventRecord = events.get(eventLabel); if (eventRecord == null) { return 0L; @@ -72,7 +72,7 @@ public Long getEventCountTotal(String eventLabel) { return eventRecord.getTotal(); } - public Long getInteractionCountTotal(String interactionId) { + public synchronized Long getInteractionCountTotal(String interactionId) { EventRecord eventRecord = interactions.get(interactionId); if (eventRecord != null) { return eventRecord.getTotal(); @@ -80,7 +80,7 @@ public Long getInteractionCountTotal(String interactionId) { return 0L; } - public Double getTimeOfLastEventInvocation(String eventLabel) { + public synchronized Double getTimeOfLastEventInvocation(String eventLabel) { EventRecord eventRecord = events.get(eventLabel); if (eventRecord != null) { return eventRecord.getLast(); @@ -88,7 +88,7 @@ public Double getTimeOfLastEventInvocation(String eventLabel) { return null; } - public Double getTimeOfLastInteractionInvocation(String interactionId) { + public synchronized Double getTimeOfLastInteractionInvocation(String interactionId) { EventRecord eventRecord = interactions.get(interactionId); if (eventRecord != null) { return eventRecord.getLast(); @@ -96,7 +96,7 @@ public Double getTimeOfLastInteractionInvocation(String interactionId) { return null; } - public Long getEventCountForVersionCode(String eventLabel, Integer versionCode) { + public synchronized Long getEventCountForVersionCode(String eventLabel, Integer versionCode) { EventRecord eventRecord = events.get(eventLabel); if (eventRecord != null) { return eventRecord.getCountForVersionCode(versionCode); @@ -104,7 +104,7 @@ public Long getEventCountForVersionCode(String eventLabel, Integer versionCode) return 0L; } - public Long getInteractionCountForVersionCode(String interactionId, Integer versionCode) { + public synchronized Long getInteractionCountForVersionCode(String interactionId, Integer versionCode) { EventRecord eventRecord = interactions.get(interactionId); if (eventRecord != null) { return eventRecord.getCountForVersionCode(versionCode); @@ -112,7 +112,7 @@ public Long getInteractionCountForVersionCode(String interactionId, Integer vers return 0L; } - public Long getEventCountForVersionName(String eventLabel, String versionName) { + public synchronized Long getEventCountForVersionName(String eventLabel, String versionName) { EventRecord eventRecord = events.get(eventLabel); if (eventRecord != null) { return eventRecord.getCountForVersionName(versionName); @@ -120,7 +120,7 @@ public Long getEventCountForVersionName(String eventLabel, String versionName) { return 0L; } - public Long getInteractionCountForVersionName(String interactionId, String versionName) { + public synchronized Long getInteractionCountForVersionName(String interactionId, String versionName) { EventRecord eventRecord = interactions.get(interactionId); if (eventRecord != null) { return eventRecord.getCountForVersionName(versionName); @@ -129,7 +129,7 @@ public Long getInteractionCountForVersionName(String interactionId, String versi } - public String toString() { + public synchronized String toString() { StringBuilder builder = new StringBuilder(); builder.append("Events: "); for (String key : events.keySet()) { @@ -147,7 +147,7 @@ public String toString() { /** * Used for migration only. */ - public void setEvents(Map events) { + public synchronized void setEvents(Map events) { this.events = events; notifyDataChanged(); } @@ -155,7 +155,7 @@ public void setEvents(Map events) { /** * Used for migration only. */ - public void setInteractions(Map interactions) { + public synchronized void setInteractions(Map interactions) { this.interactions = interactions; notifyDataChanged(); } From 9531648bdfc720c6cfab4a4afb1893e3f3a718be Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 12 Jul 2017 20:59:37 -0700 Subject: [PATCH 431/465] Fixed migrating messages --- .../module/messagecenter/MessageManager.java | 4 ++ .../sdk/storage/ApptentiveDatabaseHelper.java | 58 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 8224fb3e7..5445459f0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -223,6 +223,10 @@ public void sendMessage(ApptentiveMessage apptentiveMessage) { conversation.addPayload(apptentiveMessage); } + public void addMessages(ApptentiveMessage[] messages) { + messageStore.addOrUpdateMessages(messages); + } + /** * This doesn't need to be run during normal program execution. Testing only. */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index 53e82d067..2d270e40a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -14,6 +14,7 @@ import android.database.sqlite.SQLiteOpenHelper; import android.text.TextUtils; +import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.model.ApptentiveMessage; import com.apptentive.android.sdk.model.CompoundMessage; @@ -22,11 +23,15 @@ import com.apptentive.android.sdk.model.PayloadData; import com.apptentive.android.sdk.model.PayloadType; import com.apptentive.android.sdk.model.StoredFile; +import com.apptentive.android.sdk.module.messagecenter.MessageManager; +import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.storage.legacy.LegacyPayloadFactory; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.Util; +import com.apptentive.android.sdk.util.threading.DispatchQueue; +import com.apptentive.android.sdk.util.threading.DispatchTask; import org.json.JSONException; import org.json.JSONObject; @@ -39,6 +44,7 @@ import static com.apptentive.android.sdk.ApptentiveLogTag.DATABASE; import static com.apptentive.android.sdk.ApptentiveLogTag.PAYLOADS; import static com.apptentive.android.sdk.debug.Assert.assertFalse; +import static com.apptentive.android.sdk.debug.Assert.assertNotNull; import static com.apptentive.android.sdk.debug.Assert.notNull; import static com.apptentive.android.sdk.util.Constants.PAYLOAD_DATA_FILE_SUFFIX; @@ -449,8 +455,12 @@ private void upgradeVersion2to3(SQLiteDatabase db) { db.insert(PayloadEntry.TABLE_NAME, null, values); } - // 5. Finally, delete the temporary legacy table - ApptentiveLog.vv(DATABASE, "\t5. Delete temporary \"legacy_payloads\" database."); + // 5. Migrate messages + ApptentiveLog.vv(DATABASE, "\t6. Migrating messages."); + migrateMessages(db); + + // 6. Finally, delete the temporary legacy table + ApptentiveLog.vv(DATABASE, "\t6. Delete temporary \"legacy_payloads\" database."); db.execSQL(DELETE_LEGACY_PAYLOAD_TABLE); db.setTransactionSuccessful(); } catch (Exception e) { @@ -463,6 +473,50 @@ private void upgradeVersion2to3(SQLiteDatabase db) { } } + private void migrateMessages(SQLiteDatabase db) { + try { + final List messages = getAllMessages(db); + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + MessageManager messageManager = ApptentiveInternal.getInstance().getMessageManager(); + assertNotNull(messageManager, "Can't migrate messages: message manager is not initialized"); + if (messageManager != null) { + messageManager.addMessages(messages.toArray(new ApptentiveMessage[messages.size()])); + } + } + }); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while trying to migrate messages"); + } + } + + private List getAllMessages(SQLiteDatabase db) { + List messages = new ArrayList<>(); + Cursor cursor = null; + try { + cursor = db.rawQuery(QUERY_MESSAGE_GET_ALL_IN_ORDER, null); + while (cursor.moveToNext()) { + String json = cursor.getString(6); + ApptentiveMessage message = MessageFactory.fromJson(json); + if (message == null) { + ApptentiveLog.e("Error parsing Record json from database: %s", json); + continue; + } + message.setId(cursor.getString(1)); + message.setCreatedAt(cursor.getDouble(2)); + message.setNonce(cursor.getString(3)); + message.setState(ApptentiveMessage.State.parse(cursor.getString(4))); + message.setRead(cursor.getInt(5) == TRUE); + messages.add(message); + } + } finally { + ensureClosed(cursor); + } + return messages; + } + + //endregion //region Payloads From cf50bdb564d7ab891cf50025401c64944a863400 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 12 Jul 2017 22:22:51 -0700 Subject: [PATCH 432/465] Fix network error event in message center --- .../android/sdk/module/messagecenter/MessageManager.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 5445459f0..f2cfc88b8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -305,6 +305,10 @@ public void onSentMessage(String nonce, int responseCode, JSONObject responseJso final boolean isSuccessful = responseCode >= 200 && responseCode < 300; final boolean isRejectedTemporarily = !(isSuccessful || isRejectedPermanently); + if (responseCode == -1) { + pauseSending(SEND_PAUSE_REASON_NETWORK); + } + if (isRejectedPermanently || responseCode == -1) { if (apptentiveMessage instanceof CompoundMessage) { apptentiveMessage.setCreatedAt(Double.MIN_VALUE); From eed9ca4889e9109e29a7300f4df77dccd6a7fd71 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 13 Jul 2017 13:29:04 -0700 Subject: [PATCH 433/465] Retry failed paylods after delay --- .../sdk/storage/ApptentiveTaskManager.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 45137f244..ce5210964 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -185,12 +185,15 @@ public void onFinishSending(PayloadSender sender, PayloadData payload, boolean c ApptentiveLog.v(PAYLOADS, "Payload sending failed: %s\n%s", payload, errorMessage); if (appInBackground) { ApptentiveLog.v(PAYLOADS, "The app went to the background so we won't remove the payload from the queue"); + retrySending(5000); return; } else if (responseCode == -1) { ApptentiveLog.v(PAYLOADS, "Payload failed to send due to a connection error."); + retrySending(5000); return; } else if (responseCode > 500) { ApptentiveLog.v(PAYLOADS, "Payload failed to send due to a server error."); + retrySending(5000); return; } } else { @@ -201,6 +204,22 @@ public void onFinishSending(PayloadSender sender, PayloadData payload, boolean c deletePayload(payload.getNonce()); } + private void retrySending(long delayMillis) { + ApptentiveLog.d(PAYLOADS, "Retry sending payloads in %d ms", delayMillis); + DispatchQueue.backgroundQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + singleThreadExecutor.execute(new Runnable() { + @Override + public void run() { + ApptentiveLog.d(PAYLOADS, "Retrying sending payloads"); + sendNextPayloadSync(); + } + }); + } + }, delayMillis); + } + //endregion //region Payload Sending From 6a7c77a9f8781264f4b05f09bf8a5e5438d2786e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 13 Jul 2017 14:02:33 -0700 Subject: [PATCH 434/465] Fixed message error state --- .../android/sdk/module/messagecenter/MessageManager.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index f2cfc88b8..76f123776 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -305,10 +305,6 @@ public void onSentMessage(String nonce, int responseCode, JSONObject responseJso final boolean isSuccessful = responseCode >= 200 && responseCode < 300; final boolean isRejectedTemporarily = !(isSuccessful || isRejectedPermanently); - if (responseCode == -1) { - pauseSending(SEND_PAUSE_REASON_NETWORK); - } - if (isRejectedPermanently || responseCode == -1) { if (apptentiveMessage instanceof CompoundMessage) { apptentiveMessage.setCreatedAt(Double.MIN_VALUE); @@ -396,6 +392,10 @@ public void onReceiveNotification(ApptentiveNotification notification) { final PayloadData payload = notification.getRequiredUserInfo(NOTIFICATION_KEY_PAYLOAD, PayloadData.class); final Integer responseCode = notification.getRequiredUserInfo(NOTIFICATION_KEY_RESPONSE_CODE, Integer.class); final JSONObject responseData = successful ? notification.getRequiredUserInfo(NOTIFICATION_KEY_RESPONSE_DATA, JSONObject.class) : null; + if (responseCode == -1) { + pauseSending(SEND_PAUSE_REASON_NETWORK); + } + if (payload.getType().equals(PayloadType.message)) { onSentMessage(payload.getNonce(), responseCode, responseData); } From 81b5f6370c7abddc976d0cc0f52a9f16df1e5d7e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Fri, 14 Jul 2017 14:58:51 -0700 Subject: [PATCH 435/465] Fixed receiving push notifications --- .../android/sdk/conversation/Conversation.java | 1 + .../com/apptentive/android/sdk/storage/Device.java | 10 ++++++++-- .../android/sdk/storage/IntegrationConfig.java | 12 ++++++++++++ .../android/sdk/storage/IntegrationConfigItem.java | 8 ++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index a2588f5f1..909edaa49 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -624,6 +624,7 @@ public void setPushIntegration(int pushProvider, String token) { ApptentiveLog.e("Invalid pushProvider: %d", pushProvider); break; } + scheduleDeviceUpdate(); } /** diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java index c4d04e3a2..8859e51a5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/Device.java @@ -63,7 +63,8 @@ public void notifyDataChanged() { public void onDataChanged() { notifyDataChanged(); } - + + // TODO: unit tests public Device clone() { Device clone = new Device(); clone.uuid = uuid; @@ -92,7 +93,9 @@ public Device clone() { clone.localeLanguageCode = localeLanguageCode; clone.localeRaw = localeRaw; clone.utcOffset = utcOffset; - clone.integrationConfig = integrationConfig; // TODO: make a deep clone + if (integrationConfig != null) { + clone.integrationConfig = integrationConfig.clone(); + } clone.listener = listener; return clone; } @@ -367,6 +370,9 @@ public IntegrationConfig getIntegrationConfig() { } public void setIntegrationConfig(IntegrationConfig integrationConfig) { + if (integrationConfig == null) { + throw new IllegalArgumentException("Integration config is null"); + } this.integrationConfig = integrationConfig; this.integrationConfig.setDataChangedListener(this); notifyDataChanged(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java index 4cdf73886..cb6cd366e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java @@ -95,10 +95,22 @@ public com.apptentive.android.sdk.model.CustomData toJson() { if (parse != null) { ret.put(INTEGRATION_PARSE, parse.toJson()); } + return ret; } catch (JSONException e) { // This can't happen. } return null; } + // TODO: unit tests + public IntegrationConfig clone() { + IntegrationConfig clone = new IntegrationConfig(); + clone.apptentive = apptentive != null ? apptentive.clone() : null; + clone.amazonAwsSns = amazonAwsSns != null ? amazonAwsSns.clone() : null; + clone.urbanAirship = urbanAirship != null ? urbanAirship.clone() : null; + clone.parse = parse != null ? parse.clone() : null; + clone.listener = listener; + return clone; + } + } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java index de989b08c..70ce6f91a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java @@ -40,9 +40,17 @@ public com.apptentive.android.sdk.model.CustomData toJson() { for (String key : keys) { ret.put(key, contents.get(key)); } + return ret; } catch (JSONException e) { // This can't happen. } return null; } + + // TODO: unit testing + public IntegrationConfigItem clone() { + IntegrationConfigItem clone = new IntegrationConfigItem(); + clone.contents.putAll(contents); + return clone; + } } From 709f276c050313db38e5fdabb60e8fd3c753d625 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Sun, 16 Jul 2017 14:48:30 -0700 Subject: [PATCH 436/465] Fixed sending push token --- .../android/sdk/ApptentiveInternal.java | 2 +- .../sdk/storage/IntegrationConfig.java | 26 +++++++++++++++++++ .../sdk/storage/IntegrationConfigItem.java | 16 ++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 2b2b99cff..545094559 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -133,7 +133,7 @@ public class ApptentiveInternal implements ApptentiveNotificationObserver { private Map customData; private static final String PUSH_ACTION = "action"; - private static final String PUSH_CONVERSATION_ID = "conversationid"; + private static final String PUSH_CONVERSATION_ID = "conversation_id"; private enum PushAction { pmc, // Present Message Center. diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java index cb6cd366e..2bc42ec3d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfig.java @@ -102,6 +102,32 @@ public com.apptentive.android.sdk.model.CustomData toJson() { return null; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + IntegrationConfig that = (IntegrationConfig) o; + + if (apptentive != null ? !apptentive.equals(that.apptentive) : that.apptentive != null) + return false; + if (amazonAwsSns != null ? !amazonAwsSns.equals(that.amazonAwsSns) : that.amazonAwsSns != null) + return false; + if (urbanAirship != null ? !urbanAirship.equals(that.urbanAirship) : that.urbanAirship != null) + return false; + return parse != null ? parse.equals(that.parse) : that.parse == null; + + } + + @Override + public int hashCode() { + int result = apptentive != null ? apptentive.hashCode() : 0; + result = 31 * result + (amazonAwsSns != null ? amazonAwsSns.hashCode() : 0); + result = 31 * result + (urbanAirship != null ? urbanAirship.hashCode() : 0); + result = 31 * result + (parse != null ? parse.hashCode() : 0); + return result; + } + // TODO: unit tests public IntegrationConfig clone() { IntegrationConfig clone = new IntegrationConfig(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java index 70ce6f91a..205d3d2ae 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/IntegrationConfigItem.java @@ -47,6 +47,22 @@ public com.apptentive.android.sdk.model.CustomData toJson() { return null; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + IntegrationConfigItem that = (IntegrationConfigItem) o; + + return contents != null ? contents.equals(that.contents) : that.contents == null; + + } + + @Override + public int hashCode() { + return contents != null ? contents.hashCode() : 0; + } + // TODO: unit testing public IntegrationConfigItem clone() { IntegrationConfigItem clone = new IntegrationConfigItem(); From cc125a94cda7c870a5b8333a97a4c83eb63ad637 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Sun, 16 Jul 2017 14:52:45 -0700 Subject: [PATCH 437/465] Fixed handling authentication failures --- .../main/java/com/apptentive/android/sdk/Apptentive.java | 2 +- .../com/apptentive/android/sdk/ApptentiveInternal.java | 7 +++---- .../com/apptentive/android/sdk/network/HttpRequest.java | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index f51be67b6..04cebcc28 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -1336,7 +1336,7 @@ public static AuthenticationFailedReason parse(String errorType, String error) { AuthenticationFailedReason ret = AuthenticationFailedReason.valueOf(errorType); ret.error = error; return ret; - } catch (IllegalArgumentException e) { + } catch (Exception e) { ApptentiveLog.w("Error parsing unknown Apptentive.AuthenticationFailedReason: %s", errorType); } return UNKNOWN; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 545094559..fefd802e3 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -746,11 +746,10 @@ public void notifyAuthenticationFailedListener(Apptentive.AuthenticationFailedRe if (isConversationActive()) { String activeConversationId = getConversation().getConversationId(); if (activeConversationId.equals(conversationIdOfFailedRequest)) { - Apptentive.AuthenticationFailedListener listener = authenticationFailedListenerRef.get(); + Apptentive.AuthenticationFailedListener listener = authenticationFailedListenerRef != null ? authenticationFailedListenerRef.get() : null; if (listener != null) { - listener.onAuthenticationFailed(reason); - } - return; + listener.onAuthenticationFailed(reason); + } } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index edfbcd182..368553ce7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -587,7 +587,7 @@ public Apptentive.AuthenticationFailedReason getAuthenticationFailedReason() { String error = errorObject.optString("error", null); String errorType = errorObject.optString("error_type", null); return Apptentive.AuthenticationFailedReason.parse(errorType, error); - } catch (JSONException e) { + } catch (Exception e) { ApptentiveLog.w(e, "Error parsing authentication failure object."); } } From 66f9d81259463a77f07b483f39924db4bf9e05e6 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Sun, 16 Jul 2017 15:41:03 -0700 Subject: [PATCH 438/465] Fixed interrupting message polling thread --- .../android/sdk/module/messagecenter/MessagePollingWorker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java index 7302af62d..40aef1ca1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java @@ -58,7 +58,7 @@ public void run() { } ApptentiveLog.v(MESSAGES, "Polling messages in %d sec", pollingInterval / 1000); - if (!goToSleep(pollingInterval)) { + if (Thread.currentThread().isInterrupted() || !goToSleep(pollingInterval)) { break; } } From af51e08e08547af1493076980eb2397d9a3e3cdc Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Sun, 16 Jul 2017 17:42:54 -0700 Subject: [PATCH 439/465] Fixed encrypted payloads containers --- .../android/sdk/model/CompoundMessage.java | 39 ++++++++++--------- .../android/sdk/model/DevicePayload.java | 10 +---- .../android/sdk/model/EventPayload.java | 5 +++ .../android/sdk/model/JsonPayload.java | 35 +++++++++++------ .../apptentive/android/sdk/model/Payload.java | 4 +- .../android/sdk/model/PersonPayload.java | 11 ++---- .../sdk/model/SurveyResponsePayload.java | 10 +---- 7 files changed, 59 insertions(+), 55 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java index 73067c9ab..dc38fc330 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java @@ -286,26 +286,26 @@ public int getListItemType() { */ @Override public byte[] renderData() { - boolean encrypted = encryptionKey != null; - Encryptor encryptor = null; - if (encrypted) { - encryptor = new Encryptor(encryptionKey); - } - ByteArrayOutputStream data = new ByteArrayOutputStream(); + try { + boolean encrypted = encryptionKey != null; + Encryptor encryptor = null; + if (encrypted) { + encryptor = new Encryptor(encryptionKey); + } + ByteArrayOutputStream data = new ByteArrayOutputStream(); - // First write the message body out as the first "part". - StringBuilder header = new StringBuilder(); - header.append(twoHyphens).append(boundary).append(lineEnd); + // First write the message body out as the first "part". + StringBuilder header = new StringBuilder(); + header.append(twoHyphens).append(boundary).append(lineEnd); - StringBuilder part = new StringBuilder(); - part - .append("Content-Disposition: form-data; name=\"message\"").append(lineEnd) - .append("Content-Type: application/json;charset=UTF-8").append(lineEnd) - .append(lineEnd) - .append(marshallForSending().toString()).append(lineEnd); - byte[] partBytes = part.toString().getBytes(); + StringBuilder part = new StringBuilder(); + part + .append("Content-Disposition: form-data; name=\"message\"").append(lineEnd) + .append("Content-Type: application/json;charset=UTF-8").append(lineEnd) + .append(lineEnd) + .append(marshallForSending().toString()).append(lineEnd); + byte[] partBytes = part.toString().getBytes(); - try { if (encrypted) { header .append("Content-Disposition: form-data; name=\"message\"").append(lineEnd) @@ -374,11 +374,12 @@ public byte[] renderData() { } } data.write(("--" + boundary + "--").getBytes()); + + ApptentiveLog.d(PAYLOADS, "Total payload body bytes: %d", data.size()); + return data.toByteArray(); } catch (Exception e) { ApptentiveLog.e(PAYLOADS, "Error assembling Message Payload.", e); return null; } - ApptentiveLog.d(PAYLOADS, "Total payload body bytes: %d", data.size()); - return data.toByteArray(); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java index 501a16ad7..7150ccfe2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/DevicePayload.java @@ -179,13 +179,7 @@ public void setUtcOffset(String utcOffset) { } @Override - protected JSONObject marshallForSending() { - JSONObject jsonObject = new JSONObject(); - try { - jsonObject.put("device", super.marshallForSending()); - } catch (JSONException e) { - // This can't happen. - } - return jsonObject; + protected String getJsonContainer() { + return "device"; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java index 93c3340e2..b79280f75 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/EventPayload.java @@ -109,6 +109,11 @@ public EventPayload(String label, String trigger) { putData(data); } + @Override + protected String getJsonContainer() { + return "event"; + } + //region Http-request @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java index e6af2081c..1986e2178 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/JsonPayload.java @@ -38,9 +38,12 @@ public JsonPayload(PayloadType type, String json) throws JSONException { //region Data @Override - public byte[] renderData() { + public byte[] renderData() throws JSONException { + String jsonString = marshallForSending().toString(); + ApptentiveLog.vv(PAYLOADS, jsonString); + if (encryptionKey != null) { - byte[] bytes = marshallForSending().toString().getBytes(); + byte[] bytes = jsonString.getBytes(); Encryptor encryptor = new Encryptor(encryptionKey); try { return encryptor.encrypt(bytes); @@ -49,7 +52,7 @@ public byte[] renderData() { } return null; } else { - return marshallForSending().toString().getBytes(); + return jsonString.getBytes(); } } @@ -184,14 +187,24 @@ public void setNonce(String nonce) { //endregion - protected JSONObject marshallForSending() { - try { - if (encryptionKey != null) { - jsonObject.put("token", token); - } - } catch (Exception e) { - // Can't happen. + protected final JSONObject marshallForSending() throws JSONException { + JSONObject result; + String container = getJsonContainer(); + if (container != null) { + result = new JSONObject(); + result.put(container, jsonObject); + } else { + result = jsonObject; } - return jsonObject; + + if (encryptionKey != null) { + result.put("token", token); + } + + return result; + } + + protected String getJsonContainer() { + return null; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java index aca0c0dd4..33b24b5e8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/Payload.java @@ -9,6 +9,8 @@ import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.util.StringUtils; +import org.json.JSONException; + import java.util.List; public abstract class Payload { @@ -47,7 +49,7 @@ protected Payload(PayloadType type) { /** * Binary data to be stored in database */ - public abstract byte[] renderData(); + public abstract byte[] renderData() throws JSONException; //region diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java index b379984f3..f1b0e7537 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/PersonPayload.java @@ -39,15 +39,10 @@ public PersonPayload(String json) throws JSONException { //region Http-request + @Override - protected JSONObject marshallForSending() { - JSONObject object = new JSONObject(); - try { - object.put(KEY, super.marshallForSending()); - } catch (JSONException e) { - ApptentiveLog.e(e, "Error creating person object"); - } - return object; + protected String getJsonContainer() { + return KEY; } @Override diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java index 2c6797050..3b7f97366 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/SurveyResponsePayload.java @@ -44,14 +44,8 @@ public SurveyResponsePayload(String json) throws JSONException { } @Override - protected JSONObject marshallForSending() { - JSONObject object = new JSONObject(); - try { - object.put(KEY_RESPONSE, super.marshallForSending()); - } catch (JSONException e) { - ApptentiveLog.e(e, "Error creating survey response object"); - } - return object; + protected String getJsonContainer() { + return KEY_RESPONSE; } //region Http-request From a9f0562aec789cd6f61a098ea39335a452020f34 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Sun, 16 Jul 2017 19:37:45 -0700 Subject: [PATCH 440/465] Fixed conversation after "clean" login --- .../android/sdk/conversation/ConversationManager.java | 5 +++++ .../android/sdk/storage/ApptentiveTaskManager.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 24d3ae8ae..2b785038a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -751,6 +751,11 @@ public boolean accept(ConversationMetadataItem item) { File dataFile = new File(apptentiveConversationsStorageDir, "conversation-" + Util.generateRandomFilename()); File messagesFile = new File(apptentiveConversationsStorageDir, "messages-" + Util.generateRandomFilename()); activeConversation = new Conversation(dataFile, messagesFile); + + // FIXME: if we don't set these here - device payload would return 4xx error code + activeConversation.setDevice(DeviceManager.generateNewDevice(getContext())); + activeConversation.setAppRelease(ApptentiveInternal.getInstance().getAppRelease()); + activeConversation.setSdk(SdkManager.generateCurrentSdk()); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index ce5210964..839e6b711 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -182,7 +182,7 @@ public void onFinishSending(PayloadSender sender, PayloadData payload, boolean c } if (errorMessage != null) { - ApptentiveLog.v(PAYLOADS, "Payload sending failed: %s\n%s", payload, errorMessage); + ApptentiveLog.e(PAYLOADS, "Payload sending failed: %s\n%s", payload, errorMessage); if (appInBackground) { ApptentiveLog.v(PAYLOADS, "The app went to the background so we won't remove the payload from the queue"); retrySending(5000); From e4735e62e5b5e2c5097ccc52b4fe4aa2b0d350ef Mon Sep 17 00:00:00 2001 From: skykelsey Date: Mon, 17 Jul 2017 09:49:35 -0700 Subject: [PATCH 441/465] Fix travis upload of archive. --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 75477fc9a..3e1cd0fb3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,9 @@ before_script: script: - ./gradlew :apptentive:test -i after_script: - - if [ $TRAVIS_BRANCH = 'develop' ]; ./gradlew :apptentive:uploadArchives; fi + - if [ "$TRAVIS_BRANCH" = "develop" ]; then + ./gradlew :apptentive:uploadArchives; + fi notifications: slack: secure: HejMl0EUociwGu+5djx95snbS+m/Yw8DseQKCSqeyWvMQLrAy8bi9oa89mZvXnvjqSVY3kKRZgJncEkQdIe9c7xwgNA9QYLkc7UVbXqga291HMoNnWaIMewD2ervbzM4aBQAHnkDr+GsXgb7+1YdOktIn8dA7jdIuB90ar4So9U= From 01dfec9aa64943e7bf2b25ed0b3a8eb974ec4fbd Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 17 Jul 2017 13:03:38 -0700 Subject: [PATCH 442/465] Added local conversation identifier to each metadata item in order to be able to identify a stored pending conversation --- .../sdk/conversation/ConversationManager.java | 4 ++-- .../conversation/ConversationMetadata.java | 2 +- .../ConversationMetadataItem.java | 19 ++++++++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 2b785038a..62eecf8ff 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -301,7 +301,7 @@ public void onCancel(HttpJsonRequest request) { @Override public void onFail(HttpJsonRequest request, String reason) { - ApptentiveLog.w("Failed to fetch legacy conversation id: %s", reason); + ApptentiveLog.e("Failed to fetch legacy conversation id: %s", reason); } }); @@ -507,7 +507,7 @@ private void updateMetadataItems(Conversation conversation) { // update the state of the corresponding item ConversationMetadataItem item = conversationMetadata.findItem(conversation); if (item == null) { - item = new ConversationMetadataItem(conversation.getConversationId(), conversation.getConversationDataFile(), conversation.getConversationMessagesFile()); + item = new ConversationMetadataItem(conversation.getLocalIdentifier(), conversation.getConversationId(), conversation.getConversationDataFile(), conversation.getConversationMessagesFile()); conversationMetadata.addItem(item); } item.state = conversation.getState(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java index 5b56f1b9f..cd87f6ee1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadata.java @@ -67,7 +67,7 @@ ConversationMetadataItem findItem(final Conversation conversation) { return findItem(new Filter() { @Override public boolean accept(ConversationMetadataItem item) { - return StringUtils.equal(item.conversationId, conversation.getConversationId()); + return StringUtils.equal(item.localConversationId, conversation.getLocalIdentifier()); } }); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index a472768ff..362f7f9e7 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -21,6 +21,11 @@ public class ConversationMetadataItem implements SerializableObject { */ ConversationState state = ConversationState.UNDEFINED; + /** + * Local conversation ID + */ + final String localConversationId; + /** * Conversation ID which was received from the backend */ @@ -51,7 +56,11 @@ public class ConversationMetadataItem implements SerializableObject { */ String userId; - public ConversationMetadataItem(String conversationId, File dataFile, File messagesFile) { + public ConversationMetadataItem(String localConversationId, String conversationId, File dataFile, File messagesFile) { + if (localConversationId == null) { + throw new IllegalArgumentException("Local conversation id is null"); + } + if (dataFile == null) { throw new IllegalArgumentException("Data file is null"); } @@ -60,12 +69,14 @@ public ConversationMetadataItem(String conversationId, File dataFile, File messa throw new IllegalArgumentException("Messages file is null"); } + this.localConversationId = localConversationId; this.conversationId = conversationId; this.dataFile = dataFile; this.messagesFile = messagesFile; } public ConversationMetadataItem(DataInput in) throws IOException { + localConversationId = in.readUTF(); conversationId = readNullableUTF(in); conversationToken = readNullableUTF(in); dataFile = new File(in.readUTF()); @@ -77,6 +88,7 @@ public ConversationMetadataItem(DataInput in) throws IOException { @Override public void writeExternal(DataOutput out) throws IOException { + out.writeUTF(localConversationId); writeNullableUTF(out, conversationId); writeNullableUTF(out, conversationToken); out.writeUTF(dataFile.getAbsolutePath()); @@ -86,6 +98,10 @@ public void writeExternal(DataOutput out) throws IOException { writeNullableUTF(out, userId); } + public String getLocalConversationId() { + return localConversationId; + } + public String getConversationId() { return conversationId; } @@ -110,6 +126,7 @@ public String getConversationToken() { public String toString() { return "ConversationMetadataItem{" + "state=" + state + + ", localConversationId='" + localConversationId + '\'' + ", conversationId='" + conversationId + '\'' + ", conversationToken='" + conversationToken + '\'' + ", dataFile=" + dataFile + From 5f503534c9e248d228891567ddf7341b02ac8267 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 17 Jul 2017 15:15:47 -0700 Subject: [PATCH 443/465] Wrap background tasks with try..catch --- .../android/sdk/ApptentiveInternal.java | 4 -- .../sdk/conversation/ConversationManager.java | 8 ++++ .../ApptentiveActivityLifecycleCallbacks.java | 10 +++-- .../android/sdk/model/CompoundMessage.java | 2 +- .../module/messagecenter/MessageManager.java | 15 ++++--- .../sdk/storage/ApptentiveTaskManager.java | 44 ++++++++++++++----- 6 files changed, 58 insertions(+), 25 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index fefd802e3..33ebe29e6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -124,8 +124,6 @@ public class ApptentiveInternal implements ApptentiveNotificationObserver { private WeakReference authenticationFailedListenerRef = null; - private final ExecutorService cachedExecutor; - // Holds reference to the current foreground activity of the host app private WeakReference currentTaskStackTopActivity; @@ -162,7 +160,6 @@ protected ApptentiveInternal() { conversationManager = null; appContext = null; appRelease = null; - cachedExecutor = null; lifecycleCallbacks = null; } @@ -187,7 +184,6 @@ private ApptentiveInternal(Application application, String apptentiveKey, String appRelease = AppReleaseManager.generateCurrentAppRelease(application, this); taskManager = new ApptentiveTaskManager(appContext, apptentiveHttpClient); - cachedExecutor = Executors.newCachedThreadPool(); lifecycleCallbacks = new ApptentiveActivityLifecycleCallbacks(); ApptentiveNotificationCenter.defaultCenter() diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 62eecf8ff..34a1d9c0b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -489,6 +489,12 @@ public boolean accept(ConversationMetadataItem item) { } private void updateMetadataItems(Conversation conversation) { + ApptentiveLog.vv("Updating metadata: state=%s localId=%s conversationId=%s token=%s", + conversation.getState(), + conversation.getLocalIdentifier(), + conversation.getConversationId(), + conversation.getConversationToken()); + // if the conversation is 'logged-in' we should not have any other 'logged-in' items in metadata if (conversation.hasState(LOGGED_IN)) { for (ConversationMetadataItem item : conversationMetadata) { @@ -840,6 +846,7 @@ private void printMetadata(ConversationMetadata metadata, String title) { Object[][] rows = new Object[1 + items.size()][]; rows[0] = new Object[] { "state", + "localId", "conversationId", "userId", "dataFile", @@ -851,6 +858,7 @@ private void printMetadata(ConversationMetadata metadata, String title) { for (ConversationMetadataItem item : items) { rows[index++] = new Object[] { item.state, + item.localConversationId, item.conversationId, item.userId, item.dataFile, diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/lifecycle/ApptentiveActivityLifecycleCallbacks.java b/apptentive/src/main/java/com/apptentive/android/sdk/lifecycle/ApptentiveActivityLifecycleCallbacks.java index e6d6c0aac..f89d1bdad 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/lifecycle/ApptentiveActivityLifecycleCallbacks.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/lifecycle/ApptentiveActivityLifecycleCallbacks.java @@ -100,9 +100,13 @@ public void onActivityStopped(Activity activity) { delayedChecker.postDelayed(checkFgBgRoutine = new Runnable() { @Override public void run() { - if (foregroundActivities.get() == 0 && isAppForeground) { - appEnteredBackground(); - isAppForeground = false; + try { + if (foregroundActivities.get() == 0 && isAppForeground) { + appEnteredBackground(); + isAppForeground = false; + } + } catch (Exception e) { + ApptentiveLog.e(e, "Exception in delayed checking"); } } }, CHECK_DELAY_SHORT); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java index dc38fc330..1d4071574 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CompoundMessage.java @@ -188,7 +188,7 @@ public List getAssociatedFiles() { try { Future> future = ApptentiveInternal.getInstance().getApptentiveTaskManager().getAssociatedFiles(getNonce()); associatedFiles = future.get(); - } catch (InterruptedException | ExecutionException e) { + } catch (Exception e) { ApptentiveLog.e("Unable to get associated files in worker thread"); } finally { return associatedFiles; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 76f123776..ac9a10131 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -547,13 +547,14 @@ private void showUnreadMessageToastNotification(final CompoundMessage apptentive .setSmallIcon(R.drawable.avatar).setContentText(apptentiveMsg.getBody()) .setContentIntent(pendingIntent) .setFullScreenIntent(pendingIntent, false); - foreground.runOnUiThread(new Runnable() { - public void run() { - ApptentiveToastNotification notification = builder.buildApptentiveToastNotification(); - notification.setAvatarUrl(apptentiveMsg.getSenderProfilePhoto()); - manager.notify(TOAST_TYPE_UNREAD_MESSAGE, notification); - } - } + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + ApptentiveToastNotification notification = builder.buildApptentiveToastNotification(); + notification.setAvatarUrl(apptentiveMsg.getSenderProfilePhoto()); + manager.notify(TOAST_TYPE_UNREAD_MESSAGE, notification); + } + } ); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index 839e6b711..e503b6876 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -103,8 +103,12 @@ public void addPayload(final Payload payload) { singleThreadExecutor.execute(new Runnable() { @Override public void run() { - dbHelper.addPayload(payload); - sendNextPayloadSync(); + try { + dbHelper.addPayload(payload); + sendNextPayloadSync(); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while adding a payload: %s", payload); + } } }); } @@ -114,8 +118,12 @@ public void deletePayload(final String payloadIdentifier) { singleThreadExecutor.execute(new Runnable() { @Override public void run() { - dbHelper.deletePayload(payloadIdentifier); - sendNextPayloadSync(); + try { + dbHelper.deletePayload(payloadIdentifier); + sendNextPayloadSync(); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while deleting a payload: %s", payloadIdentifier); + } } }); } @@ -125,7 +133,11 @@ public void deleteAllPayloads() { singleThreadExecutor.execute(new Runnable() { @Override public void run() { - dbHelper.deleteAllPayloads(); + try { + dbHelper.deleteAllPayloads(); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while deleting all payloads"); + } } }); } @@ -138,7 +150,11 @@ public void deleteAssociatedFiles(final String messageNonce) { singleThreadExecutor.execute(new Runnable() { @Override public void run() { - dbHelper.deleteAssociatedFiles(messageNonce); + try { + dbHelper.deleteAssociatedFiles(messageNonce); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while deleting associated file: %s", messageNonce); + } } }); } @@ -212,8 +228,12 @@ protected void execute() { singleThreadExecutor.execute(new Runnable() { @Override public void run() { - ApptentiveLog.d(PAYLOADS, "Retrying sending payloads"); - sendNextPayloadSync(); + try { + ApptentiveLog.d(PAYLOADS, "Retrying sending payloads"); + sendNextPayloadSync(); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while trying to retry sending payloads"); + } } }); } @@ -285,8 +305,12 @@ public void onReceiveNotification(ApptentiveNotification notification) { singleThreadExecutor.execute(new Runnable() { @Override public void run() { - dbHelper.updateIncompletePayloads(conversationId, conversationToken, conversationLocalIdentifier); - sendNextPayloadSync(); // after we've updated payloads - we need to send them + try { + dbHelper.updateIncompletePayloads(conversationId, conversationToken, conversationLocalIdentifier); + sendNextPayloadSync(); // after we've updated payloads - we need to send them + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while trying to update incomplete payloads"); + } } }); } From edffdac0b5e8890f85543b26830947bc36db9d86 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 17 Jul 2017 15:40:59 -0700 Subject: [PATCH 444/465] Wrapped all public API calls with try..catch --- .../apptentive/android/sdk/Apptentive.java | 504 +++++++++++------- 1 file changed, 316 insertions(+), 188 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 04cebcc28..7656760a5 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -57,7 +57,7 @@ public class Apptentive { * @param application The {@link Application} object for this app. */ public static void register(Application application) { - Apptentive.register(application, null, null); + register(application, null, null); } public static void register(Application application, String apptentiveKey, String apptentiveSignature) { @@ -65,8 +65,12 @@ public static void register(Application application, String apptentiveKey, Strin } private static void register(Application application, String apptentiveKey, String apptentiveSignature, String serverUrl) { - ApptentiveLog.i("Registering Apptentive."); - ApptentiveInternal.createInstance(application, apptentiveKey, apptentiveSignature, serverUrl); + try { + ApptentiveLog.i("Registering Apptentive."); + ApptentiveInternal.createInstance(application, apptentiveKey, apptentiveSignature, serverUrl); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while registering Apptentive"); + } } //region Global Data Methods @@ -81,12 +85,16 @@ private static void register(Application application, String apptentiveKey, Stri * @param email The user's email address. */ public static void setPersonEmail(String email) { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getPerson().setEmail(email); - conversation.schedulePersonUpdate(); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().setEmail(email); + conversation.schedulePersonUpdate(); + } } + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while setting person email"); } } @@ -97,11 +105,15 @@ public static void setPersonEmail(String email) { * @return The person's email if set, else null. */ public static String getPersonEmail() { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - return conversation.getPerson().getEmail(); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + return conversation.getPerson().getEmail(); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while getting person email"); } return null; } @@ -116,12 +128,16 @@ public static String getPersonEmail() { * @param name The user's name. */ public static void setPersonName(String name) { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getPerson().setName(name); - conversation.schedulePersonUpdate(); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().setName(name); + conversation.schedulePersonUpdate(); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while setting person name"); } } @@ -132,11 +148,15 @@ public static void setPersonName(String name) { * @return The person's name if set, else null. */ public static String getPersonName() { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - return conversation.getPerson().getName(); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + return conversation.getPerson().getName(); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while getting person name"); } return null; } @@ -150,15 +170,19 @@ public static String getPersonName() { * @param value A String value. */ public static void addCustomDeviceData(String key, String value) { - if (ApptentiveInternal.isApptentiveRegistered()) { - if (value != null) { - value = value.trim(); - } - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getDevice().getCustomData().put(key, value); - conversation.scheduleDeviceUpdate(); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + if (value != null) { + value = value.trim(); + } + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getDevice().getCustomData().put(key, value); + conversation.scheduleDeviceUpdate(); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while adding custom device data"); } } @@ -171,11 +195,15 @@ public static void addCustomDeviceData(String key, String value) { * @param value A Number value. */ public static void addCustomDeviceData(String key, Number value) { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getDevice().getCustomData().put(key, value); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getDevice().getCustomData().put(key, value); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while adding custom device data"); } } @@ -188,29 +216,41 @@ public static void addCustomDeviceData(String key, Number value) { * @param value A Boolean value. */ public static void addCustomDeviceData(String key, Boolean value) { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getDevice().getCustomData().put(key, value); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getDevice().getCustomData().put(key, value); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while adding custom device data"); } } private static void addCustomDeviceData(String key, Version version) { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getDevice().getCustomData().put(key, version); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getDevice().getCustomData().put(key, version); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while adding custom device data"); } } private static void addCustomDeviceData(String key, DateTime dateTime) { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getDevice().getCustomData().put(key, dateTime); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getDevice().getCustomData().put(key, dateTime); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while adding custom device data"); } } @@ -220,12 +260,16 @@ private static void addCustomDeviceData(String key, DateTime dateTime) { * @param key The key to remove. */ public static void removeCustomDeviceData(String key) { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getDevice().getCustomData().remove(key); - conversation.scheduleDeviceUpdate(); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getDevice().getCustomData().remove(key); + conversation.scheduleDeviceUpdate(); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while removing custom device data"); } } @@ -238,15 +282,19 @@ public static void removeCustomDeviceData(String key) { * @param value A String value. */ public static void addCustomPersonData(String key, String value) { - if (ApptentiveInternal.isApptentiveRegistered()) { - if (value != null) { - value = value.trim(); - } - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getPerson().getCustomData().put(key, value); - conversation.schedulePersonUpdate(); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + if (value != null) { + value = value.trim(); + } + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().getCustomData().put(key, value); + conversation.schedulePersonUpdate(); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while adding custom person data"); } } @@ -259,11 +307,15 @@ public static void addCustomPersonData(String key, String value) { * @param value A Number value. */ public static void addCustomPersonData(String key, Number value) { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getPerson().getCustomData().put(key, value); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().getCustomData().put(key, value); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while adding custom person data"); } } @@ -276,32 +328,44 @@ public static void addCustomPersonData(String key, Number value) { * @param value A Boolean value. */ public static void addCustomPersonData(String key, Boolean value) { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getPerson().getCustomData().put(key, value); - conversation.schedulePersonUpdate(); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().getCustomData().put(key, value); + conversation.schedulePersonUpdate(); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while adding custom person data"); } } private static void addCustomPersonData(String key, Version version) { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getPerson().getCustomData().put(key, version); - conversation.schedulePersonUpdate(); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().getCustomData().put(key, version); + conversation.schedulePersonUpdate(); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while adding custom person data"); } } private static void addCustomPersonData(String key, DateTime dateTime) { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getPerson().getCustomData().remove(key); - conversation.schedulePersonUpdate(); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().getCustomData().remove(key); + conversation.schedulePersonUpdate(); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while adding custom person data"); } } @@ -311,12 +375,16 @@ private static void addCustomPersonData(String key, DateTime dateTime) { * @param key The key to remove. */ public static void removeCustomPersonData(String key) { - if (ApptentiveInternal.isApptentiveRegistered()) { - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.getPerson().getCustomData().remove(key); - conversation.schedulePersonUpdate(); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.getPerson().getCustomData().remove(key); + conversation.schedulePersonUpdate(); + } } + } catch (Exception e) { + ApptentiveLog.e("Exception while removing custom person data"); } } @@ -386,31 +454,35 @@ public static void removeCustomPersonData(String key) { * */ public static void setPushNotificationIntegration(final int pushProvider, final String token) { - // we only access the active conversation on the main thread to avoid concurrency issues - if (!DispatchQueue.isMainQueue()) { - DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { - @Override - protected void execute() { - setPushNotificationIntegration(pushProvider, token); - } - }); - return; - } + try { + // we only access the active conversation on the main thread to avoid concurrency issues + if (!DispatchQueue.isMainQueue()) { + DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { + @Override + protected void execute() { + setPushNotificationIntegration(pushProvider, token); + } + }); + return; + } - if (!ApptentiveInternal.isApptentiveRegistered()) { - ApptentiveLog.w("Unable to set push notification integration: Apptentive instance is not initialized"); - return; - } - // Store the push stuff globally - SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); - prefs.edit().putInt(Constants.PREF_KEY_PUSH_PROVIDER, pushProvider) - .putString(Constants.PREF_KEY_PUSH_TOKEN, token) - .apply(); + if (!ApptentiveInternal.isApptentiveRegistered()) { + ApptentiveLog.w("Unable to set push notification integration: Apptentive instance is not initialized"); + return; + } + // Store the push stuff globally + SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs(); + prefs.edit().putInt(Constants.PREF_KEY_PUSH_PROVIDER, pushProvider) + .putString(Constants.PREF_KEY_PUSH_TOKEN, token) + .apply(); - // Also set it on the active Conversation, if there is one. - Conversation conversation = ApptentiveInternal.getInstance().getConversation(); - if (conversation != null) { - conversation.setPushIntegration(pushProvider, token); + // Also set it on the active Conversation, if there is one. + Conversation conversation = ApptentiveInternal.getInstance().getConversation(); + if (conversation != null) { + conversation.setPushIntegration(pushProvider, token); + } + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while setting push notification integration"); } } @@ -425,10 +497,15 @@ protected void execute() { * @return True if the Intent came from, and should be handled by Apptentive. */ public static boolean isApptentivePushNotification(Intent intent) { - if (!ApptentiveInternal.checkRegistered()) { - return false; + try { + if (!ApptentiveInternal.checkRegistered()) { + return false; + } + return ApptentiveInternal.getApptentivePushNotificationData(intent) != null; + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while checking for Apptentive push notification intent"); } - return ApptentiveInternal.getApptentivePushNotificationData(intent) != null; + return false; } /** @@ -439,10 +516,15 @@ public static boolean isApptentivePushNotification(Intent intent) { * @return True if the push came from, and should be handled by Apptentive. */ public static boolean isApptentivePushNotification(Bundle bundle) { - if (!ApptentiveInternal.checkRegistered()) { - return false; + try { + if (!ApptentiveInternal.checkRegistered()) { + return false; + } + return ApptentiveInternal.getApptentivePushNotificationData(bundle) != null; + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while checking for Apptentive push notification bundle"); } - return ApptentiveInternal.getApptentivePushNotificationData(bundle) != null; + return false; } /** @@ -452,10 +534,15 @@ public static boolean isApptentivePushNotification(Bundle bundle) { * @return True if the push came from, and should be handled by Apptentive. */ public static boolean isApptentivePushNotification(Map data) { - if (!ApptentiveInternal.checkRegistered()) { - return false; + try { + if (!ApptentiveInternal.checkRegistered()) { + return false; + } + return ApptentiveInternal.getApptentivePushNotificationData(data) != null; + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while checking for Apptentive push notification data"); } - return ApptentiveInternal.getApptentivePushNotificationData(data) != null; + return false; } /** @@ -477,11 +564,16 @@ public static boolean isApptentivePushNotification(Map data) { * @return a valid {@link PendingIntent} to launch an Apptentive Interaction if the push data came from Apptentive, or null. */ public static PendingIntent buildPendingIntentFromPushNotification(@NonNull Intent intent) { - if (!ApptentiveInternal.checkRegistered()) { - return null; + try { + if (!ApptentiveInternal.checkRegistered()) { + return null; + } + String apptentivePushData = ApptentiveInternal.getApptentivePushNotificationData(intent); + return ApptentiveInternal.generatePendingIntentFromApptentivePushData(apptentivePushData); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while building pending intent from push notification"); } - String apptentivePushData = ApptentiveInternal.getApptentivePushNotificationData(intent); - return ApptentiveInternal.generatePendingIntentFromApptentivePushData(apptentivePushData); + return null; } /** @@ -501,11 +593,16 @@ public static PendingIntent buildPendingIntentFromPushNotification(@NonNull Inte * @return a valid {@link PendingIntent} to launch an Apptentive Interaction if the push data came from Apptentive, or null. */ public static PendingIntent buildPendingIntentFromPushNotification(@NonNull Bundle bundle) { - if (!ApptentiveInternal.checkRegistered()) { - return null; + try { + if (!ApptentiveInternal.checkRegistered()) { + return null; + } + String apptentivePushData = ApptentiveInternal.getApptentivePushNotificationData(bundle); + return ApptentiveInternal.generatePendingIntentFromApptentivePushData(apptentivePushData); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while building pending intent form a push notification"); } - String apptentivePushData = ApptentiveInternal.getApptentivePushNotificationData(bundle); - return ApptentiveInternal.generatePendingIntentFromApptentivePushData(apptentivePushData); + return null; } /** @@ -526,11 +623,16 @@ public static PendingIntent buildPendingIntentFromPushNotification(@NonNull Bund * @return a valid {@link PendingIntent} to launch an Apptentive Interaction if the push data came from Apptentive, or null. */ public static PendingIntent buildPendingIntentFromPushNotification(@NonNull Map data) { - if (!ApptentiveInternal.checkRegistered()) { - return null; + try { + if (!ApptentiveInternal.checkRegistered()) { + return null; + } + String apptentivePushData = ApptentiveInternal.getApptentivePushNotificationData(data); + return ApptentiveInternal.generatePendingIntentFromApptentivePushData(apptentivePushData); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while building pending intent form a push notification"); } - String apptentivePushData = ApptentiveInternal.getApptentivePushNotificationData(data); - return ApptentiveInternal.generatePendingIntentFromApptentivePushData(apptentivePushData); + return null; } /** @@ -578,31 +680,35 @@ public static String getBodyFromApptentivePush(Intent intent) { * @return a String value, or null. */ public static String getTitleFromApptentivePush(Bundle bundle) { - if (!ApptentiveInternal.checkRegistered()) { - return null; - } - if (bundle == null) { - return null; - } - if (bundle.containsKey(ApptentiveInternal.TITLE_DEFAULT)) { - return bundle.getString(ApptentiveInternal.TITLE_DEFAULT); - } - if (bundle.containsKey(ApptentiveInternal.PUSH_EXTRA_KEY_PARSE)) { - String parseDataString = bundle.getString(ApptentiveInternal.PUSH_EXTRA_KEY_PARSE); - if (parseDataString != null) { - try { - JSONObject parseJson = new JSONObject(parseDataString); - return parseJson.optString(ApptentiveInternal.TITLE_DEFAULT, null); - } catch (JSONException e) { - return null; - } + try { + if (!ApptentiveInternal.checkRegistered()) { + return null; } - } else if (bundle.containsKey(ApptentiveInternal.PUSH_EXTRA_KEY_UA)) { - Bundle uaPushBundle = bundle.getBundle(ApptentiveInternal.PUSH_EXTRA_KEY_UA); - if (uaPushBundle == null) { + if (bundle == null) { return null; } - return uaPushBundle.getString(ApptentiveInternal.TITLE_DEFAULT); + if (bundle.containsKey(ApptentiveInternal.TITLE_DEFAULT)) { + return bundle.getString(ApptentiveInternal.TITLE_DEFAULT); + } + if (bundle.containsKey(ApptentiveInternal.PUSH_EXTRA_KEY_PARSE)) { + String parseDataString = bundle.getString(ApptentiveInternal.PUSH_EXTRA_KEY_PARSE); + if (parseDataString != null) { + try { + JSONObject parseJson = new JSONObject(parseDataString); + return parseJson.optString(ApptentiveInternal.TITLE_DEFAULT, null); + } catch (JSONException e) { + return null; + } + } + } else if (bundle.containsKey(ApptentiveInternal.PUSH_EXTRA_KEY_UA)) { + Bundle uaPushBundle = bundle.getBundle(ApptentiveInternal.PUSH_EXTRA_KEY_UA); + if (uaPushBundle == null) { + return null; + } + return uaPushBundle.getString(ApptentiveInternal.TITLE_DEFAULT); + } + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while getting title from Apptentive push"); } return null; } @@ -616,33 +722,37 @@ public static String getTitleFromApptentivePush(Bundle bundle) { * @return a String value, or null. */ public static String getBodyFromApptentivePush(Bundle bundle) { - if (!ApptentiveInternal.checkRegistered()) { - return null; - } - if (bundle == null) { - return null; - } - if (bundle.containsKey(ApptentiveInternal.BODY_DEFAULT)) { - return bundle.getString(ApptentiveInternal.BODY_DEFAULT); - } - if (bundle.containsKey(ApptentiveInternal.PUSH_EXTRA_KEY_PARSE)) { - String parseDataString = bundle.getString(ApptentiveInternal.PUSH_EXTRA_KEY_PARSE); - if (parseDataString != null) { - try { - JSONObject parseJson = new JSONObject(parseDataString); - return parseJson.optString(ApptentiveInternal.BODY_PARSE, null); - } catch (JSONException e) { - return null; - } + try { + if (!ApptentiveInternal.checkRegistered()) { + return null; } - } else if (bundle.containsKey(ApptentiveInternal.PUSH_EXTRA_KEY_UA)) { - Bundle uaPushBundle = bundle.getBundle(ApptentiveInternal.PUSH_EXTRA_KEY_UA); - if (uaPushBundle == null) { + if (bundle == null) { return null; } - return uaPushBundle.getString(ApptentiveInternal.BODY_UA); - } else if (bundle.containsKey(ApptentiveInternal.BODY_UA)) { - return bundle.getString(ApptentiveInternal.BODY_UA); + if (bundle.containsKey(ApptentiveInternal.BODY_DEFAULT)) { + return bundle.getString(ApptentiveInternal.BODY_DEFAULT); + } + if (bundle.containsKey(ApptentiveInternal.PUSH_EXTRA_KEY_PARSE)) { + String parseDataString = bundle.getString(ApptentiveInternal.PUSH_EXTRA_KEY_PARSE); + if (parseDataString != null) { + try { + JSONObject parseJson = new JSONObject(parseDataString); + return parseJson.optString(ApptentiveInternal.BODY_PARSE, null); + } catch (JSONException e) { + return null; + } + } + } else if (bundle.containsKey(ApptentiveInternal.PUSH_EXTRA_KEY_UA)) { + Bundle uaPushBundle = bundle.getBundle(ApptentiveInternal.PUSH_EXTRA_KEY_UA); + if (uaPushBundle == null) { + return null; + } + return uaPushBundle.getString(ApptentiveInternal.BODY_UA); + } else if (bundle.containsKey(ApptentiveInternal.BODY_UA)) { + return bundle.getString(ApptentiveInternal.BODY_UA); + } + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while getting body from Apptentive push"); } return null; } @@ -657,13 +767,18 @@ public static String getBodyFromApptentivePush(Bundle bundle) { * @return a String value, or null. */ public static String getTitleFromApptentivePush(Map data) { - if (!ApptentiveInternal.checkRegistered()) { - return null; - } - if (data == null) { - return null; + try { + if (!ApptentiveInternal.checkRegistered()) { + return null; + } + if (data == null) { + return null; + } + return data.get(ApptentiveInternal.TITLE_DEFAULT); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while getting title from Apptentive push"); } - return data.get(ApptentiveInternal.TITLE_DEFAULT); + return null; } /** @@ -676,13 +791,18 @@ public static String getTitleFromApptentivePush(Map data) { * @return a String value, or null. */ public static String getBodyFromApptentivePush(Map data) { - if (!ApptentiveInternal.checkRegistered()) { - return null; - } - if (data == null) { - return null; + try { + if (!ApptentiveInternal.checkRegistered()) { + return null; + } + if (data == null) { + return null; + } + return data.get(ApptentiveInternal.BODY_DEFAULT); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while getting body from Apptentive push"); } - return data.get(ApptentiveInternal.BODY_DEFAULT); + return null; } //endregion @@ -697,8 +817,12 @@ public static String getBodyFromApptentivePush(Map data) { */ public static void setRatingProvider(IRatingProvider ratingProvider) { - if (ApptentiveInternal.isApptentiveRegistered()) { - ApptentiveInternal.getInstance().setRatingProvider(ratingProvider); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + ApptentiveInternal.getInstance().setRatingProvider(ratingProvider); + } + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while setting rating provider"); } } @@ -710,8 +834,12 @@ public static void setRatingProvider(IRatingProvider ratingProvider) { * @param value A String */ public static void putRatingProviderArg(String key, String value) { - if (ApptentiveInternal.isApptentiveRegistered()) { - ApptentiveInternal.getInstance().putRatingProviderArg(key, value); + try { + if (ApptentiveInternal.isApptentiveRegistered()) { + ApptentiveInternal.getInstance().putRatingProviderArg(key, value); + } + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while putting rating provider arg"); } } From 9578df9c3e70690c7467cd166cb5f745171702e1 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Mon, 17 Jul 2017 17:11:46 -0700 Subject: [PATCH 445/465] Fixed storing conversation id in metadata --- .../java/com/apptentive/android/sdk/ApptentiveInternal.java | 2 +- .../android/sdk/conversation/ConversationManager.java | 5 ++++- .../android/sdk/conversation/ConversationMetadataItem.java | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 33ebe29e6..3ff5a6ee6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -741,7 +741,7 @@ public void setAuthenticationFailureListener(Apptentive.AuthenticationFailedList public void notifyAuthenticationFailedListener(Apptentive.AuthenticationFailedReason reason, String conversationIdOfFailedRequest) { if (isConversationActive()) { String activeConversationId = getConversation().getConversationId(); - if (activeConversationId.equals(conversationIdOfFailedRequest)) { + if (StringUtils.equal(activeConversationId, conversationIdOfFailedRequest)) { Apptentive.AuthenticationFailedListener listener = authenticationFailedListenerRef != null ? authenticationFailedListenerRef.get() : null; if (listener != null) { listener.onAuthenticationFailed(reason); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 34a1d9c0b..7b1bf51bf 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -515,10 +515,13 @@ private void updateMetadataItems(Conversation conversation) { if (item == null) { item = new ConversationMetadataItem(conversation.getLocalIdentifier(), conversation.getConversationId(), conversation.getConversationDataFile(), conversation.getConversationMessagesFile()); conversationMetadata.addItem(item); + } else { + item.conversationId = notNull(conversation.getConversationId()); } + item.state = conversation.getState(); if (conversation.hasActiveState()) { - item.conversationToken = conversation.getConversationToken(); + item.conversationToken = notNull(conversation.getConversationToken()); } // update encryption key (if necessary) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java index 362f7f9e7..d15888a8b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationMetadataItem.java @@ -29,7 +29,7 @@ public class ConversationMetadataItem implements SerializableObject { /** * Conversation ID which was received from the backend */ - final String conversationId; + String conversationId; /** * The token for active conversations From 9037e8e646c5ce3d2bfa550aeb2bb6db3c1e4c31 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 18 Jul 2017 13:46:59 -0700 Subject: [PATCH 446/465] Fixed message polling worker --- .../messagecenter/MessagePollingWorker.java | 147 +++++++++++++----- 1 file changed, 106 insertions(+), 41 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java index 40aef1ca1..910de485f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessagePollingWorker.java @@ -15,17 +15,32 @@ import java.util.concurrent.atomic.AtomicBoolean; -import static com.apptentive.android.sdk.ApptentiveLogTag.MESSAGES; +import static com.apptentive.android.sdk.ApptentiveLogTag.*; class MessagePollingWorker implements Destroyable { + private final MessageManager messageManager; + private final long backgroundPollingInterval; + private final long foregroundPollingInterval; + private final Configuration conf; + + /** + * Worker thread for background message fetching + */ private MessagePollingThread pollingThread; - private final MessageManager manager; - final AtomicBoolean messageCenterInForeground = new AtomicBoolean(); + final AtomicBoolean messageCenterInForeground = new AtomicBoolean(); // TODO: remove this flag + + MessagePollingWorker(MessageManager messageManager) { + if (messageManager == null) { + throw new IllegalArgumentException("Message manager is null"); + } + + this.messageManager = messageManager; - MessagePollingWorker(MessageManager manager) { - this.manager = manager; + conf = Configuration.load(); + backgroundPollingInterval = conf.getMessageCenterBgPoll() * 1000; + foregroundPollingInterval = conf.getMessageCenterFgPoll() * 1000; } @Override @@ -34,46 +49,88 @@ public void destroy() { } private class MessagePollingThread extends Thread { - - private final long backgroundPollingInterval; - private final long foregroundPollingInterval; - private final Configuration conf; - - public MessagePollingThread() { - super("Message Polling Thread"); - conf = Configuration.load(); - backgroundPollingInterval = conf.getMessageCenterBgPoll() * 1000; - foregroundPollingInterval = conf.getMessageCenterFgPoll() * 1000; + /** + * Flag indicating if message polling is active (message polling stops when this flag is set to + * false + */ + private final AtomicBoolean isPolling = new AtomicBoolean(true); + + /** + * Flag indicating if polling thread is busy with message fetching. + */ + private final AtomicBoolean isFetching = new AtomicBoolean(false); + + MessagePollingThread() { + super("Message Polling Thread (" + getLocalConversationIdentifier() + ")"); } + @Override public void run() { try { - ApptentiveLog.v(MESSAGES, "Started polling messages"); + ApptentiveLog.v(MESSAGES, "%s started", getName()); - while (!Thread.currentThread().isInterrupted()) { - long pollingInterval = messageCenterInForeground.get() ? foregroundPollingInterval : backgroundPollingInterval; - if (ApptentiveInternal.getInstance().canShowMessageCenterInternal(getConversation())) { - ApptentiveLog.v(MESSAGES, "Checking server for new messages..."); - manager.fetchAndStoreMessages(messageCenterInForeground.get(), conf.isMessageCenterNotificationPopupEnabled()); - } + while (isPolling.get()) { - ApptentiveLog.v(MESSAGES, "Polling messages in %d sec", pollingInterval / 1000); - if (Thread.currentThread().isInterrupted() || !goToSleep(pollingInterval)) { + // sync poll message and mark thread as 'fetching' + isFetching.set(true); + pollMessagesSync(); + isFetching.set(false); + + // if we're done polling - no need to sleep + if (!isPolling.get()) { break; } + + // sleep until next iteration + long pollingInterval = messageCenterInForeground.get() ? foregroundPollingInterval : backgroundPollingInterval; + ApptentiveLog.v(MESSAGES, "Scheduled polling messages in %d sec", pollingInterval / 1000); + goToSleep(pollingInterval); } + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while polling messages"); } finally { - ApptentiveLog.v(MESSAGES, "Stopped polling messages"); + ApptentiveLog.v(MESSAGES, "%s stopped", getName()); } } - } - private boolean goToSleep(long millis) { - try { - Thread.sleep(millis); - return true; - } catch (InterruptedException e) { - return false; + private void pollMessagesSync() { + try { + if (ApptentiveInternal.getInstance().canShowMessageCenterInternal(getConversation())) { + ApptentiveLog.v(MESSAGES, "Checking server for new messages..."); + messageManager.fetchAndStoreMessages(messageCenterInForeground.get(), conf.isMessageCenterNotificationPopupEnabled()); + } else { + ApptentiveLog.w(MESSAGES, "Unable to fetch messages: message center can't be show at this time"); + } + } catch (Exception e) { + ApptentiveLog.e(MESSAGES, e, "Exception while polling messages"); + } + } + + private void goToSleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + ApptentiveLog.vv(MESSAGES, Thread.currentThread().getName() + " interrupted from sleep"); + } + } + + /** + * Stops current polling + */ + void stopPolling() { + isPolling.set(false); + interrupt(); + } + + /** + * Wake thread from a sleep + */ + void wakeUp() { + if (!isFetching.get()) { + interrupt(); + } else { + ApptentiveLog.vv("Can't wake up polling thread while it's synchronously fetching new messages"); + } } } @@ -88,10 +145,10 @@ void appWentToBackground() { /** * If coming from the background, wake the thread so that it immediately starts runs and runs more often. If coming - * from the foreground, let the polling interval timeout naturally, at which point the polling interval will become - * the background polling interval. + * from the foreground, let the isPolling interval timeout naturally, at which point the isPolling interval will become + * the background isPolling interval. * - * @param foreground true if the worker should be in foreground polling mode, else false. + * @param foreground true if the worker should be in foreground isPolling mode, else false. */ public void setMessageCenterInForeground(boolean foreground) { messageCenterInForeground.set(foreground); @@ -101,20 +158,28 @@ public void setMessageCenterInForeground(boolean foreground) { } synchronized void startPolling() { - stopPolling(); - - pollingThread = new MessagePollingThread(); - pollingThread.start(); + ApptentiveLog.v(MESSAGES, "Start polling messages (%s)", getLocalConversationIdentifier()); + if (pollingThread == null) { + pollingThread = new MessagePollingThread(); + pollingThread.start(); + } else { + pollingThread.wakeUp(); + } } synchronized void stopPolling() { + ApptentiveLog.v(MESSAGES, "Stop polling messages (%s)", getLocalConversationIdentifier()); if (pollingThread != null) { - pollingThread.interrupt(); + pollingThread.stopPolling(); pollingThread = null; } } private Conversation getConversation() { - return manager.getConversation(); + return messageManager.getConversation(); + } + + private String getLocalConversationIdentifier() { + return getConversation().getLocalIdentifier(); } } From 1ef477517fba39b87422fe481463c6281fb0d40f Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 18 Jul 2017 15:14:56 -0700 Subject: [PATCH 447/465] Made request initialization more predictable --- .../sdk/network/HttpRequestManagerTest.java | 6 ++++++ .../android/sdk/comm/ApptentiveHttpClient.java | 16 ++++++++-------- .../sdk/conversation/ConversationManager.java | 9 ++++++--- .../android/sdk/network/HttpRequest.java | 15 +++++++++++++-- .../android/sdk/network/HttpRequestManager.java | 7 +++---- .../sdk/storage/PayloadRequestSender.java | 6 +++--- .../android/sdk/storage/PayloadSender.java | 3 ++- .../sdk/storage/JsonPayloadSenderTest.java | 4 ++-- 8 files changed, 43 insertions(+), 23 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java index 86fdd6115..ff1b239a7 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/network/HttpRequestManagerTest.java @@ -317,5 +317,11 @@ void dispatchRequest(HttpRequest request) { } super.dispatchRequest(request); } + + @Override + synchronized HttpRequest startRequest(HttpRequest request) { + request.setRequestManager(this); + return super.startRequest(request); + } } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index f2bf99b06..c240ed99c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -66,14 +66,14 @@ public ApptentiveHttpClient(String apptentiveKey, String apptentiveSignature, St //region API Requests - public HttpJsonRequest getConversationToken(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { + public HttpJsonRequest createConversationTokenRequest(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { HttpJsonRequest request = createJsonRequest(ENDPOINT_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST); request.addListener(listener); - httpRequestManager.startRequest(request); + request.setRequestManager(httpRequestManager); return request; } - public HttpJsonRequest getLegacyConversationId(String conversationToken, HttpRequest.Listener listener) { + public HttpJsonRequest createLegacyConversationIdRequest(String conversationToken, HttpRequest.Listener listener) { if (StringUtils.isNullOrEmpty(conversationToken)) { throw new IllegalArgumentException("Conversation token is null or empty"); } @@ -81,11 +81,11 @@ public HttpJsonRequest getLegacyConversationId(String conversationToken, HttpReq HttpJsonRequest request = createJsonRequest(ENDPOINT_LEGACY_CONVERSATION, new JSONObject(), HttpRequestMethod.GET); request.setRequestProperty("Authorization", "OAuth " + conversationToken); request.addListener(listener); - httpRequestManager.startRequest(request); + request.setRequestManager(httpRequestManager); return request; } - public HttpJsonRequest login(String conversationId, String token, HttpRequest.Listener listener) { + public HttpJsonRequest createLoginRequest(String conversationId, String token, HttpRequest.Listener listener) { if (token == null) { throw new IllegalArgumentException("Token is null"); } @@ -105,7 +105,7 @@ public HttpJsonRequest login(String conversationId, String token, HttpRequest.Li } HttpJsonRequest request = createJsonRequest(endPoint, json, HttpRequestMethod.POST); request.addListener(listener); - httpRequestManager.startRequest(request); + request.setRequestManager(httpRequestManager); return request; } @@ -121,14 +121,14 @@ public HttpRequest findRequest(String tag) { //region PayloadRequestSender @Override - public HttpRequest sendPayload(PayloadData payload, HttpRequest.Listener listener) { + public HttpRequest createPayloadSendRequest(PayloadData payload, HttpRequest.Listener listener) { if (payload == null) { throw new IllegalArgumentException("Payload is null"); } HttpRequest request = createPayloadRequest(payload); request.addListener(listener); - httpRequestManager.startRequest(request); + request.setRequestManager(httpRequestManager); return request; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 7b1bf51bf..540e80b71 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -260,7 +260,7 @@ private HttpRequest fetchLegacyConversation(final Conversation conversation) { } HttpRequest request = getHttpClient() - .getLegacyConversationId(conversationToken, new HttpRequest.Listener() { + .createLegacyConversationIdRequest(conversationToken, new HttpRequest.Listener() { @Override public void onFinish(HttpJsonRequest request) { assertMainThread(); @@ -307,6 +307,7 @@ public void onFail(HttpJsonRequest request, String reason) { request.setCallbackQueue(DispatchQueue.mainQueue()); // we only deal with conversation on the main queue request.setTag(TAG_FETCH_CONVERSATION_TOKEN_REQUEST); + request.start(); return request; } @@ -363,7 +364,7 @@ private HttpRequest fetchConversationToken(final Conversation conversation) { conversationTokenRequest.setSdkAndAppRelease(SdkManager.getPayload(sdk), AppReleaseManager.getPayload(appRelease)); HttpRequest request = getHttpClient() - .getConversationToken(conversationTokenRequest, new HttpRequest.Listener() { + .createConversationTokenRequest(conversationTokenRequest, new HttpRequest.Listener() { @Override public void onFinish(HttpJsonRequest request) { assertMainThread(); @@ -424,6 +425,7 @@ public void onFail(HttpJsonRequest request, String reason) { request.setCallbackQueue(DispatchQueue.mainQueue()); // we only deal with conversation on the main queue request.setTag(TAG_FETCH_CONVERSATION_TOKEN_REQUEST); + request.start(); return request; } @@ -708,7 +710,7 @@ public void onFail(HttpRequest request, String reason) { } private void sendLoginRequest(String conversationId, final String userId, final String token, final LoginCallback callback) { - getHttpClient().login(conversationId, token, new HttpRequest.Listener() { + HttpJsonRequest request = getHttpClient().createLoginRequest(conversationId, token, new HttpRequest.Listener() { @Override public void onFinish(HttpJsonRequest request) { try { @@ -794,6 +796,7 @@ protected void execute() { }); } }); + request.start(); } public void logout() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java index 368553ce7..cd950c1a6 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequest.java @@ -165,6 +165,13 @@ public HttpRequest(String urlString) { //////////////////////////////////////////////////////////////// // Lifecycle + public void start() { + assertNotNull(requestManager); + if (requestManager != null) { + requestManager.startRequest(this); + } + } + @SuppressWarnings("unchecked") private void finishRequest() { try { @@ -508,6 +515,10 @@ public String toString() { //region Getters/Setters + public void setRequestManager(HttpRequestManager requestManager) { + this.requestManager = requestManager; + } + public void setMethod(HttpRequestMethod method) { if (method == null) { throw new IllegalArgumentException("Method is null"); @@ -655,8 +666,8 @@ public void onAfterSend(HttpRequest request) throws Exception { //endregion - private class NetworkUnavailableException extends IOException { - NetworkUnavailableException(String message) { + public static class NetworkUnavailableException extends IOException { + public NetworkUnavailableException(String message) { super(message); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java index bc563c3b9..c6a523304 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/network/HttpRequestManager.java @@ -52,7 +52,7 @@ public HttpRequestManager(DispatchQueue networkQueue) { /** * Starts network request on the network queue (method returns immediately) */ - public synchronized HttpRequest startRequest(final HttpRequest request) { + synchronized HttpRequest startRequest(HttpRequest request) { if (request == null) { throw new IllegalArgumentException("Request is null"); } @@ -93,8 +93,7 @@ public synchronized void cancelAll() { * Register active request */ synchronized void registerRequest(HttpRequest request) { - assertNull(request.requestManager); - request.requestManager = this; + assertTrue(this == request.requestManager); activeRequests.add(request); } @@ -102,11 +101,11 @@ synchronized void registerRequest(HttpRequest request) { * Unregisters active request */ synchronized void unregisterRequest(HttpRequest request) { + assertTrue(this == request.requestManager); boolean removed = activeRequests.remove(request); assertTrue(removed, "Attempted to unregister missing request: %s", request); if (removed) { - request.requestManager = null; notifyRequestFinished(request); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java index 81282835f..9fc1e44cc 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadRequestSender.java @@ -10,8 +10,8 @@ import com.apptentive.android.sdk.network.HttpRequest; /** - * Class responsible for creating and sending an {@link HttpRequest} with a given payload - * FIXME: this is a legacy workaround and would be removed soon + * Class responsible for creating a {@link HttpRequest} for a given payload + * FIXME: this is a legacy workaround and might be removed soon */ public interface PayloadRequestSender { /** @@ -20,5 +20,5 @@ public interface PayloadRequestSender { * @param payload to be sent * @param listener Http-request listener for the payload request */ - HttpRequest sendPayload(PayloadData payload, HttpRequest.Listener listener); + HttpRequest createPayloadSendRequest(PayloadData payload, HttpRequest.Listener listener); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java index d7c24920d..771570d91 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSender.java @@ -102,7 +102,7 @@ private synchronized void sendPayloadRequest(final PayloadData payload) { ApptentiveLog.v(PAYLOADS, "Sending payload: %s", payload); // create request object - final HttpRequest payloadRequest = requestSender.sendPayload(payload, new HttpRequest.Listener() { + final HttpRequest payloadRequest = requestSender.createPayloadSendRequest(payload, new HttpRequest.Listener() { @Override public void onFinish(HttpRequest request) { try { @@ -132,6 +132,7 @@ public void onFail(HttpRequest request, String reason) { // set 'retry' policy payloadRequest.setRetryPolicy(requestRetryPolicy); + payloadRequest.start(); } //endregion diff --git a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java index 3b594ce6f..822c2143c 100644 --- a/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java +++ b/apptentive/src/test/java/com/apptentive/android/sdk/storage/JsonPayloadSenderTest.java @@ -129,11 +129,11 @@ public MockPayloadRequestSender() { } @Override - public HttpRequest sendPayload(PayloadData payload, HttpRequest.Listener listener) { + public HttpRequest createPayloadSendRequest(PayloadData payload, HttpRequest.Listener listener) { MockHttpRequest request = new MockHttpRequest("http://apptentive.com"); request.setMockResponseHandler(((MockPayload) payload).getResponseHandler()); request.addListener(listener); - requestManager.startRequest(request); + request.setRequestManager(requestManager); return request; } } From e13590ca79784c3a548b9dc8efe1ee2c76be04e0 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 18 Jul 2017 15:15:14 -0700 Subject: [PATCH 448/465] Fixed assertion statement --- .../android/sdk/conversation/ConversationManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 540e80b71..84de0afc5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -518,7 +518,8 @@ private void updateMetadataItems(Conversation conversation) { item = new ConversationMetadataItem(conversation.getLocalIdentifier(), conversation.getConversationId(), conversation.getConversationDataFile(), conversation.getConversationMessagesFile()); conversationMetadata.addItem(item); } else { - item.conversationId = notNull(conversation.getConversationId()); + assertTrue(conversation.getConversationId() != null || conversation.hasState(ANONYMOUS_PENDING), "Missing conversation id for state: %s", conversation.getState()); + item.conversationId = conversation.getConversationId(); } item.state = conversation.getState(); From e05a502c1e223efe483c427d10e44c320b5695ba Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 18 Jul 2017 15:15:28 -0700 Subject: [PATCH 449/465] Fixed unit test --- .../android/sdk/storage/EncryptedPayloadSenderTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java index b4d68c571..1d4fc4707 100644 --- a/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java +++ b/apptentive/src/androidTest/java/com/apptentive/android/sdk/storage/EncryptedPayloadSenderTest.java @@ -10,6 +10,7 @@ import com.apptentive.android.sdk.encryption.Encryptor; import com.apptentive.android.sdk.model.EventPayload; +import org.json.JSONObject; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -34,8 +35,9 @@ public void testEncryptedPayload() throws Exception { try { byte[] plainText = encryptor.decrypt(cipherText); - EventPayload result = new EventPayload(new String(plainText)); - assertEquals(result.getEventLabel(), EVENT_LABEL); + JSONObject result = new JSONObject(new String(plainText)); + String label = result.getJSONObject("event").getString("label"); + assertEquals(label, EVENT_LABEL); } catch (Exception e) { fail(e.getMessage()); } From 2edacac4699a41ed65f6d25157d428fb6906759b Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 18 Jul 2017 15:29:27 -0700 Subject: [PATCH 450/465] Simplify request creating on ApptentiveHttpClient --- .../apptentive/android/sdk/comm/ApptentiveHttpClient.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index c240ed99c..4fbd2110e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -69,7 +69,6 @@ public ApptentiveHttpClient(String apptentiveKey, String apptentiveSignature, St public HttpJsonRequest createConversationTokenRequest(ConversationTokenRequest conversationTokenRequest, HttpRequest.Listener listener) { HttpJsonRequest request = createJsonRequest(ENDPOINT_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST); request.addListener(listener); - request.setRequestManager(httpRequestManager); return request; } @@ -81,7 +80,6 @@ public HttpJsonRequest createLegacyConversationIdRequest(String conversationToke HttpJsonRequest request = createJsonRequest(ENDPOINT_LEGACY_CONVERSATION, new JSONObject(), HttpRequestMethod.GET); request.setRequestProperty("Authorization", "OAuth " + conversationToken); request.addListener(listener); - request.setRequestManager(httpRequestManager); return request; } @@ -105,7 +103,6 @@ public HttpJsonRequest createLoginRequest(String conversationId, String token, H } HttpJsonRequest request = createJsonRequest(endPoint, json, HttpRequestMethod.POST); request.addListener(listener); - request.setRequestManager(httpRequestManager); return request; } @@ -128,7 +125,6 @@ public HttpRequest createPayloadSendRequest(PayloadData payload, HttpRequest.Lis HttpRequest request = createPayloadRequest(payload); request.addListener(listener); - request.setRequestManager(httpRequestManager); return request; } @@ -198,6 +194,7 @@ private RawHttpRequest createRawRequest(String endpoint, byte[] data, HttpReques } private void setupRequestDefaults(HttpRequest request) { + request.setRequestManager(httpRequestManager); request.setRequestProperty("User-Agent", userAgentString); request.setRequestProperty("Connection", "Keep-Alive"); request.setRequestProperty("Accept-Encoding", "gzip"); From a766f0af52e7f66bd870a78cc3f714ffff21ead2 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Tue, 18 Jul 2017 15:42:21 -0700 Subject: [PATCH 451/465] Update push example. --- .../push/MyFirebaseMessagingService.java | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyFirebaseMessagingService.java b/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyFirebaseMessagingService.java index f52008c27..c70515979 100644 --- a/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyFirebaseMessagingService.java +++ b/samples/apptentive-example/src/main/java/com/apptentive/android/example/push/MyFirebaseMessagingService.java @@ -32,24 +32,31 @@ public void onMessageReceived(RemoteMessage remoteMessage) { logPushBundle(remoteMessage); Map data = remoteMessage.getData(); - PendingIntent pendingIntent = Apptentive.buildPendingIntentFromPushNotification(data); - String title = Apptentive.getTitleFromApptentivePush(data); - String body = Apptentive.getBodyFromApptentivePush(data); + if (Apptentive.isApptentivePushNotification(data)) { + PendingIntent pendingIntent = Apptentive.buildPendingIntentFromPushNotification(data); + if (pendingIntent != null) { + String title = Apptentive.getTitleFromApptentivePush(data); + String body = Apptentive.getBodyFromApptentivePush(data); - if (pendingIntent != null) { - ApptentiveLog.e("Title: " + title); - ApptentiveLog.e("Body: " + body); - Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); - NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) - .setSmallIcon(R.drawable.notification) - .setContentTitle(title) - .setContentText(body) - .setAutoCancel(true) - .setSound(defaultSoundUri) - .setContentIntent(pendingIntent); - NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.notify(0, notificationBuilder.build()); + ApptentiveLog.e("Title: " + title); + ApptentiveLog.e("Body: " + body); + + Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.notification) + .setContentTitle(title) + .setContentText(body) + .setAutoCancel(true) + .setSound(defaultSoundUri) + .setContentIntent(pendingIntent); + NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.notify(0, notificationBuilder.build()); + } else { + // This push is from Apptentive, but not for the active conversation, so we can't safely display it. + } + } else { + // This push did not come from Apptentive, so handle it as your own push. } } From 84034a3da7e468e045c0140b9e9c20551ec3179e Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 18 Jul 2017 16:32:12 -0700 Subject: [PATCH 452/465] Make all the access to the Conversation data synchronized in order to avoid concurrent modification exception while serializing --- .../sdk/conversation/Conversation.java | 80 ++++++++++--------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 909edaa49..19189fe82 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -392,7 +392,7 @@ public void scheduleDeviceUpdate() { //region Getters & Setters public String getLocalIdentifier() { - return conversationData.getLocalIdentifier(); + return getConversationData().getLocalIdentifier(); } public ConversationState getState() { @@ -431,147 +431,153 @@ public boolean hasActiveState() { } public String getConversationToken() { - return conversationData.getConversationToken(); + return getConversationData().getConversationToken(); } public void setConversationToken(String conversationToken) { - conversationData.setConversationToken(conversationToken); + getConversationData().setConversationToken(conversationToken); } public String getConversationId() { - return conversationData.getConversationId(); + return getConversationData().getConversationId(); } public void setConversationId(String conversationId) { - conversationData.setConversationId(conversationId); + getConversationData().setConversationId(conversationId); } public Device getDevice() { - return conversationData.getDevice(); + return getConversationData().getDevice(); } public void setDevice(Device device) { - conversationData.setDevice(device); + getConversationData().setDevice(device); } public Device getLastSentDevice() { - return conversationData.getLastSentDevice(); + return getConversationData().getLastSentDevice(); } public void setLastSentDevice(Device lastSentDevice) { - conversationData.setLastSentDevice(lastSentDevice); + getConversationData().setLastSentDevice(lastSentDevice); } public Person getPerson() { - return conversationData.getPerson(); + return getConversationData().getPerson(); } public void setPerson(Person person) { - conversationData.setPerson(person); + getConversationData().setPerson(person); } public Person getLastSentPerson() { - return conversationData.getLastSentPerson(); + return getConversationData().getLastSentPerson(); } public void setLastSentPerson(Person lastSentPerson) { - conversationData.setLastSentPerson(lastSentPerson); + getConversationData().setLastSentPerson(lastSentPerson); } public Sdk getSdk() { - return conversationData.getSdk(); + return getConversationData().getSdk(); } public void setSdk(Sdk sdk) { - conversationData.setSdk(sdk); + getConversationData().setSdk(sdk); } public AppRelease getAppRelease() { - return conversationData.getAppRelease(); + return getConversationData().getAppRelease(); } public void setAppRelease(AppRelease appRelease) { - conversationData.setAppRelease(appRelease); + getConversationData().setAppRelease(appRelease); } public EventData getEventData() { - return conversationData.getEventData(); + return getConversationData().getEventData(); } public void setEventData(EventData eventData) { - conversationData.setEventData(eventData); + getConversationData().setEventData(eventData); } public String getLastSeenSdkVersion() { - return conversationData.getLastSeenSdkVersion(); + return getConversationData().getLastSeenSdkVersion(); } public void setLastSeenSdkVersion(String lastSeenSdkVersion) { - conversationData.setLastSeenSdkVersion(lastSeenSdkVersion); + getConversationData().setLastSeenSdkVersion(lastSeenSdkVersion); } public VersionHistory getVersionHistory() { - return conversationData.getVersionHistory(); + return getConversationData().getVersionHistory(); } public void setVersionHistory(VersionHistory versionHistory) { - conversationData.setVersionHistory(versionHistory); + getConversationData().setVersionHistory(versionHistory); } public boolean isMessageCenterFeatureUsed() { - return conversationData.isMessageCenterFeatureUsed(); + return getConversationData().isMessageCenterFeatureUsed(); } public void setMessageCenterFeatureUsed(boolean messageCenterFeatureUsed) { - conversationData.setMessageCenterFeatureUsed(messageCenterFeatureUsed); + getConversationData().setMessageCenterFeatureUsed(messageCenterFeatureUsed); } public boolean isMessageCenterWhoCardPreviouslyDisplayed() { - return conversationData.isMessageCenterWhoCardPreviouslyDisplayed(); + return getConversationData().isMessageCenterWhoCardPreviouslyDisplayed(); } public void setMessageCenterWhoCardPreviouslyDisplayed(boolean messageCenterWhoCardPreviouslyDisplayed) { - conversationData.setMessageCenterWhoCardPreviouslyDisplayed(messageCenterWhoCardPreviouslyDisplayed); + getConversationData().setMessageCenterWhoCardPreviouslyDisplayed(messageCenterWhoCardPreviouslyDisplayed); } public String getMessageCenterPendingMessage() { - return conversationData.getMessageCenterPendingMessage(); + return getConversationData().getMessageCenterPendingMessage(); } public void setMessageCenterPendingMessage(String messageCenterPendingMessage) { - conversationData.setMessageCenterPendingMessage(messageCenterPendingMessage); + getConversationData().setMessageCenterPendingMessage(messageCenterPendingMessage); } public String getMessageCenterPendingAttachments() { - return conversationData.getMessageCenterPendingAttachments(); + return getConversationData().getMessageCenterPendingAttachments(); } public void setMessageCenterPendingAttachments(String messageCenterPendingAttachments) { - conversationData.setMessageCenterPendingAttachments(messageCenterPendingAttachments); + getConversationData().setMessageCenterPendingAttachments(messageCenterPendingAttachments); } public String getTargets() { - return conversationData.getTargets(); + return getConversationData().getTargets(); } public void setTargets(String targets) { - conversationData.setTargets(targets); + getConversationData().setTargets(targets); } public String getInteractions() { - return conversationData.getInteractions(); + return getConversationData().getInteractions(); } public void setInteractions(String interactions) { - conversationData.setInteractions(interactions); + getConversationData().setInteractions(interactions); } public double getInteractionExpiration() { - return conversationData.getInteractionExpiration(); + return getConversationData().getInteractionExpiration(); } public void setInteractionExpiration(double interactionExpiration) { - conversationData.setInteractionExpiration(interactionExpiration); + getConversationData().setInteractionExpiration(interactionExpiration); + } + + // this is a synchronization hack: both save/load conversation data are synchronized so we can't + // modify conversation data while it's being serialized/deserialized + private synchronized ConversationData getConversationData() { + return conversationData; } public MessageManager getMessageManager() { From 4bdc4f9885a36e86962d1c927b6ee2e6413bf76c Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Tue, 18 Jul 2017 17:59:23 -0700 Subject: [PATCH 453/465] Use a byte buffer for object file serialization to avoid data corruption on exceptions --- .../apptentive/android/sdk/storage/FileSerializer.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java index 351e8ddbc..767aa5029 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/FileSerializer.java @@ -8,6 +8,7 @@ import com.apptentive.android.sdk.util.Util; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -37,12 +38,15 @@ public Object deserialize() throws SerializerException { } protected void serialize(File file, Object object) throws SerializerException { - FileOutputStream fos = null; + ByteArrayOutputStream bos; ObjectOutputStream oos = null; + FileOutputStream fos = null; try { - fos = new FileOutputStream(file); - oos = new ObjectOutputStream(fos); + bos = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(bos); oos.writeObject(object); + fos = new FileOutputStream(file); + fos.write(bos.toByteArray()); } catch (Exception e) { throw new SerializerException(e); } finally { From 3628c79e9653699e98ad653d017ea20023a93249 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 19 Jul 2017 10:46:07 -0700 Subject: [PATCH 454/465] Do not use built-in retry system for payload sending (use fallback logic instead) --- .../android/sdk/storage/ApptentiveTaskManager.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java index e503b6876..705c5d6b5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveTaskManager.java @@ -80,10 +80,8 @@ public ApptentiveTaskManager(Context context, ApptentiveHttpClient apptentiveHtt payloadSender = new PayloadSender(apptentiveHttpClient, new HttpRequestRetryPolicyDefault() { @Override public boolean shouldRetryRequest(int responseCode, int retryAttempt) { - if (appInBackground) { - return false; // don't retry if the app went background - } - return super.shouldRetryRequest(responseCode, retryAttempt); + return false; // don't use built-in retry logic for payloads since payload sender listener + // would handle it properly } }); payloadSender.setListener(this); From 3afd58edea05d95a072b0ac13cff4b4aaa856ae4 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 19 Jul 2017 12:04:39 -0700 Subject: [PATCH 455/465] Removed active conversation getter assertion since we can't force the dedicated access queue 100% of a time --- .../android/sdk/conversation/ConversationManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 84de0afc5..eae9ebb47 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -883,7 +883,8 @@ private void printMetadata(ConversationMetadata metadata, String title) { //region Getters/Setters public Conversation getActiveConversation() { - assertMainThread(); + // assertMainThread(); TODO: we should still only access the conversation on a dedicated queue + // but at this time we can't do it by design return activeConversation; } From 56fef089fc30902cc26eaf47d941867100692947 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 19 Jul 2017 12:05:00 -0700 Subject: [PATCH 456/465] Setup default assertion implementation --- .../apptentive/android/sdk/debug/Assert.java | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java index d3f041409..bc62d573c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/debug/Assert.java @@ -1,5 +1,6 @@ package com.apptentive.android.sdk.debug; +import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.util.ObjectUtils; import com.apptentive.android.sdk.util.StringUtils; import com.apptentive.android.sdk.util.threading.DispatchQueue; @@ -15,7 +16,12 @@ */ public class Assert { - private static AssertImp imp; + private static AssertImp imp = new AssertImp() { + @Override + public void assertFailed(String message) { + ApptentiveLog.e("Assertion failed: " + message + "\n" + getStackTrace(6)); + } + }; //region Booleans @@ -77,7 +83,9 @@ public static void assertFalse(boolean condition, String format, Object... args) //region Nullability - /** Helper function for getting non-null references */ + /** + * Helper function for getting non-null references + */ public static T notNull(T reference) { assertNotNull(reference); return reference; @@ -193,4 +201,22 @@ public static void assertFail(String message) { public static void setImp(AssertImp imp) { Assert.imp = imp; } + + private static String getStackTrace(int offset) { + StringBuilder sb = new StringBuilder(); + try { + StackTraceElement[] elements = Thread.currentThread().getStackTrace(); + + if (elements != null && elements.length > 0) { + for (int i = offset; i < elements.length; ++i) { + if (sb.length() > 0) { + sb.append('\n'); + } + sb.append(elements[i].toString()); + } + } + } catch (Exception ignored) { + } + return sb.toString(); + } } From 9584f056fcfec42c470a66727838fac1733396c4 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 19 Jul 2017 15:42:19 -0700 Subject: [PATCH 457/465] Rename the failure callback class to be more consistent. --- .../main/java/com/apptentive/android/sdk/Apptentive.java | 8 ++++---- .../com/apptentive/android/sdk/ApptentiveInternal.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index 7656760a5..814824b6a 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -1360,24 +1360,24 @@ public static void logout() { * for the current logged in conversation. If the failure is for another * conversation, or there is no active conversation, the listener is not called. */ - public static void setAuthenticationFailureListener(AuthenticationFailedListener listener) { + public static void setAuthenticationFailedListener(AuthenticationFailedListener listener) { try { if (!ApptentiveInternal.checkRegistered()) { return; } - ApptentiveInternal.getInstance().setAuthenticationFailureListener(listener); + ApptentiveInternal.getInstance().setAuthenticationFailedListener(listener); } catch (Exception e) { ApptentiveLog.w(e, "Error in Apptentive.setUnreadMessagesListener()"); MetricModule.sendError(e, null, null); } } - public static void clearAuthenticationFailureListener() { + public static void clearAuthenticationFailedListener() { try { if (!ApptentiveInternal.checkRegistered()) { return; } - ApptentiveInternal.getInstance().setAuthenticationFailureListener(null); + ApptentiveInternal.getInstance().setAuthenticationFailedListener(null); } catch (Exception e) { ApptentiveLog.w(e, "Error in Apptentive.clearUnreadMessagesListener()"); MetricModule.sendError(e, null, null); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 3ff5a6ee6..194b72c0a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -734,7 +734,7 @@ public void removeInteractionUpdateListener(InteractionManager.InteractionUpdate interactionUpdateListeners.remove(listener); } - public void setAuthenticationFailureListener(Apptentive.AuthenticationFailedListener listener) { + public void setAuthenticationFailedListener(Apptentive.AuthenticationFailedListener listener) { authenticationFailedListenerRef = new WeakReference<>(listener); } From e0144843e0a1b44626540b4c40cd6ac41846f96c Mon Sep 17 00:00:00 2001 From: skykelsey Date: Wed, 19 Jul 2017 15:46:55 -0700 Subject: [PATCH 458/465] Update docs for 4.0.0 release. --- CHANGELOG.md | 6 ++++++ README.md | 2 +- docs/APIChanges.md | 23 +++++++++++++++++++++-- docs/migrating_to_4.0.0.md | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 docs/migrating_to_4.0.0.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b28565f8..6ed158fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2017-07-19 - 4.0.0 + +#### Improvements + +* Added the ability to log customers in to different private conversations. Customers who are logged in will be able to see a private conversation that is distinct from the converations of other customers using the same app on the same device. When they log out, their data is protected with strong encryption. Logging back in unlocks their data again, and Apptentive resumes providing services to the customer. + # 2017-03-02 - 3.4.1 #### Improvements diff --git a/README.md b/README.md index 07323afc0..f14f06956 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ use your app, to talk to them at the right time, and in the right way. ##### [Release Notes](https://learn.apptentive.com/knowledge-base/android-sdk-release-notes/) -##### Binary releases are hosted for Maven [here](http://search.maven.org/#artifactdetails|com.apptentive|apptentive-android|3.4.1|aar) +##### Binary releases are hosted for Maven [here](http://search.maven.org/#artifactdetails|com.apptentive|apptentive-android|4.0.0|aar) #### Reporting Bugs diff --git a/docs/APIChanges.md b/docs/APIChanges.md index 836ac7651..59c1d0083 100644 --- a/docs/APIChanges.md +++ b/docs/APIChanges.md @@ -1,5 +1,24 @@ This document tracks changes to the API between versions. +# 4.0.0 + +Read the [Migrating to 4.0.0](migrating_to_4.0.0.md) guide. + +| Added Methods | +| ------------- | +| public static void login(String token, LoginCallback callback) | +| public static void logout() | + +| Modified Methods | +| ---------------- | +| public static void register(Application application, String apptentiveKey, String apptentiveSignature) | + +| Added Classes | +| ------------- | +| public interface Apptentive.LoginCallback | +| public interface Apptentive.AuthenticationFailedListener | +| public enum Apptentive.AuthenticationFailedReason | + # 3.4.1 | Added Methods| @@ -35,13 +54,13 @@ Read the [Migrating to 3.3.0](migrating_to_3.3.0.md) guide. | public static void onStart(android.app.Activity activity) | | public static void onStop(android.app.Activity activity) | -| Removed Class | +| Removed Classes | | ------------------ | | ApptentiveActivity.java | | ApptentiveListActivity.java | | ViewActivity.java | -| Added Class | +| Added Classes | | ------------------ | | ApptentiveViewActivity.java | diff --git a/docs/migrating_to_4.0.0.md b/docs/migrating_to_4.0.0.md new file mode 100644 index 000000000..f207d0d3b --- /dev/null +++ b/docs/migrating_to_4.0.0.md @@ -0,0 +1,34 @@ +# Migrating to 4.0.0 + +Version 4.0.0 of the Apptentive Android SDK has new APIs for customer login, as well as some changed APIs for SDK registration. If you were using any previous version of the Apptentive SDK in your app, you will need to follow one or more of the applicable steps below. + +## Major changes + +### Registration Has Changed + +* Registration now requires two pieces of information instead of one. Where before you needed to copy your API Key from the Apptentive website, now you need to copy the Apptentive App Key and Apptentive App Signature. You can find both of those in the [API & Development](https://be.apptentive.com/apps/current/settings/api) page. + +### Push Notification Changes + +* If you are not using the new Customer Login feature, you don't strictly have to follow this step, but it is a good idea in case you add it in the future. +* When you reply to your customers from the Apptentive website, we will send a push, if configured. If the customer for which the push is intended is not logged in, then the push should not be displayed. This small change means that you need to check whether the push is and Apptentive push, or should be handled by your existing push code, and then check where it can be displayed. See the [Push Notifications](https://learn.apptentive.com/knowledge-base/android-integration-reference/#push-notifications) documentation for code that demonstrates this change for the push provider you are using. + +## How to migrate + +The migration process should take 5-10 minutes to complete. + +1. Replace the call to `Apptentive.register(Application yourApp, String apiKey)` with a call to `Apptentive.register(Application yourApp, String apptentiveKey, String apptentiveSignature)`. +2. If you were calling `Apptentive.register(Application yourApp)`, and specifying the API Key in your manifest, you can continue to do so in version 4.0.0, you will just need to remove the old manifest element and add two new elements for the Apptentive App Key and Apptentive App Signature. + + Remove: + + `` + + Add: + + `` + + `` + +3. Make sure to check whether a push came from Apptentive, and handle it yourself if `Apptentive.isApptentivePushNotification()` returns `false`. + From c0a1bf46dbae4a5a1ef16fafef029e84fa985cd9 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Wed, 19 Jul 2017 16:55:06 -0700 Subject: [PATCH 459/465] Don't dispatch anything in login request callback --- .../android/sdk/conversation/ConversationManager.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index eae9ebb47..d89eb2598 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -736,9 +736,6 @@ public void onFail(HttpJsonRequest request, String reason) { } private void handleLoginFinished(final String conversationId, final String userId, final String token, final String encryptionKey) { - DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { - @Override - protected void execute() { assertFalse(isNullOrEmpty(encryptionKey),"Login finished with missing encryption key."); assertFalse(isNullOrEmpty(token), "Login finished with missing token."); assertMainThread(); @@ -785,8 +782,6 @@ public boolean accept(ConversationMetadataItem item) { handleLoginFailed("Internal error"); } } - }); - } private void handleLoginFailed(final String reason) { DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { @@ -797,6 +792,7 @@ protected void execute() { }); } }); + request.setCallbackQueue(DispatchQueue.mainQueue()); request.start(); } From f7595561d4ab263fd0e9721f10d74365d345a444 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 20 Jul 2017 10:11:27 -0700 Subject: [PATCH 460/465] Do not dispatch a second task on a login request callback queue since it's already dispatched on the correct queue --- .../android/sdk/conversation/ConversationManager.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index d89eb2598..cbff7fb63 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -783,10 +783,7 @@ public boolean accept(ConversationMetadataItem item) { } } - private void handleLoginFailed(final String reason) { - DispatchQueue.mainQueue().dispatchAsync(new DispatchTask() { - @Override - protected void execute() { + private void handleLoginFailed(String reason) { callback.onLoginFail(reason); } }); From bd4f35ee8dc5495e7063b6de6c26a9e30c15d397 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 20 Jul 2017 10:48:11 -0700 Subject: [PATCH 461/465] Added boolean flag to control multiple login requests --- .../android/sdk/ApptentiveInternal.java | 85 +++++++++++++------ 1 file changed, 61 insertions(+), 24 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index 3ff5a6ee6..d15037aff 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -79,6 +79,7 @@ import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION; import static com.apptentive.android.sdk.ApptentiveNotifications.NOTIFICATION_KEY_CONVERSATION_ID; import static com.apptentive.android.sdk.debug.Assert.assertNotNull; +import static com.apptentive.android.sdk.debug.Assert.assertTrue; import static com.apptentive.android.sdk.util.Constants.CONVERSATIONS_DIR; /** @@ -593,8 +594,8 @@ private boolean start() { // The app key can be passed in programmatically, or we can fallback to checking in the manifest. if (TextUtils.isEmpty(apptentiveKey) || apptentiveKey.contains(Constants.EXAMPLE_APPTENTIVE_KEY_VALUE)) { String errorMessage = "The Apptentive Key is not defined. You may provide your Apptentive Key in Apptentive.register(), or in as meta-data in your AndroidManifest.xml.\n" + - ""; + ""; if (appRelease.isDebug()) { throw new RuntimeException(errorMessage); } else { @@ -608,8 +609,8 @@ private boolean start() { // The app signature can be passed in programmatically, or we can fallback to checking in the manifest. if (TextUtils.isEmpty(apptentiveSignature) || apptentiveSignature.contains(Constants.EXAMPLE_APPTENTIVE_SIGNATURE_VALUE)) { String errorMessage = "The Apptentive Signature is not defined. You may provide your Apptentive Signature in Apptentive.register(), or in as meta-data in your AndroidManifest.xml.\n" + - ""; + ""; if (appRelease.isDebug()) { throw new RuntimeException(errorMessage); } else { @@ -744,8 +745,8 @@ public void notifyAuthenticationFailedListener(Apptentive.AuthenticationFailedRe if (StringUtils.equal(activeConversationId, conversationIdOfFailedRequest)) { Apptentive.AuthenticationFailedListener listener = authenticationFailedListenerRef != null ? authenticationFailedListenerRef.get() : null; if (listener != null) { - listener.onAuthenticationFailed(reason); - } + listener.onAuthenticationFailed(reason); + } } } } @@ -910,12 +911,12 @@ public boolean showMessageCenterInternal(Context context, Map cu Object value = customData.get(key); if (value != null) { if (!(value instanceof String || - value instanceof Boolean || - value instanceof Long || - value instanceof Double || - value instanceof Float || - value instanceof Integer || - value instanceof Short)) { + value instanceof Boolean || + value instanceof Long || + value instanceof Double || + value instanceof Float || + value instanceof Integer || + value instanceof Short)) { ApptentiveLog.w("Removing invalid customData type: %s", value.getClass().getSimpleName()); keysIterator.remove(); } @@ -1013,25 +1014,61 @@ private String getEndpointBase(SharedPreferences prefs) { //region Login/Logout + /** + * Flag indicating if login request is currently active (used to avoid multiple competing + * requests + */ + private boolean loginInProgress; + + /** + * Mutex object for synchronizing login request flag + */ + private final Object loginMutex = new Object(); + void login(String token, final LoginCallback callback) { - LoginCallback wrapperCallback = new LoginCallback() { - @Override - public void onLoginFinish() { - engageInternal(getApplicationContext(), "login"); + synchronized (loginMutex) { + if (loginInProgress) { if (callback != null) { - callback.onLoginFinish(); + callback.onLoginFail("Another login request is currently in progress"); } + return; } - @Override - public void onLoginFail(String errorMessage) { - if (callback != null) { - callback.onLoginFail(errorMessage); + loginInProgress = true; + + LoginCallback wrapperCallback = new LoginCallback() { + @Override + public void onLoginFinish() { + synchronized (loginMutex) { + assertTrue(loginInProgress); + try { + engageInternal(getApplicationContext(), "login"); + if (callback != null) { + callback.onLoginFinish(); + } + } finally { + loginInProgress = false; + } + } } - } - }; - conversationManager.login(token, wrapperCallback); + @Override + public void onLoginFail(String errorMessage) { + synchronized (loginMutex) { + assertTrue(loginInProgress); + try { + if (callback != null) { + callback.onLoginFail(errorMessage); + } + } finally { + loginInProgress = false; + } + } + } + }; + + conversationManager.login(token, wrapperCallback); + } } void logout() { From 63c8896c97e3fd0d43acb4b9744e8077e475ca45 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 20 Jul 2017 11:07:50 -0700 Subject: [PATCH 462/465] Build error fix --- .../sdk/conversation/ConversationManager.java | 88 +++++++++---------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index cbff7fb63..837f57597 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -736,57 +736,55 @@ public void onFail(HttpJsonRequest request, String reason) { } private void handleLoginFinished(final String conversationId, final String userId, final String token, final String encryptionKey) { - assertFalse(isNullOrEmpty(encryptionKey),"Login finished with missing encryption key."); - assertFalse(isNullOrEmpty(token), "Login finished with missing token."); - assertMainThread(); - - try { - // if we were previously logged out we might end up with no active conversation - if (activeConversation == null) { - // attempt to find previous logged out conversation - final ConversationMetadataItem conversationItem = conversationMetadata.findItem(new Filter() { - @Override - public boolean accept(ConversationMetadataItem item) { - return StringUtils.equal(item.getUserId(), userId); - } - }); - - if (conversationItem != null) { - conversationItem.conversationToken = token; - conversationItem.encryptionKey = encryptionKey; - activeConversation = loadConversation(conversationItem); - } else { - ApptentiveLog.v(CONVERSATION, "Creating new logged in conversation..."); - File dataFile = new File(apptentiveConversationsStorageDir, "conversation-" + Util.generateRandomFilename()); - File messagesFile = new File(apptentiveConversationsStorageDir, "messages-" + Util.generateRandomFilename()); - activeConversation = new Conversation(dataFile, messagesFile); - - // FIXME: if we don't set these here - device payload would return 4xx error code - activeConversation.setDevice(DeviceManager.generateNewDevice(getContext())); - activeConversation.setAppRelease(ApptentiveInternal.getInstance().getAppRelease()); - activeConversation.setSdk(SdkManager.generateCurrentSdk()); - } + assertFalse(isNullOrEmpty(encryptionKey),"Login finished with missing encryption key."); + assertFalse(isNullOrEmpty(token), "Login finished with missing token."); + assertMainThread(); + + try { + // if we were previously logged out we might end up with no active conversation + if (activeConversation == null) { + // attempt to find previous logged out conversation + final ConversationMetadataItem conversationItem = conversationMetadata.findItem(new Filter() { + @Override + public boolean accept(ConversationMetadataItem item) { + return StringUtils.equal(item.getUserId(), userId); } + }); - activeConversation.setEncryptionKey(encryptionKey); - activeConversation.setConversationToken(token); - activeConversation.setConversationId(conversationId); - activeConversation.setUserId(userId); - activeConversation.setState(LOGGED_IN); - handleConversationStateChange(activeConversation); - - // notify delegate - callback.onLoginFinish(); - } catch (Exception e) { - ApptentiveLog.e(e, "Exception while creating logged-in conversation"); - handleLoginFailed("Internal error"); + if (conversationItem != null) { + conversationItem.conversationToken = token; + conversationItem.encryptionKey = encryptionKey; + activeConversation = loadConversation(conversationItem); + } else { + ApptentiveLog.v(CONVERSATION, "Creating new logged in conversation..."); + File dataFile = new File(apptentiveConversationsStorageDir, "conversation-" + Util.generateRandomFilename()); + File messagesFile = new File(apptentiveConversationsStorageDir, "messages-" + Util.generateRandomFilename()); + activeConversation = new Conversation(dataFile, messagesFile); + + // FIXME: if we don't set these here - device payload would return 4xx error code + activeConversation.setDevice(DeviceManager.generateNewDevice(getContext())); + activeConversation.setAppRelease(ApptentiveInternal.getInstance().getAppRelease()); + activeConversation.setSdk(SdkManager.generateCurrentSdk()); } } + activeConversation.setEncryptionKey(encryptionKey); + activeConversation.setConversationToken(token); + activeConversation.setConversationId(conversationId); + activeConversation.setUserId(userId); + activeConversation.setState(LOGGED_IN); + handleConversationStateChange(activeConversation); + + // notify delegate + callback.onLoginFinish(); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while creating logged-in conversation"); + handleLoginFailed("Internal error"); + } + } + private void handleLoginFailed(String reason) { - callback.onLoginFail(reason); - } - }); + callback.onLoginFail(reason); } }); request.setCallbackQueue(DispatchQueue.mainQueue()); From 58704c83ec26e2bf3c87f462a84d750f6c3f6890 Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 20 Jul 2017 14:36:47 -0700 Subject: [PATCH 463/465] Refactor the login request for login to a new conversation so that it sends the AppRelease/Sdk and Device object. Now an immediate interaction fetch will fetch the correct interactions right away. ANDROID-1046 --- .../sdk/comm/ApptentiveHttpClient.java | 26 ++++++ .../sdk/conversation/ConversationManager.java | 86 ++++++++++++++++++- 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java index 4fbd2110e..fe30a945c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveHttpClient.java @@ -13,7 +13,13 @@ import com.apptentive.android.sdk.network.HttpRequestManager; import com.apptentive.android.sdk.network.HttpRequestMethod; import com.apptentive.android.sdk.network.RawHttpRequest; +import com.apptentive.android.sdk.storage.AppRelease; +import com.apptentive.android.sdk.storage.AppReleaseManager; +import com.apptentive.android.sdk.storage.Device; +import com.apptentive.android.sdk.storage.DeviceManager; import com.apptentive.android.sdk.storage.PayloadRequestSender; +import com.apptentive.android.sdk.storage.Sdk; +import com.apptentive.android.sdk.storage.SdkManager; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.StringUtils; @@ -106,6 +112,26 @@ public HttpJsonRequest createLoginRequest(String conversationId, String token, H return request; } + public HttpJsonRequest createFirstLoginRequest(String token, AppRelease appRelease, Sdk sdk, Device device, HttpRequest.Listener listener) { + if (token == null) { + throw new IllegalArgumentException("Token is null"); + } + + ConversationTokenRequest conversationTokenRequest = new ConversationTokenRequest(); + conversationTokenRequest.setSdkAndAppRelease(SdkManager.getPayload(sdk), AppReleaseManager.getPayload(appRelease)); + conversationTokenRequest.setDevice(DeviceManager.getDiffPayload(null, device)); + + try { + conversationTokenRequest.put("token", token); + } catch (JSONException e) { + // Can't happen + } + + HttpJsonRequest request = createJsonRequest(ENDPOINT_LOG_IN_TO_NEW_CONVERSATION, conversationTokenRequest, HttpRequestMethod.POST); + request.addListener(listener); + return request; + } + /** * Returns the first request with a given tag or null is not found */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 837f57597..eac201b48 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -641,7 +641,7 @@ public boolean accept(ConversationMetadataItem item) { if (conversationItem == null) { ApptentiveLog.w("No conversation found matching user: '%s'. Logging in as new user.", userId); - sendLoginRequest(null, userId, token, callback); + sendFirstLoginRequest(userId, token, callback); return; } @@ -791,6 +791,90 @@ private void handleLoginFailed(String reason) { request.start(); } + private void sendFirstLoginRequest(final String userId, final String token, final LoginCallback callback) { + final AppRelease appRelease = ApptentiveInternal.getInstance().getAppRelease(); + final Sdk sdk = SdkManager.generateCurrentSdk(); + final Device device = DeviceManager.generateNewDevice(getContext()); + + HttpJsonRequest request = getHttpClient().createFirstLoginRequest(token, appRelease, sdk, device, new HttpRequest.Listener() { + @Override + public void onFinish(HttpJsonRequest request) { + try { + final JSONObject responseObject = request.getResponseObject(); + final String encryptionKey = responseObject.getString("encryption_key"); + final String incomingConversationId = responseObject.getString("id"); + handleLoginFinished(incomingConversationId, userId, token, encryptionKey); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while parsing login response"); + handleLoginFailed("Internal error"); + } + } + + @Override + public void onCancel(HttpJsonRequest request) { + handleLoginFailed("Login request was cancelled"); + } + + @Override + public void onFail(HttpJsonRequest request, String reason) { + handleLoginFailed(reason); + } + + private void handleLoginFinished(final String conversationId, final String userId, final String token, final String encryptionKey) { + assertFalse(isNullOrEmpty(encryptionKey),"Login finished with missing encryption key."); + assertFalse(isNullOrEmpty(token), "Login finished with missing token."); + assertMainThread(); + + try { + // if we were previously logged out we might end up with no active conversation + if (activeConversation == null) { + // attempt to find previous logged out conversation + final ConversationMetadataItem conversationItem = conversationMetadata.findItem(new Filter() { + @Override + public boolean accept(ConversationMetadataItem item) { + return StringUtils.equal(item.getUserId(), userId); + } + }); + + if (conversationItem != null) { + conversationItem.conversationToken = token; + conversationItem.encryptionKey = encryptionKey; + activeConversation = loadConversation(conversationItem); + } else { + ApptentiveLog.v(CONVERSATION, "Creating new logged in conversation..."); + File dataFile = new File(apptentiveConversationsStorageDir, "conversation-" + Util.generateRandomFilename()); + File messagesFile = new File(apptentiveConversationsStorageDir, "messages-" + Util.generateRandomFilename()); + activeConversation = new Conversation(dataFile, messagesFile); + + activeConversation.setAppRelease(appRelease); + activeConversation.setSdk(sdk); + activeConversation.setDevice(device); + } + } + + activeConversation.setEncryptionKey(encryptionKey); + activeConversation.setConversationToken(token); + activeConversation.setConversationId(conversationId); + activeConversation.setUserId(userId); + activeConversation.setState(LOGGED_IN); + handleConversationStateChange(activeConversation); + + // notify delegate + callback.onLoginFinish(); + } catch (Exception e) { + ApptentiveLog.e(e, "Exception while creating logged-in conversation"); + handleLoginFailed("Internal error"); + } + } + + private void handleLoginFailed(String reason) { + callback.onLoginFail(reason); + } + }); + request.setCallbackQueue(DispatchQueue.mainQueue()); + request.start(); + } + public void logout() { // we only deal with an active conversation on the main thread if (!DispatchQueue.isMainQueue()) { From 30e77730698f1afe411b7acbca34846341bcd42d Mon Sep 17 00:00:00 2001 From: skykelsey Date: Thu, 20 Jul 2017 14:54:53 -0700 Subject: [PATCH 464/465] Enforce null activeConversation when logging into new conversation. --- .../sdk/conversation/ConversationManager.java | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index eac201b48..001cde1fe 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -821,35 +821,33 @@ public void onFail(HttpJsonRequest request, String reason) { } private void handleLoginFinished(final String conversationId, final String userId, final String token, final String encryptionKey) { + assertNull(activeConversation, "Finished logging into new conversation, but one was already active."); assertFalse(isNullOrEmpty(encryptionKey),"Login finished with missing encryption key."); assertFalse(isNullOrEmpty(token), "Login finished with missing token."); assertMainThread(); try { - // if we were previously logged out we might end up with no active conversation - if (activeConversation == null) { - // attempt to find previous logged out conversation - final ConversationMetadataItem conversationItem = conversationMetadata.findItem(new Filter() { - @Override - public boolean accept(ConversationMetadataItem item) { - return StringUtils.equal(item.getUserId(), userId); - } - }); - - if (conversationItem != null) { - conversationItem.conversationToken = token; - conversationItem.encryptionKey = encryptionKey; - activeConversation = loadConversation(conversationItem); - } else { - ApptentiveLog.v(CONVERSATION, "Creating new logged in conversation..."); - File dataFile = new File(apptentiveConversationsStorageDir, "conversation-" + Util.generateRandomFilename()); - File messagesFile = new File(apptentiveConversationsStorageDir, "messages-" + Util.generateRandomFilename()); - activeConversation = new Conversation(dataFile, messagesFile); - - activeConversation.setAppRelease(appRelease); - activeConversation.setSdk(sdk); - activeConversation.setDevice(device); + // attempt to find previous logged out conversation + final ConversationMetadataItem conversationItem = conversationMetadata.findItem(new Filter() { + @Override + public boolean accept(ConversationMetadataItem item) { + return StringUtils.equal(item.getUserId(), userId); } + }); + + if (conversationItem != null) { + conversationItem.conversationToken = token; + conversationItem.encryptionKey = encryptionKey; + activeConversation = loadConversation(conversationItem); + } else { + ApptentiveLog.v(CONVERSATION, "Creating new logged in conversation..."); + File dataFile = new File(apptentiveConversationsStorageDir, "conversation-" + Util.generateRandomFilename()); + File messagesFile = new File(apptentiveConversationsStorageDir, "messages-" + Util.generateRandomFilename()); + activeConversation = new Conversation(dataFile, messagesFile); + + activeConversation.setAppRelease(appRelease); + activeConversation.setSdk(sdk); + activeConversation.setDevice(device); } activeConversation.setEncryptionKey(encryptionKey); From b02417f0e7907999fb4ff540d5cdc0ae5f654f30 Mon Sep 17 00:00:00 2001 From: Alex Lementuev Date: Thu, 20 Jul 2017 15:22:05 -0700 Subject: [PATCH 465/465] Fixed scheduling conversation data save --- .../sdk/conversation/Conversation.java | 22 +++++++---- .../sdk/conversation/ConversationManager.java | 37 ++++++------------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java index 19189fe82..eb826aa36 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/Conversation.java @@ -141,12 +141,15 @@ public Conversation(File conversationDataFile, File conversationMessagesFile) { this.conversationMessagesFile = conversationMessagesFile; conversationData = new ConversationData(); - conversationData.setDataChangedListener(this); FileMessageStore messageStore = new FileMessageStore(conversationMessagesFile); messageManager = new MessageManager(this, messageStore); // it's important to initialize message manager in a constructor since other SDK parts depend on it via Apptentive singleton } + public void startListeningForChanges() { + conversationData.setDataChangedListener(this); + } + //region Payloads public void addPayload(Payload payload) { @@ -283,6 +286,15 @@ public void storeInteractionManifest(String interactionManifest) { //region Saving + public void scheduleSaveConversationData() { + boolean scheduled = DispatchQueue.backgroundQueue().dispatchAsyncOnce(saveConversationTask, 100L); + if (scheduled) { + ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); + } else { + ApptentiveLog.d(CONVERSATION, "Conversation save already scheduled."); + } + } + /** * Saves conversation data to the disk synchronously. Returns true * if succeed. @@ -320,7 +332,6 @@ synchronized void loadConversationData() throws SerializerException { ApptentiveLog.d(CONVERSATION, "Loading %sconversation data...", hasState(LOGGED_IN) ? "encrypted " : ""); conversationData = (ConversationData) serializer.deserialize(); - conversationData.setDataChangedListener(this); ApptentiveLog.d(CONVERSATION, "Conversation data loaded (took %d ms)", System.currentTimeMillis() - start); } @@ -330,12 +341,7 @@ synchronized void loadConversationData() throws SerializerException { @Override public void onDataChanged() { - boolean scheduled = DispatchQueue.backgroundQueue().dispatchAsyncOnce(saveConversationTask, 100L); - if (scheduled) { - ApptentiveLog.d(CONVERSATION, "Scheduling conversation save."); - } else { - ApptentiveLog.d(CONVERSATION, "Conversation save already scheduled."); - } + scheduleSaveConversationData(); } //endregion diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java index 001cde1fe..760d6b0a2 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/conversation/ConversationManager.java @@ -135,6 +135,9 @@ public boolean loadActiveConversation(Context context) { activeConversation = loadActiveConversationGuarded(); if (activeConversation != null) { + activeConversation.startListeningForChanges(); + activeConversation.scheduleSaveConversationData(); + dispatchDebugEvent(EVT_CONVERSATION_LOAD, "successful", Boolean.TRUE, "conversation_state", activeConversation.getState().toString(), @@ -317,7 +320,7 @@ private Conversation loadConversation(ConversationMetadataItem item) throws Seri conversation.setEncryptionKey(item.getEncryptionKey()); // it's important to set encryption key before loading data conversation.setState(item.getState()); // set the state same as the item's state conversation.setUserId(item.getUserId()); - conversation.setConversationToken(item.getConversationToken()); + conversation.setConversationToken(item.getConversationToken()); // FIXME: this would be overwritten by the next call conversation.loadConversationData(); conversation.checkInternalConsistency(); @@ -466,30 +469,6 @@ private void handleConversationStateChange(Conversation conversation) { } } - /* For testing purposes */ - public synchronized boolean setActiveConversation(final String conversationId) throws SerializerException { - final ConversationMetadataItem item = conversationMetadata.findItem(new Filter() { - @Override - public boolean accept(ConversationMetadataItem item) { - return item.conversationId.equals(conversationId); - } - }); - - if (item == null) { - ApptentiveLog.w(CONVERSATION, "Conversation not found: %s", conversationId); - return false; - } - - final Conversation conversation = loadConversation(item); - if (conversation == null) { - ApptentiveLog.w(CONVERSATION, "Conversation not loaded: %s", conversationId); - return false; - } - - handleConversationStateChange(conversation); - return true; - } - private void updateMetadataItems(Conversation conversation) { ApptentiveLog.vv("Updating metadata: state=%s localId=%s conversationId=%s token=%s", conversation.getState(), @@ -773,6 +752,10 @@ public boolean accept(ConversationMetadataItem item) { activeConversation.setConversationId(conversationId); activeConversation.setUserId(userId); activeConversation.setState(LOGGED_IN); + + activeConversation.startListeningForChanges(); + activeConversation.scheduleSaveConversationData(); + handleConversationStateChange(activeConversation); // notify delegate @@ -855,6 +838,10 @@ public boolean accept(ConversationMetadataItem item) { activeConversation.setConversationId(conversationId); activeConversation.setUserId(userId); activeConversation.setState(LOGGED_IN); + + activeConversation.startListeningForChanges(); + activeConversation.scheduleSaveConversationData(); + handleConversationStateChange(activeConversation); // notify delegate