All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ly.count.android.api.Countly Maven / Gradle / Ivy

package ly.count.android.api;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import org.OpenUDID.OpenUDID_manager;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
import android.telephony.TelephonyManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;

public class Countly {
    private static Countly sharedInstance_;
    private Timer timer_;
    private ConnectionQueue queue_;
    private EventQueue eventQueue_;
    private boolean isVisible_;
    private double unsentSessionLength_;
    private double lastTime_;
    private int activityCount_;
    private CountlyStore countlyStore_;

    protected static final int SESSION_DURATION_WHEN_TIME_ADJUSTED = 15;

    static public Countly sharedInstance() {
        if (sharedInstance_ == null)
            sharedInstance_ = new Countly();

        return sharedInstance_;
    }

    private Countly() {
        queue_ = new ConnectionQueue();
        timer_ = new Timer();
        timer_.schedule(new TimerTask() {
            @Override
            public void run() {
                onTimer();
            }
        }, 60 * 1000, 60 * 1000);

        isVisible_ = false;
        unsentSessionLength_ = 0;
        activityCount_ = 0;
    }

    public void init(Context context, String serverURL, String appKey) {
        OpenUDID_manager.sync(context);
        countlyStore_ = new CountlyStore(context);

        queue_.setContext(context);
        queue_.setServerURL(serverURL);
        queue_.setAppKey(appKey);
        queue_.setCountlyStore(countlyStore_);

        eventQueue_ = new EventQueue(countlyStore_);
    }

    public void onStart() {
        activityCount_++;
        if (activityCount_ == 1)
            onStartHelper();
    }

    public void onStop() {
        activityCount_--;
        if (activityCount_ == 0)
            onStopHelper();
    }

    public void onStartHelper() {
        lastTime_ = System.currentTimeMillis() / 1000.0;

        queue_.beginSession();

        isVisible_ = true;
    }

    public void onStopHelper() {
        if (eventQueue_.size() > 0)
            queue_.recordEvents(eventQueue_.events());

        double currTime = System.currentTimeMillis() / 1000.0;
        unsentSessionLength_ += currTime - lastTime_;

        int duration = (int) unsentSessionLength_;
        queue_.endSession(duration);
        unsentSessionLength_ -= duration;

        isVisible_ = false;
    }

    public void recordEvent(String key) {
        eventQueue_.recordEvent(key);

        if (eventQueue_.size() >= 10)
            queue_.recordEvents(eventQueue_.events());
    }

    public void recordEvent(String key, int count) {
        eventQueue_.recordEvent(key, count);

        if (eventQueue_.size() >= 10)
            queue_.recordEvents(eventQueue_.events());
    }

    public void recordEvent(String key, int count, double sum) {
        eventQueue_.recordEvent(key, count, sum);

        if (eventQueue_.size() >= 10)
            queue_.recordEvents(eventQueue_.events());
    }

    public void recordEvent(String key, Map segmentation, int count) {
        eventQueue_.recordEvent(key, segmentation, count);

        if (eventQueue_.size() >= 10)
            queue_.recordEvents(eventQueue_.events());
    }

    public void recordEvent(String key, Map segmentation, int count, double sum) {
        eventQueue_.recordEvent(key, segmentation, count, sum);

        if (eventQueue_.size() >= 10)
            queue_.recordEvents(eventQueue_.events());
    }

    private void onTimer() {
        if (isVisible_ == false)
            return;

        double currTime = System.currentTimeMillis() / 1000.0;
        unsentSessionLength_ += currTime - lastTime_;
        lastTime_ = currTime;

        int duration = (int) unsentSessionLength_;
        queue_.updateSession(duration);
        unsentSessionLength_ -= duration;

        if (eventQueue_.size() > 0)
            queue_.recordEvents(eventQueue_.events());
    }
}

class ConnectionQueue {
    private CountlyStore store_;
    private Thread thread_ = null;
    private String appKey_;
    private Context context_;
    private String serverURL_;

    public void setAppKey(String appKey) {
        appKey_ = appKey;
    }

    public void setContext(Context context) {
        context_ = context;
    }

    public void setServerURL(String serverURL) {
        serverURL_ = serverURL;
    }

