diff --git a/android-core/src/main/java/com/mparticle/identity/IdentityApi.java b/android-core/src/main/java/com/mparticle/identity/IdentityApi.java index b7348845f..bdf61b887 100644 --- a/android-core/src/main/java/com/mparticle/identity/IdentityApi.java +++ b/android-core/src/main/java/com/mparticle/identity/IdentityApi.java @@ -24,8 +24,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -46,6 +48,7 @@ public class IdentityApi { MessageManager mMessageManager; KitManager mKitManager; private Internal mInternal = new Internal(); + private long timeoutSeconds = 0L; MParticleUserDelegate mUserDelegate; private MParticleIdentityClient mApiClient; @@ -53,6 +56,10 @@ public class IdentityApi { Set identityStateListeners = new HashSet(); private static Object lock = new Object(); + public static final String LOGIN_CALL = "login"; + public static final String IDENTIFY_CALL = "identify"; + public static final String LOGOUT_CALL = "logout"; + protected IdentityApi() { } @@ -165,6 +172,7 @@ public MParticleTask logout() { */ @NonNull public MParticleTask logout(@Nullable final IdentityApiRequest logoutRequest) { + resetCache(); return makeIdentityRequest(logoutRequest, new IdentityNetworkRequestRunnable() { @Override public IdentityHttpResponse request(IdentityApiRequest request) throws Exception { @@ -175,7 +183,7 @@ public IdentityHttpResponse request(IdentityApiRequest request) throws Exception public void onPostExecute(IdentityApiResult result) { mKitManager.onLogoutCompleted(result.getUser(), logoutRequest); } - }); + }, false, LOGOUT_CALL); } /** @@ -203,14 +211,16 @@ public MParticleTask login(@Nullable final IdentityApiRequest return makeIdentityRequest(loginRequest, new IdentityNetworkRequestRunnable() { @Override public IdentityHttpResponse request(IdentityApiRequest request) throws Exception { - return getApiClient().login(request); + IdentityHttpResponse response = getApiClient().login(request); + timeoutSeconds = response.getTimeout(); + return response; } @Override public void onPostExecute(IdentityApiResult result) { mKitManager.onLoginCompleted(result.getUser(), loginRequest); } - }); + }, true, LOGIN_CALL); } /** @@ -226,14 +236,16 @@ public MParticleTask identify(@Nullable final IdentityApiRequ return makeIdentityRequest(identifyRequest, new IdentityNetworkRequestRunnable() { @Override public IdentityHttpResponse request(IdentityApiRequest request) throws Exception { - return getApiClient().identify(request); + IdentityHttpResponse response = getApiClient().identify(request); + timeoutSeconds = response.getTimeout(); + return response; } @Override public void onPostExecute(IdentityApiResult result) { mKitManager.onIdentifyCompleted(result.getUser(), identifyRequest); } - }); + }, true, IDENTIFY_CALL); } /** @@ -252,6 +264,7 @@ public BaseIdentityTask modify(@NonNull final IdentityApiRequest updateRequest) if (updateRequest.mpid == null) { updateRequest.mpid = mConfigManager.getMpid(); } + if (Constants.TEMPORARY_MPID.equals(updateRequest.mpid)) { String message = "modify() requires a non-zero MPID, please make sure a MParticleUser is present before making a modify request."; if (devMode) { @@ -266,10 +279,12 @@ public BaseIdentityTask modify(@NonNull final IdentityApiRequest updateRequest) @Override public void run() { try { + resetCache(); final IdentityHttpResponse result = getApiClient().modify(updateRequest); if (!result.isSuccessful()) { task.setFailed(result); } else { + timeoutSeconds = result.getTimeout(); MParticleUserDelegate.setUserIdentities(mUserDelegate, updateRequest.getUserIdentities(), updateRequest.mpid); task.setSuccessful(new IdentityApiResult(MParticleUserImpl.getInstance(mContext, updateRequest.mpid, mUserDelegate), null)); new Handler(Looper.getMainLooper()).post(new Runnable() { @@ -348,13 +363,59 @@ private void reset() { } } - private BaseIdentityTask makeIdentityRequest(IdentityApiRequest request, final IdentityNetworkRequestRunnable networkRequest) { + private boolean shouldMakeRequest(IdentityApiRequest identityRequest, boolean acceptCachedResponse, long lastIdentityCall) { + if (!acceptCachedResponse || !mConfigManager.isIdentityCachingEnabled()) { + return true; + } + boolean hasTimedOut = lastIdentityCall == -1L || (lastIdentityCall + (timeoutSeconds * 1000) > System.currentTimeMillis()); + if (identityRequest != null && identityRequest.mpid != null) { + MParticleUser user = getUser(identityRequest.mpid); + if (hasTimedOut || isRequestDifferent(user, identityRequest)) { + return true; + } else { + return false; + } + } else { + return true; + } + } + + private boolean isRequestDifferent(MParticleUser user, IdentityApiRequest identityRequest) { + return areIdentitiesDifferent(user, identityRequest) || mpIdNotKnown(user); + } + + private boolean mpIdNotKnown(MParticleUser user) { + return user != null && !mConfigManager.getMpids().contains(user.getId()); + } + + private boolean areIdentitiesDifferent(MParticleUser user, IdentityApiRequest identityApiRequest) { + if (user != null) { + Map userIdentities = user.getUserIdentities() != null ? user.getUserIdentities() : new HashMap<>(); + Map requestUserIdentities = identityApiRequest.getUserIdentities() != null ? identityApiRequest.getUserIdentities() : new HashMap<>(); + return !userIdentities.equals(requestUserIdentities); + } else { + return true; + } + } + + private void resetCache() { + mConfigManager.resetIdentityTypeCall(); + } + + private BaseIdentityTask makeIdentityRequest(IdentityApiRequest request, final IdentityNetworkRequestRunnable networkRequest, boolean acceptCachedResponse, String call) { + long lastIdentityCallTime = mConfigManager.getLastIdentityTypeCall(call); if (request == null) { request = IdentityApiRequest.withEmptyUser().build(); } final BaseIdentityTask task = new BaseIdentityTask(); - ConfigManager.setIdentityRequestInProgress(true); final IdentityApiRequest identityApiRequest = request; + if (!shouldMakeRequest(identityApiRequest, acceptCachedResponse, lastIdentityCallTime)) { + //Set both current and prev user as the current one, no request was done. + task.setSuccessful(new IdentityApiResult(getUser(identityApiRequest.mpid), getCurrentUser())); + Logger.debug("Identity - Returning current user from cache"); + return task; + } + ConfigManager.setIdentityRequestInProgress(true); mBackgroundHandler.post(new Runnable() { @Override public void run() { @@ -362,6 +423,7 @@ public void run() { if (mBackgroundHandler.isDisabled()) { return; } + Logger.debug("Identity - Making identity call for request"); try { long startingMpid = mConfigManager.getMpid(); final IdentityHttpResponse result = networkRequest.request(identityApiRequest); @@ -375,6 +437,9 @@ public void run() { ConfigManager.setIdentityRequestInProgress(false); mUserDelegate.setUser(mContext, startingMpid, newMpid, identityApiRequest.getUserIdentities(), identityApiRequest.getUserAliasHandler(), isLoggedIn); final MParticleUser previousUser = startingMpid != newMpid ? getUser(startingMpid) : null; + if (acceptCachedResponse) { + mConfigManager.setLastIdentityTypeCall(call); + } task.setSuccessful(new IdentityApiResult(MParticleUserImpl.getInstance(mContext, newMpid, mUserDelegate), previousUser)); new Handler(Looper.getMainLooper()).post(new Runnable() { @Override diff --git a/android-core/src/main/java/com/mparticle/identity/IdentityHttpResponse.java b/android-core/src/main/java/com/mparticle/identity/IdentityHttpResponse.java index 00ca54d5a..420faeb06 100644 --- a/android-core/src/main/java/com/mparticle/identity/IdentityHttpResponse.java +++ b/android-core/src/main/java/com/mparticle/identity/IdentityHttpResponse.java @@ -18,6 +18,7 @@ public final class IdentityHttpResponse { private String context; private int httpCode; private boolean loggedIn; + private long timeout = 0L; @NonNull public static final String MPID = "mpid"; @@ -50,8 +51,9 @@ public IdentityHttpResponse(int code, @NonNull String errorString) { this.errors.add(new Error(UNKNOWN, errorString)); } - public IdentityHttpResponse(int httpCode, @Nullable JSONObject jsonObject) throws JSONException { + public IdentityHttpResponse(int httpCode, @Nullable JSONObject jsonObject, long identityTimeout) throws JSONException { this.httpCode = httpCode; + this.timeout = identityTimeout; if (!MPUtility.isEmpty(jsonObject)) { if (jsonObject.has(MPID)) { this.mpId = Long.valueOf(jsonObject.getString(MPID)); @@ -92,6 +94,10 @@ public long getMpId() { return mpId; } + public long getTimeout() { + return timeout; + } + @Nullable public String getContext() { return context; diff --git a/android-core/src/main/java/com/mparticle/identity/MParticleIdentityClientImpl.java b/android-core/src/main/java/com/mparticle/identity/MParticleIdentityClientImpl.java index 06117fe7f..78d14cfe5 100644 --- a/android-core/src/main/java/com/mparticle/identity/MParticleIdentityClientImpl.java +++ b/android-core/src/main/java/com/mparticle/identity/MParticleIdentityClientImpl.java @@ -51,6 +51,7 @@ public class MParticleIdentityClientImpl extends MParticleBaseClientImpl impleme static final String DEVICE_APPLICATION_STAMP = "device_application_stamp"; static final String KNOWN_IDENTITIES = "known_identities"; static final String PREVIOUS_MPID = "previous_mpid"; + static final String IDENTITY_HEADER_TIMEOUT = "X-MP-Max-Age"; static final String NEW_VALUE = "new_value"; static final String OLD_VALUE = "old_value"; @@ -80,10 +81,11 @@ public IdentityHttpResponse login(IdentityApiRequest request) throws JSONExcepti String url = connection.getURL().toString(); InternalListenerManager.getListener().onNetworkRequestStarted(SdkListener.Endpoint.IDENTITY_LOGIN, url, jsonObject, request); connection = makeUrlRequest(Endpoint.IDENTITY, connection, jsonObject.toString(), false); + String headerField = connection.getHeaderField(IDENTITY_HEADER_TIMEOUT); int responseCode = connection.getResponseCode(); JSONObject response = MPUtility.getJsonResponse(connection); InternalListenerManager.getListener().onNetworkRequestFinished(SdkListener.Endpoint.IDENTITY_LOGIN, url, response, responseCode); - return parseIdentityResponse(responseCode, response); + return parseIdentityResponse(responseCode, response, headerField); } public IdentityHttpResponse logout(IdentityApiRequest request) throws JSONException, IOException { @@ -96,7 +98,7 @@ public IdentityHttpResponse logout(IdentityApiRequest request) throws JSONExcept int responseCode = connection.getResponseCode(); JSONObject response = MPUtility.getJsonResponse(connection); InternalListenerManager.getListener().onNetworkRequestFinished(SdkListener.Endpoint.IDENTITY_LOGOUT, url, response, responseCode); - return parseIdentityResponse(responseCode, response); + return parseIdentityResponse(responseCode, response, "0"); } public IdentityHttpResponse identify(IdentityApiRequest request) throws JSONException, IOException { @@ -106,10 +108,11 @@ public IdentityHttpResponse identify(IdentityApiRequest request) throws JSONExce String url = connection.getURL().toString(); InternalListenerManager.getListener().onNetworkRequestStarted(SdkListener.Endpoint.IDENTITY_IDENTIFY, url, jsonObject, request); connection = makeUrlRequest(Endpoint.IDENTITY, connection, jsonObject.toString(), false); + String headerField = connection.getHeaderField(IDENTITY_HEADER_TIMEOUT); int responseCode = connection.getResponseCode(); JSONObject response = MPUtility.getJsonResponse(connection); InternalListenerManager.getListener().onNetworkRequestFinished(SdkListener.Endpoint.IDENTITY_IDENTIFY, url, response, responseCode); - return parseIdentityResponse(responseCode, response); + return parseIdentityResponse(responseCode, response, headerField); } public IdentityHttpResponse modify(IdentityApiRequest request) throws JSONException, IOException { @@ -124,9 +127,10 @@ public IdentityHttpResponse modify(IdentityApiRequest request) throws JSONExcept InternalListenerManager.getListener().onNetworkRequestStarted(SdkListener.Endpoint.IDENTITY_MODIFY, url, jsonObject, request); connection = makeUrlRequest(Endpoint.IDENTITY, connection, jsonObject.toString(), false); int responseCode = connection.getResponseCode(); + String headerField = connection.getHeaderField(IDENTITY_HEADER_TIMEOUT); JSONObject response = MPUtility.getJsonResponse(connection); InternalListenerManager.getListener().onNetworkRequestFinished(SdkListener.Endpoint.IDENTITY_MODIFY, url, response, responseCode); - return parseIdentityResponse(responseCode, response); + return parseIdentityResponse(responseCode, response, headerField); } private JSONObject getBaseJson() throws JSONException { @@ -238,13 +242,18 @@ private JSONObject getChangeJson(IdentityApiRequest request) throws JSONExceptio return jsonObject; } - private IdentityHttpResponse parseIdentityResponse(int httpCode, JSONObject jsonObject) { + private IdentityHttpResponse parseIdentityResponse(int httpCode, JSONObject jsonObject, String identityTimeoutHeader) { + long timeoutHeader = 0L; try { Logger.verbose("Identity response code: " + httpCode); if (jsonObject != null) { Logger.verbose("Identity result: " + jsonObject.toString()); } - IdentityHttpResponse httpResponse = new IdentityHttpResponse(httpCode, jsonObject); + try { + timeoutHeader = Long.parseLong(identityTimeoutHeader); + } catch (Exception e) { + } + IdentityHttpResponse httpResponse = new IdentityHttpResponse(httpCode, jsonObject, timeoutHeader); if (!MPUtility.isEmpty(httpResponse.getContext())) { mConfigManager.setIdentityApiContext(httpResponse.getContext()); } diff --git a/android-core/src/main/java/com/mparticle/internal/ConfigManager.java b/android-core/src/main/java/com/mparticle/internal/ConfigManager.java index 01d4fb628..2fe9f6826 100644 --- a/android-core/src/main/java/com/mparticle/internal/ConfigManager.java +++ b/android-core/src/main/java/com/mparticle/internal/ConfigManager.java @@ -73,6 +73,7 @@ public class ConfigManager { static final String DATAPLAN_BLOCK_USER_IDENTITIES = "id"; public static final String KIT_CONFIG_KEY = "kit_config"; static final String MIGRATED_TO_KIT_SHARED_PREFS = "is_mig_kit_sp"; + private static final String IDENTITY_CACHING_ENABLED = "identityCachingEnabled"; private static final int DEVMODE_UPLOAD_INTERVAL_MILLISECONDS = 10 * 1000; private static final int DEFAULT_MAX_ALIAS_WINDOW_DAYS = 90; @@ -92,6 +93,7 @@ public class ConfigManager { private JSONObject mProviderPersistence; private int mRampValue = -1; private int mUserBucket = -1; + private boolean identityCachingEnabled = false; private int mSessionTimeoutInterval = -1; private int mUploadInterval = -1; @@ -421,6 +423,9 @@ private synchronized void updateCoreConfig(JSONObject responseJSON, boolean newC mSendOoEvents = false; } + //TODO Read from identityCachingEnabled feature flag + editor.putBoolean(IDENTITY_CACHING_ENABLED, identityCachingEnabled); + if (responseJSON.has(ProviderPersistence.KEY_PERSISTENCE)) { setProviderPersistence(new ProviderPersistence(responseJSON, mContext)); } else { @@ -527,6 +532,10 @@ public long getInfluenceOpenTimeoutMillis() { return mInfluenceOpenTimeout; } + public boolean isIdentityCachingEnabled() { + return identityCachingEnabled; + } + private void applyConfig() { if (getLogUnhandledExceptions()) { enableUncaughtExceptionLogging(false); @@ -1251,6 +1260,21 @@ public int getIdentityConnectionTimeout() { return sPreferences.getInt(Constants.PrefKeys.IDENTITY_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT_SECONDS) * 1000; } + public long getLastIdentityTypeCall(String call) { + return sPreferences.getLong(call, 0L); + } + + public void resetIdentityTypeCall() { + SharedPreferences.Editor editor = sPreferences.edit(); + editor.putLong(IdentityApi.LOGIN_CALL, -1); + editor.putLong(IdentityApi.IDENTIFY_CALL, -1); + editor.apply(); + } + + public void setLastIdentityTypeCall(String call) { + sPreferences.edit().putLong(call, System.currentTimeMillis()).apply(); + } + public int getConnectionTimeout() { return DEFAULT_CONNECTION_TIMEOUT_SECONDS * 1000; }