Android Open Source - piwik-sdk-android Tracker






From Project

Back to project page piwik-sdk-android.

License

The source code is released under:

Copyright 2014 Piwik team All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redi...

If you think the Android project piwik-sdk-android 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

/*
 * Android SDK for Piwik/* www  . jav a2  s  . c o  m*/
 *
 * @link https://github.com/piwik/piwik-android-sdk
 * @license https://github.com/piwik/piwik-sdk-android/blob/master/LICENSE BSD-3 Clause
 */

package org.piwik.sdk;

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;

import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.*;

/**
 * Main tracking class
 * This class is not Thread safe and should be externally synchronized or multiple instances used.
 */
public class Tracker implements Dispatchable<Integer> {

    // Piwik default parameter values
    private static final String defaultTrueValue = "1";
    private static final String defaultRecordValue = defaultTrueValue;
    private static final String defaultAPIVersionValue = "1";

    // Default dispatcher values
    private static final int piwikDefaultSessionTimeout = 30 * 60;
    private static final int piwikDefaultDispatchTimer = 120;
    private static final int piwikHTTPRequestTimeout = 5;
    private static final int piwikQueryDefaultCapacity = 14;

    /**
     * The ID of the website we're tracking a visit/action for.
     */
    private int siteId;

    /**
     * Tracking HTTP API endpoint, for example, http://your-piwik-domain.tld/piwik.php
     */
    private URL apiUrl;

    /**
     * Defines the User ID for this request.
     * User ID is any non empty unique string identifying the user (such as an email address or a username).
     * To access this value, users must be logged-in in your system so you can
     * fetch this user ID from your system, and pass it to Piwik.
     * <p/>
     * When specified, the User ID will be "enforced".
     * This means that if there is no recent visit with this User ID, a new one will be created.
     * If a visit is found in the last 30 minutes with your specified User ID,
     * then the new action will be recorded to this existing visit.
     */
    private String userId;

    /**
     * The unique visitor ID, must be a 16 characters hexadecimal string.
     * Every unique visitor must be assigned a different ID and this ID must not change after it is assigned.
     * If this value is not set Piwik will still track visits, but the unique visitors metric might be less accurate.
     */
    private String visitorId;

    /**
     * 32 character authorization key used to authenticate the API request.
     * Should be equals `token_auth` value of the Super User
     * or a user with admin access to the website visits are being tracked for.
     */
    private String authToken;


    private Piwik piwik;
    private String lastEvent;
    private boolean isDispatching = false;
    private int dispatchInterval = piwikDefaultDispatchTimer;
    private DispatchingHandler dispatchingHandler;

    private String applicationDomain;
    private static String realScreenResolution;
    private static String userAgent;
    private static String userLanguage;
    private static String userCountry;
    private long sessionTimeoutMillis;
    private long sessionStartedMillis;

    private final ArrayList<String> queue = new ArrayList<String>(20);
    private final HashMap<String, String> queryParams = new HashMap<String, String>(piwikQueryDefaultCapacity);
    private final HashMap<String, CustomVariables> customVariables = new HashMap<String, CustomVariables>(2);

    protected static final String LOGGER_TAG = Piwik.class.getName().toUpperCase();
    private final Random randomObject = new Random(new Date().getTime());


    private Tracker(String url, int siteId) throws MalformedURLException {
        setAPIUrl(url);
        setNewSession();
        setSessionTimeout(piwikDefaultSessionTimeout);
        visitorId = getRandomVisitorId();
        reportUncaughtExceptions(true);
        this.siteId = siteId;
    }

    /**
     * Use Piwik.newTracker() method to create new trackers
     *
     * @param url       (required) Tracking HTTP API endpoint, for example, http://your-piwik-domain.tld/piwik.php
     * @param siteId    (required) id of site
     * @param authToken (optional) could be null
     * @param piwik     piwik object used to gain access to application params such as name, resolution or lang
     * @throws MalformedURLException
     */
    protected Tracker(String url, int siteId, String authToken, Piwik piwik) throws MalformedURLException {
        this(url, siteId);
        this.authToken = authToken;
        this.piwik = piwik;
    }

