diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
deleted file mode 100644
index 7f68460d8..000000000
--- a/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9e85a25e6..1a1f52385 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+# 2016-06-20 - 3.1.1
+
+#### Improvements
+
+* Defer message polling task until Message Center is opened, UnreadMessagesListener is registered, or a Push is received.
+* Add internal method to set the application theme programmatically from which the Apptentive UI will inherit styles.
+
+#### Bugs Fixed
+
+* statusBarColor attribute was causing run-time exception on pre-21 devices.
+
# 2016-06-08 - 3.1.0
#### Improvements
diff --git a/README.md b/README.md
index cf48b46ee..e512cad5c 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.
##### Version history is tracked [here](CHANGELOG.md)
-##### Binary releases are hosted for Maven [here](http://search.maven.org/#artifactdetails|com.apptentive|apptentive-android|3.1.0|aar)
+##### Binary releases are hosted for Maven [here](http://search.maven.org/#artifactdetails|com.apptentive|apptentive-android|3.1.1|aar)
#### Reporting Bugs
We encourage you to help us find and fix bugs. If you find a bug, please fill in the contributor agreement, then open a [github issue](https://github.com/apptentive/apptentive-android/issues?direction=desc&sort=created&state=open).
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 05c53eb67..09d558d14 100644
--- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java
+++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java
@@ -96,8 +96,8 @@ public class ApptentiveInternal {
// toolbar theme specified in R.attr.apptentiveToolbarTheme
Resources.Theme apptentiveToolbarTheme;
- // app default theme res id, if specified in app AndroidManifest
- int appDefaultThemeId;
+ // app default appcompat theme res id, if specified in app AndroidManifest
+ int appDefaultAppCompatThemeId;
int statusBarColorDefault;
String defaultAppDisplayName = "this app";
@@ -137,9 +137,6 @@ public static PushAction parse(String name) {
private static volatile ApptentiveInternal sApptentiveInternal;
- ApptentiveInternal() {
- }
-
public static boolean isApptentiveRegistered() {
return (sApptentiveInternal != null);
@@ -208,7 +205,9 @@ public static ApptentiveInternal getInstance() {
synchronized (ApptentiveInternal.class) {
if (sApptentiveInternal != null && !isApptentiveInitialized.get()) {
isApptentiveInitialized.set(true);
- sApptentiveInternal.init();
+ if (!sApptentiveInternal.init()) {
+ ApptentiveLog.e("Apptentive init() failed");
+ }
}
}
}
@@ -249,11 +248,45 @@ static void setLifeCycleCallback() {
synchronized (ApptentiveInternal.class) {
if (sApptentiveInternal != null && sApptentiveInternal.lifecycleCallbacks == null &&
sApptentiveInternal.appContext instanceof Application) {
- sApptentiveInternal.lifecycleCallbacks = new ApptentiveActivityLifecycleCallbacks();
- ((Application) sApptentiveInternal.appContext).registerActivityLifecycleCallbacks(sApptentiveInternal.lifecycleCallbacks);
+ sApptentiveInternal.lifecycleCallbacks = new ApptentiveActivityLifecycleCallbacks();
+ ((Application) sApptentiveInternal.appContext).registerActivityLifecycleCallbacks(sApptentiveInternal.lifecycleCallbacks);
+ }
+ }
+ }
+ }
+
+
+ /*
+ * Set default theme whom Apptentive UI will inherit theme attributes from. Apptentive will only
+ * inherit from an AppCompat theme
+ * @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) {
+ try {
+ if (themeResId != 0) {
+ // If passed theme res id does not exist, an exception would be thrown and caught
+ appContext.getResources().getResourceName(themeResId);
+
+ // Check if the theme to be inheritd from is an AppCompat theme.
+ Resources.Theme appDefaultTheme = appContext.getResources().newTheme();
+ appDefaultTheme.applyStyle(themeResId, true);
+
+ TypedArray a = appDefaultTheme.obtainStyledAttributes(android.support.v7.appcompat.R.styleable.AppCompatTheme);
+ try {
+ if (a.hasValue(android.support.v7.appcompat.R.styleable.AppCompatTheme_colorPrimaryDark)) {
+ // Only set to use if it's an AppCompat theme. See updateApptentiveInteractionTheme() for theme inheritance chain
+ appDefaultAppCompatThemeId = themeResId;
+ return true;
}
+ } finally {
+ a.recycle();
}
}
+ } catch (Resources.NotFoundException e) {
+ ApptentiveLog.e("Theme Res id not found");
+ }
+ return false;
}
/*
@@ -445,29 +478,17 @@ public void onAppEnterBackground() {
*/
public void updateApptentiveInteractionTheme(Resources.Theme interactionTheme, Context context) {
/* Step 1: Apply Apptentive default theme layer.
- * If host activity is an Apptentive activity, the base theme already has Apptentive defaults applied, so skip Step 1.
- * If parent activity is NOT an Apptentive activity, first apply Apptentive defaults.
+ * If host activity is an activity, the base theme already has Apptentive defaults applied, so skip Step 1.
+ * If parent activity is NOT an activity, first apply Apptentive defaults.
*/
if (!(context instanceof Activity)) {
// If host context is not an activity, i.e. application context, treat it as initial theme setup
interactionTheme.applyStyle(R.style.ApptentiveTheme_Base_Versioned, true);
}
- // Step 2: Inherit app default theme if there is one specified in app's AndroidManifest
- if (appDefaultThemeId != 0) {
- Resources.Theme appDefaultTheme = context.getResources().newTheme();
- appDefaultTheme.applyStyle(appDefaultThemeId, true);
-
- // If the app contains colorPrimaryDark, it is using an AppCompat theme. Therefore, we want to use it.
- // If it's not using an AppCompat theme, we don't want to apply it to our SDK, and use our default theme instead.
- TypedArray a = appDefaultTheme.obtainStyledAttributes(android.support.v7.appcompat.R.styleable.AppCompatTheme);
- try {
- if (a.hasValue(android.support.v7.appcompat.R.styleable.AppCompatTheme_colorPrimaryDark)) {
- interactionTheme.applyStyle(appDefaultThemeId, true);
- }
- } finally {
- a.recycle();
- }
+ // Step 2: Inherit app default appcompat theme if there is one specified in app's AndroidManifest
+ if (appDefaultAppCompatThemeId != 0) {
+ interactionTheme.applyStyle(appDefaultAppCompatThemeId, true);
}
// Step 3: Restore Apptentive UI window properties that may have been overridden in Step 2. This theme
@@ -481,26 +502,43 @@ public void updateApptentiveInteractionTheme(Resources.Theme interactionTheme, C
interactionTheme.applyStyle(themeOverrideResId, true);
}
- int transparentColor = ContextCompat.getColor(context, android.R.color.transparent);
- TypedArray a = interactionTheme.obtainStyledAttributes(new int[]{android.R.attr.statusBarColor});
- try {
- statusBarColorDefault = a.getColor(0, transparentColor);
- } finally {
- a.recycle();
+ // Step 5: Update statusbar color
+ /* Obtain the default statusbar color. When Apptentive Modal interaction is shown,
+ * a translucent overlay would be applied on top of statusBarColorDefault
+ */
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ int transparentColor = ContextCompat.getColor(context, android.R.color.transparent);
+ TypedArray a = interactionTheme.obtainStyledAttributes(new int[]{android.R.attr.statusBarColor});
+ try {
+ statusBarColorDefault = a.getColor(0, transparentColor);
+ } finally {
+ a.recycle();
+ }
}
+ // Step 6: Update toolbar overlay theme
int toolbarThemeId = Util.getResourceIdFromAttribute(interactionTheme, R.attr.apptentiveToolbarTheme);
apptentiveToolbarTheme.setTo(interactionTheme);
apptentiveToolbarTheme.applyStyle(toolbarThemeId, true);
}
- public void init() {
-
+ public boolean init() {
+ boolean bRet = true;
codePointStore.init();
- messageManager.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
+ * 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();
+ }
conversationToken = prefs.getString(Constants.PREF_KEY_CONVERSATION_TOKEN, null);
conversationId = prefs.getString(Constants.PREF_KEY_CONVERSATION_ID, null);
personId = prefs.getString(Constants.PREF_KEY_PERSON_ID, null);
+ apptentiveToolbarTheme = appContext.getResources().newTheme();
boolean apptentiveDebug = false;
String logLevelOverride = null;
@@ -519,31 +557,10 @@ public void init() {
isAppDebuggable = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
- /*
- * Construct the theme used by Apptentive Ui in the following order
- * 1. start with Apptentive default theme : ApptentiveTheme.Base.Versioned
- * 2. merge with app default theme, if one is specified and it's a AppCompat theme.
- * (merge takes value from app default theme if both define the same attribute)
- * 3. apply Apptentive UI specific frame style
- * 4. Apply apptentive theme override specified by the app in ApptentiveThemeOverride
- */
- appDefaultThemeId = ai.theme;
- boolean appHasTheme = appDefaultThemeId != 0;
- boolean appThemeIsAppCompatTheme = false;
-
- apptentiveToolbarTheme = appContext.getResources().newTheme();
-
- // Step 2, check if app specifies a default appcompat theme
- if (appHasTheme) {
- Resources.Theme appDefaultTheme = appContext.getResources().newTheme();
- appDefaultTheme.applyStyle(appDefaultThemeId, true);
-
- // If the app contains colorPrimaryDark, it is using an AppCompat theme. Therefore, we want to use it.
- // If it's not using an AppCompat theme, we don't want to apply it to our SDK, and use our default theme instead.
- appThemeIsAppCompatTheme = Util.getThemeColor(appDefaultTheme, R.attr.colorPrimaryDark) != 0;
-
- }
+ // If the app default theme specified in AndroidManifest is an AppCompat theme, use it for Apptentive theme inheritance.
+ boolean appThemeIsAppCompatTheme = setApplicationDefaultTheme(ai.theme);
+ // check if host app defines an apptentive theme override
int themeOverrideResId = appContext.getResources().getIdentifier("ApptentiveThemeOverride", "style", appPackageName);
Integer currentVersionCode = packageInfo.versionCode;
@@ -555,6 +572,7 @@ public void init() {
appRelease.setBuildNumber(String.valueOf(currentVersionCode));
appRelease.setTargetSdkVersion(String.valueOf(packageInfo.applicationInfo.targetSdkVersion));
appRelease.setAppStore(Util.getInstallerPackageName(appContext));
+ // Set Apptentive theme inheritance metrics
appRelease.setInheritStyle(appThemeIsAppCompatTheme);
appRelease.setOverrideStyle(themeOverrideResId != 0);
@@ -580,8 +598,10 @@ public void init() {
} catch (Exception e) {
ApptentiveLog.e("Unexpected error while reading application or package info.", e);
+ bRet = false;
}
+
// Set debuggable and appropriate log level.
if (apptentiveDebug) {
ApptentiveLog.i("Apptentive debug logging set to VERBOSE.");
@@ -624,6 +644,7 @@ public void init() {
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;
}
private void onVersionChanged(Integer previousVersionCode, Integer currentVersionCode, String previousVersionName, String currentVersionName, AppRelease currentAppRelease) {
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 bdffba644..73845b9e5 100644
--- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java
+++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java
@@ -269,18 +269,22 @@ public void onFragmentTransition(ApptentiveBaseFragment currentFragment) {
private void applyApptentiveTheme(boolean isModalInteraction) {
// Update the activity theme to reflect current attributes
- ApptentiveInternal.getInstance().updateApptentiveInteractionTheme(getTheme(), this);
+ try {
+ ApptentiveInternal.getInstance().updateApptentiveInteractionTheme(getTheme(), this);
- if (isModalInteraction) {
- getTheme().applyStyle(R.style.ApptentiveBaseDialogTheme, true);
- setStatusBarColor(ApptentiveInternal.getInstance().getDefaultStatusbarColor());
- }
+ if (isModalInteraction) {
+ getTheme().applyStyle(R.style.ApptentiveBaseDialogTheme, true);
+ setStatusBarColor();
+ }
- // Change the thumbnail header color in task list
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- int colorPrimary = Util.getThemeColor(getTheme(), R.attr.colorPrimary);
- ActivityManager.TaskDescription taskDes = new ActivityManager.TaskDescription(null, null, colorPrimary);
- setTaskDescription(taskDes);
+ // Change the thumbnail header color in task list
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ int colorPrimary = Util.getThemeColor(getTheme(), R.attr.colorPrimary);
+ ActivityManager.TaskDescription taskDes = new ActivityManager.TaskDescription(null, null, colorPrimary);
+ setTaskDescription(taskDes);
+ }
+ } catch (Exception e) {
+ ApptentiveLog.e("Error apply Apptentive Theme.", e);
}
}
@@ -387,9 +391,10 @@ public void onGlobalLayout() {
* color defined by apptentive_activity_frame
* @param statusBarDefaultColor the default activity status bar color specified by the app
*/
- private void setStatusBarColor(int statusBarDefaultColor) {
+ private void setStatusBarColor() {
// Changing status bar color is a post-21 feature
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ int statusBarDefaultColor = ApptentiveInternal.getInstance().getDefaultStatusbarColor();
int overlayColor = ContextCompat.getColor(this, R.color.apptentive_activity_frame_dark);
getWindow().setStatusBarColor(Util.alphaMixColors(statusBarDefaultColor, overlayColor));
}
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 9f3c559ea..315eded3f 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,6 +10,7 @@
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;
@@ -28,6 +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.util.Util;
import org.json.JSONArray;
@@ -38,6 +40,7 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author Sky Kelsey
@@ -67,15 +70,17 @@ public class MessageManager {
*/
private final List> hostUnreadMessagesListeners = new ArrayList>();
+ AtomicBoolean appInForeground = new AtomicBoolean(false);
+
private Handler uiHandler;
private MessagePollingWorker pollingWorker;
-
public MessageManager() {
}
+ // init() will start polling worker.
public void init() {
if (uiHandler == null) {
uiHandler = new Handler(Looper.getMainLooper()) {
@@ -104,6 +109,14 @@ public void handleMessage(android.os.Message msg) {
}
if (pollingWorker == null) {
pollingWorker = new MessagePollingWorker(this);
+ /* 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();
+ }
}
}
@@ -112,6 +125,8 @@ public void handleMessage(android.os.Message msg) {
* 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();
AsyncTask