public class OfflineRequest {
	private String url;
	private Map<String, Object> params;
	
	public OfflineRequest(String url, Map<String, Object> params) {
		/**
		 * Adding timestamp and request type which are required at server.
		 */
		params.put(Constants.JSON_NODE_TIMESTAMP, System.currentTimeMillis());
		params.put(Constants.JSON_NODE_TYPE, CurioUtil.getRequestType(url));
		setParams(params);
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public Map<String, Object> getParams() {
		return params;
	}

	public void setParams(Map<String, Object> params) {
		this.params = params;
	}
}
 public class OnlineRequest {
	private String url;
	private Map<String, Object> params;
	private ICurioResultListener callback;
	private Integer priority;
	
	public OnlineRequest(String url, Map<String, Object> params, ICurioResultListener callback, Integer priority) {
		setUrl(url);
		setParams(params);
		setCallback(callback);
		setPriority(priority);
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public ICurioResultListener getCallback() {
		return callback;
	}

	public void setCallback(ICurioResultListener callback) {
		this.callback = callback;
	}

	public Integer getPriority() {
		return priority;
	}

	public void setPriority(Integer priority) {
		this.priority = priority;
	}

	public Map<String, Object> getParams() {
		return params;
	}
	
	public void setParams(Map<String, Object> params) {
		this.params = params;
	}
}
 public class Screen {
	private String hitCode;
	private String title;
	private String path;
	
	public Screen(String hitCode, String title, String path) {
		this.hitCode = hitCode;
		this.title = title;
		this.path = path;
	}

	public String getHitCode() {
		return hitCode;
	}

	public void setHitCode(String hitCode) {
		this.hitCode = hitCode;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}

}
 public class Constants {
	public static final String JSON_NODE_AUTH_TOKEN = "authToken";
	public static final String JSON_NODE_HIT_CODE = "hitCode";
	public static final String JSON_NODE_DATA = "data";
	public static final String JSON_NODE_SESSION_CODE = "sessionCode";
	public static final String JSON_NODE_TYPE = "type";
	public static final String JSON_NODE_TIMESTAMP = "timestamp";

	public static final String HTTP_PARAM_HIT_CODE = "hitCode";
	public static final String HTTP_PARAM_TRACKING_CODE = "trackingCode";
	public static final String HTTP_PARAM_VISITOR_CODE = "visitorCode";
	public static final String HTTP_PARAM_PAGE_TITLE = "pageTitle";
	public static final String HTTP_PARAM_PATH = "path";
	public static final String HTTP_PARAM_SCREEN_WIDTH = "screenWidth";
	public static final String HTTP_PARAM_SCREEN_HEIGHT = "screenHeight";
	public static final String HTTP_PARAM_ACTIVITY_WIDTH = "activityWidth";
	public static final String HTTP_PARAM_ACTIVITY_HEIGHT = "activityHeight";
	public static final String HTTP_PARAM_OS_TYPE = "osType";
	public static final String HTTP_PARAM_OS_VERSION = "osVer";
	public static final String HTTP_PARAM_CURIO_SDK_VERSION = "curioSdkVer";
	public static final String HTTP_PARAM_APP_VERSION = "appVer";
	public static final String HTTP_PARAM_BRAND = "brand";
	public static final String HTTP_PARAM_MODEL = "model";
	public static final String HTTP_PARAM_SESSION_CODE = "sessionCode";
	public static final String HTTP_PARAM_EVENT_KEY = "eventKey";
	public static final String HTTP_PARAM_EVENT_VALUE = "eventValue";
	public static final String HTTP_PARAM_SIM_OPERATOR = "simOperator";
	public static final String HTTP_PARAM_SIM_COUNTRY_ISO = "simOpCountry";
	public static final String HTTP_PARAM_NETWORK_OPERATOR_NAME = "networkOpName";
	public static final String HTTP_PARAM_INTERNET_CONN_TYPE = "connType";
	public static final String HTTP_PARAM_LANG = "lang";
	public static final String HTTP_PARAM_API_KEY = "apiKey";
	public static final String HTTP_PARAM_SESSION_TIMEOUT = "sessionTimeout";
	public static final String HTTP_PARAM_TIME = "time";
	public static final String HTTP_PARAM_JSON_DATA = "data";
	public static final String HTTP_PARAM_PUSH_TOKEN = "pushToken";
	public static final String HTTP_PARAM_PUSH_ID = "pushId";
	public static final String HTTP_PARAM_CUSTOM_ID = "customId";
	
	public static final String CONFIG_PARAM_SESSION_TIMEOUT = "session_timeout";
	public static final String CONFIG_PARAM_API_KEY = "api_key";
	public static final String CONFIG_PARAM_GCM_SENDER_ID = "gcm_senderId";
	public static final String CONFIG_PARAM_PERIODIC_DISPATCH = "periodic_dispatch_enabled";
	public static final String CONFIG_PARAM_TRACKING_CODE = "tracking_code";
	public static final String CONFIG_PARAM_SERVER_URL = "server_url";
	public static final String CONFIG_PARAM_DISPATCH_PERIOD = "dispatch_period";
	public static final String CONFIG_PARAM_MAX_CACHED_ACTIVITY_COUNT = "max_cached_activity_count";
	public static final String CONFIG_PARAM_LOGGING_ENABLED = "logging_enabled";
	public static final String CONFIG_PARAM_AUTO_PUSH_REGISTRATION = "auto_push_registration";

	public static final int CONFIG_PARAM_DEFAULT_VALUE_SESSION_TIMEOUT_IN_MINUTES = 30;
	public static final int CONFIG_PARAM_DEFAULT_VALUE_DISPATCH_PERIOD_IN_MINUTES = 5;
	public static final int CONFIG_PARAM_DEFAULT_VALUE_MAX_CACHED_ACTIVITY_COUNT = 1000;

	public static final int CONFIG_PARAM_MAX_VALUE_MAX_CACHED_ACTIVITY_COUNT = 4000;

	public static final String ERROR = "error";
	public static final String ERROR_CODE = "errorCode";
	public static final String ERROR_MESSAGE = "errorMessage";
	
