Android Open Source - KISSmetrics-Android-SDK K I S Smetrics A P I






From Project

Back to project page KISSmetrics-Android-SDK.

License

The source code is released under:

Apache License

If you think the Android project KISSmetrics-Android-SDK listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

//
// KISSmetricsSDK
///*ww  w.  j  av  a2s . co m*/
// Copyright 2014 KISSmetrics
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


package com.kissmetrics.sdk;

import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Log;

/**
 * KISSmetricsAPI
 * 
 * Public API for sending identities, events and properties to KISSmetrics from
 * Android applications. Compatible with Android 2.1+
 * 
 */
public final class KISSmetricsAPI implements VerificationDelegate {

  public enum RecordCondition {
    RECORD_ALWAYS,
    RECORD_ONCE_PER_INSTALL,
    RECORD_ONCE_PER_IDENTITY
  }
  
  private static final long FAILSAFE_MAX_VERIFICATION_DUR = 1209600000L; // 14 days
  
  private static KISSmetricsAPI sharedAPI = null;
  private static VerificationImpl verificationImpl = null;
  private static ExecutorService dataExecutor = Executors.newFixedThreadPool(2);
  
  private TrackingRunnables trackingRunnables = null;
  
  private String key;
  private Context context;

  protected static Sender sender;
  
  
  /**
   * Allows for injection of a mock Verification.
   * 
   * @param verImpl
   *            A mock Verification to use under test.
   */
  protected static void setVerificationImpl(VerificationImpl verImpl) {
    verificationImpl = verImpl;
  }

  /**
   * Initializes the default Connection if not set. Allows for injection of
   * mock HttpURLConnection within VerificationImpl.
   * 
   * Returns the injected verificationImpl if it exists.
   * 
   * @return Connection
   */
  protected static VerificationImpl verificationImpl() {
    if (verificationImpl != null) {
      return verificationImpl;
    }
    return new VerificationImpl();
  }

  /**
   * Initializes the private singleton.
   * 
   * @param productKey
   *            KISSmetrics product key.
   * @param context
   *            Android application context.
   */
  private KISSmetricsAPI(final String productKey, final Context appContext) {

    key = productKey;
    context = appContext;

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

    // Ensure an Install UUID exists
    String installUuid = ArchiverImpl.sharedArchiver().getInstallUuid();

    if (installUuid == null || installUuid.length() == 0) {
      // No install id has been set. Make and archive a new one.
      ArchiverImpl.sharedArchiver()
          .archiveInstallUuid(generateID());
      installUuid = ArchiverImpl.sharedArchiver().getInstallUuid();
    }

    // Ensure an Identity exists
    String archIdentity = ArchiverImpl.sharedArchiver().getIdentity();

    if (archIdentity == null || archIdentity.length() == 0) {
      // No identity has been set. Make and archive a new one.
      ArchiverImpl.sharedArchiver().archiveFirstIdentity(
          generateID());
    }
    
    if (sender == null) {
      sender = new Sender(!ArchiverImpl.sharedArchiver().getDoSend());
    }
    
    // Set the TrackingRunnables state
    if (ArchiverImpl.sharedArchiver().getDoTrack()) {
      trackingRunnables = new TrackingRunnablesTrackingState();
    } else {
      trackingRunnables = new TrackingRunnablesNonTrackingState();
    }
  }

  /**
   * Initializes and/or returns the KISSmetricsAPI singleton instance. This
   * method must be called before making any other calls.
   * 
   * @param productKey
   *            KISSmetrics product key.
   * @param applicationContext
   *            Android application context.
   * @return KISSmetricsAPI singleton instance.
   */
  public static synchronized KISSmetricsAPI sharedAPI(
      final String productKey, final Context applicationContext) {
    if (sharedAPI == null) {
      sharedAPI = new KISSmetricsAPI(productKey, applicationContext);
    }

    // Verifying tracking here will allow for checks from the Android app's
    // state has been cached with the SDK already initialized.
    sharedAPI.verifyForTracking();

    return sharedAPI;
  }