    public void setCountlyStore(CountlyStore countlyStore) {
        store_ = countlyStore;
    }

    public void beginSession() {
        String data;
        data = "app_key=" + appKey_;
        data += "&" + "device_id=" + DeviceInfo.getUDID();
        data += "&" + "timestamp=" + (long) (System.currentTimeMillis() / 1000.0);
        data += "&" + "sdk_version=" + "2.0";
        data += "&" + "begin_session=" + "1";
        data += "&" + "metrics=" + DeviceInfo.getMetrics(context_);

        store_.addConnection(data);

        tick();
    }

    public void updateSession(int duration) {
        String data;
        data = "app_key=" + appKey_;
        data += "&" + "device_id=" + DeviceInfo.getUDID();
        data += "&" + "timestamp=" + (long) (System.currentTimeMillis() / 1000.0);
        data += "&" + "session_duration=" + (duration > 0 ? duration : Countly.SESSION_DURATION_WHEN_TIME_ADJUSTED);

        store_.addConnection(data);

        tick();
    }

    public void endSession(int duration) {
        String data;
        data = "app_key=" + appKey_;
        data += "&" + "device_id=" + DeviceInfo.getUDID();
        data += "&" + "timestamp=" + (long) (System.currentTimeMillis() / 1000.0);
        data += "&" + "end_session=" + "1";
        data += "&" + "session_duration=" + (duration > 0 ? duration : Countly.SESSION_DURATION_WHEN_TIME_ADJUSTED);

        store_.addConnection(data);

        tick();
    }

    public void recordEvents(String events) {
        String data;
        data = "app_key=" + appKey_;
        data += "&" + "device_id=" + DeviceInfo.getUDID();
        data += "&" + "timestamp=" + (long) (System.currentTimeMillis() / 1000.0);
        data += "&" + "events=" + events;

        store_.addConnection(data);

        tick();
    }

    private void tick() {
        if (thread_ != null && thread_.isAlive())
            return;

        if (store_.isEmptyConnections())
            return;

        thread_ = new Thread() {
            @Override
            public void run() {
                while (true) {
                    String[] sessions = store_.connections();

                    if (sessions.length == 0)
                        break;

                    String initial = sessions[0], replaced = initial;

                    int index = replaced.indexOf("REPLACE_UDID");
                    if (index != -1) {
                        if (OpenUDID_manager.isInitialized() == false)
                            break;
                        replaced = replaced.replaceFirst("REPLACE_UDID", OpenUDID_manager.getOpenUDID());
                    }

                    try {
                        DefaultHttpClient httpClient = new DefaultHttpClient();
                        HttpGet method = new HttpGet(new URI(serverURL_ + "/i?" + replaced));
                        HttpResponse response = httpClient.execute(method);
                        InputStream input = response.getEntity().getContent();
                        while (input.read() != -1)
                            ;
                        httpClient.getConnectionManager().shutdown();

                        Log.d("Countly", "ok ->" + replaced);

                        store_.removeConnection(initial);
                    } catch (Exception e) {
                        Log.d("Countly", e.toString());
                        Log.d("Countly", "error ->" + initial);
                        break;
                    }
                }
            }
        };

        thread_.start();
    }
}

class DeviceInfo {
    public static String getUDID() {
        return OpenUDID_manager.isInitialized() == false ? "REPLACE_UDID" : OpenUDID_manager.getOpenUDID();
    }

    public static String getOS() {
        return "Android";
    }

    public static String getOSVersion() {
        return android.os.Build.VERSION.RELEASE;
    }

    public static String getDevice() {
        return android.os.Build.MODEL;
    }

    public static String getResolution(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        Display display = wm.getDefaultDisplay();

        DisplayMetrics metrics = new DisplayMetrics();
        display.getMetrics(metrics);

        return metrics.widthPixels + "x" + metrics.heightPixels;
    }

