package com.mixpanel.android.mpmetrics;
import com.mixpanel.android.util.MPLog;
import com.mixpanel.android.viewcrawler.UpdatesFromMixpanel;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
// Will be called from both customer threads and the Mixpanel worker thread.
/* package */ class DecideMessages {
public interface OnNewResultsListener {
void onNewResults();
}
public DecideMessages(String token, OnNewResultsListener listener, UpdatesFromMixpanel updatesFromMixpanel) {
mToken = token;
mListener = listener;
mUpdatesFromMixpanel = updatesFromMixpanel;
mDistinctId = null;
mUnseenNotifications = new LinkedList<InAppNotification>();
mNotificationIds = new HashSet<Integer>();
mVariants = new JSONArray();
}
public String getToken() {
return mToken;
}
// Called from other synchronized code. Do not call into other synchronized code or you'll
// risk deadlock
public synchronized void setDistinctId(String distinctId) {
if (mDistinctId == null || !mDistinctId.equals(distinctId)){
mUnseenNotifications.clear();
}
mDistinctId = distinctId;
}
public synchronized String getDistinctId() {
return mDistinctId;
}
public synchronized void reportResults(List<InAppNotification> newNotifications, JSONArray eventBindings, JSONArray variants) {
boolean newContent = false;
mUpdatesFromMixpanel.setEventBindings(eventBindings);
for (final InAppNotification n : newNotifications) {
final int id = n.getId();
if (! mNotificationIds.contains(id)) {
mNotificationIds.add(id);
mUnseenNotifications.add(n);
newContent = true;
}
}
// the following logic checks if the variants have been applied by looking up their id's in the HashSet
// this is needed to make sure the user defined `mListener` will get called on new variants receiving
int newVariantsLength = variants.length();
boolean hasNewVariants = false;
for (int i = 0; i < newVariantsLength; i++) {
try {
JSONObject variant = variants.getJSONObject(i);
if (!mLoadedVariants.contains(variant.getInt("id"))) {
mVariants = variants;
newContent = true;
hasNewVariants = true;
break;
}
} catch(JSONException e) {
MPLog.e(LOGTAG, "Could not convert variants[" + i + "] into a JSONObject while comparing the new variants", e);
}
}
if (hasNewVariants) {
mLoadedVariants.clear();
for (int i = 0; i < newVariantsLength; i++) {
try {
JSONObject variant = mVariants.getJSONObject(i);
mLoadedVariants.add(variant.getInt("id"));
} catch(JSONException e) {
MPLog.e(LOGTAG, "Could not convert variants[" + i + "] into a JSONObject while updating the map", e);
}
}
}
// in the case we do not receive a new variant, this means the A/B test should be turned off
if (newVariantsLength == 0 && mLoadedVariants.size() > 0) {
mLoadedVariants.clear();
mVariants = new JSONArray();
newContent = true;
}
MPLog.v(LOGTAG, "New Decide content has become available. " +
newNotifications.size() + " notifications and " +
variants.length() + " experiments have been added.");
if (newContent && null != mListener) {
mListener.onNewResults();
}
}
public synchronized JSONArray getVariants() {
return mVariants;
}
public synchronized InAppNotification getNotification(boolean replace) {
if (mUnseenNotifications.isEmpty()) {
MPLog.v(LOGTAG, "No unseen notifications exist, none will be returned.");
return null;
}
InAppNotification n = mUnseenNotifications.remove(0);
if (replace) {
mUnseenNotifications.add(n);
} else {
MPLog.v(LOGTAG, "Recording notification " + n + " as seen.");
}
return n;
}
public synchronized InAppNotification getNotification(int id, boolean replace) {
InAppNotification notif = null;
for (int i = 0; i < mUnseenNotifications.size(); i++) {
if (mUnseenNotifications.get(i).getId() == id) {
notif = mUnseenNotifications.get(i);
if (!replace) {
mUnseenNotifications.remove(i);
}
break;
}
}
return notif;
}
// if a notification was failed to show, add it back to the unseen list so that we
// won't lose it
public synchronized void markNotificationAsUnseen(InAppNotification notif) {
if (!MPConfig.DEBUG) {
mUnseenNotifications.add(notif);
}
}
public synchronized boolean hasUpdatesAvailable() {
return (! mUnseenNotifications.isEmpty()) || mVariants.length() > 0;
}
// Mutable, must be synchronized
private String mDistinctId;
private final String mToken;
private final Set<Integer> mNotificationIds;
private final List<InAppNotification> mUnseenNotifications;
private final OnNewResultsListener mListener;
private final UpdatesFromMixpanel mUpdatesFromMixpanel;
private JSONArray mVariants;
private static final Set<Integer> mLoadedVariants = new HashSet<>();
@SuppressWarnings("unused")
private static final String LOGTAG = "MixpanelAPI.DecideUpdts";
}