  /**
   * @return KISSmetricsAPI singleton instance.
   */
  public static synchronized KISSmetricsAPI sharedAPI() {
    if (sharedAPI == null) {
      Log.w("KISSmetricsAPI",
          "KISSMetricsAPI: WARNING - Returning null object in sharedAPI as "
              + "sharedAPI(<API_KEY>, <Context>): has not been called.");
    }
    return sharedAPI;
  }

  
  /************************************************
   * Private methods
   ************************************************/

  /**
   * Creates a random UUID string.
   * 
   * @return UUID string
   */
  private String generateID() {
    return UUID.randomUUID().toString();
  }

  /**
   * Verifies the KISSmetricsAPI product for tracking asynchronously.
   */
  private void verifyForTracking() {

    if (System.currentTimeMillis() < ArchiverImpl.sharedArchiver()
        .getVerificationExpDate()) {
      return;
    }

    new Thread(new Runnable() {
      public void run() {
        String installUuid = ArchiverImpl.sharedArchiver()
            .getInstallUuid();
        VerificationImpl verification = verificationImpl();
        verification.verifyTracking(key, installUuid, sharedAPI());
      }
    }).start();
  }

  /**
   * @return app version from package info
   */
  private String appVersionName() {
    try {
      PackageManager pkgManager = context.getPackageManager();
      String pkg = context.getPackageName();
      return pkgManager.getPackageInfo(pkg, 0).versionName;
    } catch (Exception e) {
      return null;
    }
  }
  
  /**
   * Starts sending to empty the sendQueue when sender is in the 
   * ready state.
   */
  protected void sendRecords() {
    sender.startSending();
  }
  
  /************************************************
   * Public methods
   ************************************************/

  /**
   * Applies an identity to the user.
   * 
   * @param identity
   *            user identity
   */
  public void identify(final String identity) {
    // Pass this call onto the mDataExecutor ExecutorService
    // as a Runnable object to be run on a background thread.
    dataExecutor.execute(trackingRunnables.identify(identity,
        ArchiverImpl.sharedArchiver(), this));
  }

  /**
   * This getter does not lock on the Archiver instance to prevent locking of
   * an application's main thread. All writes to identity lock on the
   * singleton instance of sharedArchiver.
   * 
   * @return Last provided identity string for the current user
   */
  public String identity() {
    return ArchiverImpl.sharedArchiver().getIdentity();
  }

  /**
   * Applies an alias to an identity
   * 
   * @param alias
   * @param identity
   */
  public void alias(final String alias, final String identity) {
    dataExecutor.execute(trackingRunnables.alias(alias, identity,
        ArchiverImpl.sharedArchiver(), this));
  }

  /**
   * Sets a new random identity that isn't aliased or associated with a
   * previous identity.
   */
  public void clearIdentity() {
    dataExecutor.execute(trackingRunnables.clearIdentity(generateID(),
        ArchiverImpl.sharedArchiver()));
  }

  /**
   * Records an event with optional properties.
   * 
   * @param name
   *            Event name
   * @param properties
   *            Event properties or null
   */
  // TODO: We should allow for recording properties as numbers or strings.
  public void record(final String name,
      final HashMap<String, String> properties) {
    record(name, properties, RecordCondition.RECORD_ALWAYS);
  }

  /**
   * Convenience method for recording an event without properties.
   * 
   * @param name
   *            Event name
   */
  public void record(final String name) {
    record(name, null, RecordCondition.RECORD_ALWAYS);
  }
  
  /**
   * Records an event per identity or install depending on the RecordCondition that's passed.
   * 
   * @param name
   *            Event name
   * @param properties
   *            Event properties or null
   * @param condition
   *         RecordCondition (RecordOncePerInstall, RecordOncePerIdentity).
   *         - Using RecordOncePerInstall: The event will only be recorded once 
   *         during the lifetime of the application's installation. If the event 
   *         has already been recorded, any properties passed will also be ignored.
   *         - Using RecordOncePerIdentity: The event will only be recorded once 
   *         until the identity changes or is cleared via clearIdentity. If the 
   *         event has already been recorded, any properties passed will also be 
   *         ignored.
   */
  public void record(final String name, final HashMap<String, String> properties, RecordCondition condition) {
    dataExecutor.execute(trackingRunnables.record(name, properties, condition,
        ArchiverImpl.sharedArchiver(), this));

    // 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.
    verifyForTracking();
  }
  