    public static String getDensity(Context context) {
        int density = context.getResources().getDisplayMetrics().densityDpi;

        switch (density) {
            case DisplayMetrics.DENSITY_LOW:
                return "LDPI";
            case DisplayMetrics.DENSITY_MEDIUM:
                return "MDPI";
            case DisplayMetrics.DENSITY_TV:
                return "TVDPI";
            case DisplayMetrics.DENSITY_HIGH:
                return "HDPI";
            case DisplayMetrics.DENSITY_XHIGH:
                return "XHDPI";
            case DisplayMetrics.DENSITY_XXHIGH:
                return "XXHDPI";
            case DisplayMetrics.DENSITY_XXXHIGH:
                return "XXXHDPI";
            default:
                return "";
        }
    }

    public static String getCarrier(Context context) {
        try {
            TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            return manager.getNetworkOperatorName();
        } catch (NullPointerException npe) {
            npe.printStackTrace();
            Log.e("Countly", "No carrier found");
        }
        return "";
    }

    public static String getLocale() {
        Locale locale = Locale.getDefault();
        return locale.getLanguage() + "_" + locale.getCountry();
    }

    public static String appVersion(Context context) {
        String result = "1.0";
        try {
            result = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
        } catch (NameNotFoundException e) {
        }

        return result;
    }

    public static String getMetrics(Context context) {
        String result = "";
        JSONObject json = new JSONObject();

        try {
            json.put("_device", getDevice());
            json.put("_os", getOS());
            json.put("_os_version", getOSVersion());
            json.put("_carrier", getCarrier(context));
            json.put("_resolution", getResolution(context));
            json.put("_density", getDensity(context));
            json.put("_locale", getLocale());
            json.put("_app_version", appVersion(context));
        } catch (JSONException e) {
            e.printStackTrace();
        }

        result = json.toString();

        try {
            result = java.net.URLEncoder.encode(result, "UTF-8");
        } catch (UnsupportedEncodingException e) {

        }

        return result;
    }
}

class Event {
    public String key = null;
    public Map segmentation = null;
    public int count = 0;
    public double sum = 0;
    public int timestamp = 0;

    public boolean equals(Object o) {
        if (o == null || !(o instanceof Event)) return false;

        Event e = (Event) o;

        return (key == null ? e.key == null : key.equals(e.key)) &&
                timestamp == e.timestamp && (segmentation == null ? e.segmentation == null : segmentation.equals(e.segmentation));
    }
}

class EventQueue {
    private CountlyStore countlyStore_;

    public EventQueue(CountlyStore countlyStore) {
        countlyStore_ = countlyStore;
    }

    public int size() {
        synchronized (this) {
            return countlyStore_.events().length;
        }
    }

    public String events() {
        String result = "";

        synchronized (this) {
            List events = countlyStore_.eventsList();

            JSONArray eventArray = new JSONArray();
            for (Event e : events) eventArray.put(CountlyStore.eventToJSON(e));

            result = eventArray.toString();

            countlyStore_.removeEvents(events);
        }

        try {
            result = java.net.URLEncoder.encode(result, "UTF-8");
        } catch (UnsupportedEncodingException e) {

        }

        return result;
    }

    public void recordEvent(String key) {
        recordEvent(key, null, 1, 0);
    }

    public void recordEvent(String key, int count) {
        recordEvent(key, null, count, 0);
    }

    public void recordEvent(String key, int count, double sum) {
        recordEvent(key, null, count, sum);
    }

    public void recordEvent(String key, Map segmentation, int count) {
        recordEvent(key, segmentation, count, 0);
    }

    public void recordEvent(String key, Map segmentation, int count, double sum) {
        synchronized (this) {
            countlyStore_.addEvent(key, segmentation, count, sum);
        }
    }
}

class CountlyStore {
    private static final String TAG = "COUNTLY_STORE";
    private static final String PREFERENCES = "COUNTLY_STORE";
    private static final String DELIMITER = "===";
    private static final String CONNECTIONS_PREFERENCE = "CONNECTIONS";
    private static final String EVENTS_PREFERENCE = "EVENTS";

    private SharedPreferences preferences;

    protected CountlyStore(Context ctx) {
        preferences = ctx.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
    }

    public String[] connections() {
        String array = preferences.getString(CONNECTIONS_PREFERENCE, null);
        return array == null || "".equals(array) ? new String[0] : array.split(DELIMITER);
    }

    public String[] events() {
        String array = preferences.getString(EVENTS_PREFERENCE, null);
        return array == null || "".equals(array) ? new String[0] : array.split(DELIMITER);
    }

