Skip to content

Commit

Permalink
Apptentive Android SDK 4.0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
skykelsey committed Jan 4, 2018
1 parent bbd011b commit 05ef243
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 15 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# 2018-01-03 - v4.0.3

#### Bugs Fixed

* Fixed a potential crash when used in Instant Apps that don't contain a launcher Activity.
* Don't send payloads when the app is in the background.
* Don't poll for messages when the app is in the background.

# 2017-08-15 - v4.0.2

#### Bugs Fixed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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|4.0.2|aar)
##### Binary releases are hosted for Maven [here](http://search.maven.org/#artifactdetails|com.apptentive|apptentive-android|4.0.3|aar)

#### Reporting Bugs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

public enum ApptentiveLogTag {
NETWORK(true),
APP_CONFIGURATION(true),
CONVERSATION(true),
NOTIFICATIONS(true),
MESSAGES(true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,10 +330,17 @@ private void startLauncherActivityIfRoot() {
if (isTaskRoot()) {
PackageManager packageManager = getPackageManager();
Intent intent = packageManager.getLaunchIntentForPackage(getPackageName());
ComponentName componentName = intent.getComponent();
/** Backwards compatible method that will clear all activities in the stack. */
Intent mainIntent = IntentCompat.makeRestartActivityTask(componentName);
startActivity(mainIntent);
/*
Make this work with Instant Apps. It is possible and even likely to create an Instant App
that doesn't have the Main Activity included in its APK. In such cases, this Intent is null,
and we can't do anything apart from exiting our Activity.
*/
if (intent != null) {
ComponentName componentName = intent.getComponent();
// Backwards compatible method that will clear all activities in the stack.
Intent mainIntent = Intent.makeRestartActivityTask(componentName);
startActivity(mainIntent);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class ApptentiveHttpClient implements PayloadRequestSender {

// Active API
private static final String ENDPOINT_CONVERSATION = "/conversation";
private static final String ENDPOINT_CONFIGURATION = "/conversations/%s/configuration";
private static final String ENDPOINT_LEGACY_CONVERSATION = "/conversation/token";
private static final String ENDPOINT_LOG_IN_TO_EXISTING_CONVERSATION = "/conversations/%s/session";
private static final String ENDPOINT_LOG_IN_TO_NEW_CONVERSATION = "/conversations";
Expand Down Expand Up @@ -112,6 +113,22 @@ public HttpJsonRequest createLoginRequest(String conversationId, String token, H
return request;
}

public HttpJsonRequest createAppConfigurationRequest(String conversationId, String token, HttpRequest.Listener<HttpJsonRequest> listener) {
if (StringUtils.isNullOrEmpty(conversationId)) {
throw new IllegalArgumentException("Conversation id is null or empty");
}

if (StringUtils.isNullOrEmpty(token)) {
throw new IllegalArgumentException("Conversation token is null or empty");
}

String endPoint = StringUtils.format(ENDPOINT_CONFIGURATION, conversationId);
HttpJsonRequest request = createJsonRequest(endPoint, new JSONObject(), HttpRequestMethod.GET);
request.setRequestProperty("Authorization", "Bearer " + token);
request.addListener(listener);
return request;
}

public HttpJsonRequest createFirstLoginRequest(String token, AppRelease appRelease, Sdk sdk, Device device, HttpRequest.Listener<HttpJsonRequest> listener) {
if (token == null) {
throw new IllegalArgumentException("Token is null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +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.Configuration;
import com.apptentive.android.sdk.model.ConversationTokenRequest;
import com.apptentive.android.sdk.module.engagement.EngagementModule;
import com.apptentive.android.sdk.network.HttpJsonRequest;
Expand All @@ -35,6 +35,7 @@
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.RuntimeUtils;
import com.apptentive.android.sdk.util.StringUtils;
import com.apptentive.android.sdk.util.Util;
import com.apptentive.android.sdk.util.threading.DispatchQueue;
Expand Down Expand Up @@ -67,10 +68,14 @@
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 static final String TAG_FETCH_APP_CONFIGURATION_REQUEST = "fetch_app_configuration";

private final WeakReference<Context> contextRef;

private boolean appIsInForeground;

/**
* A basic directory for storing conversation-related data.
*/
Expand All @@ -96,17 +101,28 @@ public ConversationManager(Context context, File apptentiveConversationsStorageD
@Override
public void onReceiveNotification(ApptentiveNotification notification) {
assertMainThread();
appIsInForeground = true;
if (activeConversation != null && activeConversation.hasActiveState()) {
ApptentiveLog.v(CONVERSATION, "App entered foreground notification received. Trying to fetch interactions...");
ApptentiveLog.v(CONVERSATION, "App entered foreground notification received. Trying to fetch app configuration and interactions...");
final Context context = getContext();
if (context != null) {
fetchAppConfiguration(activeConversation);
activeConversation.fetchInteractions(context);
} else {
ApptentiveLog.w(CONVERSATION, "Can't fetch conversation interactions: context is lost");
ApptentiveLog.w(CONVERSATION, "Can't fetch app configuration and conversation interactions: context is lost");
}
}
}
});

ApptentiveNotificationCenter.defaultCenter()
.addObserver(NOTIFICATION_APP_ENTERED_BACKGROUND, new ApptentiveNotificationObserver() {
@Override
public void onReceiveNotification(ApptentiveNotification notification) {
assertMainThread();
appIsInForeground = false;
}
});
}

//region Conversations
Expand Down Expand Up @@ -450,8 +466,15 @@ private void handleConversationStateChange(Conversation conversation) {
ObjectUtils.toMap(NOTIFICATION_KEY_CONVERSATION, conversation));

if (conversation.hasActiveState()) {
conversation.fetchInteractions(getContext());
conversation.getMessageManager().startPollingMessages();
if (appIsInForeground) {
// ConversationManager listens to the foreground event to fetch interactions when it comes to foreground
conversation.fetchInteractions(getContext());
// Message Manager listens to foreground/background events itself
conversation.getMessageManager().attemptToStartMessagePolling();
}

// Fetch app configuration
fetchAppConfiguration(conversation);

// Update conversation with push configuration changes that happened while it wasn't active.
SharedPreferences prefs = ApptentiveInternal.getInstance().getGlobalSharedPrefs();
Expand All @@ -469,6 +492,65 @@ private void handleConversationStateChange(Conversation conversation) {
}
}

private void fetchAppConfiguration(Conversation conversation) {
try {
fetchAppConfigurationGuarded(conversation);
} catch (Exception e) {
ApptentiveLog.e(e, "Exception while fetching app configuration");
}
}

private void fetchAppConfigurationGuarded(Conversation conversation) {
ApptentiveLog.d(APP_CONFIGURATION, "Fetching app configuration...");

HttpRequest existingRequest = getHttpClient().findRequest(TAG_FETCH_APP_CONFIGURATION_REQUEST);
if (existingRequest != null) {
ApptentiveLog.d(APP_CONFIGURATION, "Can't fetch app configuration: another request already pending");
return;
}

if (!Configuration.load().hasConfigurationCacheExpired()) {
// if configuration hasn't expired we would fetch it anyway for debug apps
boolean debuggable = RuntimeUtils.isAppDebuggable(getContext());
if (!debuggable) {
ApptentiveLog.d(APP_CONFIGURATION, "Can't fetch app configuration: the old configuration is still valid");
return;
}
}

HttpJsonRequest request = getHttpClient()
.createAppConfigurationRequest(conversation.getConversationId(), conversation.getConversationToken(),
new HttpRequest.Listener<HttpJsonRequest>() {
@Override
public void onFinish(HttpJsonRequest request) {
try {
String cacheControl = request.getResponseHeader("Cache-Control");
Integer cacheSeconds = Util.parseCacheControlHeader(cacheControl);
if (cacheSeconds == null) {
cacheSeconds = Constants.CONFIG_DEFAULT_APP_CONFIG_EXPIRATION_DURATION_SECONDS;
}
ApptentiveLog.d(APP_CONFIGURATION, "Caching configuration for %d seconds.", cacheSeconds);
Configuration config = new Configuration(request.getResponseObject().toString());
config.setConfigurationCacheExpirationMillis(System.currentTimeMillis() + cacheSeconds * 1000);
config.save();
} catch (Exception e) {
ApptentiveLog.e(e, "Exception while parsing app configuration response");
}
}

@Override
public void onCancel(HttpJsonRequest request) {
}

@Override
public void onFail(HttpJsonRequest request, String reason) {
ApptentiveLog.e(APP_CONFIGURATION, "App configuration request failed: %s", reason);
}
});
request.setTag(TAG_FETCH_APP_CONFIGURATION_REQUEST);
request.start();
}

private void updateMetadataItems(Conversation conversation) {
ApptentiveLog.vv("Updating metadata: state=%s localId=%s conversationId=%s token=%s",
conversation.getState(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,10 @@ public void destroy() {

//region Polling

public void startPollingMessages() {
pollingWorker.startPolling();
public void attemptToStartMessagePolling() {
if (conversation.isMessageCenterFeatureUsed()) {
pollingWorker.startPolling();
}
}

public void stopPollingMessages() {
Expand Down Expand Up @@ -527,6 +529,7 @@ private void setCurrentForegroundActivity(Activity activity) {
}

public void setMessageCenterInForeground(boolean bInForeground) {
conversation.setMessageCenterFeatureUsed(true);
pollingWorker.setMessageCenterInForeground(bInForeground);
}

Expand Down Expand Up @@ -563,7 +566,9 @@ protected void execute() {

private void appWentToForeground() {
appInForeground.set(true);
pollingWorker.appWentToForeground();
if (conversation.isMessageCenterFeatureUsed()) {
pollingWorker.appWentToForeground();
}
}

private void appWentToBackground() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class MessagePollingWorker implements Destroyable {
conf = Configuration.load();
backgroundPollingInterval = conf.getMessageCenterBgPoll() * 1000;
foregroundPollingInterval = conf.getMessageCenterFgPoll() * 1000;
ApptentiveLog.vv("Message Polling Worker: bg=%d, fg=%d", backgroundPollingInterval, foregroundPollingInterval);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,10 @@ public int getResponseCode() {
return responseCode;
}

public String getResponseHeader(String key) {
return responseHeaders != null ? responseHeaders.get(key) : null;
}

public boolean isAuthenticationFailure() {
return responseCode == 401;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class ApptentiveTaskManager implements PayloadStore, EventStore, Apptenti
private final ThreadPoolExecutor singleThreadExecutor; // TODO: replace with a private concurrent dispatch queue

private final PayloadSender payloadSender;
private boolean appInBackground;
private boolean appInBackground = true;

/*
* Creates an asynchronous task manager with one worker thread. This constructor must be invoked on the UI thread.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
public class Constants {

public static final int API_VERSION = 9;
public static final String APPTENTIVE_SDK_VERSION = "4.0.2";
public static final String APPTENTIVE_SDK_VERSION = "4.0.3";

public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 45000;
public static final int DEFAULT_READ_TIMEOUT_MILLIS = 45000;
Expand Down

0 comments on commit 05ef243

Please sign in to comment.