  /**
   * Convenience method for recording an event on a RecordCondition without properties.
   * 
   * @param name
   *            Event name
   * @param condition
   *         RecordCondition (RecordOncePerInstall, RecordOncePerIdentity).
   *         - Using RecordOncePerInstall: The event will only be recorded once 
   *         during the lifetime of the application's installation. If the event 
   *         has already been recorded, any properties passed will also be ignored.
   *         - Using RecordOncePerIdentity: The event will only be recorded once 
   *         until the identity changes or is cleared via clearIdentity. If the 
   *         event has already been recorded, any properties passed will also be 
   *         ignored.
   *         
   */
  public void record(final String name, RecordCondition condition) {
    record(name, null, condition);
  }
  
  /**
   * Sets one or more properties.
   * 
   * @param properties
   *            User properties
   */
  // TODO: We should allow for recording properties as numbers or strings.
  public void set(final HashMap<String, String> properties) {
    dataExecutor.execute(trackingRunnables.set(properties,
        ArchiverImpl.sharedArchiver(), this));
  }

  /**
   * Sets a single property if the value is different from the last set value.
   * 
   * @param propertyName
   * @param value
   */
  // TODO: We should allow for recording properties as numbers or strings.
  public void setDistinct(final String propertyName, final String value) {
    dataExecutor.execute(trackingRunnables.setDistinct(propertyName,
        value, ArchiverImpl.sharedArchiver(), this));
  }
  
  /**
   * Automatically records the following events "Installed App" "Updated App"
   */
  public void autoRecordInstalls() {

    String versionName = appVersionName();
    
    if (versionName == null) {
      versionName = "";
    } else {
      setDistinct("App Version", versionName);
    }

    // There is no reliable place to store data that will persist between
    // app install and uninstall. We use Archiver's settings store.
    String lastAppVersion = ArchiverImpl.sharedArchiver().getAppVersion();
    
    if (versionName.equals(lastAppVersion)) {
      // Most common case. No action required.
      return;
    }
    
    ArchiverImpl.sharedArchiver().archiveAppVersion(versionName);

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

  /**
   * Automatically collects and sets the following hardware properties as
   * distinct properties: "Device Manufacturer" : (Asus) "Device Model" :
   * (Nexus 7) "System Name" : (Android) "System Version" : (4.4)
   */
  public void autoSetHardwareProperties() {
    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);
  }

  /**
   * Automatically collects and sets the following applcation properties as
   * distinct properties: "App Version" : (1.0) aka versionName "App Build" :
   * (10) aka versionCode
   */
  public void autoSetAppProperties() {

    PackageManager pkgManager = context.getPackageManager();

    try {
      String pkg = context.getPackageName();
      String versionName = pkgManager.getPackageInfo(pkg, 0).versionName;
      setDistinct("App Version", versionName);
    } catch (Exception e) {
      // Catch intentionally blank
    }

    try {
      String pkg = context.getPackageName();
      int versionCode = pkgManager.getPackageInfo(pkg, 0).versionCode;
      setDistinct("App Build", String.valueOf(versionCode));
    } catch (Exception e) {
      // Catch intentionally blank
    }
  }

  /************************************************
   * VerificationDelegateInterface methods
   ************************************************/
  @Override
  public void verificationComplete(final boolean success,
      final boolean doTrack, final String baseUrl,
      final long expirationDate) {

    // We have 3 cases here.
    // 1. verification URL request was unsuccessful.
    // - We will continue to track but not send any data to KM trk
    // 2. verification URL request was successful. !doTrack
    // 3. verification URL request was successful. doTrack

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

      // Do not send by default.
      sender.disableSending();
      ArchiverImpl.sharedArchiver().archiveDoSend(false);

      // Do not modify baseUrl.
      return;
    }

    long maxExpDate = (System.currentTimeMillis() + FAILSAFE_MAX_VERIFICATION_DUR);