	public static final String URL = "url";
	public static final String PAIRS = "pairs";
	public static final String CALLBACK = "callback";
	public static final String PRIORITY = "priority";
	public static final String HTTP = "http://";
	public static final String HTTPS = "https://";
	public static final String BACKSLASH = "/";
	public static final String API = "api";
	
	public static final String SERVER_URL_SUFFIX_SESSION_START = "/visit/create";
	public static final String SERVER_URL_SUFFIX_SESSION_END = "/visit/end";
	public static final String SERVER_URL_SUFFIX_SCREEN_START = "/hit/create";
	public static final String SERVER_URL_SUFFIX_SCREEN_END = "/hit/end";
	public static final String SERVER_URL_SUFFIX_SEND_EVENT = "/event/create";
	public static final String SERVER_URL_SUFFIX_PERIODIC_BATCH = "/batch/create";
	public static final String SERVER_URL_SUFFIX_OFFLINE_CACHE = "/offline/create";
	public static final String SERVER_URL_SUFFIX_PUSH_DATA = "/visitor/setPushData";
	public static final String SERVER_URL_SUFFIX_UNREGISTER = "/visitor/unregister";

	public static final String UTF8_ENCODING = "utf-8";
	public static final int NOT_IN_PROCESS = 0;
	public static final String NOT_IN_PROCESS_STR = "0";
	public static final int IN_PROCESS = 1;
	public static final String IN_PROCESS_STR = "1";
	public static final String OS_NAME_STR = "Android";
	
	public static final String CONNECTION_TYPE_STR_WIFI = "wifi";
	public static final String CONNECTION_TYPE_STR_MOBILE = "mobile";
	public static final String CONNECTION_TYPE_STR_OTHER = "other";
	
	public static final String THREAD_NAME_CURIO_REQ_PROC = "Curio Request Processor";
	public static final String THREAD_NAME_DB_REQ_PROC = "DB Request Processor";
	public static final int REQUEST_QUEUE_CAPACITY = 100;
	public static final int HONEYCOMB_SDK_INT = 11;
	public static final int HONEYCOMB_3_2_SDK_INT = 13;
	public static final int JELLYBEAN_4_2_SDK_INT = 17;
	public static final int GINGERBREAD_2_3_3_SDK_INT = 10;
	
	//Important, update this value on every version update!!!
	public static final String CURIO_SDK_VER = "1.06";
	
	public static final String SHARED_PREF_NAME_GCM = "gcm";
	public static final String SHARED_PREF_KEY_GCM_REGID = "gcm_registration_id";
	public static final String SHARED_PREF_KEY_APP_VERSION = "app_version";
	
	public static final String PAYLOAD_KEY_PUSH_TOKEN = "pId";
}
 public class CurioClientSettings {
	private static final String TAG = "CurioClientSettings";
	private static CurioClientSettings instance;
	private String apiKey;
	private int sessionTimeout;
	private boolean isPeriodicDispatchEnabled;
	private String gcmSenderId;
	private int dispatchPeriod;
	private String trackingCode;
	private String serverUrl;
	private int maxCachedActivityCount;
	private static boolean loggingEnabled = true;
	private boolean autoPushRegistration;

	/**
	 * Private constructor.
	 * 
	 * @param context
	 */
	private CurioClientSettings(Context context){
		ParameterLoader paramLoader = new ParameterLoader(context);
		
		loggingEnabled = paramLoader.getBoolean(Constants.CONFIG_PARAM_LOGGING_ENABLED, true);
		apiKey = paramLoader.getString(Constants.CONFIG_PARAM_API_KEY, null);
		gcmSenderId = paramLoader.getString(Constants.CONFIG_PARAM_GCM_SENDER_ID, null);
		trackingCode = paramLoader.getString(Constants.CONFIG_PARAM_TRACKING_CODE, null);
		serverUrl = paramLoader.getString(Constants.CONFIG_PARAM_SERVER_URL, null);
		autoPushRegistration = paramLoader.getBoolean(Constants.CONFIG_PARAM_AUTO_PUSH_REGISTRATION, false);

//		if(apiKey == null || trackingCode == null || serverUrl == null){
//			throw new IllegalStateException("api_key, tracking_code and server_url are required parameters, they can NOT be null. Please be sure that you defined those parameters in curio.xml config file.");
//		}
		
		sessionTimeout = paramLoader.getInteger(Constants.CONFIG_PARAM_SESSION_TIMEOUT, Constants.CONFIG_PARAM_DEFAULT_VALUE_SESSION_TIMEOUT_IN_MINUTES);
		isPeriodicDispatchEnabled = paramLoader.getBoolean(Constants.CONFIG_PARAM_PERIODIC_DISPATCH, false);
		dispatchPeriod = paramLoader.getInteger(Constants.CONFIG_PARAM_DISPATCH_PERIOD, Constants.CONFIG_PARAM_DEFAULT_VALUE_DISPATCH_PERIOD_IN_MINUTES);
		maxCachedActivityCount = paramLoader.getInteger(Constants.CONFIG_PARAM_MAX_CACHED_ACTIVITY_COUNT, Constants.CONFIG_PARAM_DEFAULT_VALUE_MAX_CACHED_ACTIVITY_COUNT);
		
		/**
		 * User defined max. cached activity count cannot be greater than defined max.
		 */
		if(maxCachedActivityCount > Constants.CONFIG_PARAM_MAX_VALUE_MAX_CACHED_ACTIVITY_COUNT){
			CurioLogger.w(TAG, "Max number of cached activity cannot be greater then " + Constants.CONFIG_PARAM_MAX_VALUE_MAX_CACHED_ACTIVITY_COUNT + ". Will be set to max value.");
			maxCachedActivityCount = Constants.CONFIG_PARAM_MAX_VALUE_MAX_CACHED_ACTIVITY_COUNT;
		}
	}
	
	/**
	 * Static instance getter.
	 * 
	 * @param context
	 * @return
	 */
	public static CurioClientSettings getInstance(Context context){
		if(instance == null){
			instance = new CurioClientSettings(context);
		}
		return instance;
	}

	
	public String getApiKey() {
		return apiKey;
	}

	public void setApiKey(String apiKey) {
		this.apiKey = apiKey;
	}

	/**
	 * Gets session timeout in minutes
	 * 
	 * @return
	 */
	public int getSessionTimeout() {
		return sessionTimeout;
	}

	public void setSessionTimeout(int sessionTimeout) {
		this.sessionTimeout = sessionTimeout;
	}

	public boolean isPeriodicDispatchEnabled() {
		return isPeriodicDispatchEnabled;
	}

	public void setPeriodicDispatchEnabled(boolean isPeriodicDispatchEnabled) {
		this.isPeriodicDispatchEnabled = isPeriodicDispatchEnabled;
	}

	public int getDispatchPeriod() {
		return dispatchPeriod;
	}

	public void setDispatchPeriod(int dispatchPeriod) {
		this.dispatchPeriod = dispatchPeriod;
	}

	public String getTrackingCode() {
		return trackingCode;
	}

	public void setTrackingCode(String trackingCode) {
		this.trackingCode = trackingCode;
	}

	public String getServerUrl() {
		return serverUrl;
	}
	
	public String getGcmSenderId() {
		return gcmSenderId;
	}

	public void setGcmSenderId(String gcmSenderId) {
		this.gcmSenderId = gcmSenderId;
	}

	public void setServerUrl(String serverUrl) {
		this.serverUrl = serverUrl;
	}

	public int getMaxCachedActivityCount() {
		return maxCachedActivityCount;
	}

	public void setMaxCachedActivityCount(int maxCachedActivityCount) {
		this.maxCachedActivityCount = maxCachedActivityCount;
	}

	public static boolean isLoggingEnabled() {
		return loggingEnabled;
	}

	public boolean isAutoPushRegistration() {
		return autoPushRegistration;
	}

	public void setAutoPushRegistration(boolean autoPushRegistration) {
		this.autoPushRegistration = autoPushRegistration;
	}

	@SuppressWarnings("static-access")
	public void setLoggingEnabled(boolean loggingEnabled) {
		this.loggingEnabled = loggingEnabled;
	}
	
}
 public class CurioDBHelper extends SQLiteOpenHelper {
	private static final String TAG = "CurioDBHelper";

	private static final String TEXT_TYPE = " TEXT";
	private static final String INTEGER_TYPE = " INTEGER";
	private static final String COMMA_SEP = ",";
	private static final String SQL_CREATE_TABLE_PERIODIC_DISPATCH = "CREATE TABLE " + CurioPeriodicDispatchEntry.TABLE_NAME + " (" + CurioPeriodicDispatchEntry._ID
			+ " INTEGER PRIMARY KEY AUTOINCREMENT," + CurioPeriodicDispatchEntry.COLUMN_NAME_DATA + TEXT_TYPE + COMMA_SEP + CurioPeriodicDispatchEntry.COLUMN_NAME_IN_PROCESS + INTEGER_TYPE
			+ COMMA_SEP + CurioPeriodicDispatchEntry.COLUMN_NAME_TIMESTAMP + INTEGER_TYPE + " )";

	private static final String SQL_CREATE_TABLE_OFFLINE_CACHE = "CREATE TABLE " + CurioOfflineCacheEntry.TABLE_NAME + " (" + CurioOfflineCacheEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
			+ CurioOfflineCacheEntry.COLUMN_NAME_SESSION_CODE + TEXT_TYPE + COMMA_SEP + CurioOfflineCacheEntry.COLUMN_NAME_DATA + TEXT_TYPE + COMMA_SEP + CurioOfflineCacheEntry.COLUMN_NAME_IN_PROCESS
			+ INTEGER_TYPE + COMMA_SEP + CurioOfflineCacheEntry.COLUMN_NAME_TIMESTAMP + INTEGER_TYPE + " )";

	private static final String SQL_DROP_TABLE_PERIODIC_DISPATCH = "DROP TABLE IF EXISTS " + CurioPeriodicDispatchEntry.TABLE_NAME;
	private static final String SQL_DROP_TABLE_OFFLINE_CACHE = "DROP TABLE IF EXISTS " + CurioOfflineCacheEntry.TABLE_NAME;

	private static final int DATABASE_VERSION = 1;
	private static final String DATABASE_NAME = "Curio.db";

	private static CurioDBHelper instance;

	/**
	 * Using atomic integer for concurrency!!!
	 */
	private AtomicInteger openDBCount = new AtomicInteger();

	private SQLiteDatabase database;

	private Context context;

	private CurioClient clientInstance;

	/**
	 * Variable to check if any periodic dispatch request stored.
	 */
	private boolean periodicDispatchRequestExists = true;

	/**
	 * Should be called first to create instance.
	 */
	public static synchronized CurioDBHelper createInstance(CurioClient clientInstance) {
		if (instance == null) {
			instance = new CurioDBHelper(clientInstance);
		}
		return instance;
	}

	/**
	 * Be sure that createInstance is called first.
	 * 
	 * Getter for singleton instance of SQLiteOpenHelper for Curio.
	 * 
	 * Same instance should be used through whole SDK.
	 * 
	 * @return
	 */
	public static synchronized CurioDBHelper getInstance() {
		if (instance == null) {
			throw new IllegalStateException("CurioDBHelper is not created. You should call createInstance method first.");
		}
		return instance;
	}

	/**
	 * Private constructor.
	 * 
	 * @param clientInstance
	 */
	private CurioDBHelper(CurioClient clientInstance) {
		super(clientInstance.getContext(), DATABASE_NAME, null, DATABASE_VERSION);
		this.context = clientInstance.getContext();
		this.clientInstance = clientInstance;
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		// Create periodic dispatch table. Requests (start/end screen, send event) are stored in this table for periodic dispatching.
		db.execSQL(SQL_CREATE_TABLE_PERIODIC_DISPATCH);

		// Create offline cache table. All requests are stored in this table for offline caching.
		db.execSQL(SQL_CREATE_TABLE_OFFLINE_CACHE);
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		db.execSQL(SQL_DROP_TABLE_PERIODIC_DISPATCH);
		db.execSQL(SQL_DROP_TABLE_OFFLINE_CACHE);
		onCreate(db);
	}

	@SuppressLint("NewApi")
	@Override
	public void onConfigure(SQLiteDatabase db) {
		/**
		 * SQLiteDatabase.enableWriteAheadLogging() is not available below API level 11.
		 */
		if (Build.VERSION.SDK_INT >= Constants.HONEYCOMB_SDK_INT) {
			// Enable write ahead logging of sqlite for concurrency.
			db.enableWriteAheadLogging();
		}else{
			CurioLogger.d(TAG, "Write ahead logging for SQLite is not available for this SDK Level. So cannot enable write ahead logging.");
		}
	}

	/**
	 * This method should always be used to get a DB connection since no need to open a DB connection if there is already an open one.
	 * 
	 * @return
	 */
	public synchronized SQLiteDatabase openDatabase() {
		if (openDBCount.incrementAndGet() == 1) {
			// Open new database
			this.database = getWritableDatabase();
		}
		return this.database;
	}

	/**
	 * This method should always be used to close a DB connection since if connection is used it should not be closed until nobody is using it.
	 */
	public synchronized void closeDatabase() {
		if (openDBCount.decrementAndGet() == 0) {
			// Close database
			this.database.close();
		}
	}

	/**
	 * Writes an periodic dispatch request to DB for later dispatch.
	 * 
	 * @param offlineRequest
	 * @return true if write operation is successful.
	 */
	public boolean persistOfflineRequestForPeriodicDispatch(OfflineRequest offlineRequest) {
		// To be on the safe side, set this param before inserting the actual data.
		periodicDispatchRequestExists = true;

		SQLiteDatabase db = null;

		try {
			db = openDatabase();

			ContentValues values = new ContentValues();

			/**
			 * Timestamp field is mandatory (for internal use not for server) for all requests stored in DB for ordered request fetching.
			 */
			values.put(CurioPeriodicDispatchEntry.COLUMN_NAME_TIMESTAMP, Long.toString((Long) offlineRequest.getParams().get(Constants.JSON_NODE_TIMESTAMP)));

			/**
			 * Generate JSON data.
			 */
			values.put(CurioPeriodicDispatchEntry.COLUMN_NAME_DATA, generateJSONData(offlineRequest.getParams()));

			/**
			 * Mark it as not in process
			 */
			values.put(CurioPeriodicDispatchEntry.COLUMN_NAME_IN_PROCESS, Constants.NOT_IN_PROCESS);

			long i = db.insert(CurioPeriodicDispatchEntry.TABLE_NAME, null, values);

			CurioLogger.d(TAG, "Row ID of newly inserted periodic dispatch request is " + i);
			return true;
		} catch (SQLiteException e) {
			CurioLogger.e(TAG, e.getMessage());
			return false;
		} finally {
			closeDatabase();
		}
	}

	/**
	 * Writes an offline request to DB for caching
	 * 
	 * @param offlineRequest
	 * 
	 * @return true if write operation is successful.
	 */
	public boolean persistOfflineRequestForCaching(OfflineRequest offlineRequest) {
		/**
		 * Check if max. offline cache size is reached.
		 * If yes, do not store offline requests anymore.
		 */
		if (hasMaxCacheSizeReached()) {
			CurioLogger.i(TAG, "Cache size limit has been reached. No offline request will be stored until device goes online and sends stored analytics to server.");
			return false;
		}

		SQLiteDatabase db = null;

		try {
			db = openDatabase();

			ContentValues values = new ContentValues();

			/**
			 * Timestamp field is mandatory (for internal use not for server) for all requests stored in DB for ordered request fetching.
			 */
			values.put(CurioOfflineCacheEntry.COLUMN_NAME_TIMESTAMP, Long.toString((Long) offlineRequest.getParams().get(Constants.JSON_NODE_TIMESTAMP)));

			/**
			 * Generate JSON data.
			 */
			values.put(CurioOfflineCacheEntry.COLUMN_NAME_DATA, generateJSONData(offlineRequest.getParams()));

			/**
			 * Mark it as not in process
			 */
			values.put(CurioOfflineCacheEntry.COLUMN_NAME_IN_PROCESS, Constants.NOT_IN_PROCESS);

			long i = db.insert(CurioOfflineCacheEntry.TABLE_NAME, null, values);
			CurioLogger.d(TAG, "Row ID of newly inserted offline request is " + i);
			return true;
		} catch (SQLiteException e) {
			CurioLogger.e(TAG, e.getMessage());
			return false;
		} finally {
			closeDatabase();
		}
	}

	/**
	 * Generates JSON "data" node string from parameter map.
	 * 
	 * @param params
	 * @return JSON string
	 */
	private String generateJSONData(Map<String, Object> params) {
		JSONObject json = new JSONObject();

		try {
			for (String key : params.keySet()) {
				Object paramValue = params.get(key);
				json.put(key, paramValue);
			}
		} catch (JSONException e) {
			CurioLogger.e(TAG, e.getMessage(), e);
		}

		return json.toString();
	}


	/**
	 * Deletes periodic dispatch data which are marked as "in process"
	 */
	public void deleteInProcessPeriodicRequests() {
		SQLiteDatabase db = null;
		try {
			db = openDatabase();

			String whereClause = CurioPeriodicDispatchEntry.COLUMN_NAME_IN_PROCESS + "=?";
			String[] whereArgs = new String[] { Constants.IN_PROCESS_STR };

			int i = db.delete(CurioPeriodicDispatchEntry.TABLE_NAME, whereClause, whereArgs);

			CurioLogger.d(TAG, i + " rows deleted, since their process completed.");
		} catch (Exception e) {
			CurioLogger.e(TAG, e.getMessage(), e);
		} finally {
			closeDatabase();
		}
	}

	/**
	 * Sets "in process" periodic dispatch data as "not in process"
	 */
	public void setInProcessPeriodicRequestsAsNotInProcess() {
		SQLiteDatabase db = null;

		try {
			db = openDatabase();

			ContentValues values = new ContentValues();

			values.put(CurioPeriodicDispatchEntry.COLUMN_NAME_IN_PROCESS, Constants.NOT_IN_PROCESS);

			String whereClause = CurioPeriodicDispatchEntry.COLUMN_NAME_IN_PROCESS + "=?";
			String[] whereArgs = new String[] { Constants.IN_PROCESS_STR };

			int i = db.update(CurioPeriodicDispatchEntry.TABLE_NAME, values, whereClause, whereArgs);

			CurioLogger.d(TAG, i + " rows updated as NOT in process until next periodic dispatch check.");
		} catch (Exception e) {
			CurioLogger.e(TAG, e.getMessage(), e);
		} finally {
			closeDatabase();
		}
	}


	/**
	 * Fetches stored requests from given table (periodic dispatch or offline cache)
	 * 
	 * @param tableName
	 * @return
	 */
	public String fetchStoredRequestsFromTable(String tableName) {
		SQLiteDatabase db = openDatabase();
		Cursor cursor = null;

		JSONArray array = null;

		try {
			/**
			 * The whole process will be in a transaction
			 */
			 db.beginTransaction();

			/**
			 * Fetch stored requests
			 */
			String selection = BaseOfflineEntryColumns.COLUMN_NAME_IN_PROCESS + "=?";
			String[] selectionArgs = new String[] { Constants.NOT_IN_PROCESS_STR };
			String orderBy = BaseOfflineEntryColumns.COLUMN_NAME_TIMESTAMP + " DESC";

			cursor = db.query(tableName, null, selection, selectionArgs, null, null, orderBy);

			if (cursor.moveToFirst()) {
				array = new JSONArray();
				do {
					String data = cursor.getString(cursor.getColumnIndex(BaseOfflineEntryColumns.COLUMN_NAME_DATA));
					try {
						array.put(new JSONObject(data));
					} catch (JSONException e) {
						CurioLogger.e(TAG, e.getMessage(), e);
					}
				} while (cursor.moveToNext());
			}

			/**
			 * If no request data stored, abort dispatch.
			 */
			if (array == null || array.length() == 0) {
				CurioLogger.d(TAG, "No stored activity found. Will check in " + CurioClientSettings.getInstance(context).getDispatchPeriod() + " min. again.");
				return null;
			}

			/**
			 * Set fetched requests as "in process"
			 */
			ContentValues values = new ContentValues();
			values.put(BaseOfflineEntryColumns.COLUMN_NAME_IN_PROCESS, Constants.IN_PROCESS);

			String whereClause = BaseOfflineEntryColumns.COLUMN_NAME_IN_PROCESS + "=?";
			String[] whereArgs = new String[] { Constants.NOT_IN_PROCESS_STR };

			int i = db.update(tableName, values, whereClause, whereArgs);

			CurioLogger.d(TAG, i + " rows updated as in process until they sent...");

			db.setTransactionSuccessful();
		} catch (Exception e1) {
			CurioLogger.e(TAG, e1.getMessage(), e1);
		} finally {
			db.endTransaction();
			
			if(cursor != null){
				cursor.close();
			}
			
			closeDatabase();
		}

		if(array == null || array.length() == 0){
			return null;
		}
		
		return array.toString();
	}

	/**
	 * Deletes offline requests which are marked as "in process"
	 */
	public void deleteInProcessOfflineRequests() {
		SQLiteDatabase db = null;
		try {
			db = openDatabase();

			String whereClause = CurioOfflineCacheEntry.COLUMN_NAME_IN_PROCESS + "=?";
			String[] whereArgs = new String[] { Constants.IN_PROCESS_STR };

			int i = db.delete(CurioOfflineCacheEntry.TABLE_NAME, whereClause, whereArgs);

			CurioLogger.d(TAG, i + " rows deleted, since their process completed.");

		} catch (Exception e) {
			CurioLogger.e(TAG, e.getMessage(), e);
		} finally {
			closeDatabase();
		}
	}

	/**
	 * Sets "in process" offline requests as "not in process"
	 */
	public void setInProcessOfflineRequestsAsNotInProcess() {
		SQLiteDatabase db = null;

		try {
			db = openDatabase();

			ContentValues values = new ContentValues();

			values.put(CurioOfflineCacheEntry.COLUMN_NAME_IN_PROCESS, Constants.NOT_IN_PROCESS);

			String whereClause = CurioOfflineCacheEntry.COLUMN_NAME_IN_PROCESS + "=?";
			String[] whereArgs = new String[] { Constants.IN_PROCESS_STR };

			int i = db.update(CurioOfflineCacheEntry.TABLE_NAME, values, whereClause, whereArgs);

			CurioLogger.d(TAG, i + " rows updated as NOT in process and will be send next time device is online");
		} catch (Exception e) {
			CurioLogger.e(TAG, e.getMessage(), e);
		} finally {
			closeDatabase();
		}
	}

	
	/**
	 * Converts and inserts all stored periodic dispatch data into offline request table.
	 */
	public void moveAllExistingPeriodicDispatchDataToOfflineTable() {
		
		/**
		 * Check if there is any stored periodic request.
		 */
		if (!periodicDispatchRequestExists) {
			return;
		}

		/**
		 * Check if offline cache is full or not.
		 */
		if (hasMaxCacheSizeReached()) {
			CurioLogger.i(TAG, "Cache capacity limit has been reached. No offline request will be stored until device goes online and sends stored analytics to server.");
		}

		SQLiteDatabase db = openDatabase();

		Cursor cursor = null;
		
		List<String[]> rowList = new ArrayList<String[]>();

		try {
			/**
			 * The whole process will be in a transaction
			 */
			 db.beginTransaction();

			/**
			 * Fetch stored requests
			 */
			String selection = CurioPeriodicDispatchEntry.COLUMN_NAME_IN_PROCESS + "=?";
			String[] selectionArgs = new String[] { Constants.NOT_IN_PROCESS_STR };
			String orderBy = CurioPeriodicDispatchEntry.COLUMN_NAME_TIMESTAMP + " DESC";

			cursor = db.query(CurioPeriodicDispatchEntry.TABLE_NAME, null, selection, selectionArgs, null, null, orderBy);

			if (cursor.moveToFirst()) {
				String[] row = new String[5];
				do {
					row[0] = cursor.getString(cursor.getColumnIndex(CurioPeriodicDispatchEntry.COLUMN_NAME_DATA));
					row[1] = cursor.getString(cursor.getColumnIndex(CurioPeriodicDispatchEntry.COLUMN_NAME_TIMESTAMP));

					rowList.add(row);
				} while (cursor.moveToNext());
			}

			/**
			 * Update them as in process...
			 */
			ContentValues values = new ContentValues();
			values.put(CurioPeriodicDispatchEntry.COLUMN_NAME_IN_PROCESS, Constants.IN_PROCESS);

			String whereClause = CurioPeriodicDispatchEntry.COLUMN_NAME_IN_PROCESS + "=?";
			String[] whereArgs = new String[] { Constants.NOT_IN_PROCESS_STR };

			int i = db.update(CurioPeriodicDispatchEntry.TABLE_NAME, values, whereClause, whereArgs);

			CurioLogger.d(TAG, i + " rows updated as in process until processing finishes...");

			/**
			 * Add fetched requests to offline table.
			 * 
			 * Also add session code to the requests while storing them on offline cache table.
			 */
			for (String[] row : rowList) {
				values = new ContentValues();
				values.put(CurioOfflineCacheEntry.COLUMN_NAME_SESSION_CODE, clientInstance.getSessionCode(false));
				values.put(CurioOfflineCacheEntry.COLUMN_NAME_DATA, row[0]);
				values.put(CurioOfflineCacheEntry.COLUMN_NAME_TIMESTAMP, row[1]);
				values.put(CurioOfflineCacheEntry.COLUMN_NAME_IN_PROCESS, Constants.NOT_IN_PROCESS);

				long rowId = db.insert(CurioOfflineCacheEntry.TABLE_NAME, null, values);
				CurioLogger.d(TAG, "Row ID of newly added offline request is " + rowId);
			}

			/**
			 * Delete all from periodic dispatch table
			 */
			deleteInProcessPeriodicRequests();
			periodicDispatchRequestExists = false;

			db.setTransactionSuccessful();
		} catch (Exception e1) {
			CurioLogger.e(TAG, e1.getMessage(), e1);
		} finally {
			db.endTransaction();
			
			if(cursor != null){
				cursor.close();
			}
			
			closeDatabase();
		}
	}

	/**
	 * Checks if offline cache size limit reached or not.
	 * 
	 * @return
	 */
	private boolean hasMaxCacheSizeReached() {
		int rowCount = getRowCount();

		if (rowCount < CurioClientSettings.getInstance(context).getMaxCachedActivityCount()) {
			return false;
		}

		return true;
	}

	/**
	 * Efficient way of getting row count.
	 * 
	 * @param tableName
	 * @return
	 */
	private int getRowCount() {
		int count = 0;
		SQLiteDatabase db = openDatabase();
		Cursor countCursor = null;

		try {
			countCursor = db.query(CurioOfflineCacheEntry.TABLE_NAME, new String[] { "count(*) AS count" }, null, null, null, null, null);

			countCursor.moveToFirst();
			count = countCursor.getInt(0);
		} catch (Exception e) {
			CurioLogger.e(TAG, e.getMessage(), e);
		} finally {
			if(countCursor != null){
				countCursor.close();
			}
			
			closeDatabase();
		}

		return count;
	}
}
 public class CurioLogger {
	public static void i(String tag, String string) {
		if (CurioClientSettings.isLoggingEnabled()) {
			android.util.Log.i(tag, string);
		}
	}

	public static void e(String tag, String string) {
		if (CurioClientSettings.isLoggingEnabled()) {
			android.util.Log.e(tag, string);
		}
	}

	public static void d(String tag, String string) {
		if (CurioClientSettings.isLoggingEnabled() && BuildConfig.DEBUG) {
			android.util.Log.d(tag, string);
		}
	}

	public static void v(String tag, String string) {
		if (CurioClientSettings.isLoggingEnabled() && BuildConfig.DEBUG) {
			android.util.Log.v(tag, string);
		}
	}

	public static void w(String tag, String string) {
		if (CurioClientSettings.isLoggingEnabled()) {
			android.util.Log.w(tag, string);
		}
	}

	public static void i(String tag, String string, Throwable t) {
		if (CurioClientSettings.isLoggingEnabled()) {
			android.util.Log.i(tag, string, t);
		}
	}

	public static void e(String tag, String string, Throwable t) {
		if (CurioClientSettings.isLoggingEnabled()) {
			android.util.Log.e(tag, string, t);
		}
	}

	public static void d(String tag, String string, Throwable t) {
		if (CurioClientSettings.isLoggingEnabled() && BuildConfig.DEBUG) {
			android.util.Log.d(tag, string, t);
		}
	}

	public static void v(String tag, String string, Throwable t) {
		if (CurioClientSettings.isLoggingEnabled() && BuildConfig.DEBUG) {
			android.util.Log.v(tag, string, t);
		}
	}

	public static void w(String tag, String string, Throwable t) {
		if (CurioClientSettings.isLoggingEnabled()) {
			android.util.Log.w(tag, string, t);
		}
	}
}
 public class CurioUtil {
	
//	/**
//	 * !!Legacy method from 8digits. May be removed!!
//	 * @param urlPrefix
//	 * @return
//	 */
//	public static String formatUrlPrefix(String urlPrefix) {
//		if (!urlPrefix.startsWith(Constants.HTTP) && !urlPrefix.startsWith(Constants.HTTPS))
//			urlPrefix = Constants.HTTPS + urlPrefix;
//
//		if (urlPrefix.endsWith(Constants.BACKSLASH))
//			urlPrefix = urlPrefix.substring(0, urlPrefix.length() - 1);
//
//		if (urlPrefix.endsWith(Constants.API))
//			urlPrefix = urlPrefix.substring(0, urlPrefix.length() - (Constants.API.length() + 1));
//
//		return urlPrefix;
//	}
	
	/**
	 * Generates a version 1 Universally Unique Identifier.
	 * 
	 * @return id String.
	 */
	public static String generateTimeBasedUUID(long timeStamp){
		return UUIDGenerator.generateTimeBasedUUID(timeStamp).toString();
	}
	
	/**
	 * Generates a version 4 Universally Unique Identifier.
	 * 
	 * @return
	 */
	public static String generateRandomUUID(){
		return UUID.randomUUID().toString();
	}
	
	/**
	 * Gets request type according to the request url.
	 * 
	 * @param url
	 * @return
	 */
	public static int getRequestType(String url){
		int type = -1;
		if(url.endsWith(Constants.SERVER_URL_SUFFIX_SESSION_START)){
			type = 0;
		}else if(url.endsWith(Constants.SERVER_URL_SUFFIX_SESSION_END)){
			type = 1;
		}else if(url.endsWith(Constants.SERVER_URL_SUFFIX_SCREEN_START)){
			type = 2;
		}else if(url.endsWith(Constants.SERVER_URL_SUFFIX_SCREEN_END)){
			type = 3;
		}else if(url.endsWith(Constants.SERVER_URL_SUFFIX_SEND_EVENT)){
			type = 4;
		}
		
		return type;
	}
}
 public class NetworkUtil {

	private static NetworkUtil instance;
	private boolean isConnected;
	private boolean previosConnectionState;
	private String connectionType;
	private INetworkConnectivityChangeListener listener;

	/**
	 * Should be called first to create instance.
	 */
	public static synchronized NetworkUtil createInstance(Context context, INetworkConnectivityChangeListener listener){
		if(instance == null){
			instance = new NetworkUtil(context, listener);
		}
		return instance;
	}

	/**
	 * Be sure that createInstance is called first.
	 * @return
	 */
	public static synchronized NetworkUtil getInstance(){
		if(instance == null){
			throw new IllegalStateException("NetworkUtil is not created. You should call createInstance method first.");
		}
		return instance;
	}
	
	/**
	 * Private constructor.
	 * 
	 * @param context
	 * @param listener 
	 */
	private NetworkUtil(Context context, INetworkConnectivityChangeListener listener) {
		this.listener = listener;
		
		setConnectivityState(context);
		
		context.getApplicationContext().registerReceiver(new BroadcastReceiver() {
			@Override
			public void onReceive(Context context, Intent intent) {
				setConnectivityState(context);
				
				if(previosConnectionState != isConnected()){
					previosConnectionState = isConnected();
					NetworkUtil.this.listener.networkConnectivityChanged(isConnected());
				}
			}
		}, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
	}

	/**
	 * Sets connectivity state.
	 * 
	 * @param context
	 */
	protected void setConnectivityState(Context context) {
		// Check if there is an active Internet connection.
		ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
		NetworkInfo activeNetwork = connMgr.getActiveNetworkInfo();
		setConnected(activeNetwork != null && activeNetwork.isConnectedOrConnecting());

		// Get connection type
		if (isConnected) {
			if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
				connectionType = Constants.CONNECTION_TYPE_STR_WIFI;
			} else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
				connectionType = Constants.CONNECTION_TYPE_STR_MOBILE;
			} else {
				connectionType = Constants.CONNECTION_TYPE_STR_OTHER;
			}
		}else{
			connectionType = "";
		}
	}

	private void setConnected(boolean isConnected) {
		this.isConnected = isConnected;
	}
	
	public boolean isConnected() {
		return isConnected;
	}
	
	public String getConnectionType() {
		return connectionType;
	}
}
 public class PushUtil {

	private static String TAG = "GCMHelper";

	/**
	 * Check the device to make sure it has the Google Play Services APK.
	 * 
	 */
	private static boolean checkPlayServices(Context context) {
		int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
		if (resultCode != ConnectionResult.SUCCESS) {
			CurioLogger.e(TAG, "This device does not support Google Play Services.");
			return false;
		}
		return true;
	}

	public static void checkForGCMRegistration(final Context context) {
		if (checkPlayServices(context)) {
			String gcmRegistrationId = getStoredRegistrationId(context);
			if (gcmRegistrationId == null) {
				Thread gcmRegisterThread = new Thread(new Runnable() {
					@Override
					public void run() {
						try {
							String registrationId = GoogleCloudMessaging.getInstance(context).register(CurioClient.getInstance().getStaticFeatureSet().getGcmSenderId());
							CurioLogger.d(TAG, "GCM Registration Id acquired: " + registrationId);
							storeRegistrationId(context, registrationId);
							CurioClient.getInstance().sendRegistrationId(context, registrationId);
						} catch (IOException e) {
							CurioLogger.i(TAG, "An error occured while registering/storing GCM registration id.", e);
						}
					}
				});
				gcmRegisterThread.start();
			}else {
				CurioClient.getInstance().sendRegistrationId(context, gcmRegistrationId);
			}
		}
	}

	/**
	 * Saves GCM registration id to shared prefs.
	 * 
	 * @param context
	 */
	private static void storeRegistrationId(Context context, String gcmRegistrationId) {
		SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SHARED_PREF_NAME_GCM, Context.MODE_PRIVATE);
		SharedPreferences.Editor editor = sharedPreferences.edit();
		editor.putString(Constants.SHARED_PREF_KEY_GCM_REGID, gcmRegistrationId);
		editor.putInt(Constants.SHARED_PREF_KEY_APP_VERSION, getAppVersion(context));
		editor.commit();
	}

	/**
	 * Gets stored GCM registration id from shared prefs.
	 * 
	 * @param context
	 */
	public static String getStoredRegistrationId(Context context) {
		SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SHARED_PREF_NAME_GCM, Context.MODE_PRIVATE);
		String registrationId = sharedPreferences.getString(Constants.SHARED_PREF_KEY_GCM_REGID, null);
		int currentAppVersion = sharedPreferences.getInt(Constants.SHARED_PREF_KEY_APP_VERSION, 0);
		if (getAppVersion(context) != currentAppVersion){
			return null;
		}
		return registrationId;
	}
	

	/**
	 * Deletes stored GCM registration id from shared prefs.
	 * 
	 * @param context
	 */
	public static void deleteRegistrationId(Context context) {
		SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SHARED_PREF_NAME_GCM, Context.MODE_PRIVATE);
		SharedPreferences.Editor editor = sharedPreferences.edit();
		editor.remove(Constants.SHARED_PREF_KEY_GCM_REGID);
		editor.remove(Constants.SHARED_PREF_KEY_APP_VERSION);
		editor.commit();
		CurioLogger.d(TAG, "Registration Id removed from shared prefs.");
	}

	private static int getAppVersion(Context context) {
		try {
			PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
			return packageInfo.versionCode;
		} catch (NameNotFoundException e) {
			// should never happen
			throw new RuntimeException("Could not get package name: " + e);
		}
	}
}
 public class VisitorCodeManager {
  private static String sID = null;
  private static final String INSTALLATION = "INSTALLATION";
private static final String TAG = "VisitorCodeManager";

  public synchronized static String id(String trackingCode, Context context) {
      if (sID == null) {  
          File installation = new File(context.getFilesDir(), INSTALLATION + "-" + trackingCode);
          try {
              if (!installation.exists())
                  writeInstallationFile(installation);
              sID = readInstallationFile(installation);
          } catch (Exception e) {
              throw new RuntimeException(e);
          }
      } else {
    	  CurioLogger.d(TAG, "Curio installation file exists.");
      }
      return sID;
  }

  private static String readInstallationFile(File installation) throws IOException {
      RandomAccessFile f = new RandomAccessFile(installation, "r");
      byte[] bytes = new byte[(int) f.length()];
      f.readFully(bytes);
      f.close();
      return new String(bytes);
  }

  private static void writeInstallationFile(File installation) throws IOException {
      FileOutputStream out = new FileOutputStream(installation);
      String id = UUID.randomUUID().toString();
      out.write(id.getBytes());
      out.close();
  }
}

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.http.HttpStatus;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Point;
import android.os.Build;
import android.os.Handler;
import android.telephony.TelephonyManager;
import android.view.Display;
import android.view.WindowManager;
import com.google.android.gms.ads.identifier.AdvertisingIdClient;
import com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException;
import com.turkcell.curio.model.OfflineRequest;
import com.turkcell.curio.model.OnlineRequest;
import com.turkcell.curio.model.Screen;
import com.turkcell.curio.utils.Constants;
import com.turkcell.curio.utils.CurioClientSettings;
import com.turkcell.curio.utils.CurioDBHelper;
import com.turkcell.curio.utils.CurioLogger;
import com.turkcell.curio.utils.CurioUtil;
import com.turkcell.curio.utils.NetworkUtil;
import com.turkcell.curio.utils.PushUtil;
import com.turkcell.curio.utils.VisitorCodeManager;
/*
 * Copyright (C) 2014 Turkcell
 * 
 * Created by Can Ciloglu on 10 Haz 2014
 *
 */
package com.turkcell.curio;





/**
 * Main entrance class for the curio client SDK. SDK can be used first by calling createInstance, and then can be called by calling getInstance method.
 * 
 * @author Can Ciloglu
 * 
 */
@SuppressLint("FieldGetter")
public class CurioClient implements INetworkConnectivityChangeListener {
	private static final String TAG = "CurioClient";
	private static boolean getParamsFromResource = true;
	public static CurioClient instance = null;

	private Context context;
	private String urlPrefix;
	private String sessionCode;
	private boolean isPeriodicDispatchEnabled;
	private int dispatchPeriod;
	private Map<String, Screen> contextHitcodeMap = new HashMap<String, Screen>();
	private CurioRequestProcessor curioRequestProcessor;
	private DBRequestProcessor dbRequestProcessor;
	private boolean endingSession = false;
	private boolean isOfflineRequestDispatchInProgress;
	private boolean isOfflineCachingOn;
	private Boolean initialConnectionState;
	private StaticFeatureSet staticFeatureSet;
	private boolean offlineReqExists = true;
	private Boolean isAdIdAvailable = null;
	private String pushMessageId = null;
	private String customId = null;
	private boolean isTriggeredByUnregisterRequest = false;

	protected int unauthCount = 0;
	protected boolean isParamLoadingFinished = false;
	protected boolean isSessionStartSent;
	private String tmpTrackingCode;
	private String tmpApiKey;
	private String tmpGcmSenderId;
	private boolean tmpAutoPushRegistration;
	private int tmpSessionTimeout;
	private boolean tmpLoggingEnabled;

	/**
	 * Be sure to call createInstance first.
	 * 
	 * @return
	 */
	public static synchronized CurioClient getInstance() {
		if (instance == null) {
			throw new IllegalStateException("CurioClient is not created. You should call createInstance method first.");
		}
		return instance;
	}

	/**
	 * Creates a client instance. Should be called first.
	 * 
	 * @param context
	 * @return
	 */
	public static synchronized CurioClient createInstance(Context context) {
		if (instance == null) {
			instance = new CurioClient(context);

			instance.startDBRequestProcessorThread();
			instance.startMainRequestProcessorThread();
		}

		return instance;
	}

	/**
	 * Gets and creates instance if needed.
	 * 
	 * @param context
	 * @return
	 */
	public static synchronized CurioClient getInstance(Context context) {
		if (instance == null) {
			createInstance(context);
		}

		return instance;
	}

	/**
	 * Gets and creates instance if needed.
	 * 
	 * @param context
	 * @return
	 */
	public static synchronized CurioClient getInstance(Context context, boolean loadParamsFromResource) {
		if (instance == null) {
			if (!loadParamsFromResource) {
				getParamsFromResource = loadParamsFromResource;
			}

			createInstance(context);
		}

		return instance;
	}

	/**
	 * Starts main request processor thread.
	 */
	private void startMainRequestProcessorThread() {
		curioRequestProcessor = new CurioRequestProcessor(this);
		new Thread(curioRequestProcessor, Constants.THREAD_NAME_CURIO_REQ_PROC).start();
	}

	/**
	 * Starts DB request processor thread.
	 */
	private void startDBRequestProcessorThread() {
		dbRequestProcessor = new DBRequestProcessor();
		new Thread(dbRequestProcessor, Constants.THREAD_NAME_DB_REQ_PROC).start();
	}

	/**
	 * Private constructor.
	 * 
	 * @param context
	 * @param getParamsFromResource
	 */
	private CurioClient(Context context) {
		this.setContext(context);

		NetworkUtil.createInstance(context, this);