Skip to content

Commit

Permalink
Merge pull request #6 from kissmetrics/SenderStates
Browse files Browse the repository at this point in the history
Sender states
  • Loading branch information
willrust committed May 24, 2014
2 parents 07a3748 + 0111e2f commit db43a8c
Show file tree
Hide file tree
Showing 15 changed files with 439 additions and 295 deletions.
2 changes: 1 addition & 1 deletion KISSmetricsAPI/src/com/kissmetrics/sdk/ConnectionImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public void sendRecord(final String urlString, final ConnectionDelegate delegate
connection.setUseCaches(false);
connection.setRequestMethod("GET");
connection.setConnectTimeout(CONNECTION_TIMEOUT*1000);
connection.setRequestProperty("User-Agent", "KISSmetrics-Android/2.0");
connection.setRequestProperty("User-Agent", "KISSmetrics-Android/2.0.3");
// TODO: Apply any easily obtainable device/OS info to the user agent value

// addressing java.io.EOFException
Expand Down
172 changes: 44 additions & 128 deletions KISSmetricsAPI/src/com/kissmetrics/sdk/KISSmetricsAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,32 +34,22 @@
* Android applications. Compatible with Android 2.1+
*
*/
public final class KISSmetricsAPI implements VerificationDelegate,
ConnectionDelegate {
public final class KISSmetricsAPI implements VerificationDelegate {

private static final long FAILSAFE_MAX_VERIFICATION_DUR = 1209600000L; // 14 days

private static KISSmetricsAPI sharedAPI = null;
private static ConnectionImpl connectionImpl = null;
private static VerificationImpl verificationImpl = null;
private static ExecutorService connectionExecutor = Executors.newSingleThreadExecutor();
private static ExecutorService dataExecutor = Executors.newFixedThreadPool(2);

private SendingRunnables sendingRunnables = null;
private TrackingRunnables trackingRunnables = null;

private String key;
private Context context;

/**
* Allows for injection of a mock Connection.
*
* @param connImpl
* A mock Connection to use under test.
*/
protected static void setConnectionImpl(ConnectionImpl connImpl) {
connectionImpl = connImpl;
}

protected static Sender sender;


/**
* Allows for injection of a mock Verification.
*
Expand All @@ -72,20 +62,7 @@ protected static void setVerificationImpl(VerificationImpl verImpl) {

/**
* Initializes the default Connection if not set. Allows for injection of
* mock HttpURLConnection within ConnectionImpl via method override.
*
* @return Connection
*/
protected static ConnectionImpl connection() {
if (connectionImpl == null) {
connectionImpl = new ConnectionImpl();
}
return connectionImpl;
}

/**
* Initializes the default Connection if not set. Allows for injection of
* mock HttpURLConnection within VerificationImpl via method override.
* mock HttpURLConnection within VerificationImpl.
*
* Returns the injected verificationImpl if it exists.
*
Expand All @@ -106,10 +83,10 @@ protected static VerificationImpl verificationImpl() {
* @param context
* Android application context.
*/
private KISSmetricsAPI(final String productKey, final Context context) {
private KISSmetricsAPI(final String productKey, final Context appContext) {

this.key = productKey;
this.context = context;
key = productKey;
context = appContext;

ArchiverImpl.sharedArchiver(this.key, this.context);

Expand All @@ -119,7 +96,7 @@ private KISSmetricsAPI(final String productKey, final Context context) {
if (installUuid == null || installUuid.length() == 0) {
// No install id has been set. Make and archive a new one.
ArchiverImpl.sharedArchiver()
.archiveInstallUuid(this.generateID());
.archiveInstallUuid(generateID());
installUuid = ArchiverImpl.sharedArchiver().getInstallUuid();
}

Expand All @@ -129,16 +106,13 @@ private KISSmetricsAPI(final String productKey, final Context context) {
if (archIdentity == null || archIdentity.length() == 0) {
// No identity has been set. Make and archive a new one.
ArchiverImpl.sharedArchiver().archiveFirstIdentity(
this.generateID());
generateID());
}

// Set the SendingRunnables state
if (ArchiverImpl.sharedArchiver().getDoSend()) {
this.sendingRunnables = new SendingRunnablesSendingState();
} else {
this.sendingRunnables = new SendingRunnablesNonSendingState();

if (sender == null) {
sender = new Sender(!ArchiverImpl.sharedArchiver().getDoSend());
}

// Set the TrackingRunnables state
if (ArchiverImpl.sharedArchiver().getDoTrack()) {
trackingRunnables = new TrackingRunnablesTrackingState();
Expand Down Expand Up @@ -216,41 +190,15 @@ public void run() {
}).start();
}


/**
* Starts a recursive send of the sendQueue within a single threaded
* ExecutorService.
*/
protected void recursiveSend() {

if (ArchiverImpl.sharedArchiver().getQueueCount() == 0) {
resetConnectionExecutor();
return;
}

synchronized (connectionExecutor) {
connectionExecutor.execute(this.sendingRunnables
.runnableRecursiveSend(ArchiverImpl.sharedArchiver(),
connection(), this));
}
}

/**
* Stops and resets the single threaded ExecutorService that handles all
* tracking API URL requests.
* Starts sending to empty the sendQueue when sender is in the
* ready state.
*/
private void resetConnectionExecutor() {

synchronized (connectionExecutor) {

// Cancel any queued operations on our connection ExecutorService
connectionExecutor.shutdownNow();

// Anytime we shutdown an ExecutorService we need to
// make sure a new one is ready to take new Runnables.
connectionExecutor = Executors.newSingleThreadExecutor();
}
protected void sendRecords() {
sender.startSending();
}

/************************************************
* Public methods
************************************************/
Expand Down Expand Up @@ -316,7 +264,7 @@ public void record(final String name,
// The main activity's onCreate method will likely not be called
// frequently enough to re-verify.
// In most cases this will only be checking the expiration date.
this.verifyForTracking();
verifyForTracking();
}

/**
Expand All @@ -326,7 +274,7 @@ public void record(final String name,
* Event name
*/
public void record(final String name) {
this.record(name, null);
record(name, null);
}

/**
Expand Down Expand Up @@ -375,7 +323,7 @@ public void autoRecordInstalls() {
try {
String pkg = context.getPackageName();
versionName = pkgManager.getPackageInfo(pkg, 0).versionName;
this.setDistinct("App Version", versionName);
setDistinct("App Version", versionName);
} catch (Exception e) {
// Catch intentionally blank
}
Expand All @@ -391,10 +339,10 @@ public void autoRecordInstalls() {

if (lastAppVersion == null) {
// This is a fresh install
this.record("Installed App");
record("Installed App");
} else if (!lastAppVersion.equals(versionName)) {
// This is an update
this.record("Updated App");
record("Updated App");
}

ArchiverImpl.sharedArchiver().archiveAppVersion(versionName);
Expand All @@ -406,10 +354,10 @@ public void autoRecordInstalls() {
* (Nexus 7) "System Name" : (Android) "System Version" : (4.4)
*/
public void autoSetHardwareProperties() {
this.setDistinct("Device Manufacturer", android.os.Build.MANUFACTURER);
this.setDistinct("Device Model", android.os.Build.MODEL);
this.setDistinct("System Name", "Android");
this.setDistinct("System Version", android.os.Build.VERSION.RELEASE);
setDistinct("Device Manufacturer", android.os.Build.MANUFACTURER);
setDistinct("Device Model", android.os.Build.MODEL);
setDistinct("System Name", "Android");
setDistinct("System Version", android.os.Build.VERSION.RELEASE);
}

/**
Expand All @@ -424,15 +372,15 @@ public void autoSetAppProperties() {
try {
String pkg = context.getPackageName();
String versionName = pkgManager.getPackageInfo(pkg, 0).versionName;
this.setDistinct("App Version", versionName);
setDistinct("App Version", versionName);
} catch (Exception e) {
// Catch intentionally blank
}

try {
String pkg = context.getPackageName();
int versionCode = pkgManager.getPackageInfo(pkg, 0).versionCode;
this.setDistinct("App Build", String.valueOf(versionCode));
setDistinct("App Build", String.valueOf(versionCode));
} catch (Exception e) {
// Catch intentionally blank
}
Expand All @@ -453,15 +401,15 @@ public void verificationComplete(final boolean success,
// 3. verification URL request was successful. doTrack

if (!success) {
// do Track by default.
this.trackingRunnables = new TrackingRunnablesTrackingState();
// Do Track by default.
trackingRunnables = new TrackingRunnablesTrackingState();
ArchiverImpl.sharedArchiver().archiveDoTrack(true);

// do not send by default.
this.sendingRunnables = new SendingRunnablesNonSendingState();
// Do not send by default.
sender.disableSending();
ArchiverImpl.sharedArchiver().archiveDoSend(false);

// do not modify baseUrl
// Do not modify baseUrl.
return;
}

Expand All @@ -470,18 +418,12 @@ public void verificationComplete(final boolean success,
ArchiverImpl.sharedArchiver().archiveVerificationExpDate(
Math.min(expirationDate, maxExpDate));
if (!doTrack) {
this.trackingRunnables = new TrackingRunnablesNonTrackingState();
// First cancel any running connections by resetting the connection
// ExecutorService.
// If we don't do this then a running connection may attempt to
// remove a query from
// the empty sendQueue.
resetConnectionExecutor();
ArchiverImpl.sharedArchiver().clearSendQueue();
trackingRunnables = new TrackingRunnablesNonTrackingState();
sender.disableSending();
} else {
this.trackingRunnables = new TrackingRunnablesTrackingState();
trackingRunnables = new TrackingRunnablesTrackingState();
// If we should be tracking, then we should be sending
this.sendingRunnables = new SendingRunnablesSendingState();
sender.enableSending();
ArchiverImpl.sharedArchiver().archiveDoSend(true);
}

Expand All @@ -490,33 +432,7 @@ public void verificationComplete(final boolean success,
ArchiverImpl.sharedArchiver().archiveBaseUrl(baseUrl);
}

/************************************************
* ConnectionDelegateInterface Methods
************************************************/
@Override
public void connectionComplete(final String urlString,
final boolean success, final boolean malformed) {

if (success || malformed) {
// We call to remove the query string in the current
// sConnectionExecutor's thread.
// This call will be synchronized within the Archiver and ensure
// that the following
// reads from the Archiver's sendQueue will provide the correct
// value.
ArchiverImpl.sharedArchiver().removeQueryString(0);
}

// Only if the last call was successful should we attempt to send the
// next request
if (success) {
// _recursiveSend will add the next runnable to the
// sConnectionExecutor
// No need to call this from another thread
recursiveSend();
}
}


/************************************************
* Deprecated Public Methods
************************************************/
Expand Down Expand Up @@ -559,7 +475,7 @@ public static synchronized KISSmetricsAPI sharedAPI(String apiKey,
*/
@Deprecated
public void recordEvent(String name, HashMap<String, String> properties) {
this.record(name, properties);
record(name, properties);
}

/**
Expand All @@ -572,7 +488,7 @@ public void recordEvent(String name, HashMap<String, String> properties) {
*/
@Deprecated
public void setProperties(HashMap<String, String> properties) {
this.set(properties);
set(properties);
}

}
Loading

0 comments on commit db43a8c

Please sign in to comment.