    /**
     * Processes all queued events in background thread
     *
     * @return true if there are any queued events and opt out is inactive
     */
    public boolean dispatch() {
        if (!piwik.isOptOut() && queue.size() > 0) {

            ArrayList<String> events = new ArrayList<String>(queue);
            queue.clear();

            TrackerBulkURLProcessor worker =
                    new TrackerBulkURLProcessor(this, piwikHTTPRequestTimeout, piwik.isDryRun());
            worker.processBulkURLs(apiUrl, events, authToken);

            return true;
        }
        return false;
    }

    /**
     * Does dispatch immediately if dispatchInterval == 0
     * if dispatchInterval is greater than zero auto dispatching will be launched
     */
    private void tryDispatch() {
        if (dispatchInterval == 0) {
            dispatch();
        } else if (dispatchInterval > 0) {
            ensureAutoDispatching();
        }
    }

    /**
     * Starts infinity loop of dispatching process
     * Auto invoked when any Tracker.track* method is called and dispatchInterval > 0
     */
    private void ensureAutoDispatching() {
        if (dispatchingHandler == null) {
            dispatchingHandler = new DispatchingHandler(this);
        }
        dispatchingHandler.start();
    }

    /**
     * Auto invoked when negative interval passed in setDispatchInterval
     * or Activity is paused
     */
    private void stopAutoDispatching() {
        if (dispatchingHandler != null) {
            dispatchingHandler.stop();
        }
    }

    /**
     * Set the interval to 0 to dispatch events as soon as they are queued.
     * If a negative value is used the dispatch timer will never run, a manual dispatch must be used.
     *
     * @param dispatchInterval in seconds
     */
    public Tracker setDispatchInterval(int dispatchInterval) {
        this.dispatchInterval = dispatchInterval;
        if (dispatchInterval < 1) {
            stopAutoDispatching();
        }
        return this;
    }

    public int getDispatchInterval() {
        return dispatchInterval;
    }

    @Override
    public long getDispatchIntervalMillis() {
        if (dispatchInterval > 0) {
            return dispatchInterval * 1000;
        }
        return -1;
    }

    @Override
    public void dispatchingCompleted(Integer count) {
        isDispatching = false;
        Log.d(Tracker.LOGGER_TAG, String.format("dispatched %s url(s)", count));
    }

    @Override
    public void dispatchingStarted() {
        isDispatching = true;
    }

    @Override
    public boolean isDispatching() {
        return isDispatching;
    }

    /**
     * You can set any additional Tracking API Parameters within the SDK.
     * This includes for example the local time (parameters h, m and s).
     * <pre>
     * tracker.set(QueryParams.HOURS, "10");
     * tracker.set(QueryParams.MINUTES, "45");
     * tracker.set(QueryParams.SECONDS, "30");
     * </pre>
     *
     * @param key   query params name
     * @param value value
     * @return tracker instance
     */
    public Tracker set(QueryParams key, String value) {
        if (value != null && value.length() > 0) {
            queryParams.put(key.toString(), value);
        }
        return this;
    }

    public Tracker set(QueryParams key, Integer value) {
        if (value != null) {
            set(key, Integer.toString(value));
        }
        return this;
    }

    /**
     * Sets a User ID to this user (such as an email address or a username)
     *
     * @param userId this parameter can be set to any string.
     *               The string will be hashed, and used as "User ID".
     */
    public Tracker setUserId(String userId) {
        if (userId != null && userId.length() > 0) {
            this.userId = userId;
        }
        return this;
    }

    public Tracker setUserId(long userId) {
        return setUserId(Long.toString(userId));
    }

    public Tracker clearUserId() {
        userId = null;
        return this;
    }

    public Tracker setVisitorId(String visitorId) throws IllegalArgumentException {
        if (confirmVisitorIdFormat(visitorId)) {
            this.visitorId = visitorId;
        }
        return this;
    }