    public List eventsList() {
        String[] array = events();
        if (array.length == 0) return new ArrayList();
        else {
            List events = new ArrayList();
            for (String s : array) {
                try {
                    events.add(jsonToEvent(new JSONObject(s)));
                } catch (JSONException e) {
                    Log.e(TAG, "Cannot parse Event json", e);
                }
            }

            Collections.sort(events, new Comparator() {
                @Override
                public int compare(Event e1, Event e2) {
                    return e2.timestamp - e1.timestamp;
                }
            });

            return events;
        }
    }

    public boolean isEmptyConnections() {
        return connections().length == 0;
    }

    public boolean isEmptyEvents() {
        return events().length == 0;
    }

    public void addConnection(String str) {
        List connections = new ArrayList(Arrays.asList(connections()));
        connections.add(str);
        preferences.edit().putString(CONNECTIONS_PREFERENCE, join(connections, DELIMITER)).commit();
    }

    public void removeConnection(String str) {
        List connections = new ArrayList(Arrays.asList(connections()));
        connections.remove(str);
        preferences.edit().putString(CONNECTIONS_PREFERENCE, join(connections, DELIMITER)).commit();
    }

    public void addEvent(Event event) {
        List events = eventsList();
        if (!events.contains(event)) events.add(event);
        preferences.edit().putString(EVENTS_PREFERENCE, joinEvents(events, DELIMITER)).commit();
    }

    public void addEvent(String key, Map segmentation, int count, double sum) {
        List events = eventsList();
        Event event = null;
        for (Event e : events) if (e.key != null && e.key.equals(key)) event = e;

        if (event == null) {
            event = new Event();
            event.key = key;
            event.segmentation = segmentation;
            event.count = 0;
            event.sum = 0;
            event.timestamp = (int) (System.currentTimeMillis() / 1000);
        } else {
            removeEvent(event);
            event.timestamp = Math.round((event.timestamp + (System.currentTimeMillis() / 1000)) / 2);
        }

        event.count += count;
        event.sum += sum;

        addEvent(event);
    }

    public void removeEvent(Event event) {
        List events = eventsList();
        events.remove(event);
        preferences.edit().putString(EVENTS_PREFERENCE, joinEvents(events, DELIMITER)).commit();
    }

    public void removeEvents(Collection eventsToRemove) {
        List events = eventsList();
        for (Event e : eventsToRemove) events.remove(e);
        preferences.edit().putString(EVENTS_PREFERENCE, joinEvents(events, DELIMITER)).commit();
    }

    protected static JSONObject eventToJSON(Event event) {
        JSONObject json = new JSONObject();

        try {
            json.put("key", event.key);
            json.put("count", event.count);
            json.put("sum", event.sum);
            json.put("timestamp", event.timestamp);

            if (event.segmentation != null) {
                json.put("segmentation", new JSONObject(event.segmentation));
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return json;
    }

    protected static Event jsonToEvent(JSONObject json) {
        Event event = new Event();

        try {
            event.key = json.get("key").toString();
            event.count = Integer.valueOf(json.get("count").toString());
            event.sum = Double.valueOf(json.get("sum").toString());
            event.timestamp = Integer.valueOf(json.get("timestamp").toString());

            if (json.has("segmentation")) {
                JSONObject segm = json.getJSONObject("segmentation");
                HashMap segmentation = new HashMap();
                Iterator nameItr = segm.keys();

                while (nameItr.hasNext()) {
                    Object obj = nameItr.next();
                    if (obj instanceof String) {
                        segmentation.put((String) obj, ((JSONObject) json.get("segmentation")).getString((String) obj));
                    }
                }

                event.segmentation = segmentation;
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return event;
    }

    private static String joinEvents(Collection collection, String delimiter) {
        List strings = new ArrayList();
        for (Event e : collection) strings.add(eventToJSON(e).toString());
        return join(strings, delimiter);
    }

    private static String join(Collection collection, String delimiter) {
        StringBuilder builder = new StringBuilder();

        int i = 0;
        for (String s : collection) {
            builder.append(s);
            if (++i < collection.size()) builder.append(delimiter);
        }

        return builder.toString();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy