com.mparticle.MParticle.java Source code

Java tutorial

Introduction

Here is the source code for com.mparticle.MParticle.java

Source

package com.mparticle;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.webkit.WebView;

import com.mparticle.commerce.Cart;
import com.mparticle.commerce.CommerceApi;
import com.mparticle.commerce.CommerceEvent;
import com.mparticle.commerce.Product;
import com.mparticle.commerce.ProductBagApi;
import com.mparticle.internal.AppStateManager;
import com.mparticle.internal.ConfigManager;
import com.mparticle.internal.Constants;
import com.mparticle.internal.Constants.MessageKey;
import com.mparticle.internal.Constants.PrefKeys;
import com.mparticle.internal.KitKatHelper;
import com.mparticle.internal.KitFrameworkWrapper;
import com.mparticle.internal.MPLocationListener;
import com.mparticle.internal.MPUtility;
import com.mparticle.internal.MParticleJSInterface;
import com.mparticle.internal.MessageManager;
import com.mparticle.internal.PushRegistrationHelper;
import com.mparticle.media.MPMediaAPI;
import com.mparticle.media.MediaCallbacks;
import com.mparticle.messaging.CloudAction;
import com.mparticle.messaging.MPCloudNotificationMessage;
import com.mparticle.messaging.MPMessagingAPI;
import com.mparticle.messaging.ProviderCloudMessage;
import com.mparticle.segmentation.SegmentListener;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * The primary access point to the mParticle SDK. In order to use this class, you must first call {@link #start(Context)}, which requires
 * configuration via <code><a href="http://developer.android.com/guide/topics/resources/providing-resources.html">Android Resources</a></code>. You can then retrieve a reference
 * to an instance of this class via {@link #getInstance()}
 * <p></p>
 * It's recommended to keep configuration parameters in a single xml file located within your res/values folder. The full list of configuration options is as follows:
 * <p></p>
 * Required parameters
 * <ul>
 * <li>mp_key - <code><a href="http://developer.android.com/guide/topics/resources/string-resource.html#String">String</a></code> - This is the key used to authenticate with the mParticle SDK server API</li>
 * <li>mp_secret - <code> <a href="http://developer.android.com/guide/topics/resources/string-resource.html#String">String</a></code> - This is the secret used to authenticate with the mParticle SDK server API</li>
 * </ul>
 * Required for push notifications
 * <ul>
 * <li> mp_enablePush - <code><a href="http://developer.android.com/guide/topics/resources/more-resources.html#Bool">Bool</a></code> - Enable push registration, notifications, and analytics. <i>Default: false</i></li>
 * <li> mp_pushSenderId - <code><a href="http://developer.android.com/guide/topics/resources/string-resource.html#String">String</a></code> - <code><a href="http://developer.android.com/google/gcm/gcm.html#senderid">GCM Sender ID</a></code></li>
 * </ul>
 * Required for licensing
 * <ul>
 * <li> mp_enableLicenseCheck - <code><a href="http://developer.android.com/guide/topics/resources/more-resources.html#Bool">Bool</a></code> - By enabling license check, MParticle will automatically validate that the app was downloaded and/or bought via Google Play, or if it was "pirated" or "side-loaded". <i>Default: false</i></li>
 * <li> mp_appLicenseKey - <code><a href="http://developer.android.com/guide/topics/resources/string-resource.html#String">String</a></code> - The <code><a href="http://developer.android.com/google/play/licensing/adding-licensing.html#account-key">public key</a></code> used by your app to verify the user's license with Google Play.</li>
 * </ul>
 * Optional
 * <ul>
 * <li>mp_enableAutoScreenTracking - <code> <a href="http://developer.android.com/guide/topics/resources/more-resources.html#Integer">Integer</a></code> - Enable automatic screen view events. Note that *prior to ICS/API level 14*, this functionality requires instrumentation via an mParticle Activity implementation or manually. </li>
 * <li>mp_productionUploadInterval - <code> <a href="http://developer.android.com/guide/topics/resources/more-resources.html#Integer">Integer</a></code> - The length of time in seconds to send batches of messages to mParticle. Setting this too low could have an adverse effect on the device battery. <i>Default: 600</i></li>
 * <li>mp_reportUncaughtExceptions - <code> <a href="http://developer.android.com/guide/topics/resources/more-resources.html#Bool">Bool</a></code> - By enabling this, the MParticle SDK will automatically log and report any uncaught exceptions, including stack traces. <i>Default: false</i></li>
 * <li>mp_sessionTimeout - <code> <a href="http://developer.android.com/guide/topics/resources/more-resources.html#Integer">Integer</a></code> - The length of time (in seconds) that a user session will remain valid while application has been paused and put into the background. <i>Default: 60</i></li>
 * </ul>
 */
public class MParticle {
    /**
     * The ConfigManager is tasked with incorporating server-based, run-time, and XML configuration,
     * and surfacing the result/winner.
     */
    protected ConfigManager mConfigManager;

    /**
     * Used to delegate messages, events, user actions, etc on to embedded kits.
     */

    protected KitFrameworkWrapper mKitManager;
    /**
     * The state manager is primarily concerned with Activity lifecycle and app visibility in order to manage sessions,
     * automatically log screen views, and pass lifecycle information on top embedded kits.
     */
    AppStateManager mAppStateManager;

    protected MessageManager mMessageManager;
    private static volatile MParticle instance;
    protected SharedPreferences mPreferences;
    protected MPLocationListener mLocationListener;
    private Context mAppContext;
    protected MPMessagingAPI mMessaging;
    protected MPMediaAPI mMedia;
    protected CommerceApi mCommerce;
    protected ProductBagApi mProductBags;
    protected volatile DeepLinkListener mDeepLinkListener;
    private static volatile boolean androidIdDisabled;
    private static volatile boolean devicePerformanceMetricsDisabled;

    MParticle() {
    }

    /**
     * Start the mParticle SDK and begin tracking a user session. This method must be called prior to {@link #getInstance()}.
     * This method requires that your API key and secret are contained in your XML configuration.
     *
     * @param context Required reference to a Context object
     */

    public static void start(Context context) {
        start(context, InstallType.AutoDetect);
    }

    /**
     * Start the mParticle SDK and begin tracking a user session. This method must be called prior to {@link #getInstance()}.
     * This method requires that your API key and secret are contained in your XML configuration.
     *
     * @param context Required reference to a Context object
     * @param apiKey Your application's mParticle key retrieved from app.mparticle.com/apps
     * @param apiSecret Your application's mParticle secret retrieved from app.mparticle.com/apps
     *
     */

    public static void start(Context context, String apiKey, String apiSecret) {
        start(context, InstallType.AutoDetect, apiKey, apiSecret);
    }

    /**
     * Start the mParticle SDK and begin tracking a user session. This method must be called prior to {@link #getInstance()}.
     * This method requires that your API key and secret are contained in your XML configuration.
     * <p></p>
     * The InstallType parameter is used to determine if this is a new install or an upgrade. In
     * the case where the mParticle SDK is being added to an existing app with existing users, this
     * parameter prevents mParticle from categorizing all users as new users.
     *
     * @param context     Required reference to a Context object
     * @param installType Specify whether this is a new install or an upgrade, or let mParticle detect
     * @see MParticle.InstallType
     */

    public static void start(Context context, InstallType installType) {
        start(context, installType, Environment.AutoDetect);
    }

    /**
     * Start the mParticle SDK and begin tracking a user session. This method must be called prior to {@link #getInstance()}.
     * This method requires that your API key and secret are contained in your XML configuration.
     * <p></p>
     * The InstallType parameter is used to determine if this is a new install or an upgrade. In
     * the case where the mParticle SDK is being added to an existing app with existing users, this
     * parameter prevents mParticle from categorizing all users as new users.
     *
     * @param context       Required reference to a Context object
     * @param installType   Specify whether this is a new install or an upgrade, or let mParticle detect
     * @param apiKey        Your application's mParticle key retrieved from app.mparticle.com/apps
     * @param apiSecret     Your application's mParticle secret retrieved from app.mparticle.com/apps
     *
     * @see MParticle.InstallType
     */

    public static void start(Context context, InstallType installType, String apiKey, String apiSecret) {
        start(context, installType, Environment.AutoDetect, apiKey, apiSecret);
    }

    /**
     * Start the mParticle SDK and begin tracking a user session. This method must be called prior to {@link #getInstance()}.
     * This method requires that your API key and secret are contained in your XML configuration.
     * <p></p>
     *
     *
     * @param context     Required reference to a Context object
     * @param installType The InstallType parameter is used to determine if this is a new install or an upgrade. In
     * the case where the mParticle SDK is being added to an existing app with existing users, this
     * parameter prevents mParticle from categorizing all users as new users.
     * @param environment Force the SDK into either Production or Development mode. See {@link MParticle.Environment}
     * for implications of each mode. The SDK automatically determines which mode it should be in depending
     * on the signing and the DEBUGGABLE flag of your application's AndroidManifest.xml, so this initializer is not typically needed.
     * <p></p>
     *
     * This initializer can however be useful while you're testing a release-signed version of your application, and you have *not* set the
     * debuggable flag in your AndroidManifest.xml. In this case, you can force the SDK into development mode to prevent sending
     * your test usage/data as production data. It's crucial, however, that prior to submission to Google Play that you ensure
     * you are no longer forcing development mode.
     */
    public static void start(Context context, InstallType installType, Environment environment) {
        if (context == null) {
            throw new IllegalArgumentException("mParticle failed to start: context is required.");
        }
        MParticle.getInstance(context.getApplicationContext(), installType, environment, null, null);
    }

    /**
     * Start the mParticle SDK and begin tracking a user session. This method must be called prior to {@link #getInstance()}.
     * This method requires that your API key and secret are contained in your XML configuration.
     * <p></p>
     *
     * @param context       Required reference to a Context object
     * @param installType   The InstallType parameter is used to determine if this is a new install or an upgrade. In
     * the case where the mParticle SDK is being added to an existing app with existing users, this
     * parameter prevents mParticle from categorizing all users as new users.
     * @param environment   Force the SDK into either Production or Development mode. See {@link MParticle.Environment}
     * for implications of each mode. The SDK automatically determines which mode it should be in depending
     * on the signing and the DEBUGGABLE flag of your application's AndroidManifest.xml, so this initializer is not typically needed.
     * @param apiKey Your application's mParticle key retrieved from app.mparticle.com/apps
     * @param apiSecret Your application's mParticle secret retrieved from app.mparticle.com/apps
     */
    public static void start(Context context, InstallType installType, Environment environment, String apiKey,
            String apiSecret) {
        if (context == null) {
            throw new IllegalArgumentException("mParticle failed to start: context is required.");
        }
        MParticle.getInstance(context.getApplicationContext(), installType, environment, apiKey, apiSecret);
    }

    /**
     * Initialize or return a thread-safe instance of the mParticle SDK, specifying the API credentials to use. If this
     * or any other {@link #getInstance()} has already been called in the application's lifecycle, the
     * API credentials will be ignored and the current instance will be returned.
     *
     * @param context the Activity that is creating the instance
     * @return An instance of the mParticle SDK configured with your API key
     * @param apiKey Your application's mParticle key retrieved from app.mparticle.com/apps
     * @param apiSecret Your application's mParticle secret retrieved from app.mparticle.com/apps
     *
     */
    private static MParticle getInstance(Context context, InstallType installType, Environment environment,
            String apiKey, String apiSecret) {
        if (instance == null) {
            synchronized (MParticle.class) {
                if (instance == null) {
                    if (!MPUtility.checkPermission(context, Manifest.permission.INTERNET)) {
                        Log.e(Constants.LOG_TAG, "mParticle requires android.permission.INTERNET permission");
                    }

                    ConfigManager configManager = new ConfigManager(context, environment, apiKey, apiSecret);
                    AppStateManager appStateManager = new AppStateManager(context);
                    appStateManager.setConfigManager(configManager);

                    instance = new MParticle();
                    instance.mAppContext = context;
                    instance.mConfigManager = configManager;
                    instance.mAppStateManager = appStateManager;
                    instance.mCommerce = new CommerceApi(context);
                    instance.mProductBags = new ProductBagApi(context);
                    instance.mMessageManager = new MessageManager(context, configManager, installType,
                            appStateManager);
                    instance.mPreferences = context.getSharedPreferences(Constants.PREFS_FILE,
                            Context.MODE_PRIVATE);
                    instance.mKitManager = new KitFrameworkWrapper(context, instance.mMessageManager, configManager,
                            appStateManager);
                    instance.mMessageManager.refreshConfiguration();

                    if (configManager.getLogUnhandledExceptions()) {
                        instance.enableUncaughtExceptionLogging();
                    }

                    //there are a number of settings that don't need to be enabled right away
                    //queue up a delayed init and let the start() call return ASAP.
                    instance.mMessageManager.initConfigDelayed();
                    appStateManager.init(Build.VERSION.SDK_INT);
                }
            }
        }
        return instance;
    }

    public KitFrameworkWrapper getKitManager() {
        return mKitManager;
    }

    public ConfigManager getConfigManager() {
        return mConfigManager;
    }

    public AppStateManager getAppStateManager() {
        return mAppStateManager;
    }

    /**
     * Retrieve an instance of the MParticle class. {@link #start(Context)} must
     * be called prior to this.
     *
     * @return An instance of the mParticle SDK configured with your API key
     */
    public static MParticle getInstance() {
        if (instance == null) {
            Log.e(Constants.LOG_TAG, "Failed to get MParticle instance, getInstance() called prior to start().");
            return null;
        }
        return getInstance(null, null, null, null, null);
    }

    /**
     *
     * Use this method for your own unit testing. Using a framework such as Mockito, or
     * by extending MParticle, use this method to set a mock of mParticle.
     *
     * @param instance
     */
    public static void setInstance(MParticle instance) {
        MParticle.instance = instance;
    }

    /**
     * Query the status of Android ID collection.
     *
     * By default, the SDK will collect <a href="http://developer.android.com/reference/android/provider/Settings.Secure.html#ANDROID_ID">Android Id</a> for the purpose
     * of anonymous analytics. If you're not using an mParticle integration that consumes Android ID, the value will be sent to the mParticle
     * servers and then immediately discarded. Use this API if you would like to additionally disable it from being collected entirely.
     *
     *
     * @return true if Android ID collection is disabled. (false by default)
        
     * @see MParticle#setAndroidIdDisabled(boolean)
     */
    public static boolean isAndroidIdDisabled() {
        return androidIdDisabled;
    }

    /**
     * Disable Android ID collection. This *must* be called before {@link MParticle#start(Context)}.
     *
     * By default, the SDK will collect <a href="http://developer.android.com/reference/android/provider/Settings.Secure.html#ANDROID_ID">Android Id</a> for the purpose
     * of anonymous analytics. If you're not using an mParticle integration that consumes Android ID, the value will be sent to the mParticle
     * servers and then immediately discarded. Use this API if you would like to additionally disable it from being collected entirely.
     *
     * @param disable true to disable collection (false by default)
     */
    public static void setAndroidIdDisabled(boolean disable) {
        androidIdDisabled = disable;
    }

    /**
     * Disable CPU and memory usage collection.
     *
     * @param disable
     */
    public static void setDevicePerformanceMetricsDisabled(boolean disable) {
        devicePerformanceMetricsDisabled = disable;
    }

    public static boolean isDevicePerformanceMetricsDisabled() {
        return devicePerformanceMetricsDisabled;
    }

    /**
     * Track that an Activity has started. Should only be called within the onStart method of your Activities,
     * and is only necessary for pre-API level 14 devices. Not necessary to use if your Activity extends an mParticle
     * Activity implementation.
     *
     * @see com.mparticle.activity.MPActivity
     * @see com.mparticle.activity.MPListActivity
     */
    public void activityStarted(Activity activity) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            if (mConfigManager.isEnabled()) {
                mAppStateManager.ensureActiveSession();
                mAppStateManager.onActivityStarted(activity);
            }
        }
    }

    /**
     * Track that an Activity has stopped. Should only be called within the onStop method of your Activities,
     * and is only necessary for pre-API level 14 devices. Not necessary to use if your Activity extends an mParticle
     * Activity implementation.
     *
     * @see com.mparticle.activity.MPActivity
     * @see com.mparticle.activity.MPListActivity
     */
    public void activityStopped(Activity activity) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            if (mConfigManager.isEnabled()) {
                mAppStateManager.ensureActiveSession();
                mAppStateManager.onActivityStopped(activity);
            }
        }
    }

    /**
     * Explicitly terminate the current user's session.
     */
    private void endSession() {
        if (mConfigManager.isEnabled()) {
            mAppStateManager.getSession().mLastEventTime = System.currentTimeMillis();
            mAppStateManager.endSession();
        }
    }

    boolean isSessionActive() {
        return mAppStateManager.getSession().isActive();
    }

    /**
     * Force upload all queued messages to the mParticle server.
     */
    public void upload() {
        mMessageManager.doUpload();
    }

    /**
     * Manually set the install referrer. This will replace any install referrer that was
     * automatically retrieved upon installation from Google Play.
     */
    public void setInstallReferrer(String referrer) {
        ReferrerReceiver.setInstallReferrer(mAppContext, referrer);
    }

    public String getInstallReferrer() {
        return mPreferences.getString(PrefKeys.INSTALL_REFERRER, null);
    }

    /**
     * Logs an event
     *
     * @param eventName the name of the event to be tracked (required not null)
     * @param eventType the type of the event to be tracked
     */
    public void logEvent(String eventName, EventType eventType) {
        logEvent(eventName, eventType, null, 0, null);
    }

    /**
     * Logs an event
     *
     * @param eventName the name of the event to be tracked (required not null)
     * @param eventType the type of the event to be tracked
     * @param category  the Google Analytics category with which to associate this event
     */
    public void logEvent(String eventName, EventType eventType, String category) {
        logEvent(eventName, eventType, null, 0, category);
    }

    /**
     * Logs an event
     *
     * @param eventName   the name of the event to be tracked  (required not null)
     * @param eventType   the type of the event to be tracked
     * @param eventLength the duration of the event in milliseconds
     */
    public void logEvent(String eventName, EventType eventType, long eventLength) {
        logEvent(eventName, eventType, null, eventLength);
    }

    /**
     * Log an event with data attributes
     *
     * @param eventName the name of the event to be tracked  (required not null)
     * @param eventType the type of the event to be tracked
     * @param eventInfo a Map of data attributes
     */
    public void logEvent(String eventName, EventType eventType, Map<String, String> eventInfo) {
        logEvent(eventName, eventType, eventInfo, 0);
    }

    /**
     * Log an event with data attributes
     *
     * @param eventName the name of the event to be tracked  (required not null)
     * @param eventType the type of the event to be tracked
     * @param eventInfo a Map of data attributes
     * @param category  the Google Analytics category with which to associate this event
     */
    public void logEvent(String eventName, EventType eventType, Map<String, String> eventInfo, String category) {
        logEvent(eventName, eventType, eventInfo, 0, category);
    }

    /**
     * Log an event with data attributes
     *
     * @param eventName   the name of the event to be tracked  (required not null)
     * @param eventType   the type of the event to be tracked
     * @param eventInfo   a Map of data attributes to associate with the event
     * @param eventLength the duration of the event in milliseconds
     */
    public void logEvent(String eventName, EventType eventType, Map<String, String> eventInfo, long eventLength) {
        logEvent(eventName, eventType, eventInfo, eventLength, null);
    }

    /**
     * Log an event with data attributes
     *
     * @param eventName   the name of the event to be tracked  (required not null)
     * @param eventType   the type of the event to be tracked
     * @param eventInfo   a Map of data attributes to associate with the event
     * @param eventLength the duration of the event in milliseconds
     * @param category    the Google Analytics category with which to associate this event
     */
    public void logEvent(String eventName, EventType eventType, Map<String, String> eventInfo, long eventLength,
            String category) {
        logEvent(new MPEvent.Builder(eventName, eventType).info(eventInfo).duration(eventLength).category(category)
                .build());
    }

    /**
     * Log an event with an {@link MPEvent} object
     *
     * @param event the event object to log
     */
    public void logEvent(MPEvent event) {
        if (mConfigManager.isEnabled() && checkEventLimit()) {
            mAppStateManager.ensureActiveSession();
            mMessageManager.logEvent(event, mAppStateManager.getCurrentActivityName());
            ConfigManager.log(LogLevel.DEBUG, "Logged event - \n", event.toString());
            mKitManager.logEvent(event);

        }
    }

    /**
     * Log an e-Commerce related event with a {@link CommerceEvent} object
     *
     * @param event the event to log
     *
     * @see CommerceEvent
     */
    public void logEvent(CommerceEvent event) {
        if (mConfigManager.isEnabled() && checkEventLimit()) {
            Cart cart = Cart.getInstance(mAppContext);
            if (event.getProductAction() != null) {
                List<Product> productList = event.getProducts();
                if (event.getProductAction().equalsIgnoreCase(Product.ADD_TO_CART)) {
                    if (productList != null) {
                        for (Product product : productList) {
                            cart.add(product, false);
                        }
                    }
                } else if (event.getProductAction().equalsIgnoreCase(Product.REMOVE_FROM_CART)) {
                    if (productList != null) {
                        for (Product product : productList) {
                            cart.remove(product, false);
                        }
                    }
                }
            }
            mAppStateManager.ensureActiveSession();
            mMessageManager.logEvent(event);
            ConfigManager.log(LogLevel.DEBUG, "Logged commerce event - \n", event.toString());
            mKitManager.logCommerceEvent(event);
        }
    }

    /**
     * Logs an increase in the lifetime value of a user. This will signify an increase
     * in the revenue assigned to this user for service providers that support revenue tracking.
     *
     * @param valueIncreased The currency value by which to increase the current user's LTV (required)
     * @param eventName      An event name to be associated with this increase in LTV (optional)
     * @param contextInfo    An MPProduct or any set of data to associate with this increase in LTV (optional)
     */
    public void logLtvIncrease(BigDecimal valueIncreased, String eventName, Map<String, String> contextInfo) {
        if (valueIncreased == null) {
            ConfigManager.log(LogLevel.ERROR, "ValueIncreased must not be null.");
            return;
        }
        if (contextInfo == null) {
            contextInfo = new HashMap<String, String>();
        }
        contextInfo.put(MessageKey.RESERVED_KEY_LTV, valueIncreased.toPlainString());
        contextInfo.put(Constants.MethodName.METHOD_NAME, Constants.MethodName.LOG_LTV);
        logEvent(eventName == null ? "Increase LTV" : eventName, EventType.Transaction, contextInfo);
    }

    /**
     * Logs a screen view event
     *
     * @param screenName the name of the screen to be tracked
     */
    public void logScreen(String screenName) {
        logScreen(screenName, null);
    }

    /**
     * Logs a screen view event
     *
     * @param screenName the name of the screen to be tracked
     * @param eventData  a Map of data attributes to associate with this screen view
     */
    public void logScreen(String screenName, Map<String, String> eventData) {
        logScreen(new MPEvent.Builder(screenName).info(eventData).build().setScreenEvent(true));
    }

    /**
     * Logs a screen view event
     *
     * @param screenEvent an event object, the name of the event will be used as the screen name
     */
    public void logScreen(MPEvent screenEvent) {
        screenEvent.setScreenEvent(true);
        if (MPUtility.isEmpty(screenEvent.getEventName())) {
            ConfigManager.log(LogLevel.ERROR, "screenName is required for logScreen");
            return;
        }
        if (screenEvent.getEventName().length() > Constants.LIMIT_EVENT_NAME) {
            ConfigManager.log(LogLevel.ERROR, "The screen name was too long. Discarding event.");
            return;
        }
        if (checkEventLimit()) {
            mAppStateManager.ensureActiveSession();
            if (mConfigManager.isEnabled()) {
                mMessageManager.logScreen(screenEvent, screenEvent.getNavigationDirection());

                if (null == screenEvent.getInfo()) {
                    ConfigManager.log(LogLevel.DEBUG, "Logged screen: ", screenEvent.toString());
                }

            }
            if (screenEvent.getNavigationDirection()) {
                mKitManager.logScreen(screenEvent);
            }
        }
    }

    /**
     * Leave a breadcrumb to be included with error and exception logging, as well as
     * with regular session events.
     *
     * @param breadcrumb
     */
    public void leaveBreadcrumb(String breadcrumb) {
        if (mConfigManager.isEnabled()) {
            if (MPUtility.isEmpty(breadcrumb)) {
                ConfigManager.log(LogLevel.ERROR, "breadcrumb is required for leaveBreadcrumb");
                return;
            }
            if (breadcrumb.length() > Constants.LIMIT_EVENT_NAME) {
                ConfigManager.log(LogLevel.ERROR, "The breadcrumb name was too long. Discarding event.");
                return;
            }
            mAppStateManager.ensureActiveSession();
            mMessageManager.logBreadcrumb(breadcrumb);
            ConfigManager.log(LogLevel.DEBUG, "Logged breadcrumb: " + breadcrumb);
            mKitManager.leaveBreadcrumb(breadcrumb);
        }
    }

    /**
     * Logs an error event
     *
     * @param message the name of the error event to be tracked
     */
    public void logError(String message) {
        logError(message, null);
    }

    /**
     * Logs an error event
     *
     * @param message   the name of the error event to be tracked
     * @param errorAttributes a Map of data attributes to associate with this error
     */
    public void logError(String message, Map<String, String> errorAttributes) {
        if (mConfigManager.isEnabled()) {
            if (MPUtility.isEmpty(message)) {
                ConfigManager.log(LogLevel.ERROR, "message is required for logErrorEvent");
                return;
            }
            mAppStateManager.ensureActiveSession();
            if (checkEventLimit()) {
                JSONObject eventDataJSON = MPUtility.enforceAttributeConstraints(errorAttributes);
                mMessageManager.logErrorEvent(message, null, eventDataJSON);
                ConfigManager.log(LogLevel.DEBUG,
                        "Logged error with message: " + (message == null ? "<none>" : message) + " with data: "
                                + (eventDataJSON == null ? "<none>" : eventDataJSON.toString()));
            }
            mKitManager.logError(message, errorAttributes);
        }
    }

    public void logNetworkPerformance(String url, long startTime, String method, long length, long bytesSent,
            long bytesReceived, String requestString, int responseCode) {
        if (mConfigManager.isEnabled()) {
            mAppStateManager.ensureActiveSession();
            if (checkEventLimit()) {
                mMessageManager.logNetworkPerformanceEvent(startTime, method, url, length, bytesSent, bytesReceived,
                        requestString);
            }
            mKitManager.logNetworkPerformance(url, startTime, method, length, bytesSent, bytesReceived,
                    requestString, responseCode);

        }
    }

    /**
     * Logs an Exception
     *
     * @param exception an Exception
     */
    public void logException(Exception exception) {
        logException(exception, null, null);
    }

    /**
     * Logs an Exception
     *
     * @param exception an Exception
     * @param eventData a Map of data attributes
     */
    public void logException(Exception exception, Map<String, String> eventData) {
        logException(exception, eventData, null);
    }

    /**
     * Query Kits to determine if this user installed and/or opened the app by way
     * of a deeplink.
     *
     * @param deepLinkListener Your deep link listener implementation. Use this to react to the result of the deep link query.
     */
    public void checkForDeepLink(DeepLinkListener deepLinkListener) {
        setDeepLinkListener(deepLinkListener);
        checkForDeepLink();
    }

    private void checkForDeepLink() {
        if (mDeepLinkListener != null) {
            mKitManager.checkForDeepLink();
        }
    }

    /**
     * Set the deep link listener. Call this to set the listener to null once you
     * have finished querying for deep links.
     *
     * @param deepLinkListener
     */
    public void setDeepLinkListener(DeepLinkListener deepLinkListener) {
        mDeepLinkListener = deepLinkListener;
    }

    /**
     * Retrieve the current deeplink listener
     *
     * @return
     */
    public DeepLinkListener getDeepLinkListener() {
        return mDeepLinkListener;
    }

    /**
     * Logs an Exception
     *
     * @param exception an Exception
     * @param eventData a Map of data attributes
     * @param message   the name of the error event to be tracked
     */
    public void logException(Exception exception, Map<String, String> eventData, String message) {
        if (mConfigManager.isEnabled()) {
            mAppStateManager.ensureActiveSession();
            if (checkEventLimit()) {
                JSONObject eventDataJSON = MPUtility.enforceAttributeConstraints(eventData);
                mMessageManager.logErrorEvent(message, exception, eventDataJSON);
                ConfigManager.log(LogLevel.DEBUG,
                        "Logged exception with message: " + (message == null ? "<none>" : message) + " with data: "
                                + (eventDataJSON == null ? "<none>" : eventDataJSON.toString())
                                + " with exception: " + (exception == null ? "<none>" : exception.getMessage()));
            }
            mKitManager.logException(exception, eventData, message);
        }
    }

    /**
     * Enables location tracking given a provider and update frequency criteria. The provider must
     * be available and the correct permissions must have been requested within your application's manifest XML file.
     *
     * @param provider    the provider key
     * @param minTime     the minimum time (in milliseconds) to trigger an update
     * @param minDistance the minimum distance (in meters) to trigger an update
     */
    public void enableLocationTracking(String provider, long minTime, long minDistance) {
        if (mConfigManager.isEnabled()) {
            try {
                LocationManager locationManager = (LocationManager) mAppContext
                        .getSystemService(Context.LOCATION_SERVICE);
                if (!locationManager.isProviderEnabled(provider)) {
                    ConfigManager.log(LogLevel.ERROR, "That requested location provider is not available");
                    return;
                }

                if (null == mLocationListener) {
                    mLocationListener = new MPLocationListener(this);
                } else {
                    // clear the location listener, so it can be added again
                    locationManager.removeUpdates(mLocationListener);
                }
                locationManager.requestLocationUpdates(provider, minTime, minDistance, mLocationListener);
                SharedPreferences.Editor editor = mPreferences.edit();
                editor.putString(PrefKeys.LOCATION_PROVIDER, provider).putLong(PrefKeys.LOCATION_MINTIME, minTime)
                        .putLong(PrefKeys.LOCATION_MINDISTANCE, minDistance).apply();

            } catch (SecurityException e) {
                ConfigManager.log(LogLevel.ERROR,
                        "The app must require the appropriate permissions to track location using this provider");
            }
        }
    }

    /**
     * Disables any mParticle location tracking that had been started
     */
    public void disableLocationTracking() {
        disableLocationTracking(true);
    }

    /**
     * Disables any mParticle location tracking that had been started
     */
    private void disableLocationTracking(boolean userTriggered) {
        if (mLocationListener != null) {
            try {
                LocationManager locationManager = (LocationManager) mAppContext
                        .getSystemService(Context.LOCATION_SERVICE);

                if (MPUtility.checkPermission(mAppContext, Manifest.permission.ACCESS_FINE_LOCATION)
                        || MPUtility.checkPermission(mAppContext, Manifest.permission.ACCESS_COARSE_LOCATION)) {
                    locationManager.removeUpdates(mLocationListener);
                }
                mLocationListener = null;
                if (userTriggered) {
                    SharedPreferences.Editor editor = mPreferences.edit();
                    editor.remove(PrefKeys.LOCATION_PROVIDER).remove(PrefKeys.LOCATION_MINTIME)
                            .remove(PrefKeys.LOCATION_MINDISTANCE).apply();
                }
            } catch (Exception e) {

            }
        }
    }

    /**
     * Set the current location of the active session.
     *
     * @param location
     */
    public void setLocation(Location location) {
        mMessageManager.setLocation(location);
        mKitManager.setLocation(location);

    }

    /**
     * Set a single <i>session</i> attribute. The attribute will combined with any existing session attributes.
     *
     * @param key   the attribute key
     * @param value the attribute value. This value will be converted to its String representation as dictated by its <code>toString()</code> method.
     */
    public void setSessionAttribute(String key, Object value) {
        if (key == null) {
            ConfigManager.log(LogLevel.WARNING, "setSessionAttribute called with null key. Ignoring...");
            return;
        }
        if (value != null) {
            value = value.toString();
        }
        if (mConfigManager.isEnabled()) {
            mAppStateManager.ensureActiveSession();
            ConfigManager.log(LogLevel.DEBUG, "Set session attribute: " + key + "=" + value);

            if (MPUtility.setCheckedAttribute(mAppStateManager.getSession().mSessionAttributes, key, value, true,
                    false)) {
                mMessageManager.setSessionAttributes();
            }
        }
    }

    /**
     * Increment a single <i>session</i> attribute. If the attribute does not exist, it will be added as a new attribute.
     *
     * @param key   the attribute key
     * @param value the attribute value
     */
    public void incrementSessionAttribute(String key, int value) {
        if (key == null) {
            ConfigManager.log(LogLevel.WARNING, "incrementSessionAttribute called with null key. Ignoring...");
            return;
        }
        if (mConfigManager.isEnabled()) {
            mAppStateManager.ensureActiveSession();
            ConfigManager.log(LogLevel.DEBUG, "Incrementing session attribute: " + key + "=" + value);

            if (MPUtility.setCheckedAttribute(mAppStateManager.getSession().mSessionAttributes, key, value, true,
                    true)) {
                mMessageManager.setSessionAttributes();
            }
        }
    }

    /**
     * Signal to the mParticle platform that the current user has logged out. As of 1.6.x of
     * the SDK, this function is only used as a signaling mechanism to server providers
     * that support an explicit logout. As of 1.6.x, after calling this method, all user attributes
     * and identities will stay the same.
     */
    public void logout() {
        if (mConfigManager.isEnabled()) {
            mAppStateManager.ensureActiveSession();
            ConfigManager.log(LogLevel.DEBUG, "Logging out.");
            mMessageManager.logProfileAction(Constants.ProfileActions.LOGOUT);
        }
        mKitManager.logout();

    }

    /**
     * Set a single <i>user</i> attribute. The attribute will be combined with any existing user attributes.
     *
     * @param key   the attribute key
     * @param value the attribute value. This value will be converted to its String representation as dictated by its <code>toString()</code> method.
     */
    public boolean setUserAttribute(String key, Object value) {
        if (MPUtility.isEmpty(key)) {
            ConfigManager.log(LogLevel.WARNING, "setUserAttribute called with null key. This is a no-op.");
            return false;
        }
        if (key.length() > Constants.LIMIT_ATTR_NAME) {
            ConfigManager.log(LogLevel.WARNING, "User attribute keys cannot be longer than "
                    + Constants.LIMIT_ATTR_NAME + " characters, attribute not set: " + key);
            return false;
        }

        if (value != null && value instanceof List) {
            List<Object> values = (List<Object>) value;
            if (values.size() > Constants.LIMIT_USER_ATTR_LIST_LENGTH) {
                ConfigManager.log(LogLevel.WARNING, "setUserAttribute called with list longer than "
                        + Constants.LIMIT_USER_ATTR_LIST_LENGTH + " elements, list not set.");
                return false;
            }
            List<String> clonedList = new ArrayList<String>();
            try {
                for (int i = 0; i < values.size(); i++) {
                    if (values.get(i).toString().length() > Constants.LIMIT_USER_ATTR_LIST_ITEM_LENGTH) {
                        ConfigManager.log(LogLevel.WARNING,
                                "setUserAttribute called with list containing element longer than "
                                        + Constants.LIMIT_USER_ATTR_LIST_ITEM_LENGTH
                                        + " characters, dropping entire list.");
                        return false;
                    } else {
                        clonedList.add(values.get(i).toString());
                    }
                }
                ConfigManager.log(LogLevel.DEBUG,
                        "Set user attribute list: " + key + " with values: " + values.toString());
                mMessageManager.setUserAttribute(key, clonedList);
                mKitManager.setUserAttributeList(key, clonedList);
            } catch (Exception e) {
                ConfigManager.log(LogLevel.DEBUG, "Error while setting attribute list: " + e.toString());
                return false;
            }
        } else {
            String stringValue = null;
            if (value != null) {
                stringValue = value.toString();
                if (stringValue.length() > Constants.LIMIT_USER_ATTR_VALUE) {
                    ConfigManager.log(LogLevel.WARNING, "setUserAttribute called with string-value longer than "
                            + Constants.LIMIT_USER_ATTR_VALUE + " characters. Attribute not set.");
                    return false;
                }
                ConfigManager.log(LogLevel.DEBUG, "Set user attribute: " + key + " with value: " + stringValue);
            } else {
                ConfigManager.log(LogLevel.DEBUG, "Set user tag: " + key);
            }
            mMessageManager.setUserAttribute(key, stringValue);
            mKitManager.setUserAttribute(key, stringValue);
        }
        return true;
    }

    /**
     *
     * @param key
     * @param attributeList
     * @return
     */
    public boolean setUserAttributeList(String key, List<String> attributeList) {
        if (attributeList == null) {
            ConfigManager.log(LogLevel.WARNING, "setUserAttributeList called with null list, this is a no-op.");
            return false;
        }
        return setUserAttribute(key, attributeList);
    }

    /**
     * Increment a single <i>user</i> attribute. If the attribute does not already exist, a new one will be created.
     *
     * If the value of the attribute cannot be parsed as an integer, this method is a no-op.
     *
     * @param key   the attribute key
     * @param value the attribute value
     */
    public boolean incrementUserAttribute(String key, int value) {
        if (key == null) {
            ConfigManager.log(LogLevel.WARNING, "incrementUserAttribute called with null key. Ignoring...");
            return false;
        }
        ConfigManager.log(LogLevel.DEBUG, "Incrementing user attribute: " + key + " with value " + value);
        mMessageManager.incrementUserAttribute(key, value);
        return true;
    }

    /**
     * Remove a <i>user</i> attribute - this applies both to lists and single-value attributes
     *
     * @param key the key of the attribute
     */
    public boolean removeUserAttribute(String key) {
        if (MPUtility.isEmpty(key)) {
            ConfigManager.log(LogLevel.DEBUG, "removeUserAttribute called with empty key.");
            return false;
        }

        ConfigManager.log(LogLevel.DEBUG, "Removing user attribute: " + key);
        mMessageManager.removeUserAttribute(key);
        mKitManager.removeUserAttribute(key);
        return true;
    }

    /**
     * Set a single user tag, it will be combined with any existing tags.
     *
     * @param tag a tag assigned to a user
     */
    public boolean setUserTag(String tag) {
        return setUserAttribute(tag, null);
    }

    /**
     * Remove a user tag. This is the same as calling {@link MParticle#removeUserAttribute(String)}.
     *
     * @param tag a tag that was previously added
     */
    public boolean removeUserTag(String tag) {
        return removeUserAttribute(tag);
    }

    /**
     * Set the current user's identity
     *
     * @param id
     * @param identityType
     */
    public synchronized void setUserIdentity(String id, IdentityType identityType) {
        if (identityType != null) {
            if (id == null) {
                ConfigManager.log(LogLevel.DEBUG, "Removing User Identity type: " + identityType.name());
            } else {
                ConfigManager.log(LogLevel.DEBUG, "Setting User Identity: " + id);
            }

            if (!MPUtility.isEmpty(id) && id.length() > Constants.LIMIT_ATTR_VALUE) {
                ConfigManager.log(LogLevel.WARNING,
                        "User Identity value length exceeds limit. Will not set id: " + id);
                return;
            }

            JSONArray userIdentities = mMessageManager.getUserIdentityJson();
            JSONObject oldIdentity = null;
            try {
                int index = -1;
                for (int i = 0; i < userIdentities.length(); i++) {
                    if (identityType.value == userIdentities.getJSONObject(i).optInt(MessageKey.IDENTITY_NAME)) {
                        oldIdentity = userIdentities.getJSONObject(i);
                        index = i;
                        break;
                    }
                }

                if (id == null && oldIdentity == null) {
                    ConfigManager.log(LogLevel.DEBUG,
                            "Attempted to remove ID type that didn't exist: " + identityType.name());
                }

                JSONObject newObject = null;
                if (id != null) {
                    newObject = new JSONObject();
                    newObject.put(MessageKey.IDENTITY_NAME, identityType.value);
                    newObject.put(MessageKey.IDENTITY_VALUE, id);
                    if (oldIdentity != null) {
                        newObject.put(MessageKey.IDENTITY_DATE_FIRST_SEEN, oldIdentity
                                .optLong(MessageKey.IDENTITY_DATE_FIRST_SEEN, System.currentTimeMillis()));
                        newObject.put(MessageKey.IDENTITY_FIRST_SEEN, false);
                        userIdentities.put(index, newObject);
                    } else {
                        newObject.put(MessageKey.IDENTITY_DATE_FIRST_SEEN, System.currentTimeMillis());
                        newObject.put(MessageKey.IDENTITY_FIRST_SEEN, true);
                        userIdentities.put(newObject);
                    }
                } else {

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                        KitKatHelper.remove(userIdentities, index);
                    } else {
                        JSONArray newIdentities = new JSONArray();
                        for (int i = 0; i < userIdentities.length(); i++) {
                            if (i != index) {
                                newIdentities.put(userIdentities.get(i));
                            }
                        }
                        userIdentities = newIdentities;
                    }
                }
                mMessageManager.logUserIdentityChangeMessage(newObject, oldIdentity, userIdentities);

                if (id == null) {
                    getKitManager().removeUserIdentity(identityType);
                } else {
                    getKitManager().setUserIdentity(id, identityType);
                }
            } catch (JSONException e) {
                ConfigManager.log(LogLevel.ERROR, "Error setting identity: " + id);
                return;
            }

        }
    }

    public Map<IdentityType, String> getUserIdentities() {
        JSONArray identities = mMessageManager.getUserIdentityJson();
        Map<IdentityType, String> identityTypeStringMap = new HashMap<IdentityType, String>(identities.length());

        for (int i = 0; i < identities.length(); i++) {
            try {
                JSONObject identity = identities.getJSONObject(i);
                identityTypeStringMap.put(IdentityType.parseInt(identity.getInt(MessageKey.IDENTITY_NAME)),
                        identity.getString(MessageKey.IDENTITY_VALUE));
            } catch (JSONException jse) {

            }
        }

        return identityTypeStringMap;
    }

    /**
     * Remove an identity matching this id
     * <p></p>
     * Note: this will only remove the *first* matching id
     *
     * @param id the id to remove
     */
    public synchronized void removeUserIdentity(String id) {
        JSONArray userIdentities = mMessageManager.getUserIdentityJson();
        if (id != null && id.length() > 0) {
            try {
                IdentityType identityType = null;
                for (int i = 0; i < userIdentities.length(); i++) {
                    if (id.equals(userIdentities.getJSONObject(i).getString(MessageKey.IDENTITY_VALUE))) {
                        try {
                            identityType = IdentityType
                                    .parseInt(userIdentities.getJSONObject(i).getInt(MessageKey.IDENTITY_NAME));
                        } catch (Exception e) {

                        }
                        break;
                    }
                }
                if (identityType != null) {
                    setUserIdentity(null, identityType);
                }
            } catch (JSONException jse) {
                ConfigManager.log(MParticle.LogLevel.WARNING, "Error removing identity: " + id);
            }
        }
    }

    /**
     * Get the current opt-out status for the application.
     *
     * @return the opt-out status
     */
    public Boolean getOptOut() {
        return mConfigManager.getOptedOut();
    }

    /**
     * Control the opt-in/opt-out status for the application.
     *
     * @param optOutStatus set to <code>true</code> to opt out of event tracking
     */
    public void setOptOut(Boolean optOutStatus) {
        if (optOutStatus != null) {
            if (optOutStatus != mConfigManager.getOptedOut()) {
                if (!optOutStatus) {
                    mAppStateManager.ensureActiveSession();
                }
                mMessageManager.optOut(System.currentTimeMillis(), optOutStatus);
                if (optOutStatus && isSessionActive()) {
                    endSession();
                }

                mConfigManager.setOptOut(optOutStatus);

                ConfigManager.log(LogLevel.DEBUG, "Set opt-out: " + optOutStatus);
            }
            mKitManager.setOptOut(optOutStatus);
        }
    }

    /**
     * Retrieve a URL to be loaded within a {@link WebView} to show the user a survey
     * or feedback form.
     *
     * @param kitId The ID of the desired survey/feedback service.
     * @return a fully-formed URI, or null if no URL exists for the given ID.
     *
     * @see MParticle.ServiceProviders
     */
    public Uri getSurveyUrl(final int kitId) {
        return mKitManager.getSurveyUrl(kitId, getUserAttributes(), getUserAttributeLists());
    }

    /**
     *
     * This method is deprecated. Use <code>start()</code> or XML configuration if you need to customize the environment.
     *
     * @see #start(Context, MParticle.InstallType, MParticle.Environment)
     *
     * @param environment
     */
    @Deprecated
    public void setEnvironment(Environment environment) {
        Log.w(Constants.LOG_TAG,
                "setEnvironment is deprecated and is a no-op. Use start() or XML configuration if you must customize environment.");
    }

    /**
     * Get the current Environment that the SDK has interpreted. Will never return AutoDetect.
     *
     * @return the current environment, either production or development
     */
    public Environment getEnvironment() {
        return ConfigManager.getEnvironment();
    }

    /**
     * Set the upload interval period to control how frequently uploads occur.
     *
     * @param uploadInterval the number of seconds between uploads
     */
    public void setUploadInterval(int uploadInterval) {
        mConfigManager.setUploadInterval(uploadInterval);
    }

    /**
     * Enable mParticle exception handling to automatically log events on uncaught exceptions
     */
    public void enableUncaughtExceptionLogging() {
        mConfigManager.enableUncaughtExceptionLogging(true);
    }

    /**
     * Disables mParticle exception handling and restores the original UncaughtExceptionHandler
     */
    public void disableUncaughtExceptionLogging() {
        mConfigManager.disableUncaughtExceptionLogging(true);
    }

    /**
     * This method checks the event count is below the limit and increments the event count. A
     * warning is logged if the limit has been reached.
     *
     * @return true if event count is below limit
     */
    private Boolean checkEventLimit() {
        return mAppStateManager.getSession().checkEventLimit();
    }

    /**
     * Retrieves the current setting of automatic screen tracking.
     *
     * @return The current setting of automatic screen tracking.
     */

    public Boolean isAutoTrackingEnabled() {
        return mConfigManager.isAutoTrackingEnabled();
    }

    /**
     * Retrieves the current session timeout setting in seconds
     *
     * @return The current session timeout setting in seconds
     */
    public int getSessionTimeout() {
        return mConfigManager.getSessionTimeout() / 1000;
    }

    /**
     * Set the user session timeout interval.
     * <p></p>
     * A session has ended once the application has been in the background for more than this timeout
     *
     * @param sessionTimeout Session timeout in seconds
     */
    public void setSessionTimeout(int sessionTimeout) {
        mConfigManager.setSessionTimeout(sessionTimeout);
    }

    public void getUserSegments(long timeout, String endpointId, SegmentListener listener) {
        if (mMessageManager != null && mMessageManager.mUploadHandler != null) {
            mMessageManager.mUploadHandler.fetchSegments(timeout, endpointId, listener);
        }
    }

    /**
     * Instrument a WebView so that the mParticle Javascript SDK may be used within the given website or web app
     *
     * @param webView
     */
    @SuppressLint("AddJavascriptInterface")
    public void registerWebView(WebView webView) {
        if (webView != null) {
            webView.addJavascriptInterface(new MParticleJSInterface(), MParticleJSInterface.INTERFACE_NAME);
        }
    }

    /**
     * The log level is used to moderate the amount of messages that are printed
     * by the SDK to the console. Note that while the SDK is in the Production,
     * <i>no log messages will be printed</i>.
     *
     * @see MParticle.LogLevel
     *
     * @param level
     */
    public void setLogLevel(LogLevel level) {
        if (level != null) {
            mConfigManager.setLogLevel(level);
        }
    }

    /**
     * Entry point to the Messaging APIs
     *
     * @return a helper object that allows for interaction with the Messaging APIs
     */
    public MPMessagingAPI Messaging() {
        if (mMessaging == null) {
            mMessaging = new MPMessagingAPI(mAppContext);
        }
        return mMessaging;
    }

    /**
     * Retrieve an instance of the {@link CommerceApi} helper class, used to access the {@link Cart} and as a helper class to log {@link CommerceEvent} events
     * with the {@link Product} objects currently in the Cart.
     *
     * @return returns a global CommerceApi instance.
     */
    public CommerceApi Commerce() {
        return mCommerce;
    }

    /**
     * Retrieve the global {@link ProductBagApi} instance. Use this API to associate {@link com.mparticle.commerce.ProductBag} objects the user.
     *
     * @return a global ProductBagApi instance
     *
     * @see ProductBagApi
     */
    public ProductBagApi ProductBags() {
        return mProductBags;
    }

    /**
     * Entry point to the Media APIs
     *
     * @return a helper object that allows for interaction with the Media APIs
     */
    public MPMediaAPI Media() {
        if (mMedia == null) {
            mMedia = new MPMediaAPI(mAppContext, new MediaCallbacks() {
                @Override
                public void onAudioPlaying() {
                    mAppStateManager.ensureActiveSession();
                }

                @Override
                public void onAudioStopped() {
                    try {
                        mAppStateManager.getSession().mLastEventTime = System.currentTimeMillis();
                    } catch (Exception e) {

                    }
                }
            });
        }
        return mMedia;
    }

    /**
     * Detect whether the given service provider is active. Use this method
     * only when you need to make direct calls to an embedded SDK.
     *
     * You can also register a {@link android.content.BroadcastReceiver} with an {@link android.content.IntentFilter}, using an action of
     * {@link MParticle.ServiceProviders#BROADCAST_ACTIVE} or {@link MParticle.ServiceProviders#BROADCAST_DISABLED}
     * concatenated with the service provider ID:
     *
     * <pre>
     * {@code
     * Context.registerReceiver(yourReceiver, new IntentFilter(MParticle.ServiceProviders.BROADCAST_ACTIVE + MParticle.ServiceProviders.APPBOY));}
     * </pre>
     *
     * @deprecated
     *
     * @param serviceProviderId
     * @return True if you can safely make direct calls to the given service provider.
     *
     * @see MParticle.ServiceProviders
     */
    public boolean isProviderActive(int serviceProviderId) {
        return isKitActive(serviceProviderId);
    }

    /**
     * Detect whether the given service provider kit is active. Use this method
     * only when you need to make direct calls to an embedded SDK.
     *
     * You can also register a {@link android.content.BroadcastReceiver} with an {@link android.content.IntentFilter}, using an action of
     * {@link MParticle.ServiceProviders#BROADCAST_ACTIVE} or {@link MParticle.ServiceProviders#BROADCAST_DISABLED}
     * concatenated with the service provider ID:
     *
     * <pre>
     * {@code
     * Context.registerReceiver(yourReceiver, new IntentFilter(MParticle.ServiceProviders.BROADCAST_ACTIVE + MParticle.ServiceProviders.APPBOY));}
     * </pre>
     *
     * @param serviceProviderId
     * @return True if you can safely make direct calls to the given service provider.
     *
     * @see MParticle.ServiceProviders
     */
    public boolean isKitActive(int serviceProviderId) {
        return mKitManager.isKitActive(serviceProviderId);
    }

    /**
     * Retrieve the underlying object for the given Kit Id for direct calls.
     *
     * @param kitId
     * @return
     */
    public Object getKitInstance(int kitId) {
        return mKitManager.getKitInstance(kitId);
    }

    void saveGcmMessage(MPCloudNotificationMessage cloudMessage, String appState) {
        mMessageManager.saveGcmMessage(cloudMessage, appState);
    }

    void saveGcmMessage(ProviderCloudMessage cloudMessage, String appState) {
        mMessageManager.saveGcmMessage(cloudMessage, appState);
    }

    public void logPushRegistration(String instanceId, String senderId) {
        mAppStateManager.ensureActiveSession();
        PushRegistrationHelper.PushRegistration registration = new PushRegistrationHelper.PushRegistration();
        registration.instanceId = instanceId;
        registration.senderId = senderId;
        PushRegistrationHelper.setInstanceId(mAppContext, registration);
        mMessageManager.setPushRegistrationId(instanceId, true);
        mKitManager.onPushRegistration(instanceId, senderId);
    }

    void logNotification(MPCloudNotificationMessage cloudMessage, CloudAction action, boolean startSession,
            String appState, int behavior) {
        if (mConfigManager.isEnabled()) {
            if (startSession) {
                mAppStateManager.ensureActiveSession();
            }
            mMessageManager.logNotification(cloudMessage.getId(), cloudMessage.getRedactedJsonPayload().toString(),
                    action, appState, behavior);
        }
    }

    void logNotification(ProviderCloudMessage cloudMessage, boolean startSession, String appState) {
        if (mConfigManager.isEnabled()) {
            if (startSession) {
                mAppStateManager.ensureActiveSession();
            }
            mMessageManager.logNotification(cloudMessage, appState);
        }
    }

    void refreshConfiguration() {
        ConfigManager.log(LogLevel.DEBUG, "Refreshing configuration...");
        mMessageManager.refreshConfiguration();
    }

    /**
     * Event type to use when logging events.
     *
     * @see #logEvent(String, MParticle.EventType)
     */
    public enum EventType {
        Unknown, Navigation, Location, Search, Transaction, UserContent, UserPreference, Social, Other;

        public String toString() {
            return name();
        }
    }

    /**
     * To be used when initializing MParticle
     *
     * @see #start(Context, MParticle.InstallType)
     */
    public enum InstallType {
        /**
         * This is the default value. Using this value will rely on the mParticle SDK to differentiate a new install vs. an upgrade
         */
        AutoDetect,
        /**
         * In the case where your app has never seen this user before.
         */
        KnownInstall,
        /**
         * In the case where you app has seen this user before
         */
        KnownUpgrade;

        public String toString() {
            return name();
        }
    }

    /**
     * Identity type to use when setting the user identity.
     *
     * @see #setUserIdentity(String, MParticle.IdentityType)
     */

    public enum IdentityType {
        Other(0), CustomerId(1), Facebook(2), Twitter(3), Google(4), Microsoft(5), Yahoo(6), Email(7), Alias(
                8), FacebookCustomAudienceId(9);

        private final int value;

        IdentityType(int value) {
            this.value = value;
        }

        public static IdentityType parseInt(int val) {
            switch (val) {
            case 1:
                return CustomerId;
            case 2:
                return Facebook;
            case 3:
                return Twitter;
            case 4:
                return Google;
            case 5:
                return Microsoft;
            case 6:
                return Yahoo;
            case 7:
                return Email;
            case 8:
                return Alias;
            case 9:
                return FacebookCustomAudienceId;
            default:
                return Other;

            }
        }

        public int getValue() {
            return value;
        }

    }

    public Map<String, String> getUserAttributes() {
        return mMessageManager.getUserAttributes(null);
    }

    public Map<String, List<String>> getUserAttributeLists() {
        return mMessageManager.getUserAttributeLists();
    }

    public Map<String, Object> getAllUserAttributes() {
        return mMessageManager.getAllUserAttributes(null);
    }

    public void getAllUserAttributes(UserAttributeListener listener) {
        mMessageManager.getAllUserAttributes(listener);
    }

    /**
     * The Environment in which the SDK and hosting app are running. The SDK
     * automatically detects the Environment based on the <code>DEBUGGABLE</code> flag of your application. The <code>DEBUGGABLE</code>  flag of your
     * application will be <code>TRUE</code> when signing with a debug certificate during development, or if you have explicitly set your
     * application to debug within your AndroidManifest.xml.
     *
     * @see MParticle#start(Context, InstallType, Environment)
     * to override this behavior.
     *
     */
    public enum Environment {
        /**
         * AutoDetect mode (default). In this mode, the SDK will automatically configure itself based on the signing configuration
         * and the <code>DEBUGGABLE</code> flag of your application.
         */
        AutoDetect(0),
        /**
         * Development mode. In this mode, all data from the SDK will be treated as development data, and will be siloed from your
         * production data. Additionally, the SDK will more aggressively upload data to the mParticle platform, to aide in a faster implementation.
         */
        Development(1),
        /**
         * Production mode. In this mode, all data from the SDK will be treated as production data, and will be forwarded to all configured
         * integrations for your application. The SDK will honor the configured upload interval.
         *
         * @see #setUploadInterval(int)
         */
        Production(2);
        private final int value;

        public int getValue() {
            return value;
        }

        Environment(int value) {
            this.value = value;
        }
    }

    /**
     * Enumeration used to moderate the amount of messages that are printed
     * by the SDK to the console. Note that while the SDK is in the Production,
     * <i>no log messages will be printed</i>.
     * <p></p>
     * The default is WARNING, which means only ERROR and WARNING level messages will appear in the console, viewable by logcat or another utility.
     *
     * @see #setLogLevel(MParticle.LogLevel)
     */
    public enum LogLevel {
        /**
         * Disable logging completely.
         */
        NONE,
        /**
         * Used for critical issues with the SDK or its configuration.
         */
        ERROR,
        /**
         * (default) Used to warn developers of potentially unintended consequences of their use of the SDK.
         */
        WARNING,
        /**
         * Used to communicate the internal state and processes of the SDK.
         */
        DEBUG
    }

    void logUnhandledError(Throwable t) {
        if (mConfigManager.isEnabled()) {
            mMessageManager.logErrorEvent(t != null ? t.getMessage() : null, t, null, false);
            //we know that the app is about to crash and therefore exit
            mAppStateManager.logStateTransition(Constants.StateTransitionType.STATE_TRANS_EXIT,
                    mAppStateManager.getCurrentActivityName());
            mAppStateManager.getSession().mLastEventTime = System.currentTimeMillis();
            mAppStateManager.endSession();
        }
    }

    /**
     * This interface defines constants that can be used to interact with specific 3rd-party services.
     *
     * @see #getSurveyUrl(int)
     */
    public interface ServiceProviders {
        int URBAN_AIRSHIP = 25;
        int APPBOY = 28;
        int TUNE = 32;
        int KOCHAVA = 37;
        int COMSCORE = 39;
        int KAHUNA = 56;
        int FORESEE_ID = 64;
        int ADJUST = 68;
        int BRANCH_METRICS = 80;
        int FLURRY = 83;
        int LOCALYTICS = 84;
        int CRITTERCISM = 86;
        int WOOTRIC = 90;
        int APPSFLYER = 92;
        int APPTENTIVE = 97;
        int APPTIMIZE = 105;
        int BUTTON = 1022;
        int LEANPLUM = 98;
        String BROADCAST_ACTIVE = "MPARTICLE_SERVICE_PROVIDER_ACTIVE_";
        String BROADCAST_DISABLED = "MPARTICLE_SERVICE_PROVIDER_DISABLED_";
    }

    /**
     * This interface defines a series of constants that can be used to specify certain characteristics of a user. There are many 3rd party services
     * that support, for example, specifying a gender of a user. The mParticle platform will look for these constants within the user attributes that
     * you have set for a given user, and forward any attributes to the services that support them.
     *
     * @see #setUserAttribute(String, Object)
     */
    public interface UserAttributes {
        /**
         * A special attribute string to specify the mobile number of the consumer's device
         */
        String MOBILE_NUMBER = "$Mobile";
        /**
         * A special attribute string to specify the user's gender.
         */
        String GENDER = "$Gender";
        /**
         * A special attribute string to specify the user's age.
         */
        String AGE = "$Age";
        /**
         * A special attribute string to specify the user's country.
         */
        String COUNTRY = "$Country";
        /**
         * A special attribute string to specify the user's zip code.
         */
        String ZIPCODE = "$Zip";
        /**
         * A special attribute string to specify the user's city.
         */
        String CITY = "$City";
        /**
         * A special attribute string to specify the user's state or region.
         */
        String STATE = "$State";
        /**
         * A special attribute string to specify the user's street address and apartment number.
         */
        String ADDRESS = "$Address";
        /**
         * A special attribute string to specify the user's first name.
         */
        String FIRSTNAME = "$FirstName";
        /**
         * A special attribute string to specify the user's last name.
         */
        String LASTNAME = "$LastName";
    }
}