    ArchiverImpl.sharedArchiver().archiveVerificationExpDate(
        Math.min(expirationDate, maxExpDate));
    if (!doTrack) {
      trackingRunnables = new TrackingRunnablesNonTrackingState();
      sender.disableSending();
    } else {
      trackingRunnables = new TrackingRunnablesTrackingState();
      // If we should be tracking, then we should be sending
      sender.enableSending();
      ArchiverImpl.sharedArchiver().archiveDoSend(true);
    }

    ArchiverImpl.sharedArchiver().archiveDoTrack(doTrack);

    ArchiverImpl.sharedArchiver().archiveBaseUrl(baseUrl);
  }

  
  /************************************************
   * Deprecated Public Methods
   ************************************************/

  /**
   * @deprecated use {@link sharedAPI(String apiKey)} instead. All requests
   *             are now made over https. secure(boolean) is ignored.
   * 
   *             Initializes and/or returns the KISSmetricsAPI singleton
   *             instance.
   * 
   * @param apiKey
   *            KISSmetrics product key
   * @param context
   *            Android application context
   * @param secure
   *            !Ignored!
   * @return singleton instance of the KISSmeticsAPI
   */
  @Deprecated
  public static synchronized KISSmetricsAPI sharedAPI(String apiKey,
      Context context, boolean secure) {
    if (sharedAPI == null) {
      sharedAPI = new KISSmetricsAPI(apiKey, context);
    }
    return sharedAPI;
  }

  /**
   * @deprecated use {@link record(String name, HashMap<String, String>
   *             properties)} instead. 'recordEvent' method name has been
   *             changed to 'record' for consistency across our various APIs.
   * 
   *             Records an event with optional properties.
   * 
   * @param name
   *            Event name
   * @param properties
   *            Event properties or null
   */
  @Deprecated
  public void recordEvent(String name, HashMap<String, String> properties) {
    record(name, properties);
  }

  /**
   * @deprecated use {@link record(String name, HashMap<String, String>
   *             properties), RecordCondition condition} instead. 'recordOnce' 
   *             would only restrict the recording of events per identity. A 
   *             more flexible solution was needed to allow recording of 
   *             events once per installation or identity.
   * 
   * @param name
   *            Event name
   */
  @Deprecated
  public void recordOnce(final String name) {
    dataExecutor.execute(trackingRunnables.recordOnce(name,
        ArchiverImpl.sharedArchiver(), this));
  }
  
  /**
   * @deprecated use {@link set(HashMap<String, String> properties)} instead.
   *             'setProperties' method name has been changed to 'set' for
   *             consistency across our various APIs.
   * 
   * @param properties
   *            User properties
   */
  @Deprecated
  public void setProperties(HashMap<String, String> properties) {
    set(properties);
  }

}




Java Source Code List

com.kissmetrics.api.MainActivity.java
com.kissmetrics.sdk.ArchiverImplActTest.java
com.kissmetrics.sdk.ArchiverImplTest.java
com.kissmetrics.sdk.ArchiverImpl.java
com.kissmetrics.sdk.Archiver.java
com.kissmetrics.sdk.ConnectionDelegate.java
com.kissmetrics.sdk.ConnectionImplTest.java
com.kissmetrics.sdk.ConnectionImpl.java
com.kissmetrics.sdk.Connection.java
com.kissmetrics.sdk.KISSmetricsAPITest.java
com.kissmetrics.sdk.KISSmetricsAPI.java
com.kissmetrics.sdk.QueryEncoderTest.java
com.kissmetrics.sdk.QueryEncoder.java
com.kissmetrics.sdk.SenderDisabledState.java
com.kissmetrics.sdk.SenderReadyState.java
com.kissmetrics.sdk.SenderSendingState.java
com.kissmetrics.sdk.SenderState.java
com.kissmetrics.sdk.Sender.java
com.kissmetrics.sdk.TestableConnectionImpl.java
com.kissmetrics.sdk.TestableVerificationImpl.java
com.kissmetrics.sdk.TrackingRunnablesNonTrackingState.java
com.kissmetrics.sdk.TrackingRunnablesTrackingState.java
com.kissmetrics.sdk.TrackingRunnables.java
com.kissmetrics.sdk.VerificationDelegate.java
com.kissmetrics.sdk.VerificationImplTest.java
com.kissmetrics.sdk.VerificationImpl.java
org.apache.cactus.mock.MockHttpURLConnection.java