    private boolean confirmVisitorIdFormat(String visitorId) throws IllegalArgumentException {
        String visitorIdRegexPattern = "^[0-9a-f]{16}$";
        Pattern visitorIdPattern = Pattern.compile(visitorIdRegexPattern);
        Matcher visitorIdMatcher = visitorIdPattern.matcher(visitorId);
        if (visitorIdMatcher.find()) {
            return true;
        }
        throw new IllegalArgumentException("VisitorId: " + visitorId + " is not of valid format, " +
            " the format must match the regular expression: " + visitorIdRegexPattern);
    }

    /**
     * Domain used to build required parameter url (http://developer.piwik.org/api-reference/tracking-api)
     * If domain wasn't set `Application.getPackageName()` method will be used
     * @param domain your-domain.com
     */
    public Tracker setApplicationDomain(String domain) {
        applicationDomain = domain;
        return this;
    }

    protected String getApplicationDomain(){
        return applicationDomain != null ? applicationDomain : piwik.getApplicationDomain();
    }

    /**
     * Sets the screen resolution of the browser which sends the request.
     *
     * @param width  the screen width as an int value
     * @param height the screen height as an int value
     */
    public Tracker setResolution(final int width, final int height) {
        return set(QueryParams.SCREEN_RESOLUTION, formatResolution(width, height));
    }

    private String formatResolution(final int width, final int height) {
        return String.format("%sx%s", width, height);
    }

    /**
     * Returns real screen size if QueryParams.SCREEN_RESOLUTION is empty
     * Note that the results also depend on the current device orientation.
     * http://stackoverflow.com/a/9316553
     *
     * @return formatted string: WxH
     */
    public String getResolution() {
        if (!queryParams.containsKey(QueryParams.SCREEN_RESOLUTION.toString())) {
            if (realScreenResolution == null) {
                try {
                    DisplayMetrics dm = new DisplayMetrics();
                    WindowManager wm = (WindowManager) piwik.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
                    wm.getDefaultDisplay().getMetrics(dm);

                    realScreenResolution = formatResolution(dm.widthPixels, dm.heightPixels);
                } catch (Exception e) {
                    Log.w(Tracker.LOGGER_TAG, "Cannot grab resolution", e);

                    realScreenResolution = "unknown";
                }
            }
            return realScreenResolution;
        }

        return queryParams.get(QueryParams.SCREEN_RESOLUTION.toString());
    }

    /**
     * This methods have to be called before a call to trackScreenView.
     * A custom variable is a custom name-value pair that you can assign to your users or screen views,
     * and then visualize the reports of how many visits, conversions, etc. for each custom variable.
     * A custom variable is defined by a name  for example,
     * "User status"  and a value  for example, "LoggedIn" or "Anonymous".
     * You can track up to 5 custom variables for each user to your app.
     *
     * @param index this Integer accepts values from 1 to 5.
     *              A given custom variable name must always be stored in the same "index" per session.
     *              For example, if you choose to store the variable name = "Gender" in
     *              index = 1 and you record another custom variable in index = 1, then the
     *              "Gender" variable will be deleted and replaced with the new custom variable stored in index 1.
     * @param name  String defines the name of a specific Custom Variable such as "User type".
     * @param value String defines the value of a specific Custom Variable such as "Customer".
     *              Custom variable names and values are limited to 200 characters in length each.
     */
    public Tracker setUserCustomVariable(int index, String name, String value) {
        return setCustomVariable(QueryParams.VISIT_SCOPE_CUSTOM_VARIABLES, index, name, value);
    }

    /**
     * Does exactly the same as setUserCustomVariable but use screen scope
     * You can track up to 5 custom variables for each screen view.
     */
    public Tracker setScreenCustomVariable(int index, String name, String value) {
        return setCustomVariable(QueryParams.SCREEN_SCOPE_CUSTOM_VARIABLES, index, name, value);
    }

    /**
     * Correspondents to action_name of Piwik Tracking API
     *
     * @param title string The title of the action being tracked. It is possible to use
     *              slashes / to set one or several categories for this action.
     *              For example, Help / Feedback will create the Action Feedback in the category Help.
     */
    public Tracker setScreenTitle(String title) {
        return set(QueryParams.ACTION_NAME, title);
    }

    /**
     * Returns android system user agent
     *
     * @return well formatted user agent
     */
    public String getUserAgent() {
        if (userAgent == null) {
            userAgent = System.getProperty("http.agent");
        }
        return userAgent;
    }

    /**
     * Returns user language
     *
     * @return language
     */
    public String getLanguage() {
        if (userLanguage == null) {
            userLanguage = Locale.getDefault().getLanguage();
        }
        return userLanguage;
    }

    /**
     * Returns user country
     *
     * @return country
     */
    public String getCountry() {
        if (userCountry == null) {
            userCountry = Locale.getDefault().getCountry();
        }
        return userCountry;
    }

    /**
     * Set action_name param from activity's title and track view
     *
     * @param activity Current Activity instance
     */
    public void activityStart(final Activity activity) {
        if (activity != null) {
            String breadcrumbs = getBreadcrumbs(activity);
            trackScreenView(breadcrumbsToPath(breadcrumbs), breadcrumbs);
        }
    }

    /**
     * Force dispatching events if main activity is about to stop
     *
     * @param activity Current Activity instance
     */
    public void activityStop(final Activity activity) {
        if (activity != null && activity.isTaskRoot()) {
            dispatch();
        }
    }

    /**
     * @param activity current activity
     */
    public void activityPaused(final Activity activity) {
        activityStop(activity);
    }

    /**
     * Don't need to start auto dispatching
     * due this will be started when any track event occurred
     *
     * @param activity current activity
     */
    public void activityResumed(final Activity activity) {
        activityStart(activity);
    }

    /**
     * Session handling
     */
    public void setNewSession() {
        touchSession();
        set(QueryParams.SESSION_START, defaultTrueValue);
    }

    private void touchSession() {
        sessionStartedMillis = System.currentTimeMillis();
    }

    public void setSessionTimeout(int seconds) {
        sessionTimeoutMillis = seconds * 1000;
    }

    protected void checkSessionTimeout() {
        if (isExpired()) {
            setNewSession();
        }
    }

    protected boolean isExpired() {
        return System.currentTimeMillis() - sessionStartedMillis > sessionTimeoutMillis;
    }

    public int getSessionTimeout() {
        return (int) sessionTimeoutMillis / 1000;
    }

    /**
     * Tracking methods
     *
     * @param path required tracking param, for example: "/user/settings/billing"
     */
    public Tracker trackScreenView(String path) {
        set(QueryParams.URL_PATH, path);
        return doTrack();
    }

    /**
     * @param path  view path for example: "/user/settings/billing" or just empty string ""
     * @param title string The title of the action being tracked. It is possible to use
     *              slashes / to set one or several categories for this action.
     *              For example, Help / Feedback will create the Action Feedback in the category Help.
     */
    public Tracker trackScreenView(String path, String title) {
        setScreenTitle(title);
        return trackScreenView(path);
    }

    /**
     * Events are a useful way to collect data about a user's interaction with interactive components of your app,
     * like button presses or the use of a particular item in a game.
     *
     * @param category (required)  this String defines the event category.
     *                 You might define event categories based on the class of user actions,
     *                 like clicks or gestures or voice commands, or you might define them based upon the
     *                 features available in your application (play, pause, fast forward, etc.).
     * @param action   (required) this String defines the specific event action within the category specified.
     *                 In the example, we are basically saying that the category of the event is user clicks,
     *                 and the action is a button click.
     * @param label    defines a label associated with the event. For example, if you have multiple Button controls on a
     *                 screen, you might use the label to specify the specific View control identifier that was clicked.
     * @param value    defines a numeric value associated with the event. For example, if you were tracking "Buy"
     *                 button clicks, you might log the number of items being purchased, or their total cost.
     */
    public Tracker trackEvent(String category, String action, String label, Integer value) {
        if (category != null && action != null) {
            set(QueryParams.EVENT_ACTION, action);
            set(QueryParams.EVENT_CATEGORY, category);
            set(QueryParams.EVENT_NAME, label);
            set(QueryParams.EVENT_VALUE, value);
            doTrack();
        }
        return this;
    }

    public Tracker trackEvent(String category, String action, String label) {
        return trackEvent(category, action, label, null);
    }

    public Tracker trackEvent(String category, String action) {
        return trackEvent(category, action, null, null);
    }

    /**
     * By default, Goals in Piwik are defined as "matching" parts of the screen path or screen title.
     * In this case a conversion is logged automatically. In some situations, you may want to trigger
     * a conversion manually on other types of actions, for example:
     * when a user submits a form
     * when a user has stayed more than a given amount of time on the page
     * when a user does some interaction in your Android application
     *
     * @param idGoal id of goal as defined in piwik goal settings
     */
    public Tracker trackGoal(Integer idGoal) {
        if (idGoal != null && idGoal > 0) {
            set(QueryParams.GOAL_ID, idGoal);
            return doTrack();
        }
        return this;
    }

    /**
     * Tracking request will trigger a conversion for the goal of the website being tracked with this ID
     *
     * @param idGoal  id of goal as defined in piwik goal settings
     * @param revenue a monetary value that was generated as revenue by this goal conversion.
     */
    public Tracker trackGoal(Integer idGoal, int revenue) {
        set(QueryParams.REVENUE, revenue);
        return trackGoal(idGoal);
    }

    /**
     * Ensures that tracking application downloading will be fired only once
     * by using SharedPreferences as flag storage
     */
    public Tracker trackAppDownload() {
        SharedPreferences prefs = piwik.getSharedPreferences(
                Piwik.class.getPackage().getName(), Context.MODE_PRIVATE);

        if (!prefs.getBoolean("downloaded", false)) {
            SharedPreferences.Editor editor = prefs.edit();
            editor.putBoolean("downloaded", true);
            editor.commit();
            trackNewAppDownload();
        }
        return this;
    }

    /**
     * Make sure to call this method only once per user
     */
    public Tracker trackNewAppDownload() {
        set(QueryParams.DOWNLOAD, getApplicationBaseURL());
        set(QueryParams.ACTION_NAME, "application/downloaded");
        set(QueryParams.URL_PATH, "/application/downloaded");
        return trackEvent("Application", "downloaded");
    }

    /**
     * Tracking the impressions
     *
     * @param contentName   The name of the content. For instance 'Ad Foo Bar'
     * @param contentPiece  The actual content. For instance the path to an image, video, audio, any text
     * @param contentTarget (optional) The target of the content. For instance the URL of a landing page.
     */
    public Tracker trackContentImpression(String contentName, String contentPiece, String contentTarget) {
        if (contentName != null && contentName.length() > 0) {
            set(QueryParams.CONTENT_NAME, contentName);
            set(QueryParams.CONTENT_PIECE, contentPiece);
            set(QueryParams.CONTENT_TARGET, contentTarget);
            return doTrack();
        }
        return this;
    }

    /**
     * Tracking the interactions
     *
     * @param interaction   The name of the interaction with the content. For instance a 'click'
     * @param contentName   The name of the content. For instance 'Ad Foo Bar'
     * @param contentPiece  The actual content. For instance the path to an image, video, audio, any text
     * @param contentTarget (optional) The target the content leading to when an interaction occurs. For instance the URL of a landing page.
     */
    public Tracker trackContentInteraction(String interaction, String contentName, String contentPiece, String contentTarget) {
        if(interaction != null && interaction.length() > 0){
            set(QueryParams.CONTENT_INTERACTION, interaction);
            return trackContentImpression(contentName, contentPiece, contentTarget);
        }
        return this;
    }

    /**
     * Caught exceptions are errors in your app for which you've defined exception handling code,
     * such as the occasional timeout of a network connection during a request for data.
     *
     * @param className   $ClassName:$lineNumber
     * @param description exception message
     * @param isFatal     true if it's RunTimeException
     */
    public void trackException(String className, String description, boolean isFatal) {
        className = className != null && className.length() > 0 ? className : "Unknown";
        String actionName = "exception/" +
                (isFatal ? "fatal/" : "") +
                (className + "/") + description;

        set(QueryParams.ACTION_NAME, actionName);
        trackEvent("Exception", className, description, isFatal ? 1 : 0);
    }

    /**
     * Caught exceptions are errors in your app for which you've defined exception handling code,
     * such as the occasional timeout of a network connection during a request for data.
     *
     * @param ex   exception instance
     * @param description exception message
     * @param isFatal     true if it's fatal exeption
     */
    public void trackException(Throwable ex, String description, boolean isFatal) {
        String className;
        try {
            StackTraceElement trace = ex.getStackTrace()[0];
            className = trace.getClassName() + "/" + trace.getMethodName() + ":" + trace.getLineNumber();
        } catch (Exception e) {
            Log.w(Tracker.LOGGER_TAG, "Couldn't get stack info", e);
            className = ex.getClass().getName();
        }

        trackException(className, description, isFatal);
    }

    protected final Thread.UncaughtExceptionHandler customUEH =
            new Thread.UncaughtExceptionHandler() {

                @Override
                public void uncaughtException(Thread thread, Throwable ex) {
                    try {
                        String excInfo = ex.getMessage();

                        // track
                        trackException(ex, excInfo, true);

                        // dispatch immediately
                        dispatch();
                    } catch (Exception e) {
                        // fail silently
                        Log.e(Tracker.LOGGER_TAG, "Couldn't track uncaught exception", e);
                    } finally {
                        // re-throw critical exception further to the os (important)
                        if (Piwik.defaultUEH != null && Piwik.defaultUEH != customUEH) {
                            Piwik.defaultUEH.uncaughtException(thread, ex);
                        }
                    }

                }
            };

    /**
     * Uncaught exceptions are sent to Piwik automatically by default
     *
     * @param toggle true if reporting should be enabled
     */
    public Tracker reportUncaughtExceptions(boolean toggle) {
        if (toggle) {
            // Setup handler for uncaught exception
            Thread.setDefaultUncaughtExceptionHandler(customUEH);
        } else {
            Thread.setDefaultUncaughtExceptionHandler(Piwik.defaultUEH);
        }
        return this;
    }


    /**
     * Set up required params
     */
    protected void beforeTracking() {
        set(QueryParams.API_VERSION, defaultAPIVersionValue);
        set(QueryParams.SEND_IMAGE, "0");
        set(QueryParams.SITE_ID, siteId);
        set(QueryParams.RECORD, defaultRecordValue);
        set(QueryParams.RANDOM_NUMBER, randomObject.nextInt(100000));
        set(QueryParams.SCREEN_RESOLUTION, getResolution());
        set(QueryParams.URL_PATH, getParamURL());
        set(QueryParams.USER_AGENT, getUserAgent());
        set(QueryParams.LANGUAGE, getLanguage());
        set(QueryParams.COUNTRY, getCountry());
        set(QueryParams.VISITOR_ID, visitorId);
        set(QueryParams.USER_ID, getUserId());
        set(QueryParams.DATETIME_OF_REQUEST, getCurrentDatetime());
        set(QueryParams.SCREEN_SCOPE_CUSTOM_VARIABLES, getCustomVariables(QueryParams.SCREEN_SCOPE_CUSTOM_VARIABLES).toString());
        set(QueryParams.VISIT_SCOPE_CUSTOM_VARIABLES, getCustomVariables(QueryParams.VISIT_SCOPE_CUSTOM_VARIABLES).toString());
        checkSessionTimeout();
        touchSession();
    }

