1
0
mirror of https://github.com/roytam1/UXP.git synced 2026-06-18 06:19:27 +00:00
Files
UXP/mobile/android/thirdparty/com/adjust/sdk/ActivityHandler.java
T

782 lines
25 KiB
Java

//
// ActivityHandler.java
// Adjust
//
// Created by Christian Wellenbrock on 2013-06-25.
// Copyright (c) 2013 adjust GmbH. All rights reserved.
// See the file MIT-LICENSE for copying permission.
//
package com.adjust.sdk;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import org.json.JSONObject;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static com.adjust.sdk.Constants.ACTIVITY_STATE_FILENAME;
import static com.adjust.sdk.Constants.ATTRIBUTION_FILENAME;
import static com.adjust.sdk.Constants.LOGTAG;
public class ActivityHandler extends HandlerThread implements IActivityHandler {
private static long TIMER_INTERVAL;
private static long TIMER_START;
private static long SESSION_INTERVAL;
private static long SUBSESSION_INTERVAL;
private static final String TIME_TRAVEL = "Time travel!";
private static final String ADJUST_PREFIX = "adjust_";
private static final String ACTIVITY_STATE_NAME = "Activity state";
private static final String ATTRIBUTION_NAME = "Attribution";
private SessionHandler sessionHandler;
private IPackageHandler packageHandler;
private ActivityState activityState;
private ILogger logger;
private static ScheduledExecutorService timer;
private boolean enabled;
private boolean offline;
private DeviceInfo deviceInfo;
private AdjustConfig adjustConfig; // always valid after construction
private AdjustAttribution attribution;
private IAttributionHandler attributionHandler;
private ActivityHandler(AdjustConfig adjustConfig) {
super(LOGTAG, MIN_PRIORITY);
setDaemon(true);
start();
logger = AdjustFactory.getLogger();
sessionHandler = new SessionHandler(getLooper(), this);
enabled = true;
init(adjustConfig);
Message message = Message.obtain();
message.arg1 = SessionHandler.INIT;
sessionHandler.sendMessage(message);
}
@Override
public void init(AdjustConfig adjustConfig) {
this.adjustConfig = adjustConfig;
}
public static ActivityHandler getInstance(AdjustConfig adjustConfig) {
if (adjustConfig == null) {
AdjustFactory.getLogger().error("AdjustConfig missing");
return null;
}
if (!adjustConfig.isValid()) {
AdjustFactory.getLogger().error("AdjustConfig not initialized correctly");
return null;
}
ActivityHandler activityHandler = new ActivityHandler(adjustConfig);
return activityHandler;
}
@Override
public void trackSubsessionStart() {
Message message = Message.obtain();
message.arg1 = SessionHandler.START;
sessionHandler.sendMessage(message);
}
@Override
public void trackSubsessionEnd() {
Message message = Message.obtain();
message.arg1 = SessionHandler.END;
sessionHandler.sendMessage(message);
}
@Override
public void trackEvent(AdjustEvent event) {
Message message = Message.obtain();
message.arg1 = SessionHandler.EVENT;
message.obj = event;
sessionHandler.sendMessage(message);
}
@Override
public void finishedTrackingActivity(JSONObject jsonResponse) {
if (jsonResponse == null) {
return;
}
Message message = Message.obtain();
message.arg1 = SessionHandler.FINISH_TRACKING;
message.obj = jsonResponse;
sessionHandler.sendMessage(message);
}
@Override
public void setEnabled(boolean enabled) {
if (enabled == this.enabled) {
if (enabled) {
logger.debug("Adjust already enabled");
} else {
logger.debug("Adjust already disabled");
}
return;
}
this.enabled = enabled;
if (activityState != null) {
activityState.enabled = enabled;
}
if (enabled) {
if (toPause()) {
logger.info("Package and attribution handler remain paused due to the SDK is offline");
} else {
logger.info("Resuming package handler and attribution handler to enabled the SDK");
}
trackSubsessionStart();
} else {
logger.info("Pausing package handler and attribution handler to disable the SDK");
trackSubsessionEnd();
}
}
@Override
public void setOfflineMode(boolean offline) {
if (offline == this.offline) {
if (offline) {
logger.debug("Adjust already in offline mode");
} else {
logger.debug("Adjust already in online mode");
}
return;
}
this.offline = offline;
if (offline) {
logger.info("Pausing package and attribution handler to put in offline mode");
} else {
if (toPause()) {
logger.info("Package and attribution handler remain paused because the SDK is disabled");
} else {
logger.info("Resuming package handler and attribution handler to put in online mode");
}
}
updateStatus();
}
@Override
public boolean isEnabled() {
if (activityState != null) {
return activityState.enabled;
} else {
return enabled;
}
}
@Override
public void readOpenUrl(Uri url, long clickTime) {
Message message = Message.obtain();
message.arg1 = SessionHandler.DEEP_LINK;
UrlClickTime urlClickTime = new UrlClickTime(url, clickTime);
message.obj = urlClickTime;
sessionHandler.sendMessage(message);
}
@Override
public boolean tryUpdateAttribution(AdjustAttribution attribution) {
if (attribution == null) return false;
if (attribution.equals(this.attribution)) {
return false;
}
saveAttribution(attribution);
launchAttributionListener();
return true;
}
private void saveAttribution(AdjustAttribution attribution) {
this.attribution = attribution;
writeAttribution();
}
private void launchAttributionListener() {
if (adjustConfig.onAttributionChangedListener == null) {
return;
}
Handler handler = new Handler(adjustConfig.context.getMainLooper());
Runnable runnable = new Runnable() {
@Override
public void run() {
adjustConfig.onAttributionChangedListener.onAttributionChanged(attribution);
}
};
handler.post(runnable);
}
@Override
public void setAskingAttribution(boolean askingAttribution) {
activityState.askingAttribution = askingAttribution;
writeActivityState();
}
@Override
public ActivityPackage getAttributionPackage() {
long now = System.currentTimeMillis();
PackageBuilder attributionBuilder = new PackageBuilder(adjustConfig,
deviceInfo,
activityState,
now);
return attributionBuilder.buildAttributionPackage();
}
@Override
public void sendReferrer(String referrer, long clickTime) {
Message message = Message.obtain();
message.arg1 = SessionHandler.SEND_REFERRER;
ReferrerClickTime referrerClickTime = new ReferrerClickTime(referrer, clickTime);
message.obj = referrerClickTime;
sessionHandler.sendMessage(message);
}
private class UrlClickTime {
Uri url;
long clickTime;
UrlClickTime(Uri url, long clickTime) {
this.url = url;
this.clickTime = clickTime;
}
}
private class ReferrerClickTime {
String referrer;
long clickTime;
ReferrerClickTime(String referrer, long clickTime) {
this.referrer = referrer;
this.clickTime = clickTime;
}
}
private void updateStatus() {
Message message = Message.obtain();
message.arg1 = SessionHandler.UPDATE_STATUS;
sessionHandler.sendMessage(message);
}
private static final class SessionHandler extends Handler {
private static final int BASE_ADDRESS = 72630;
private static final int INIT = BASE_ADDRESS + 1;
private static final int START = BASE_ADDRESS + 2;
private static final int END = BASE_ADDRESS + 3;
private static final int EVENT = BASE_ADDRESS + 4;
private static final int FINISH_TRACKING = BASE_ADDRESS + 5;
private static final int DEEP_LINK = BASE_ADDRESS + 6;
private static final int SEND_REFERRER = BASE_ADDRESS + 7;
private static final int UPDATE_STATUS = BASE_ADDRESS + 8;
private final WeakReference<ActivityHandler> sessionHandlerReference;
protected SessionHandler(Looper looper, ActivityHandler sessionHandler) {
super(looper);
this.sessionHandlerReference = new WeakReference<ActivityHandler>(sessionHandler);
}
@Override
public void handleMessage(Message message) {
super.handleMessage(message);
ActivityHandler sessionHandler = sessionHandlerReference.get();
if (sessionHandler == null) {
return;
}
switch (message.arg1) {
case INIT:
sessionHandler.initInternal();
break;
case START:
sessionHandler.startInternal();
break;
case END:
sessionHandler.endInternal();
break;
case EVENT:
AdjustEvent event = (AdjustEvent) message.obj;
sessionHandler.trackEventInternal(event);
break;
case FINISH_TRACKING:
JSONObject jsonResponse = (JSONObject) message.obj;
sessionHandler.finishedTrackingActivityInternal(jsonResponse);
break;
case DEEP_LINK:
UrlClickTime urlClickTime = (UrlClickTime) message.obj;
sessionHandler.readOpenUrlInternal(urlClickTime.url, urlClickTime.clickTime);
break;
case SEND_REFERRER:
ReferrerClickTime referrerClickTime = (ReferrerClickTime) message.obj;
sessionHandler.sendReferrerInternal(referrerClickTime.referrer, referrerClickTime.clickTime);
break;
case UPDATE_STATUS:
sessionHandler.updateStatusInternal();
break;
}
}
}
private void initInternal() {
TIMER_INTERVAL = AdjustFactory.getTimerInterval();
TIMER_START = AdjustFactory.getTimerStart();
SESSION_INTERVAL = AdjustFactory.getSessionInterval();
SUBSESSION_INTERVAL = AdjustFactory.getSubsessionInterval();
deviceInfo = new DeviceInfo(adjustConfig.context, adjustConfig.sdkPrefix);
if (adjustConfig.environment == AdjustConfig.ENVIRONMENT_PRODUCTION) {
logger.setLogLevel(LogLevel.ASSERT);
} else {
logger.setLogLevel(adjustConfig.logLevel);
}
if (adjustConfig.eventBufferingEnabled) {
logger.info("Event buffering is enabled");
}
String playAdId = Util.getPlayAdId(adjustConfig.context);
if (playAdId == null) {
logger.info("Unable to get Google Play Services Advertising ID at start time");
}
if (adjustConfig.defaultTracker != null) {
logger.info("Default tracker: '%s'", adjustConfig.defaultTracker);
}
if (adjustConfig.referrer != null) {
sendReferrer(adjustConfig.referrer, adjustConfig.referrerClickTime); // send to background queue to make sure that activityState is valid
}
readAttribution();
readActivityState();
packageHandler = AdjustFactory.getPackageHandler(this, adjustConfig.context, toPause());
startInternal();
}
private void startInternal() {
// it shouldn't start if it was disabled after a first session
if (activityState != null
&& !activityState.enabled) {
return;
}
updateStatusInternal();
processSession();
checkAttributionState();
startTimer();
}
private void processSession() {
long now = System.currentTimeMillis();
// very first session
if (activityState == null) {
activityState = new ActivityState();
activityState.sessionCount = 1; // this is the first session
transferSessionPackage(now);
activityState.resetSessionAttributes(now);
activityState.enabled = this.enabled;
writeActivityState();
return;
}
long lastInterval = now - activityState.lastActivity;
if (lastInterval < 0) {
logger.error(TIME_TRAVEL);
activityState.lastActivity = now;
writeActivityState();
return;
}
// new session
if (lastInterval > SESSION_INTERVAL) {
activityState.sessionCount++;
activityState.lastInterval = lastInterval;
transferSessionPackage(now);
activityState.resetSessionAttributes(now);
writeActivityState();
return;
}
// new subsession
if (lastInterval > SUBSESSION_INTERVAL) {
activityState.subsessionCount++;
activityState.sessionLength += lastInterval;
activityState.lastActivity = now;
writeActivityState();
logger.info("Started subsession %d of session %d",
activityState.subsessionCount,
activityState.sessionCount);
}
}
private void checkAttributionState() {
// if there is no attribution saved, or there is one being asked
if (attribution == null || activityState.askingAttribution) {
getAttributionHandler().getAttribution();
}
}
private void endInternal() {
packageHandler.pauseSending();
getAttributionHandler().pauseSending();
stopTimer();
if (updateActivityState(System.currentTimeMillis())) {
writeActivityState();
}
}
private void trackEventInternal(AdjustEvent event) {
if (!checkEvent(event)) return;
if (!activityState.enabled) return;
long now = System.currentTimeMillis();
activityState.eventCount++;
updateActivityState(now);
PackageBuilder eventBuilder = new PackageBuilder(adjustConfig, deviceInfo, activityState, now);
ActivityPackage eventPackage = eventBuilder.buildEventPackage(event);
packageHandler.addPackage(eventPackage);
if (adjustConfig.eventBufferingEnabled) {
logger.info("Buffered event %s", eventPackage.getSuffix());
} else {
packageHandler.sendFirstPackage();
}
writeActivityState();
}
private void finishedTrackingActivityInternal(JSONObject jsonResponse) {
if (jsonResponse == null) {
return;
}
String deeplink = jsonResponse.optString("deeplink", null);
launchDeeplinkMain(deeplink);
getAttributionHandler().checkAttribution(jsonResponse);
}
private void sendReferrerInternal(String referrer, long clickTime) {
ActivityPackage clickPackage = buildQueryStringClickPackage(referrer,
"reftag",
clickTime);
if (clickPackage == null) {
return;
}
getAttributionHandler().getAttribution();
packageHandler.sendClickPackage(clickPackage);
}
private void readOpenUrlInternal(Uri url, long clickTime) {
if (url == null) {
return;
}
String queryString = url.getQuery();
ActivityPackage clickPackage = buildQueryStringClickPackage(queryString, "deeplink", clickTime);
if (clickPackage == null) {
return;
}
getAttributionHandler().getAttribution();
packageHandler.sendClickPackage(clickPackage);
}
private ActivityPackage buildQueryStringClickPackage(String queryString, String source, long clickTime) {
if (queryString == null) {
return null;
}
long now = System.currentTimeMillis();
Map<String, String> queryStringParameters = new HashMap<String, String>();
AdjustAttribution queryStringAttribution = new AdjustAttribution();
boolean hasAdjustTags = false;
String[] queryPairs = queryString.split("&");
for (String pair : queryPairs) {
if (readQueryString(pair, queryStringParameters, queryStringAttribution)) {
hasAdjustTags = true;
}
}
if (!hasAdjustTags) {
return null;
}
String reftag = queryStringParameters.remove("reftag");
PackageBuilder builder = new PackageBuilder(adjustConfig, deviceInfo, activityState, now);
builder.extraParameters = queryStringParameters;
builder.attribution = queryStringAttribution;
builder.reftag = reftag;
ActivityPackage clickPackage = builder.buildClickPackage(source, clickTime);
return clickPackage;
}
private boolean readQueryString(String queryString,
Map<String, String> extraParameters,
AdjustAttribution queryStringAttribution) {
String[] pairComponents = queryString.split("=");
if (pairComponents.length != 2) return false;
String key = pairComponents[0];
if (!key.startsWith(ADJUST_PREFIX)) return false;
String value = pairComponents[1];
if (value.length() == 0) return false;
String keyWOutPrefix = key.substring(ADJUST_PREFIX.length());
if (keyWOutPrefix.length() == 0) return false;
if (!trySetAttribution(queryStringAttribution, keyWOutPrefix, value)) {
extraParameters.put(keyWOutPrefix, value);
}
return true;
}
private boolean trySetAttribution(AdjustAttribution queryStringAttribution,
String key,
String value) {
if (key.equals("tracker")) {
queryStringAttribution.trackerName = value;
return true;
}
if (key.equals("campaign")) {
queryStringAttribution.campaign = value;
return true;
}
if (key.equals("adgroup")) {
queryStringAttribution.adgroup = value;
return true;
}
if (key.equals("creative")) {
queryStringAttribution.creative = value;
return true;
}
return false;
}
private void updateStatusInternal() {
updateAttributionHandlerStatus();
updatePackageHandlerStatus();
}
private void updateAttributionHandlerStatus() {
if (attributionHandler == null) {
return;
}
if (toPause()) {
attributionHandler.pauseSending();
} else {
attributionHandler.resumeSending();
}
}
private void updatePackageHandlerStatus() {
if (packageHandler == null) {
return;
}
if (toPause()) {
packageHandler.pauseSending();
} else {
packageHandler.resumeSending();
}
}
private void launchDeeplinkMain(String deeplink) {
if (deeplink == null) return;
Uri location = Uri.parse(deeplink);
Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);
mapIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// Verify it resolves
PackageManager packageManager = adjustConfig.context.getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(mapIntent, 0);
boolean isIntentSafe = activities.size() > 0;
// Start an activity if it's safe
if (!isIntentSafe) {
logger.error("Unable to open deep link (%s)", deeplink);
return;
}
logger.info("Open deep link (%s)", deeplink);
adjustConfig.context.startActivity(mapIntent);
}
private boolean updateActivityState(long now) {
long lastInterval = now - activityState.lastActivity;
// ignore late updates
if (lastInterval > SESSION_INTERVAL) {
return false;
}
activityState.lastActivity = now;
if (lastInterval < 0) {
logger.error(TIME_TRAVEL);
} else {
activityState.sessionLength += lastInterval;
activityState.timeSpent += lastInterval;
}
return true;
}
public static boolean deleteActivityState(Context context) {
return context.deleteFile(ACTIVITY_STATE_FILENAME);
}
public static boolean deleteAttribution(Context context) {
return context.deleteFile(ATTRIBUTION_FILENAME);
}
private void transferSessionPackage(long now) {
PackageBuilder builder = new PackageBuilder(adjustConfig, deviceInfo, activityState, now);
ActivityPackage sessionPackage = builder.buildSessionPackage();
packageHandler.addPackage(sessionPackage);
packageHandler.sendFirstPackage();
}
private void startTimer() {
stopTimer();
if (!activityState.enabled) {
return;
}
timer = Executors.newSingleThreadScheduledExecutor();
timer.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
timerFired();
}
}, TIMER_START, TIMER_INTERVAL, TimeUnit.MILLISECONDS);
}
private void stopTimer() {
if (timer != null) {
timer.shutdown();
timer = null;
}
}
private void timerFired() {
if (!activityState.enabled) {
stopTimer();
return;
}
packageHandler.sendFirstPackage();
if (updateActivityState(System.currentTimeMillis())) {
writeActivityState();
}
}
private void readActivityState() {
try {
/**
* Mozilla:
* readObject is a generic object, and can therefore return arbitrary generic objects
* that might not match the expected type. Therefore there will be an implicit cast
* here, which can fail. Therefore we have to add the catch (ClassCastException)
* Note: this has been fixed in upstream, we only need this for the version we are still shipping.
*/
activityState = Util.readObject(adjustConfig.context, ACTIVITY_STATE_FILENAME, ACTIVITY_STATE_NAME);
} catch (ClassCastException e) {
activityState = null;
}
}
private void readAttribution() {
try {
/**
* Mozilla: (same as in readActivityState() )
* readObject is a generic object, and can therefore return arbitrary generic objects
* that might not match the expected type. Therefore there will be an implicit cast
* here, which can fail. Therefore we have to add the catch (ClassCastException)
* Note: this has been fixed in upstream, we only need this for the version we are still shipping.
*/
attribution = Util.readObject(adjustConfig.context, ATTRIBUTION_FILENAME, ATTRIBUTION_NAME);
} catch (ClassCastException e) {
activityState = null;
}
}
private void writeActivityState() {
Util.writeObject(activityState, adjustConfig.context, ACTIVITY_STATE_FILENAME, ACTIVITY_STATE_NAME);
}
private void writeAttribution() {
Util.writeObject(attribution, adjustConfig.context, ATTRIBUTION_FILENAME, ATTRIBUTION_NAME);
}
private boolean checkEvent(AdjustEvent event) {
if (event == null) {
logger.error("Event missing");
return false;
}
if (!event.isValid()) {
logger.error("Event not initialized correctly");
return false;
}
return true;
}
// lazy initialization to prevent null activity state before first session
private IAttributionHandler getAttributionHandler() {
if (attributionHandler == null) {
ActivityPackage attributionPackage = getAttributionPackage();
attributionHandler = AdjustFactory.getAttributionHandler(this,
attributionPackage,
toPause());
}
return attributionHandler;
}
private boolean toPause() {
return offline || !isEnabled();
}
}