    /**
     * Builds URL, adds event to queue, clean all params after url was added
     */
    protected Tracker doTrack() {
        beforeTracking();

        String event = getQuery();
        if (piwik.isOptOut()) {
            lastEvent = event;
            Log.d(Tracker.LOGGER_TAG, String.format("URL omitted due to opt out: %s", event));
        } else {
            Log.d(Tracker.LOGGER_TAG, String.format("URL added to the queue: %s", event));
            queue.add(event);

            tryDispatch();
        }

        afterTracking();
        return this;
    }

    /**
     * Clean up params
     */
    protected void afterTracking() {
        clearQueryParams();
        clearAllCustomVariables();
    }

    /**
     *
     * HELPERS
     *
     */

    /**
     * Gets all custom vars from screen or visit scope
     *
     * @param namespace `_cvar` or `cvar` stored in
     *                  QueryParams.SCREEN_SCOPE_CUSTOM_VARIABLES and
     *                  QueryParams.VISIT_SCOPE_CUSTOM_VARIABLES
     * @return CustomVariables HashMap
     */
    private CustomVariables getCustomVariables(String namespace) {
        if (namespace == null) {
            return null;
        }
        CustomVariables vars = customVariables.get(namespace);
        if (vars == null) {
            vars = new CustomVariables();
            customVariables.put(namespace, vars);
        }
        return vars;
    }

    private CustomVariables getCustomVariables(QueryParams namespace) {
        if (namespace == null) {
            return null;
        }

        return getCustomVariables(namespace.toString());
    }

    private void clearAllCustomVariables() {
        getCustomVariables(QueryParams.SCREEN_SCOPE_CUSTOM_VARIABLES).clear();
        getCustomVariables(QueryParams.VISIT_SCOPE_CUSTOM_VARIABLES).clear();
    }

    private Tracker setCustomVariable(QueryParams namespace, int index, String name, String value) {
        getCustomVariables(namespace.toString()).put(index, name, value);
        return this;
    }

    private void clearQueryParams() {
        // To avoid useless shrinking and resizing of the Map
        // the capacity is held the same when clear() is called.
        queryParams.clear();
    }

    private String getCurrentDatetime() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ").format(new Date());
    }

    private String getBreadcrumbs(final Activity activity) {
        Activity currentActivity = activity;
        ArrayList<String> breadcrumbs = new ArrayList<String>();

        while (currentActivity != null) {
            breadcrumbs.add(currentActivity.getTitle().toString());
            currentActivity = currentActivity.getParent();
        }
        return joinSlash(breadcrumbs);
    }

    private String joinSlash(List<String> sequence) {
        if (sequence != null && sequence.size() > 0) {
            return TextUtils.join("/", sequence);
        }
        return "";
    }

    private String breadcrumbsToPath(String breadcrumbs) {
        return breadcrumbs.replaceAll("\\s", "");
    }

    protected String getQuery() {
        return TrackerBulkURLProcessor.urlEncodeUTF8(queryParams);
    }

    /**
     * For testing purposes
     *
     * @return query of the event ?r=1&sideId=1..
     */
    protected String getLastEvent() {
        return lastEvent;
    }

    protected void clearLastEvent() {
        lastEvent = null;
    }

    protected String getApplicationBaseURL() {
        return String.format("http://%s", getApplicationDomain());
    }

    protected String getParamURL() {
        String url = queryParams.get(QueryParams.URL_PATH.toString());

        if (url == null) {
            url = "/";
        } else if (url.startsWith("http://") || url.startsWith("https://")) {
            return url;
        } else if (!url.startsWith("/")) {
            url = "/" + url;
        }

        return getApplicationBaseURL() + url;
    }

    protected String getUserId() {
        return userId;
    }

    protected String getVisitorId() {
        return visitorId;
    }

    /**
     * Sets the url of the piwik installation the dispatchable will track to.
     * <p/>
     * The given string should be in the format of RFC2396. The string will be converted to an url with no other url as
     * its context.
     *
     * @param APIUrl as a string object
     * @throws MalformedURLException
     */
    protected final void setAPIUrl(final String APIUrl) throws MalformedURLException {
        if (APIUrl == null) {
            throw new MalformedURLException("You must provide the Piwik Tracker URL! e.g. http://piwik.website.org/piwik.php");
        }

        URL url = new URL(APIUrl);
        String path = url.getPath();

        if (path.endsWith("piwik.php") || path.endsWith("piwik-proxy.php")) {
            this.apiUrl = url;
        } else {
            if (!path.endsWith("/")) {
                path += "/";
            }
            this.apiUrl = new URL(url, path + "piwik.php");
        }
    }

    protected String getAPIUrl() {
        return apiUrl.toString();
    }

    private String getRandomVisitorId() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Tracker tracker = (Tracker) o;

        return siteId == tracker.siteId && apiUrl.equals(tracker.apiUrl);
    }

    @Override
    public int hashCode() {
        int result = siteId;
        result = 31 * result + apiUrl.hashCode();
        return result;
    }

    /**
     * CONSTANTS
     */
    public enum QueryParams {
        SITE_ID("idsite"),
        @Deprecated AUTHENTICATION_TOKEN("token_auth"),
        RECORD("rec"),
        SEND_IMAGE("send_image"),
        API_VERSION("apiv"),
        SCREEN_RESOLUTION("res"),
        HOURS("h"),
        MINUTES("m"),
        SECONDS("s"),
        ACTION_NAME("action_name"),
        URL_PATH("url"),
        USER_AGENT("ua"),
        VISITOR_ID("_id"),
        USER_ID("uid"),

        VISIT_SCOPE_CUSTOM_VARIABLES("_cvar"),
        SCREEN_SCOPE_CUSTOM_VARIABLES("cvar"),
        RANDOM_NUMBER("r"),
        FIRST_VISIT_TIMESTAMP("_idts"),
        PREVIOUS_VISIT_TIMESTAMP("_viewts"),
        TOTAL_NUMBER_OF_VISITS("_idvc"),
        GOAL_ID("idgoal"),
        REVENUE("revenue"),
        SESSION_START("new_visit"),
        LANGUAGE("lang"),
        COUNTRY("country"),
        LATITUDE("lat"),
        LONGITUDE("long"),
        SEARCH_KEYWORD("search"),
        SEARCH_CATEGORY("search_cat"),
        SEARCH_NUMBER_OF_HITS("search_count"),
        REFERRER("urlref"),
        DATETIME_OF_REQUEST("cdt"),
        DOWNLOAD("download"),
        LINK("link"),

        // Campaign
        CAMPAIGN_NAME("_rcn"),
        CAMPAIGN_KEYWORD("_rck"),

        // Content Tracking
        CONTENT_INTERACTION("c_i"),
        CONTENT_NAME("c_n"),
        CONTENT_PIECE("c_p"),
        CONTENT_TARGET("c_t"),

        // Events
        EVENT_CATEGORY("e_c"),
        EVENT_ACTION("e_a"),
        EVENT_NAME("e_n"),
        EVENT_VALUE("e_v");

        private final String value;

        QueryParams(String value) {
            this.value = value;
        }

        public String toString() {
            return value;
        }
    }

}




Java Source Code List

com.piwik.demo.ApplicationTest.java
com.piwik.demo.DemoActivity.java
com.piwik.demo.DemoApp.java
com.piwik.demo.SettingsActivity.java
org.piwik.sdk.CustomVariables.java
org.piwik.sdk.Dispatchable.java
org.piwik.sdk.DispatchingHandler.java
org.piwik.sdk.PiwikApplication.java
org.piwik.sdk.Piwik.java
org.piwik.sdk.TrackerBulkURLProcessor.java
org.piwik.sdk.TrackerBulkURLWrapper.java
org.piwik.sdk.Tracker.java