diff --git a/.idea/libraries/crittercism.xml b/.idea/libraries/crittercism.xml deleted file mode 100644 index 7adc26c0e..000000000 --- a/.idea/libraries/crittercism.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/findbugs_annotations.xml b/.idea/libraries/findbugs_annotations.xml deleted file mode 100644 index 925fee97b..000000000 --- a/.idea/libraries/findbugs_annotations.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/api/src/com/todoroo/astrid/sync/SyncProvider.java b/api/src/com/todoroo/astrid/sync/SyncProvider.java index 60fc82ccf..93ffd44ef 100644 --- a/api/src/com/todoroo/astrid/sync/SyncProvider.java +++ b/api/src/com/todoroo/astrid/sync/SyncProvider.java @@ -389,7 +389,6 @@ public abstract class SyncProvider { * whether to display a dialog */ protected void handleException(String tag, Exception e, boolean displayError) { - //TODO: When Crittercism supports it, report error to them final Context context = ContextManager.getContext(); getUtilities().setLastError(e.toString(), ""); diff --git a/api/src/com/todoroo/astrid/sync/SyncV2Provider.java b/api/src/com/todoroo/astrid/sync/SyncV2Provider.java index 2d3c7d059..0eeaa8f5d 100644 --- a/api/src/com/todoroo/astrid/sync/SyncV2Provider.java +++ b/api/src/com/todoroo/astrid/sync/SyncV2Provider.java @@ -18,7 +18,6 @@ abstract public class SyncV2Provider { public class SyncExceptionHandler { public void handleException(String tag, Exception e, String type) { - //TODO: When Crittercism supports it, report error to them getUtilities().setLastError(e.toString(), type); // occurs when application was closed diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index 69d8e9d39..97148bc03 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -26,8 +26,6 @@ - - @@ -191,11 +189,6 @@ android:screenOrientation="portrait" android:theme="@android:style/Theme" /> - - - - - diff --git a/astrid/astrid.iml b/astrid/astrid.iml index 5d745536e..a4288446f 100644 --- a/astrid/astrid.iml +++ b/astrid/astrid.iml @@ -41,8 +41,6 @@ - - diff --git a/astrid/common-src/com/localytics/android/Constants.java b/astrid/common-src/com/localytics/android/Constants.java deleted file mode 100644 index 087cc19fa..000000000 --- a/astrid/common-src/com/localytics/android/Constants.java +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.localytics.android; - -import android.text.format.DateUtils; - -/** - * Build constants for the Localytics library. - *

- * This is not a public API. - */ -/* package */final class Constants -{ - - /** - * Version number of this library. This number is primarily important in terms of changes to the upload format. - */ - //@formatter:off - /* - * Version history: - * - * 1.6: Fixed network type reporting. Added reporting of app signature, device SDK level, device manufacturer, serial number. - * 2.0: New upload format. - */ - //@formatter:on - public static final String LOCALYTICS_CLIENT_LIBRARY_VERSION = "android_2.2"; //$NON-NLS-1$ - - /** - * The package name of the Localytics library. - */ - /* - * Note: This value cannot be changed without significant consequences to the data in the database. - */ - public static final String LOCALYTICS_PACKAGE_NAME = "com.localytics.android"; //$NON-NLS-1$ - - /** - * Maximum number of sessions to store on disk. - */ - public static final int MAX_NUM_SESSIONS = 10; - - /** - * Maximum number of attributes per event session. - */ - public static final int MAX_NUM_ATTRIBUTES = 10; - - /** - * Maximum characters in an event name or attribute key/value. - */ - public static final int MAX_NAME_LENGTH = 128; - - /** - * Milliseconds after which a session is considered closed and cannot be reattached to. - *

- * For example, if the user opens an app, presses home, and opens the app again in less than {@link #SESSION_EXPIRATION} - * milliseconds, that will count as one session rather than two sessions. - */ - public static long SESSION_EXPIRATION = 15 * DateUtils.SECOND_IN_MILLIS; - - /** - * logcat log tag - */ - public static final String LOG_TAG = "Localytics"; //$NON-NLS-1$ - - /** - * Boolean indicating whether logcat messages are enabled. - *

- * Before releasing a production version of an app, this should be set to false for privacy and performance reasons. When - * logging is enabled, sensitive information such as the device ID may be printed to the log. - */ - public static boolean IS_LOGGABLE = false; - - /** - * Flag indicating whether runtime method parameter checking is performed. - */ - public static boolean ENABLE_PARAMETER_CHECKING = true; - - /** - * Cached copy of the current Android API level - * - * @see DatapointHelper#getApiLevel() - */ - /*package*/ static final int CURRENT_API_LEVEL = DatapointHelper.getApiLevel(); - - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private Constants() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } -} diff --git a/astrid/common-src/com/localytics/android/DatapointHelper.java b/astrid/common-src/com/localytics/android/DatapointHelper.java deleted file mode 100755 index 95a7d143e..000000000 --- a/astrid/common-src/com/localytics/android/DatapointHelper.java +++ /dev/null @@ -1,392 +0,0 @@ -//@formatter:off -/** - * DatapointHelper.java Copyright (C) 2011 Char Software Inc., DBA Localytics This code is provided under the Localytics Modified - * BSD License. A copy of this license has been distributed in a file called LICENSE with this source code. Please visit - * www.localytics.com for more information. - */ -//@formatter:on - -package com.localytics.android; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import android.Manifest.permission; -import android.content.Context; -import android.content.pm.PackageManager; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.Build; -import android.telephony.TelephonyManager; -import android.util.Log; - -/** - * Provides a number of static functions to aid in the collection and formatting of datapoints. - *

- * Note: this is not a public API. - */ -/* package */final class DatapointHelper -{ - /** - * AndroidID known to be duplicated across many devices due to manufacturer bugs. - */ - private static final String INVALID_ANDROID_ID = "9774d56d682e549c"; //$NON-NLS-1$ - - /** - * The path to the device_id file in previous versions of the Localytics library - */ - private static final String LEGACY_DEVICE_ID_FILE = "/localytics/device_id"; //$NON-NLS-1$ - - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private DatapointHelper() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * @return current Android API level. - */ - /* package */static int getApiLevel() - { - try - { - // Although the Build.VERSION.SDK field has existed since API 1, it is deprecated and could be removed - // in the future. Therefore use reflection to retrieve it for maximum forward compatibility. - final Class buildClass = Build.VERSION.class; - final String sdkString = (String) buildClass.getField("SDK").get(null); //$NON-NLS-1$ - return Integer.parseInt(sdkString); - } - catch (final Exception e) - { - Log.w(Constants.LOG_TAG, "Caught exception", e); //$NON-NLS-1$ - - // Although probably not necessary, protects from the aforementioned deprecation - try - { - final Class buildClass = Build.VERSION.class; - return buildClass.getField("SDK_INT").getInt(null); //$NON-NLS-1$ - } - catch (final Exception ignore) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Caught exception", ignore); //$NON-NLS-1$ - } - } - } - - // Worse-case scenario, assume Cupcake - return 3; - } - - /** - * Gets a 1-way hashed value of the device's Android ID. This value is encoded using a SHA-256 one way hash and therefore - * cannot be used to determine what device this data came from. - * - * @param context The context used to access the settings resolver - * @return An 1-way hashed version of the {@link android.provider.Settings.Secure#ANDROID_ID}. May return null if an Android - * ID or the hashing algorithm is not available. - */ - public static String getAndroidIdHashOrNull(final Context context) - { - // Make sure a legacy version of the SDK didn't leave behind a device ID. - // If it did, this ID must be used to keep user counts accurate - final File fp = new File(context.getFilesDir() + LEGACY_DEVICE_ID_FILE); - if (fp.exists() && fp.length() > 0) - { - try - { - BufferedReader reader = null; - try - { - final char[] buf = new char[100]; - int numRead; - reader = new BufferedReader(new FileReader(fp), 128); - numRead = reader.read(buf); - final String deviceId = String.copyValueOf(buf, 0, numRead); - reader.close(); - return deviceId; - } - catch (final FileNotFoundException e) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Caught exception", e); //$NON-NLS-1$ - } - } - finally - { - if (null != reader) - { - reader.close(); - } - } - } - catch (final IOException e) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Caught exception", e); //$NON-NLS-1$ - } - } - } - - final String androidId = android.provider.Settings.Secure.getString(context.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID); - if (androidId == null || androidId.toLowerCase().equals(INVALID_ANDROID_ID)) - { - return null; - } - - return getSha256(androidId); - } - - /** - * Gets a 1-way hashed value of the device's unique serial number. This value is encoded using a SHA-256 one way hash and - * therefore cannot be used to determine what device this data came from. - *

- * Note: {@link android.os.Build#SERIAL} was introduced in SDK 9. For older SDKs, this method will return null. - * - * @return An 1-way hashed version of the {@link android.os.Build#SERIAL}. May return null if a serial or the hashing - * algorithm is not available. - */ - /* - * Suppress JavaDoc warnings because the {@link android.os.Build#SERIAL} fails when built with SDK 4. - */ - public static String getSerialNumberHashOrNull() - { - /* - * Obtain the device serial number using reflection, since serial number was added in SDK 9 - */ - String serialNumber = null; - if (Constants.CURRENT_API_LEVEL >= 9) - { - try - { - serialNumber = (String) Build.class.getField("SERIAL").get(null); //$NON-NLS-1$ - } - catch (final Exception e) - { - /* - * This should never happen, as SERIAL is a public field added in SDK 9. - */ - throw new RuntimeException(e); - } - } - - if (serialNumber == null) - { - return null; - } - - return getSha256(serialNumber); - } - - /** - * Gets the device's telephony ID (e.g. IMEI/MEID). - *

- * Note: this method will return null if {@link permission#READ_PHONE_STATE} is not available. This method will also return - * null on devices that do not have telephony. - * - * @param context The context used to access the phone state. - * @return An the {@link TelephonyManager#getDeviceId()}. Null if an ID is not available, or if - * {@link permission#READ_PHONE_STATE} is not available. - */ - public static String getTelephonyDeviceIdOrNull(final Context context) - { - if (Constants.CURRENT_API_LEVEL >= 8) - { - final Boolean hasTelephony = ReflectionUtils.tryInvokeInstance(context.getPackageManager(), "hasSystemFeature", new Class[] { String.class }, new Object[] { "android.hardware.telephony" }); //$NON-NLS-1$//$NON-NLS-2$ - - if (!hasTelephony.booleanValue()) - { - if (Constants.IS_LOGGABLE) - { - Log.i(Constants.LOG_TAG, "Device does not have telephony; cannot read telephony id"); //$NON-NLS-1$ - } - - return null; - } - } - - /* - * Note: Sometimes Android will deny a package READ_PHONE_STATE permissions, even if the package has the permission. It - * appears to be a race condition that occurs during installation. - */ - String id = null; - if (context.getPackageManager().checkPermission(permission.READ_PHONE_STATE, context.getPackageName()) == PackageManager.PERMISSION_GRANTED) - { - final TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - id = manager.getDeviceId(); - } - else - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Application does not have permission READ_PHONE_STATE; determining device id is not possible. Please consider requesting READ_PHONE_STATE in the AndroidManifest"); //$NON-NLS-1$ - } - } - - return id; - } - - /** - * Gets a 1-way hashed value of the device's IMEI/MEID ID. This value is encoded using a SHA-256 one way hash and cannot be - * used to determine what device this data came from. - *

- * Note: this method will return null if this is a non-telephony device. - *

- * Note: this method will return null if {@link permission#READ_PHONE_STATE} is not available. - * - * @param context The context used to access the phone state. - * @return An 1-way hashed version of the {@link TelephonyManager#getDeviceId()}. Null if an ID or the hashing algorithm is - * not available, or if {@link permission#READ_PHONE_STATE} is not available. - */ - public static String getTelephonyDeviceIdHashOrNull(final Context context) - { - final String id = getTelephonyDeviceIdOrNull(context); - - if (null == id) - { - return null; - } - - return getSha256(id); - } - - /** - * Determines the type of network this device is connected to. - * - * @param context the context used to access the device's WIFI - * @param telephonyManager The manager used to access telephony info - * @return The type of network, or unknown if the information is unavailable - */ - public static String getNetworkType(final Context context, final TelephonyManager telephonyManager) - { - if (context.getPackageManager().checkPermission(permission.ACCESS_WIFI_STATE, context.getPackageName()) == PackageManager.PERMISSION_GRANTED) - { - final NetworkInfo wifiInfo = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getNetworkInfo(ConnectivityManager.TYPE_WIFI); - if (wifiInfo != null && wifiInfo.isConnectedOrConnecting()) - { - return "wifi"; //$NON-NLS-1$ - } - } - else - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Application does not have permission ACCESS_WIFI_STATE; determining Wi-Fi connectivity is unavailable"); //$NON-NLS-1$ - } - } - - return "android_network_type_" + telephonyManager.getNetworkType(); //$NON-NLS-1$ - } - - /** - * Gets the device manufacturer's name. This is only available on SDK 4 or greater, so on SDK 3 this method returns the - * constant string "unknown". - * - * @return A string naming the manufacturer - */ - public static String getManufacturer() - { - String mfg = "unknown"; //$NON-NLS-1$ - if (Constants.CURRENT_API_LEVEL > 3) - { - try - { - final Class buildClass = Build.class; - mfg = (String) buildClass.getField("MANUFACTURER").get(null); //$NON-NLS-1$ - } - catch (final Exception ignore) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Caught exception", ignore); //$NON-NLS-1$ - } - } - } - return mfg; - } - - /** - * Gets the versionName of the application. - * - * @param context {@link Context}. Cannot be null. - * @return The application's version - */ - public static String getAppVersion(final Context context) - { - final PackageManager pm = context.getPackageManager(); - - try - { - final String versionName = pm.getPackageInfo(context.getPackageName(), 0).versionName; - - /* - * If there is no versionName in the Android Manifest, the versionName will be null. - */ - if (versionName == null) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "versionName was null--is a versionName attribute set in the Android Manifest?"); //$NON-NLS-1$ - } - - return "unknown"; //$NON-NLS-1$ - } - - return versionName; - } - catch (final PackageManager.NameNotFoundException e) - { - /* - * This should never occur--our own package must exist for this code to be running - */ - throw new RuntimeException(e); - } - } - - /** - * Helper method to generate a SHA-256 hash of a given String - * - * @param string String to hash. Cannot be null. - * @return hashed version of the string using SHA-256. - */ - /* package */static String getSha256(final String string) - { - if (Constants.ENABLE_PARAMETER_CHECKING) - { - if (null == string) - { - throw new IllegalArgumentException("string cannot be null"); //$NON-NLS-1$ - } - } - - try - { - final MessageDigest md = MessageDigest.getInstance("SHA-256"); //$NON-NLS-1$ - final byte[] digest = md.digest(string.getBytes("UTF-8")); //$NON-NLS-1$ - final BigInteger hashedNumber = new BigInteger(1, digest); - return hashedNumber.toString(16); - } - catch (final NoSuchAlgorithmException e) - { - throw new RuntimeException(e); - } - catch (final UnsupportedEncodingException e) - { - throw new RuntimeException(e); - } - } -} diff --git a/astrid/common-src/com/localytics/android/ExceptionHandler.java b/astrid/common-src/com/localytics/android/ExceptionHandler.java deleted file mode 100644 index 8f0f4c2e7..000000000 --- a/astrid/common-src/com/localytics/android/ExceptionHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.localytics.android; - -import android.util.Log; - -/** - * Exception handler for background threads used by the Localytics library. - *

- * Analytics are secondary to any other functions performed by an app, which means that analytics should never cause an app to - * crash. This handler therefore suppresses all uncaught exceptions from the Localytics library. - */ -/* package */final class ExceptionHandler implements Thread.UncaughtExceptionHandler -{ - @Override - public void uncaughtException(final Thread thread, final Throwable throwable) - { - /* - * Wrap all the work done by the exception handler in a try-catch. It would be ironic if this exception handler itself - * caused the parent process to crash. - */ - try - { - if (Constants.IS_LOGGABLE) - { - Log.e(Constants.LOG_TAG, "Localytics library threw an uncaught exception", throwable); //$NON-NLS-1$ - } - - // TODO: Upload uncaught exceptions so that we can fix them - } - catch (final Exception e) - { - if (Constants.IS_LOGGABLE) - { - Log.e(Constants.LOG_TAG, "Exception handler threw an exception", e); //$NON-NLS-1$ - } - } - } -} diff --git a/astrid/common-src/com/localytics/android/JsonObjects.java b/astrid/common-src/com/localytics/android/JsonObjects.java deleted file mode 100644 index f1f76e64b..000000000 --- a/astrid/common-src/com/localytics/android/JsonObjects.java +++ /dev/null @@ -1,574 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.localytics.android; - -import org.json.JSONArray; - -import android.Manifest.permission; - -/** - * Set of constants for building JSON objects that get sent to the Localytics web service. - */ -/* package */final class JsonObjects -{ - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private JsonObjects() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * Set of constants for the blob header JSON object. - */ - public static final class BlobHeader - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private BlobHeader() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * Data type for the JSON object. - * - * @see #VALUE_DATA_TYPE - */ - public static final String KEY_DATA_TYPE = "dt"; //$NON-NLS-1$ - - /** - * @see #KEY_DATA_TYPE - */ - public static final String VALUE_DATA_TYPE = "h"; //$NON-NLS-1$ - - /** - * Timestamp when the app was first launched and the persistent storage was created. Represented as seconds since the Unix - * Epoch. (Note: This is SECONDS and not milliseconds. This requires care, because Android represents time as - * milliseconds). - */ - public static final String KEY_PERSISTENT_STORAGE_CREATION_TIME_SECONDS = "pa"; //$NON-NLS-1$ - - /** - * Sequence number. A monotonically increasing count for each new blob. - */ - public static final String KEY_SEQUENCE_NUMBER = "seq"; //$NON-NLS-1$ - - /** - * A UUID for the blob. - */ - public static final String KEY_UNIQUE_ID = "u"; //$NON-NLS-1$ - - /** - * A JSON Object for attributes for the session. - */ - public static final String KEY_ATTRIBUTES = "attrs"; //$NON-NLS-1$ - - /** - * Attributes under {@link BlobHeader#KEY_ATTRIBUTES} - */ - public static final class Attributes - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private Attributes() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * Type: {@code String} - *

- * Data connection type. - */ - public static final String KEY_DATA_CONNECTION = "dac"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * Version name of the application, taken from the Android Manifest. - */ - public static final String KEY_CLIENT_APP_VERSION = "av"; //$NON-NLS-1$ - - /** - * Key which maps to the SHA-256 of the device's {@link android.provider.Settings.Secure#ANDROID_ID}. - */ - public static final String KEY_DEVICE_ANDROID_ID_HASH = "du"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- */ - public static final String KEY_DEVICE_COUNTRY = "dc"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * Manufacturer of the device (e.g. HTC, Samsung, Motorola, Kyocera, etc.) - */ - public static final String KEY_DEVICE_MANUFACTURER = "dma"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * Model of the device (e.g. dream, - */ - public static final String KEY_DEVICE_MODEL = "dmo"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * Android version (e.g. 1.6 or 2.3.4). - */ - public static final String KEY_DEVICE_OS_VERSION = "dov"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * Telephony ID of the device, if the device has telephony and the app has {@link permission#READ_PHONE_STATE}. - * Otherwise null. - */ - public static final String KEY_DEVICE_TELEPHONY_ID = "tdid"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * Platform of the device. For Android devices, this is always "android" - */ - public static final String KEY_DEVICE_PLATFORM = "dp"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * SHA-256 hash of the device's serial number. Only reported for Android 2.3 or later. Otherwise null. - */ - public static final String KEY_DEVICE_SERIAL_HASH = "dms"; //$NON-NLS-1$ - - /** - * Type: {@code int} - *

- * SDK compatibility level of the device. - * - * @see android.os.Build.VERSION#SDK - */ - public static final String KEY_DEVICE_SDK_LEVEL = "dsdk"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * SHA-256 hash of the device's Telephony ID, if the device has telephony and the app has - * {@link permission#READ_PHONE_STATE}. Otherwise null. - */ - public static final String KEY_DEVICE_TELEPHONY_ID_HASH = "dtidh"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * Country for the device's current locale settings - */ - public static final String KEY_LOCALE_COUNTRY = "dlc"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * Language for the device's current locale settings - */ - public static final String KEY_LOCALE_LANGUAGE = "dll"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * Api key - */ - public static final String KEY_LOCALYTICS_API_KEY = "au"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * Localytics library version - * - * @see Constants#LOCALYTICS_CLIENT_LIBRARY_VERSION - */ - public static final String KEY_LOCALYTICS_CLIENT_LIBRARY_VERSION = "lv"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * Data type for the JSON object. - * - * @see #VALUE_DATA_TYPE - */ - public static final String KEY_LOCALYTICS_DATA_TYPE = "dt"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * Network carrier of the device - */ - public static final String KEY_NETWORK_CARRIER = "nca"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- */ - public static final String KEY_NETWORK_COUNTRY = "nc"; //$NON-NLS-1$ - - /** - * @see #KEY_LOCALYTICS_DATA_TYPE - */ - @SuppressWarnings("hiding") - public static final String VALUE_DATA_TYPE = "a"; //$NON-NLS-1$ - - /** - * Value for the platform. - * - * @see #KEY_DEVICE_PLATFORM - */ - public static final String VALUE_PLATFORM = "Android"; //$NON-NLS-1$ - } - } - - /** - * Set of constants for the session open event. - */ - /* package */static final class SessionOpen - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private SessionOpen() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * Type: {@code String} - *

- * Data type for the JSON object. - * - * @see #VALUE_DATA_TYPE - */ - public static final String KEY_DATA_TYPE = "dt"; //$NON-NLS-1$ - - /** - * @see #KEY_DATA_TYPE - */ - public static final String VALUE_DATA_TYPE = "s"; //$NON-NLS-1$ - - /** - * Type: {@code long} - *

- * Epoch timestamp when the session was started in seconds. - */ - public static final String KEY_WALL_TIME_SECONDS = "ct"; //$NON-NLS-1$ - - /** - * Type: {@code long} - *

- * UUID of the event, which is the same thing as the session UUID - */ - public static final String KEY_EVENT_UUID = "u"; //$NON-NLS-1$ - - /** - * Type: {@code long} - *

- * Count for the number of sessions - */ - public static final String KEY_COUNT = "nth"; //$NON-NLS-1$ - } - - /** - * Set of constants for the session close event. - */ - /* package */static final class SessionClose - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private SessionClose() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * Type: {@code String} - *

- * Data type for the JSON object. - * - * @see #VALUE_DATA_TYPE - */ - public static final String KEY_DATA_TYPE = "dt"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * UUID of the event. - */ - public static final String KEY_EVENT_UUID = "u"; //$NON-NLS-1$ - - /** - * Type: {@code String[]} (technically, a JSON array of strings) - *

- * Ordered list of flow events that occurred - */ - public static final String KEY_FLOW_ARRAY = "fl"; //$NON-NLS-1$ - - /** - * Type: {@code long} - *

- * Epoch timestamp when the session was started - */ - public static final String KEY_SESSION_LENGTH_SECONDS = "ctl"; //$NON-NLS-1$ - - /** - * Type: {@code long} - *

- * Start time of the parent session - */ - public static final String KEY_SESSION_START_TIME = "ss"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * UUID of the session. - */ - public static final String KEY_SESSION_UUID = "su"; //$NON-NLS-1$ - - /** - * Type: {@code long} - *

- * Epoch timestamp when the session was started in seconds. - */ - public static final String KEY_WALL_TIME_SECONDS = "ct"; //$NON-NLS-1$ - - /** - * Data type for close events. - * - * @see #KEY_DATA_TYPE - */ - public static final String VALUE_DATA_TYPE = "c"; //$NON-NLS-1$ - } - - /** - * Set of constants for the session event event. - */ - /* package */static final class SessionEvent - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private SessionEvent() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * Type: {@code String} - *

- * Data type for the JSON object. - * - * @see #VALUE_DATA_TYPE - */ - public static final String KEY_DATA_TYPE = "dt"; //$NON-NLS-1$ - - /** - * Data type for application events. - * - * @see #KEY_DATA_TYPE - */ - public static final String VALUE_DATA_TYPE = "e"; //$NON-NLS-1$ - - /** - * Type: {@code long} - *

- * Epoch timestamp when the session was started in seconds. - */ - public static final String KEY_WALL_TIME_SECONDS = "ct"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * UUID of the session. - */ - public static final String KEY_SESSION_UUID = "su"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * UUID of the event. - */ - public static final String KEY_EVENT_UUID = "u"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * Name of the event. - */ - public static final String KEY_NAME = "n"; //$NON-NLS-1$ - - /** - * Type: {@code JSONObject}. - *

- * Maps to the attributes of the event. - *

- * Note that this key is optional. If it is present, it will point to a non-null value representing the attributes of the - * event. Otherwise the key will not exist, indicating the event had no attributes. - */ - public static final String KEY_ATTRIBUTES = "attrs"; //$NON-NLS-1$ - } - - /** - * Set of constants for the session opt in/out event - */ - /* package */static final class OptEvent - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private OptEvent() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * Type: {@code String} - *

- * Data type for the JSON object. - * - * @see #VALUE_DATA_TYPE - */ - public static final String KEY_DATA_TYPE = "dt"; //$NON-NLS-1$ - - /** - * Data type for opt in/out events. - * - * @see #KEY_DATA_TYPE - */ - public static final String VALUE_DATA_TYPE = "o"; //$NON-NLS-1$ - - /** - * Type: {@code long} - *

- * Epoch timestamp when the session was started in seconds. - */ - public static final String KEY_WALL_TIME_SECONDS = "ct"; //$NON-NLS-1$ - - /** - * Type: {@code String} - *

- * API key - */ - public static final String KEY_API_KEY = "u"; //$NON-NLS-1$ - - /** - * Type: {@code boolean} - *

- * True to opt-out. False to opt-in - */ - public static final String KEY_OPT = "out"; //$NON-NLS-1$ - } - - /** - * Set of constants for the session flow event. - */ - /* package */static final class EventFlow - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private EventFlow() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * Type: {@code String} - *

- * Data type for the JSON object. - * - * @see #VALUE_DATA_TYPE - */ - public static final String KEY_DATA_TYPE = "dt"; //$NON-NLS-1$ - - /** - * Type: {@code long} - *

- * UUID of the event, which is the same thing as the session UUID - */ - public static final String KEY_EVENT_UUID = "u"; //$NON-NLS-1$ - - /** - * Type: {@code long} - *

- * Start time of the parents session. - */ - public static final String KEY_SESSION_START_TIME = "ss"; //$NON-NLS-1$ - - /** - * Type: {@code Element[]} (technically a {@link JSONArray} of {@link Element} objects) - *

- * Ordered set of new flow elements that occurred since the last upload for this session. - */ - public static final String KEY_FLOW_NEW = "nw"; //$NON-NLS-1$ - - /** - * Type: {@code Element[]} (technically a {@link JSONArray} of {@link Element} objects) - *

- * Ordered set of old flow elements that occurred during all previous uploads for this session. - */ - public static final String KEY_FLOW_OLD = "od"; //$NON-NLS-1$ - - /** - * @see #KEY_DATA_TYPE - */ - public static final String VALUE_DATA_TYPE = "f"; //$NON-NLS-1$ - - /** - * Flow event element that indicates the type and name of the flow event. - */ - /* package */static final class Element - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private Element() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * A flow event that was due to an {@link SessionEvent}. - */ - public static final String TYPE_EVENT = "e"; //$NON-NLS-1$ - - /** - * A flow event that was due to a screen event. - */ - public static final String TYPE_SCREEN = "s"; //$NON-NLS-1$ - } - } -} diff --git a/astrid/common-src/com/localytics/android/LocalyticsProvider.java b/astrid/common-src/com/localytics/android/LocalyticsProvider.java deleted file mode 100644 index 7ed58bdd1..000000000 --- a/astrid/common-src/com/localytics/android/LocalyticsProvider.java +++ /dev/null @@ -1,1149 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.localytics.android; - -import java.io.File; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteQueryBuilder; -import android.provider.BaseColumns; -import android.util.Log; - -/** - * Implements the storage mechanism for the Localytics library. The interface and implementation are similar to a ContentProvider - * but modified to be better suited to a library. The interface is table-oriented, rather than Uri-oriented. - *

- * This is not a public API. - */ -/* package */final class LocalyticsProvider -{ - /** - * Name of the Localytics database, stored in the host application's {@link Context#getDatabasePath(String)}. - *

- * This is not a public API. - */ - /* - * This field is made package-accessible for unit testing. While the exact file name is arbitrary, this name was chosen to - * avoid collisions with app developers because it is sufficiently long and uses the Localytics package namespace. - */ - /* package */static final String DATABASE_FILE = "com.localytics.android.%s.sqlite"; //$NON-NLS-1$ - - /** - * Version of the database. - *

- * Version history: - *

    - *
  1. 1: Initial version
  2. - *
  3. 2: No format changes--just deleting bad data stranded in the database
  4. - *
- */ - private static final int DATABASE_VERSION = 2; - - /** - * Singleton instance of the {@link LocalyticsProvider}. Lazily initialized via {@link #getInstance(Context, String)}. - */ - private static final Map sLocalyticsProviderMap = new HashMap(); - - /** - * Intrinsic lock for synchronizing the initialization of {@link #sLocalyticsProviderMap}. - */ - /* - * Fun fact: Object[0] is more efficient that Object for an intrinsic lock - */ - private static final Object[] sLocalyticsProviderIntrinsicLock = new Object[0]; - - /** - * Unmodifiable set of valid table names. - */ - private static final Set sValidTables = Collections.unmodifiableSet(getValidTables()); - - /** - * SQLite database owned by the provider. - */ - private final SQLiteDatabase mDb; - - /** - * Obtains an instance of the Localytics Provider. Since the provider is a singleton object, only a single instance will be - * returned. - *

- * Note: if {@code context} is an instance of {@link android.test.RenamingDelegatingContext}, then a new object will be - * returned every time. This is not a "public" API, but is documented here as it aids unit testing. - * - * @param context Application context. Cannot be null. - * @param apiKey TODO - * @return An instance of {@link LocalyticsProvider}. - * @throws IllegalArgumentException if {@code context} is null - */ - public static LocalyticsProvider getInstance(final Context context, final String apiKey) - { - /* - * Note: Don't call getApplicationContext() on the context, as that would return a different context and defeat useful - * contexts such as RenamingDelegatingContext. - */ - - if (Constants.ENABLE_PARAMETER_CHECKING) - { - if (null == context) - { - throw new IllegalArgumentException("context cannot be null"); //$NON-NLS-1$ - } - } - - /* - * Although RenamingDelegatingContext is part of the Android SDK, the class isn't present in the ClassLoader unless the - * process is being run as a unit test. For that reason, comparing class names is necessary instead of doing instanceof. - */ - if (context.getClass().getName().equals("android.test.RenamingDelegatingContext")) //$NON-NLS-1$ - { - return new LocalyticsProvider(context, apiKey); - } - - synchronized (sLocalyticsProviderIntrinsicLock) - { - LocalyticsProvider provider = sLocalyticsProviderMap.get(apiKey); - - if (null == provider) - { - provider = new LocalyticsProvider(context, apiKey); - sLocalyticsProviderMap.put(apiKey, provider); - } - - return provider; - } - } - - /** - * Constructs a new Localytics Provider. - *

- * Note: this method may perform disk operations. - * - * @param context application context. Cannot be null. - */ - private LocalyticsProvider(final Context context, final String apiKey) - { - /* - * Rather than use the API key directly in the file name, it is put through SHA-256. The main reason for doing that is to - * decouple the requirements of the Android file system from the possible values of the API key string. There is a very, - * very small risk of a collision with the SHA-256 algorithm, but most clients will only have a single API key. Those with - * multiple keys may have 2 or 3, so the risk of a collision there is also very low. - */ - - mDb = new DatabaseHelper(context, String.format(DATABASE_FILE, DatapointHelper.getSha256(apiKey)), DATABASE_VERSION).getWritableDatabase(); - } - - /** - * Inserts a new record. - *

- * Note: this method may perform disk operations. - * - * @param tableName name of the table operate on. Must be one of the recognized tables. Cannot be null. - * @param values ContentValues to insert. Cannot be null. - * @return the {@link BaseColumns#_ID} of the inserted row or -1 if an error occurred. - * @throws IllegalArgumentException if tableName is null or not a valid table name. - * @throws IllegalArgumentException if values are null. - */ - public long insert(final String tableName, final ContentValues values) - { - if (Constants.ENABLE_PARAMETER_CHECKING) - { - if (!isValidTable(tableName)) - { - throw new IllegalArgumentException(String.format("tableName %s is invalid", tableName)); //$NON-NLS-1$ - } - - if (null == values) - { - throw new IllegalArgumentException("values cannot be null"); //$NON-NLS-1$ - } - } - - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("Insert table: %s, values: %s", tableName, values.toString())); //$NON-NLS-1$ - } - - final long result = mDb.insertOrThrow(tableName, null, values); - - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("Inserted row with new id %d", Long.valueOf(result))); //$NON-NLS-1$ - } - - return result; - } - - /** - * Performs a query. - *

- * Note: this method may perform disk operations. - * - * @param tableName name of the table operate on. Must be one of the recognized tables. Cannot be null. - * @param projection The list of columns to include. If null, then all columns are included by default. - * @param selection A filter to apply to all rows, like the SQLite WHERE clause. Passing null will query all rows. This param - * may contain ? symbols, which will be replaced by values from the {@code selectionArgs} param. - * @param selectionArgs An optional string array of replacements for ? symbols in {@code selection}. May be null. - * @param sortOrder How the rows in the cursor should be sorted. If null, then the sort order is undefined. - * @return Cursor for the query. To the receiver: Don't forget to call .close() on the cursor when finished with it. - * @throws IllegalArgumentException if tableName is null or not a valid table name. - */ - public Cursor query(final String tableName, final String[] projection, final String selection, final String[] selectionArgs, final String sortOrder) - { - if (Constants.ENABLE_PARAMETER_CHECKING) - { - if (!isValidTable(tableName)) - { - throw new IllegalArgumentException(String.format("tableName %s is invalid", tableName)); //$NON-NLS-1$ - } - } - - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("Query table: %s, projection: %s, selection: %s, selectionArgs: %s", tableName, Arrays.toString(projection), selection, Arrays.toString(selectionArgs))); //$NON-NLS-1$ - } - - final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(tableName); - - final Cursor result = qb.query(mDb, projection, selection, selectionArgs, null, null, sortOrder); - - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, "Query result is: " + DatabaseUtils.dumpCursorToString(result)); //$NON-NLS-1$ - } - - return result; - } - - /** - * Updates row(s). - *

- * Note: this method may perform disk operations. - * - * @param tableName name of the table operate on. Must be one of the recognized tables. Cannot be null. - * @param values A ContentValues mapping from column names (see the associated BaseColumns class for the table) to new column - * values. - * @param selection A filter to limit which rows are updated, like the SQLite WHERE clause. Passing null implies all rows. - * This param may contain ? symbols, which will be replaced by values from the {@code selectionArgs} param. - * @param selectionArgs An optional string array of replacements for ? symbols in {@code selection}. May be null. - * @return int representing the number of rows modified, which is in the range from 0 to the number of items in the table. - * @throws IllegalArgumentException if tableName is null or not a valid table name. - */ - public int update(final String tableName, final ContentValues values, final String selection, final String[] selectionArgs) - { - if (Constants.ENABLE_PARAMETER_CHECKING) - { - if (!isValidTable(tableName)) - { - throw new IllegalArgumentException(String.format("tableName %s is invalid", tableName)); //$NON-NLS-1$ - } - } - - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("Update table: %s, values: %s, selection: %s, selectionArgs: %s", tableName, values.toString(), selection, Arrays.toString(selectionArgs))); //$NON-NLS-1$ - } - - return mDb.update(tableName, values, selection, selectionArgs); - } - - /** - * Deletes row(s). - *

- * Note: this method may perform disk operations. - * - * @param tableName name of the table operate on. Must be one of the recognized tables. Cannot be null. - * @param selection A filter to limit which rows are deleted, like the SQLite WHERE clause. Passing null implies all rows. - * This param may contain ? symbols, which will be replaced by values from the {@code selectionArgs} param. - * @param selectionArgs An optional string array of replacements for ? symbols in {@code selection}. May be null. - * @return The number of rows affected, which is in the range from 0 to the number of items in the table. - * @throws IllegalArgumentException if tableName is null or not a valid table name. - */ - public int delete(final String tableName, final String selection, final String[] selectionArgs) - { - if (Constants.ENABLE_PARAMETER_CHECKING) - { - if (!isValidTable(tableName)) - { - throw new IllegalArgumentException(String.format("tableName %s is invalid", tableName)); //$NON-NLS-1$ - } - } - - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("Delete table: %s, selection: %s, selectionArgs: %s", tableName, selection, Arrays.toString(selectionArgs))); //$NON-NLS-1$ - } - - final int count; - if (null == selection) - { - count = mDb.delete(tableName, "1", null); //$NON-NLS-1$ - } - else - { - count = mDb.delete(tableName, selection, selectionArgs); - } - - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("Deleted %d rows", Integer.valueOf(count))); //$NON-NLS-1$ - } - - return count; - } - - /** - * Executes an arbitrary runnable with exclusive access to the database, essentially allowing an atomic transaction. - * - * @param runnable Runnable to execute. Cannot be null. - * @throws IllegalArgumentException if {@code runnable} is null - */ - /* - * This implementation is sort of a hack. In the future, it would be better model this after applyBatch() with a list of - * ContentProviderOperation objects. But that API isn't available until Android 2.0. - * - * An alternative implementation would have been to expose the begin/end transaction methods on the Provider object. While - * that would work, it makes it harder to transition to a ContentProviderOperation model in the future. - */ - public void runBatchTransaction(final Runnable runnable) - { - if (Constants.ENABLE_PARAMETER_CHECKING) - { - if (null == runnable) - { - throw new IllegalArgumentException("runnable cannot be null"); //$NON-NLS-1$ - } - } - - mDb.beginTransaction(); - try - { - runnable.run(); - - mDb.setTransactionSuccessful(); - } - finally - { - mDb.endTransaction(); - } - } - - /** - * Private helper to test whether a given table name is valid - * - * @param table name of a table to check. This param may be null. - * @return true if the table is valid, false if the table is invalid. If {@code table} is null, returns false. - */ - private static boolean isValidTable(final String table) - { - if (null == table) - { - return false; - } - - if (!sValidTables.contains(table)) - { - return false; - } - - return true; - } - - /** - * Private helper that knows all the tables that {@link LocalyticsProvider} can operate on. - * - * @return returns a set of the valid tables. - */ - private static Set getValidTables() - { - final HashSet tables = new HashSet(); - - tables.add(ApiKeysDbColumns.TABLE_NAME); - tables.add(AttributesDbColumns.TABLE_NAME); - tables.add(EventsDbColumns.TABLE_NAME); - tables.add(EventHistoryDbColumns.TABLE_NAME); - tables.add(SessionsDbColumns.TABLE_NAME); - tables.add(UploadBlobsDbColumns.TABLE_NAME); - tables.add(UploadBlobEventsDbColumns.TABLE_NAME); - - return tables; - } - - /** - * Private helper that deletes files from older versions of the Localytics library. - *

- * Note: This is a private method that is only made package-accessible for unit testing. - * - * @param context application context - * @throws IllegalArgumentException if {@code context} is null - */ - /* package */static void deleteOldFiles(final Context context) - { - if (Constants.ENABLE_PARAMETER_CHECKING) - { - if (null == context) - { - throw new IllegalArgumentException("context cannot be null"); //$NON-NLS-1$ - } - } - - deleteDirectory(new File(context.getFilesDir(), "localytics")); //$NON-NLS-1$ - } - - /** - * Private helper to delete a directory, regardless of whether the directory is empty. - * - * @param directory Directory or file to delete. Cannot be null. - * @return true if deletion was successful. False if deletion failed. - */ - private static boolean deleteDirectory(final File directory) - { - if (directory.exists() && directory.isDirectory()) - { - for (final String child : directory.list()) - { - final boolean success = deleteDirectory(new File(directory, child)); - if (!success) - { - return false; - } - } - } - - // The directory is now empty so delete it - return directory.delete(); - } - - /** - * A private helper class to open and create the Localytics SQLite database. - */ - private static final class DatabaseHelper extends SQLiteOpenHelper - { - /** - * Constant representing the SQLite value for true - */ - private static final String SQLITE_BOOLEAN_TRUE = "1"; //$NON-NLS-1$ - - /** - * Constant representing the SQLite value for false - */ - private static final String SQLITE_BOOLEAN_FALSE = "0"; //$NON-NLS-1$ - - /** - * @param context Application context. Cannot be null. - * @param name File name of the database. Cannot be null or empty. A database with this name will be opened in - * {@link Context#getDatabasePath(String)}. - * @param version version of the database. - */ - public DatabaseHelper(final Context context, final String name, final int version) - { - super(context, name, null, version); - } - - /** - * Initializes the tables of the database. - *

- * If an error occurs during initialization and an exception is thrown, {@link SQLiteDatabase#close()} will not be called - * by this method. That responsibility is left to the caller. - * - * @param db The database to perform post-creation processing on. db cannot not be null - * @throws IllegalArgumentException if db is null - */ - @Override - public void onCreate(final SQLiteDatabase db) - { - if (null == db) - { - throw new IllegalArgumentException("db cannot be null"); //$NON-NLS-1$ - } - - // api_keys table - db.execSQL(String.format("CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s TEXT UNIQUE NOT NULL, %s TEXT UNIQUE NOT NULL, %s INTEGER NOT NULL CHECK (%s >= 0), %s INTEGER NOT NULL CHECK(%s IN (%s, %s)));", ApiKeysDbColumns.TABLE_NAME, ApiKeysDbColumns._ID, ApiKeysDbColumns.API_KEY, ApiKeysDbColumns.UUID, ApiKeysDbColumns.CREATED_TIME, ApiKeysDbColumns.CREATED_TIME, ApiKeysDbColumns.OPT_OUT, ApiKeysDbColumns.OPT_OUT, SQLITE_BOOLEAN_FALSE, SQLITE_BOOLEAN_TRUE)); //$NON-NLS-1$ - - // sessions table - db.execSQL(String.format("CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s INTEGER REFERENCES %s(%s) NOT NULL, %s TEXT UNIQUE NOT NULL, %s INTEGER NOT NULL CHECK (%s >= 0), %s TEXT NOT NULL, %s TEXT NOT NULL, %s INTEGER NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT, %s TEXT, %s TEXT, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s TEXT);", SessionsDbColumns.TABLE_NAME, SessionsDbColumns._ID, SessionsDbColumns.API_KEY_REF, ApiKeysDbColumns.TABLE_NAME, ApiKeysDbColumns._ID, SessionsDbColumns.UUID, SessionsDbColumns.SESSION_START_WALL_TIME, SessionsDbColumns.SESSION_START_WALL_TIME, SessionsDbColumns.LOCALYTICS_LIBRARY_VERSION, SessionsDbColumns.APP_VERSION, SessionsDbColumns.ANDROID_VERSION, SessionsDbColumns.ANDROID_SDK, SessionsDbColumns.DEVICE_MODEL, SessionsDbColumns.DEVICE_MANUFACTURER, SessionsDbColumns.DEVICE_ANDROID_ID_HASH, SessionsDbColumns.DEVICE_TELEPHONY_ID, SessionsDbColumns.DEVICE_TELEPHONY_ID_HASH, SessionsDbColumns.DEVICE_SERIAL_NUMBER_HASH, SessionsDbColumns.LOCALE_LANGUAGE, SessionsDbColumns.LOCALE_COUNTRY, SessionsDbColumns.NETWORK_CARRIER, SessionsDbColumns.NETWORK_COUNTRY, SessionsDbColumns.NETWORK_TYPE, SessionsDbColumns.DEVICE_COUNTRY, SessionsDbColumns.LATITUDE, SessionsDbColumns.LONGITUDE)); //$NON-NLS-1$ - - // events table - db.execSQL(String.format("CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s INTEGER REFERENCES %s(%s) NOT NULL, %s TEXT UNIQUE NOT NULL, %s TEXT NOT NULL, %s INTEGER NOT NULL CHECK (%s >= 0), %s INTEGER NOT NULL CHECK (%s >= 0));", EventsDbColumns.TABLE_NAME, EventsDbColumns._ID, EventsDbColumns.SESSION_KEY_REF, SessionsDbColumns.TABLE_NAME, SessionsDbColumns._ID, EventsDbColumns.UUID, EventsDbColumns.EVENT_NAME, EventsDbColumns.REAL_TIME, EventsDbColumns.REAL_TIME, EventsDbColumns.WALL_TIME, EventsDbColumns.WALL_TIME)); //$NON-NLS-1$ - - // event_history table - /* - * Note: the events history should be using foreign key constrains on the upload blobs table, but that is currently - * disabled to simplify the implementation of the upload processing. - */ - db.execSQL(String.format("CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s INTEGER REFERENCES %s(%s) NOT NULL, %s TEXT NOT NULL CHECK(%s IN (%s, %s)), %s TEXT NOT NULL, %s INTEGER);", EventHistoryDbColumns.TABLE_NAME, EventHistoryDbColumns._ID, EventHistoryDbColumns.SESSION_KEY_REF, SessionsDbColumns.TABLE_NAME, SessionsDbColumns._ID, EventHistoryDbColumns.TYPE, EventHistoryDbColumns.TYPE, Integer.valueOf(EventHistoryDbColumns.TYPE_EVENT), Integer.valueOf(EventHistoryDbColumns.TYPE_SCREEN), EventHistoryDbColumns.NAME, EventHistoryDbColumns.PROCESSED_IN_BLOB)); //$NON-NLS-1$ - //db.execSQL(String.format("CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s INTEGER REFERENCES %s(%s) NOT NULL, %s TEXT NOT NULL CHECK(%s IN (%s, %s)), %s TEXT NOT NULL, %s INTEGER REFERENCES %s(%s));", EventHistoryDbColumns.TABLE_NAME, EventHistoryDbColumns._ID, EventHistoryDbColumns.SESSION_KEY_REF, SessionsDbColumns.TABLE_NAME, SessionsDbColumns._ID, EventHistoryDbColumns.TYPE, EventHistoryDbColumns.TYPE, Integer.valueOf(EventHistoryDbColumns.TYPE_EVENT), Integer.valueOf(EventHistoryDbColumns.TYPE_SCREEN), EventHistoryDbColumns.NAME, EventHistoryDbColumns.PROCESSED_IN_BLOB, UploadBlobsDbColumns.TABLE_NAME, UploadBlobsDbColumns._ID)); //$NON-NLS-1$ - - // attributes table - db.execSQL(String.format("CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s INTEGER REFERENCES %s(%s) NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL);", AttributesDbColumns.TABLE_NAME, AttributesDbColumns._ID, AttributesDbColumns.EVENTS_KEY_REF, EventsDbColumns.TABLE_NAME, EventsDbColumns._ID, AttributesDbColumns.ATTRIBUTE_KEY, AttributesDbColumns.ATTRIBUTE_VALUE)); //$NON-NLS-1$ - - // upload blobs - db.execSQL(String.format("CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s TEXT UNIQUE NOT NULL);", UploadBlobsDbColumns.TABLE_NAME, UploadBlobsDbColumns._ID, UploadBlobsDbColumns.UUID)); //$NON-NLS-1$ - - // upload events - db.execSQL(String.format("CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s INTEGER REFERENCES %s(%s) NOT NULL, %s INTEGER REFERENCES %s(%s) NOT NULL);", UploadBlobEventsDbColumns.TABLE_NAME, UploadBlobEventsDbColumns._ID, UploadBlobEventsDbColumns.UPLOAD_BLOBS_KEY_REF, UploadBlobsDbColumns.TABLE_NAME, UploadBlobsDbColumns._ID, UploadBlobEventsDbColumns.EVENTS_KEY_REF, EventsDbColumns.TABLE_NAME, EventsDbColumns._ID)); //$NON-NLS-1$ - } - - @Override - public void onOpen(final SQLiteDatabase db) - { - super.onOpen(db); - - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("SQLite library version is: %s", DatabaseUtils.stringForQuery(db, "select sqlite_version()", null))); //$NON-NLS-1$//$NON-NLS-2$ - } - - if (!db.isReadOnly()) - { - /* - * Enable foreign key support - */ - db.execSQL("PRAGMA foreign_keys = ON;"); //$NON-NLS-1$ - - // if (Constants.IS_LOGGABLE) - // { - // try - // { - // final String result1 = DatabaseUtils.stringForQuery(db, "PRAGMA foreign_keys;", null); //$NON-NLS-1$ - // Log.v(Constants.LOG_TAG, String.format("Foreign keys support result was: %s", result1)); //$NON-NLS-1$ - // } - // catch (final SQLiteDoneException e) - // { - // Log.w(Constants.LOG_TAG, e); - // } - // } - } - } - - @Override - public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) - { - if (1 == oldVersion) - { - // delete stranded sessions that don't have any events - Cursor sessionsCursor = null; - try - { - sessionsCursor = db.query(SessionsDbColumns.TABLE_NAME, new String[] - { SessionsDbColumns._ID }, null, null, null, null, null); - - while (sessionsCursor.moveToNext()) - { - Cursor eventsCursor = null; - try - { - String sessionId = Long.toString(sessionsCursor.getLong(sessionsCursor.getColumnIndexOrThrow(SessionsDbColumns._ID))); - eventsCursor = db.query(EventsDbColumns.TABLE_NAME, new String[] - { EventsDbColumns._ID }, String.format("%s = ?", EventsDbColumns.SESSION_KEY_REF), new String[] //$NON-NLS-1$ - { sessionId }, null, null, null); - - if (eventsCursor.getCount() == 0) - { - db.delete(SessionsDbColumns.TABLE_NAME, String.format("%s = ?", SessionsDbColumns._ID), new String[] { sessionId }); //$NON-NLS-1$ - } - } - finally - { - if (null != eventsCursor) - { - eventsCursor.close(); - eventsCursor = null; - } - } - } - } - finally - { - if (null != sessionsCursor) - { - sessionsCursor.close(); - sessionsCursor = null; - } - } - } - } - - // @Override - // public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) - // { - // } - } - - /** - * Table for the API keys used and the opt-out preferences for each API key. - *

- * This is not a public API. - */ - public static final class ApiKeysDbColumns implements BaseColumns - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private ApiKeysDbColumns() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * SQLite table name - */ - public static final String TABLE_NAME = "api_keys"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * The Localytics API key. - *

- * Constraints: This column is unique and cannot be null. - */ - public static final String API_KEY = "api_key"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * A UUID for the installation. - *

- * Constraints: This column is unique and cannot be null. - */ - public static final String UUID = "uuid"; //$NON-NLS-1$ - - /** - * TYPE: {@code boolean} - *

- * A flag indicating whether the user has opted out of data collection. - *

- * Constraints: This column must be in the set {0, 1} and cannot be null. - */ - public static final String OPT_OUT = "opt_out"; //$NON-NLS-1$ - - /** - * TYPE: {@code long} - *

- * A long representing the {@link System#currentTimeMillis()} when the row was created. Once created, this row will not be - * modified. - *

- * Constraints: This column must be >=0. This column cannot be null. - */ - public static final String CREATED_TIME = "created_time"; //$NON-NLS-1$ - } - - /** - * Database table for the session attributes. There is a one-to-many relationship between one event in the - * {@link EventsDbColumns} table and the many attributes associated with that event. - *

- * This is not a public API. - */ - public static final class AttributesDbColumns implements BaseColumns - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private AttributesDbColumns() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * SQLite table name - */ - public static final String TABLE_NAME = "attributes"; //$NON-NLS-1$ - - /** - * TYPE: {@code long} - *

- * A one-to-many relationship with {@link EventsDbColumns#_ID}. - *

- * Constraints: This is a foreign key with the {@link EventsDbColumns#_ID} column. This cannot be null. - */ - public static final String EVENTS_KEY_REF = "events_key_ref"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * String representing the key name of the attribute. - *

- * Constraints: This cannot be null. - */ - public static final String ATTRIBUTE_KEY = "attribute_key"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * String representing the value of the attribute. - *

- * Constraints: This cannot be null. - */ - public static final String ATTRIBUTE_VALUE = "attribute_value"; //$NON-NLS-1$ - - } - - /** - * Database table for the session events. There is a one-to-many relationship between one session data entry in the - * {@link SessionsDbColumns} table and the many events associated with that session. - *

- * This is not a public API. - */ - public static final class EventsDbColumns implements BaseColumns - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private EventsDbColumns() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * SQLite table name - */ - public static final String TABLE_NAME = "events"; //$NON-NLS-1$ - - /** - * TYPE: {@code long} - *

- * A one-to-many relationship with {@link SessionsDbColumns#_ID}. - *

- * Constraints: This is a foreign key with the {@link SessionsDbColumns#_ID} column. This cannot be null. - */ - public static final String SESSION_KEY_REF = "session_key_ref"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * Unique ID of the event, as generated from {@link java.util.UUID}. - *

- * Constraints: This is unique and cannot be null. - */ - public static final String UUID = "uuid"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * String representing the name of the event. - *

- * Constraints: This cannot be null. - */ - public static final String EVENT_NAME = "event_name"; //$NON-NLS-1$ - - /** - * TYPE: {@code long} - *

- * A long representing the {@link android.os.SystemClock#elapsedRealtime()} when the event occurred. - *

- * Constraints: This column must be >=0. This column cannot be null. - */ - public static final String REAL_TIME = "real_time"; //$NON-NLS-1$ - - /** - * TYPE: {@code long} - *

- * A long representing the {@link System#currentTimeMillis()} when the event occurred. - *

- * Constraints: This column must be >=0. This column cannot be null. - */ - public static final String WALL_TIME = "wall_time"; //$NON-NLS-1$ - - } - - /** - * Database table for tracking the history of events and screens. There is a one-to-many relationship between one session data - * entry in the {@link SessionsDbColumns} table and the many historical events associated with that session. - *

- * This is not a public API. - */ - public static final class EventHistoryDbColumns implements BaseColumns - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private EventHistoryDbColumns() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * SQLite table name - */ - public static final String TABLE_NAME = "event_history"; //$NON-NLS-1$ - - /** - * TYPE: {@code long} - *

- * A one-to-many relationship with {@link SessionsDbColumns#_ID}. - *

- * Constraints: This is a foreign key with the {@link SessionsDbColumns#_ID} column. This cannot be null. - */ - public static final String SESSION_KEY_REF = "session_key_ref"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * Unique ID of the event, as generated from {@link java.util.UUID}. - *

- * Constraints: This is unique and cannot be null. - */ - public static final String TYPE = "type"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * String representing the name of the screen or event. - *

- * Constraints: This cannot be null. - */ - public static final String NAME = "name"; //$NON-NLS-1$ - - /** - * TYPE: {@code boolean} - *

- * Foreign key to the upload blob that this event was processed in. May be null indicating that this event wasn't - * processed yet. - */ - public static final String PROCESSED_IN_BLOB = "processed_in_blob"; //$NON-NLS-1$ - - /** - * Type value for {@link #TYPE} indicates an event event. - */ - public static final int TYPE_EVENT = 0; - - /** - * Type value for {@link #TYPE} that indicates a screen event. - */ - public static final int TYPE_SCREEN = 1; - } - - /** - * Database table for the session data. There is a one-to-many relationship between one API key entry in the - * {@link ApiKeysDbColumns} table and many sessions for that API key. - *

- * This is not a public API. - */ - public static final class SessionsDbColumns implements BaseColumns - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private SessionsDbColumns() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * SQLite table name - */ - public static final String TABLE_NAME = "sessions"; //$NON-NLS-1$ - - /** - * TYPE: {@code long} - *

- * A one-to-one relationship with {@link ApiKeysDbColumns#_ID}. - *

- * Constraints: This is a foreign key with the {@link ApiKeysDbColumns#_ID} column. This cannot be null. - */ - public static final String API_KEY_REF = "api_key_ref"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * Unique ID of the event, as generated from {@link java.util.UUID}. - *

- * Constraints: This is unique and cannot be null. - */ - public static final String UUID = "uuid"; //$NON-NLS-1$ - - /** - * TYPE: {@code long} - *

- * The wall time when the session started. - *

- * Constraints: This column must be >=0. This column cannot be null. - */ - /* - * Note: While this same information is encoded in {@link EventsDbColumns#WALL_TIME} for the session open event, that row - * may not be available when an upload occurs and the upload needs to compute the duration of the session. - */ - public static final String SESSION_START_WALL_TIME = "session_start_wall_time"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * Version of the Localytics client library. - * - * @see Constants#LOCALYTICS_CLIENT_LIBRARY_VERSION - */ - public static final String LOCALYTICS_LIBRARY_VERSION = "localytics_library_version"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * String representing the app's versionName - *

- * Constraints: This cannot be null. - */ - public static final String APP_VERSION = "app_version"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * String representing the version of Android - *

- * Constraints: This cannot be null. - */ - public static final String ANDROID_VERSION = "android_version"; //$NON-NLS-1$ - - /** - * TYPE: {@code int} - *

- * Integer the Android SDK - *

- * Constraints: Must be an integer and cannot be null. - * - * @see android.os.Build.VERSION#SDK - */ - public static final String ANDROID_SDK = "android_sdk"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * String representing the device model - *

- * Constraints: None - * - * @see android.os.Build#MODEL - */ - public static final String DEVICE_MODEL = "device_model"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * String representing the device manufacturer - *

- * Constraints: None - * - * @see android.os.Build#MANUFACTURER - */ - public static final String DEVICE_MANUFACTURER = "device_manufacturer"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * String representing a hash of the device Android ID - *

- * Constraints: None - * - * @see android.provider.Settings.Secure#ANDROID_ID - */ - public static final String DEVICE_ANDROID_ID_HASH = "device_android_id_hash"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * String representing the telephony ID of the device. May be null for non-telephony devices. May also be null if the - * parent application doesn't have {@link android.Manifest.permission#READ_PHONE_STATE}. - *

- * Constraints: None - * - * @see android.telephony.TelephonyManager#getDeviceId() - */ - public static final String DEVICE_TELEPHONY_ID = "device_telephony_id"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * String representing a hash of the telephony ID of the device. May be null for non-telephony devices. May also be null - * if the parent application doesn't have {@link android.Manifest.permission#READ_PHONE_STATE}. - *

- * Constraints: None - * - * @see android.telephony.TelephonyManager#getDeviceId() - */ - public static final String DEVICE_TELEPHONY_ID_HASH = "device_telephony_id_hash"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * String representing a hash of the the serial number of the device. May be null for some telephony devices. - *

- * Constraints: None - */ - public static final String DEVICE_SERIAL_NUMBER_HASH = "device_serial_number_hash"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * Represents the locale language of the device. - *

- * Constraints: Cannot be null. - */ - public static final String LOCALE_LANGUAGE = "locale_language"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * Represents the locale country of the device. - *

- * Constraints: Cannot be null. - */ - public static final String LOCALE_COUNTRY = "locale_country"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * Represents the locale country of the device, according to the SIM card. - *

- * Constraints: Cannot be null. - */ - public static final String DEVICE_COUNTRY = "device_country"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * Represents the network carrier of the device. May be null for non-telephony devices. - *

- * Constraints: None - */ - public static final String NETWORK_CARRIER = "network_carrier"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * Represents the network country of the device. May be null for non-telephony devices. - *

- * Constraints: None - */ - public static final String NETWORK_COUNTRY = "network_country"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * Represents the primary network connection type for the device. This could be any type, including Wi-Fi, various cell - * networks, Ethernet, etc. - *

- * Constraints: None - * - * @see android.telephony.TelephonyManager - */ - public static final String NETWORK_TYPE = "network_type"; //$NON-NLS-1$ - - /** - * TYPE: {@code double} - *

- * Represents the latitude of the device. May be null if no longitude is known. - *

- * Constraints: None - */ - public static final String LATITUDE = "latitude"; //$NON-NLS-1$ - - /** - * TYPE: {@code double} - *

- * Represents the longitude of the device. May be null if no longitude is known. - *

- * Constraints: None - */ - public static final String LONGITUDE = "longitude"; //$NON-NLS-1$ - - } - - /** - * Database table for the events associated with a given upload blob. There is a one-to-many relationship between one upload - * blob in the {@link UploadBlobsDbColumns} table and the blob events. There is a one-to-one relationship between each blob - * event entry and the actual events in the {@link EventsDbColumns} table. * - *

- * This is not a public API. - */ - public static final class UploadBlobEventsDbColumns implements BaseColumns - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private UploadBlobEventsDbColumns() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * SQLite table name - */ - public static final String TABLE_NAME = "upload_blob_events"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * A one-to-many relationship with {@link UploadBlobsDbColumns#_ID}. - *

- * Constraints: This is a foreign key with the {@link UploadBlobsDbColumns#_ID} column. This cannot be null. - */ - public static final String UPLOAD_BLOBS_KEY_REF = "upload_blobs_key_ref"; //$NON-NLS-1$ - - /** - * TYPE: {@code long} - *

- * A one-to-one relationship with {@link EventsDbColumns#_ID}. - *

- * Constraints: This is a foreign key with the {@link EventsDbColumns#_ID} column. This cannot be null. - */ - public static final String EVENTS_KEY_REF = "events_key_ref"; //$NON-NLS-1$ - } - - /** - * Database table for the upload blobs. Logically, a blob owns many events. In terms of the implementation, some indirection - * is introduced by a blob having a one-to-many relationship with {@link UploadBlobsDbColumns} and - * {@link UploadBlobsDbColumns} having a one-to-one relationship with {@link EventsDbColumns} - *

- * This is not a public API. - */ - public static final class UploadBlobsDbColumns implements BaseColumns - { - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private UploadBlobsDbColumns() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * SQLite table name - */ - public static final String TABLE_NAME = "upload_blobs"; //$NON-NLS-1$ - - /** - * TYPE: {@code String} - *

- * Unique ID of the upload blob, as generated from {@link java.util.UUID}. - *

- * Constraints: This is unique and cannot be null. - */ - public static final String UUID = "uuid"; //$NON-NLS-1$ - - } -} diff --git a/astrid/common-src/com/localytics/android/LocalyticsSession.java b/astrid/common-src/com/localytics/android/LocalyticsSession.java deleted file mode 100755 index 3ef2f0881..000000000 --- a/astrid/common-src/com/localytics/android/LocalyticsSession.java +++ /dev/null @@ -1,2765 +0,0 @@ -// @formatter:off -/* - * LocalyticsSession.java Copyright (C) 2011 Char Software Inc., DBA Localytics This code is provided under the Localytics - * Modified BSD License. A copy of this license has been distributed in a file called LICENSE with this source code. Please visit - * www.localytics.com for more information. - */ -// @formatter:on - -package com.localytics.android; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeMap; -import java.util.UUID; -import java.util.zip.GZIPOutputStream; - -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.Manifest.permission; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.CursorJoiner; -import android.os.Build; -import android.os.Build.VERSION; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.SystemClock; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.util.Log; - -import com.localytics.android.JsonObjects.BlobHeader; -import com.localytics.android.LocalyticsProvider.ApiKeysDbColumns; -import com.localytics.android.LocalyticsProvider.AttributesDbColumns; -import com.localytics.android.LocalyticsProvider.EventHistoryDbColumns; -import com.localytics.android.LocalyticsProvider.EventsDbColumns; -import com.localytics.android.LocalyticsProvider.SessionsDbColumns; -import com.localytics.android.LocalyticsProvider.UploadBlobEventsDbColumns; -import com.localytics.android.LocalyticsProvider.UploadBlobsDbColumns; - -/** - * This class manages creating, collecting, and uploading a Localytics session. Please see the following guides for information on - * how to best use this library, sample code, and other useful information: - *

- *

- * Permissions required: - *

    - *
  • {@link permission#INTERNET}
  • - Necessary to upload data to the webservice. - *
- * Permissions recommended: - *
    - *
  • {@link permission#ACCESS_WIFI_STATE}
  • - Without this users connecting via Wi-Fi will show up as having a connection - * type of 'unknown' on the webservice - *
- *

- * This library will create a database called "com.android.localytics.sqlite" within the host application's - * {@link Context#getDatabasePath(String)} directory. For security, this file directory will be created - * {@link Context#MODE_PRIVATE}. The host application must not modify this database file. If the host application implements a - * backup/restore mechanism, such as {@code android.app.backup.BackupManager}, the host application should not worry about backing - * up the data in the Localytics database. - *

- *

- * This library is thread-safe but is not multi-process safe. Unless the application explicitly uses different process attributes - * in the Android Manifest, this is not an issue. - *

- * Best Practices - *
    - *
  • Instantiate and open a {@link LocalyticsSession} object in {@code Activity#onCreate(Bundle)}. This will cause every new - * Activity displayed to reconnect to any running session.
  • - *
  • Consider also performing {@link #upload()} in {@code Activity#onCreate(Bundle)}. This makes it more likely for the upload - * to complete before the Activity is finished, and also causes the upload to start before the user has a chance to begin any data - * intensive actions of his own.
  • - *
  • Close the session in {@code Activity#onPause()}. Based on the Activity lifecycle documentation, this is the last - * terminating method which is guaranteed to be called. The final call to {@link #close()} is the only one considered, so don't - * worry about Activity re-entrance.
  • - *
  • Do not call any {@link LocalyticsSession} methods inside a loop. Instead, calls such as {@link #tagEvent(String)} should - * follow user actions. This limits the amount of data which is stored and uploaded.
  • - *
- *

- * This class is thread-safe. - * - * @version 2.0 - */ -public final class LocalyticsSession -{ - /* - * DESIGN NOTES - * - * The LocalyticsSession stores all of its state as a SQLite database in the parent application's private database storage - * directory. - * - * Every action performed within (open, close, opt-in, opt-out, customer events) are all treated as events by the library. - * Events are given a package prefix to ensure a namespace without collisions. Events internal to the library are flagged with - * the Localytics package name, while events from the customer's code are flagged with the customer's package name. There's no - * need to worry about the customer changing the package name and disrupting the naming convention, as changing the package - * name means that a new user is created in Android and the app with a new package name gets its own storage directory. - * - * - * MULTI-THREADING - * - * The LocalyticsSession stores all of its state as a SQLite database in the parent application's private database storage - * directory. Disk access is slow and can block the UI in Android, so the LocalyticsSession object is a wrapper around a pair - * of Handler objects, with each Handler object running on its own separate thread. - * - * All requests made of the LocalyticsSession are passed along to the mSessionHandler object, which does most of the work. The - * mSessionHandler will pass off upload requests to the mUploadHandler, to prevent the mSessionHandler from being blocked by - * network traffic. - * - * If an upload request is made, the mSessionHandler will set a flag that an upload is in progress (this flag is important for - * thread-safety of the session data stored on disk). Then the upload request is passed to the mUploadHandler's queue. If a - * second upload request is made while the first one is underway, the mSessionHandler notifies the mUploadHandler, which will - * notify the mSessionHandler to retry that upload request when the first upload is completed. - * - * Although each LocalyticsSession object will have its own unique instance of mSessionHandler, thread-safety is handled by - * using a single sSessionHandlerThread. - */ - - /** - * Format string for events - */ - /* package */static final String EVENT_FORMAT = "%s:%s"; //$NON-NLS-1$ - - /** - * Open event - */ - /* package */static final String OPEN_EVENT = String.format(EVENT_FORMAT, Constants.LOCALYTICS_PACKAGE_NAME, "open"); //$NON-NLS-1$ - - /** - * Close event - */ - /* package */static final String CLOSE_EVENT = String.format(EVENT_FORMAT, Constants.LOCALYTICS_PACKAGE_NAME, "close"); //$NON-NLS-1$ - - /** - * Opt-in event - */ - /* package */static final String OPT_IN_EVENT = String.format(EVENT_FORMAT, Constants.LOCALYTICS_PACKAGE_NAME, "opt_in"); //$NON-NLS-1$ - - /** - * Opt-out event - */ - /* package */static final String OPT_OUT_EVENT = String.format(EVENT_FORMAT, Constants.LOCALYTICS_PACKAGE_NAME, "opt_out"); //$NON-NLS-1$ - - /** - * Flow event - */ - /* package */static final String FLOW_EVENT = String.format(EVENT_FORMAT, Constants.LOCALYTICS_PACKAGE_NAME, "flow"); //$NON-NLS-1$ - - /** - * Background thread used for all Localytics session processing. This thread is shared across all instances of - * LocalyticsSession within a process. - */ - /* - * By using the class name for the HandlerThread, obfuscation through Proguard is more effective: if Proguard changes the - * class name, the thread name also changes. - */ - private static final HandlerThread sSessionHandlerThread = getHandlerThread(SessionHandler.class.getSimpleName()); - - /** - * Background thread used for all Localytics upload processing. This thread is shared across all instances of - * LocalyticsSession within a process. - */ - /* - * By using the class name for the HandlerThread, obfuscation through Proguard is more effective: if Proguard changes the - * class name, the thread name also changes. - */ - private static final HandlerThread sUploadHandlerThread = getHandlerThread(UploadHandler.class.getSimpleName()); - - /** - * Helper to obtain a new {@link HandlerThread}. - * - * @param name to give to the HandlerThread. Useful for debugging, as the thread name is shown in DDMS. - * @return HandlerThread whose {@link HandlerThread#start()} method has already been called. - */ - private static HandlerThread getHandlerThread(final String name) - { - final HandlerThread thread = new HandlerThread(name, android.os.Process.THREAD_PRIORITY_BACKGROUND); - - thread.start(); - - /* - * The exception handler needs to be set after start() is called. If it is set before, sometime's the HandlerThread's - * looper is null. This appears to be a bug in Android. - */ - thread.setUncaughtExceptionHandler(new ExceptionHandler()); - - return thread; - } - - /** - * Handler object where all session requests of this instance of LocalyticsSession are handed off to. - *

- * This Handler is the key thread synchronization point for all work inside the LocalyticsSession. - *

- * This handler runs on {@link #sSessionHandlerThread}. - */ - private final Handler mSessionHandler; - - /** - * Application context - */ - private final Context mContext; - - /** - * Localytics application key - */ - private final String mLocalyticsKey; - - /** - * Keeps track of which Localytics clients are currently uploading, in order to allow only one upload for a given key at a - * time. - *

- * This field can only be read/written to from the {@link #sSessionHandlerThread}. This invariant is maintained by only - * accessing this field from within the {@link #mSessionHandler}. - */ - private static Map sIsUploadingMap = new HashMap(); - - /** - * Constructs a new {@link LocalyticsSession} object. - * - * @param context The context used to access resources on behalf of the app. It is recommended to use - * {@link Context#getApplicationContext()} to avoid the potential memory leak incurred by maintaining references to - * {@code Activity} instances. Cannot be null. - * @param key The key unique for each application generated at www.localytics.com. Cannot be null or empty. - * @throws IllegalArgumentException if {@code context} is null - * @throws IllegalArgumentException if {@code key} is null or empty - */ - public LocalyticsSession(final Context context, final String key) - { - if (context == null) - { - throw new IllegalArgumentException("context cannot be null"); //$NON-NLS-1$ - } - if (TextUtils.isEmpty(key)) - { - throw new IllegalArgumentException("key cannot be null or empty"); //$NON-NLS-1$ - } - - /* - * Get the application context to avoid having the Localytics object holding onto an Activity object. Using application - * context is very important to prevent the customer from giving the library multiple different contexts with different - * package names, which would corrupt the events in the database. - * - * Although RenamingDelegatingContext is part of the Android SDK, the class isn't present in the ClassLoader unless the - * process is being run as a unit test. For that reason, comparing class names is necessary instead of doing instanceof. - * - * Note that getting the application context may have unpredictable results for apps sharing a process running Android 2.1 - * and earlier. See for details. - */ - mContext = !(context.getClass().getName().equals("android.test.RenamingDelegatingContext")) && Constants.CURRENT_API_LEVEL >= 8 ? context.getApplicationContext() : context; //$NON-NLS-1$ - mLocalyticsKey = key; - - mSessionHandler = new SessionHandler(mContext, mLocalyticsKey, sSessionHandlerThread.getLooper()); - - /* - * Complete Handler initialization on a background thread. Note that this is not generally a good best practice, as the - * LocalyticsSession object (and its child objects) should be fully initialized by the time the constructor returns. - * However this implementation is safe, as the Handler will process this initialization message before any other message. - */ - mSessionHandler.sendMessage(mSessionHandler.obtainMessage(SessionHandler.MESSAGE_INIT)); - } - - /** - * Sets the Localytics opt-out state for this application. This call is not necessary and is provided for people who wish to - * allow their users the ability to opt out of data collection. It can be called at any time. Passing true causes all further - * data collection to stop, and an opt-out event to be sent to the server so the user's data is removed from the charts.
- * There are very serious implications to the quality of your data when providing an opt out option. For example, users who - * have opted out will appear as never returning, causing your new/returning chart to skew.
- * If two instances of the same application are running, and one is opted in and the second opts out, the first will also - * become opted out, and neither will collect any more data.
- * If a session was started while the app was opted out, the session open event has already been lost. For this reason, all - * sessions started while opted out will not collect data even after the user opts back in or else it taints the comparisons - * of session lengths and other metrics. - * - * @param isOptedOut True if the user should be be opted out and have all his Localytics data deleted. - */ - public void setOptOut(final boolean isOptedOut) - { - mSessionHandler.sendMessage(mSessionHandler.obtainMessage(SessionHandler.MESSAGE_OPT_OUT, isOptedOut ? 1 : 0, 0)); - } - - /** - * Opens the Localytics session. The session time as presented on the website is the time between the first open - * and the final close so it is recommended to open the session as early as possible, and close it at the last - * moment. The session must be opened before {@link #tagEvent(String)} or {@link #tagEvent(String, Map)} can be called, so - * this call should be placed in {@code Activity#onCreate(Bundle)}. - *

- * If for any reason this is called more than once without an intervening call to {@link #close()}, subsequent calls to open - * will be ignored. - *

- * For applications with multiple Activities, every Activity should call open in onCreate. This will - * cause each Activity to reconnect to the currently running session. - */ - public void open() - { - mSessionHandler.sendEmptyMessage(SessionHandler.MESSAGE_OPEN); - } - - /** - * Closes the Localytics session. This should be done when the application or activity is ending. Because of the way the - * Android lifecycle works, this call could end up in a place which gets called multiple times (such as onPause - * which is the recommended location). This is fine because only the last close is processed by the server.
- * Closing does not cause the session to stop collecting data. This is a result of the application life cycle. It is possible - * for onPause to be called long before the application is actually ready to close the session. - */ - public void close() - { - mSessionHandler.sendEmptyMessage(SessionHandler.MESSAGE_CLOSE); - } - - /** - * Allows a session to tag a particular event as having occurred. For example, if a view has three buttons, it might make - * sense to tag each button click with the name of the button which was clicked. For another example, in a game with many - * levels it might be valuable to create a new tag every time the user gets to a new level in order to determine how far the - * average user is progressing in the game.
- * Tagging Best Practices - *

    - *
  • DO NOT use tags to record personally identifiable information.
  • - *
  • The best way to use tags is to create all the tag strings as predefined constants and only use those. This is more - * efficient and removes the risk of collecting personal information.
  • - *
  • Do not set tags inside loops or any other place which gets called frequently. This can cause a lot of data to be stored - * and uploaded.
  • - *
- *
- * - * @param event The name of the event which occurred. Cannot be null or empty string. - * @throws IllegalArgumentException if {@code event} is null. - * @throws IllegalArgumentException if {@code event} is empty. - */ - public void tagEvent(final String event) - { - tagEvent(event, null); - } - - /** - * Allows a session to tag a particular event as having occurred, and optionally attach a collection of attributes to it. For - * example, if a view has three buttons, it might make sense to tag each button with the name of the button which was clicked. - * For another example, in a game with many levels it might be valuable to create a new tag every time the user gets to a new - * level in order to determine how far the average user is progressing in the game.
- * Tagging Best Practices - *
    - *
  • DO NOT use tags to record personally identifiable information.
  • - *
  • The best way to use tags is to create all the tag strings as predefined constants and only use those. This is more - * efficient and removes the risk of collecting personal information.
  • - *
  • Do not set tags inside loops or any other place which gets called frequently. This can cause a lot of data to be stored - * and uploaded.
  • - *
- *
- * - * @param event The name of the event which occurred. - * @param attributes The collection of attributes for this particular event. If this parameter is null or empty, then calling - * this method has the same effect as calling {@link #tagEvent(String)}. This parameter may not contain null or - * empty keys or values. - * @throws IllegalArgumentException if {@code event} is null. - * @throws IllegalArgumentException if {@code event} is empty. - * @throws IllegalArgumentException if {@code attributes} contains null keys, empty keys, null values, or empty values. - */ - public void tagEvent(final String event, final Map attributes) - { - if (Constants.ENABLE_PARAMETER_CHECKING) - { - if (null == event) - { - throw new IllegalArgumentException("event cannot be null"); //$NON-NLS-1$ - } - - if (0 == event.length()) - { - throw new IllegalArgumentException("event cannot be empty"); //$NON-NLS-1$ - } - - if (null != attributes) - { - /* - * Calling this with empty attributes is a smell that indicates a possible programming error on the part of the - * caller - */ - if (attributes.isEmpty()) - { - if (Constants.IS_LOGGABLE) - { - Log.i(Constants.LOG_TAG, "attributes is empty. Did the caller make an error?"); //$NON-NLS-1$ - } - } - - for (final Entry entry : attributes.entrySet()) - { - final String key = entry.getKey(); - final String value = entry.getValue(); - - if (null == key) - { - throw new IllegalArgumentException("attributes cannot contain null keys"); //$NON-NLS-1$ - } - if (null == value) - { - throw new IllegalArgumentException("attributes cannot contain null values"); //$NON-NLS-1$ - } - if (0 == key.length()) - { - throw new IllegalArgumentException("attributes cannot contain empty keys"); //$NON-NLS-1$ - } - if (0 == value.length()) - { - throw new IllegalArgumentException("attributes cannot contain empty values"); //$NON-NLS-1$ - } - } - } - } - - final String eventString = String.format(EVENT_FORMAT, mContext.getPackageName(), event); - - if (null == attributes) - { - mSessionHandler.sendMessage(mSessionHandler.obtainMessage(SessionHandler.MESSAGE_TAG_EVENT, new Pair>(eventString, null))); - } - else - { - /* - * Note: it is important to make a copy of the map, to ensure that a client can't modify the map after this method is - * called. A TreeMap is used to ensure that the order that the attributes are written is deterministic. For example, - * if the maximum number of attributes is exceeded the entries that occur later alphabetically will be skipped - * consistently. - */ - mSessionHandler.sendMessage(mSessionHandler.obtainMessage(SessionHandler.MESSAGE_TAG_EVENT, new Pair>( - eventString, - new TreeMap( - attributes)))); - } - } - - /** - * Note: This implementation will perform duplicate suppression on two identical screen events that occur in a row within a - * single session. For example, in the set of screens {"Screen 1", "Screen 1"} the second screen would be suppressed. However - * in the set {"Screen 1", "Screen 2", "Screen 1"}, no duplicate suppression would occur. - * - * @param screen Name of the screen that was entered. Cannot be null or the empty string. - * @throws IllegalArgumentException if {@code event} is null. - * @throws IllegalArgumentException if {@code event} is empty. - */ - public void tagScreen(final String screen) - { - if (null == screen) - { - throw new IllegalArgumentException("event cannot be null"); //$NON-NLS-1$ - } - - if (0 == screen.length()) - { - throw new IllegalArgumentException("event cannot be empty"); //$NON-NLS-1$ - } - - mSessionHandler.sendMessage(mSessionHandler.obtainMessage(SessionHandler.MESSAGE_TAG_SCREEN, screen)); - } - - /** - * Initiates an upload of any Localytics data for this session's API key. This should be done early in the process life in - * order to guarantee as much time as possible for slow connections to complete. It is necessary to do this even if the user - * has opted out because this is how the opt out is transported to the webservice. - */ - public void upload() - { - mSessionHandler.sendMessage(mSessionHandler.obtainMessage(SessionHandler.MESSAGE_UPLOAD, null)); - } - - /* - * This is useful, but not necessarily needed for the public API. If so desired, someone can uncomment this out. - */ - // /** - // * Initiates an upload of any Localytics data for this session's API key. This should be done early in the process life in - // * order to guarantee as much time as possible for slow connections to complete. It is necessary to do this even if the user - // * has opted out because this is how the opt out is transported to the webservice. - // * - // * @param callback a Runnable to execute when the upload completes. A typical use case would be to notify the caller that - // the - // * upload has completed. This runnable will be executed on an undefined thread, so the caller should anticipate - // * this runnable NOT executing on the main thread or the thread that calls {@link #upload}. This parameter may be - // * null. - // */ - // public void upload(final Runnable callback) - // { - // mSessionHandler.sendMessage(mSessionHandler.obtainMessage(SessionHandler.MESSAGE_UPLOAD, callback)); - // } - - /** - * Sorts an int value into a set of regular intervals as defined by the minimum, maximum, and step size. Both the min and max - * values are inclusive, and in the instance where (max - min + 1) is not evenly divisible by step size, the method guarantees - * only the minimum and the step size to be accurate to specification, with the new maximum will be moved to the next regular - * step. - * - * @param actualValue The int value to be sorted. - * @param minValue The int value representing the inclusive minimum interval. - * @param maxValue The int value representing the inclusive maximum interval. - * @param step The int value representing the increment of each interval. - * @return a ranged attribute suitable for passing as the argument to {@link #tagEvent(String)} or - * {@link #tagEvent(String, Map)}. - */ - public static String createRangedAttribute(final int actualValue, final int minValue, final int maxValue, final int step) - { - // Confirm there is at least one bucket - if (step < 1) - { - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, "Step must not be less than zero. Returning null."); //$NON-NLS-1$ - } - return null; - } - if (minValue >= maxValue) - { - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, "maxValue must not be less than minValue. Returning null."); //$NON-NLS-1$ - } - return null; - } - - // Determine the number of steps, rounding up using int math - final int stepQuantity = (maxValue - minValue + step) / step; - final int[] steps = new int[stepQuantity + 1]; - for (int currentStep = 0; currentStep <= stepQuantity; currentStep++) - { - steps[currentStep] = minValue + (currentStep) * step; - } - return createRangedAttribute(actualValue, steps); - } - - /** - * Sorts an int value into a predefined, pre-sorted set of intervals, returning a string representing the new expected value. - * The array must be sorted in ascending order, with the first element representing the inclusive lower bound and the last - * element representing the exclusive upper bound. For instance, the array [0,1,3,10] will provide the following buckets: less - * than 0, 0, 1-2, 3-9, 10 or greater. - * - * @param actualValue The int value to be bucketed. - * @param steps The sorted int array representing the bucketing intervals. - * @return String representation of {@code actualValue} that has been bucketed into the range provided by {@code steps}. - * @throws IllegalArgumentException if {@code steps} is null. - * @throws IllegalArgumentException if {@code steps} has length 0. - */ - @SuppressWarnings("nls") - public static String createRangedAttribute(final int actualValue, final int[] steps) - { - if (null == steps) - { - throw new IllegalArgumentException("steps cannot be null"); //$NON-NLS-1$ - } - - if (steps.length == 0) - { - throw new IllegalArgumentException("steps length must be greater than 0"); //$NON-NLS-1$ - } - - String bucket = null; - - // if less than smallest value - if (actualValue < steps[0]) - { - bucket = "less than " + steps[0]; - } - // if greater than largest value - else if (actualValue >= steps[steps.length - 1]) - { - bucket = steps[steps.length - 1] + " and above"; - } - else - { - // binarySearch returns the index of the value, or (-(insertion point) - 1) if not found - int bucketIndex = Arrays.binarySearch(steps, actualValue); - if (bucketIndex < 0) - { - // if the index wasn't found, then we want the value before the insertion point as the lower end - // the special case where the insertion point is 0 is covered above, so we don't have to worry about it here - bucketIndex = (-bucketIndex) - 2; - } - if (steps[bucketIndex] == (steps[bucketIndex + 1] - 1)) - { - bucket = Integer.toString(steps[bucketIndex]); - } - else - { - bucket = steps[bucketIndex] + "-" + (steps[bucketIndex + 1] - 1); //$NON-NLS-1$ - } - } - return bucket; - } - - /** - * Helper class to handle session-related work on the {@link LocalyticsSession#sSessionHandlerThread}. - */ - /* package */static final class SessionHandler extends Handler - { - /** - * Empty handler message to initialize the callback. - *

- * This message must be sent before any other messages. - */ - public static final int MESSAGE_INIT = 0; - - /** - * Empty handler message to open a localytics session - */ - public static final int MESSAGE_OPEN = 1; - - /** - * Empty handler message to close a localytics session - */ - public static final int MESSAGE_CLOSE = 2; - - /** - * Handler message to tag an event. - *

- * {@link Message#obj} is a {@link Pair} instance. This object cannot be null. - */ - public static final int MESSAGE_TAG_EVENT = 3; - - /** - * Handler message to upload all data collected so far - *

- * {@link Message#obj} is a {@code Runnable} to execute when upload is complete. The thread that this runnable will - * executed on is undefined. - */ - public static final int MESSAGE_UPLOAD = 4; - - /** - * Empty Handler message indicating that a previous upload attempt was completed. - */ - public static final int MESSAGE_UPLOAD_COMPLETE = 5; - - /** - * Handler message indicating an opt-out choice. - *

- * {@link Message#arg1} == 1 for true (opt out). 0 means opt-in. - */ - public static final int MESSAGE_OPT_OUT = 6; - - /** - * Handler message indicating a tag screen event - *

- * {@link Message#obj} is a string representing the screen visited. - */ - public static final int MESSAGE_TAG_SCREEN = 7; - - /** - * Sort order for the upload blobs. - *

- * This is a workaround for Android bug 3707 . - */ - private static final String UPLOAD_BLOBS_EVENTS_SORT_ORDER = String.format("CAST(%s AS TEXT)", UploadBlobEventsDbColumns.EVENTS_KEY_REF); //$NON-NLS-1$ - - /** - * Sort order for the events. - *

- * This is a workaround for Android bug 3707 . - */ - private static final String EVENTS_SORT_ORDER = String.format("CAST(%s as TEXT)", EventsDbColumns._ID); //$NON-NLS-1$ - - /** - * Application context - */ - private final Context mContext; - - /** - * Localytics database - */ - private LocalyticsProvider mProvider; - - /** - * The Localytics API key for the session. - */ - private final String mApiKey; - - /** - * {@link ApiKeysDbColumns#_ID} for the {@link LocalyticsSession#mLocalyticsKey}. - */ - private long mApiKeyId; - - /** - * {@link SessionsDbColumns#_ID} for the session. - */ - private long mSessionId; - - /** - * Flag variable indicating whether {@link #MESSAGE_OPEN} has been received yet. - */ - private boolean mIsSessionOpen = false; - - /** - * Flag variable indicating whether the user has opted out of data collection. - */ - private boolean mIsOptedOut = false; - - /** - * Handler object where all upload of this instance of LocalyticsSession are handed off to. - *

- * This handler runs on {@link #sUploadHandlerThread}. - */ - private Handler mUploadHandler; - - /** - * Constructs a new Handler that runs on the given looper. - * - * @param context The context used to access resources on behalf of the app. It is recommended to use - * {@link Context#getApplicationContext()} to avoid the potential memory leak incurred by maintaining - * references to {@code Activity} instances. Cannot be null. - * @param key The key unique for each application generated at www.localytics.com. Cannot be null or empty. - * @param looper to run the Handler on. Cannot be null. - * @throws IllegalArgumentException if {@code context} is null - * @throws IllegalArgumentException if {@code key} is null or empty - */ - public SessionHandler(final Context context, final String key, final Looper looper) - { - super(looper); - - if (Constants.ENABLE_PARAMETER_CHECKING) - { - if (context == null) - { - throw new IllegalArgumentException("context cannot be null"); //$NON-NLS-1$ - } - if (TextUtils.isEmpty(key)) - { - throw new IllegalArgumentException("key cannot be null or empty"); //$NON-NLS-1$ - } - } - - mContext = context; - mApiKey = key; - } - - @Override - public void handleMessage(final Message msg) - { - switch (msg.what) - { - case MESSAGE_INIT: - { - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, "Handler received MESSAGE_INIT"); //$NON-NLS-1$ - } - - init(); - - break; - } - case MESSAGE_OPT_OUT: - { - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, "Handler received MESSAGE_OPT_OUT"); //$NON-NLS-1$ - } - - final boolean isOptingOut = msg.arg1 == 0 ? false : true; - - SessionHandler.this.optOut(isOptingOut); - - break; - } - case MESSAGE_OPEN: - { - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, "Handler received MESSAGE_OPEN"); //$NON-NLS-1$ - } - - SessionHandler.this.open(false); - - break; - } - case MESSAGE_CLOSE: - { - if (Constants.IS_LOGGABLE) - { - Log.d(Constants.LOG_TAG, "Handler received MESSAGE_CLOSE"); //$NON-NLS-1$ - } - - SessionHandler.this.close(); - - break; - } - case MESSAGE_TAG_EVENT: - { - if (Constants.IS_LOGGABLE) - { - Log.d(Constants.LOG_TAG, "Handler received MESSAGE_TAG"); //$NON-NLS-1$ - } - - @SuppressWarnings("unchecked") - final Pair> pair = (Pair>) msg.obj; - final String event = pair.first; - final Map attributes = pair.second; - - SessionHandler.this.tagEvent(event, attributes); - - break; - } - case MESSAGE_TAG_SCREEN: - { - if (Constants.IS_LOGGABLE) - { - Log.d(Constants.LOG_TAG, "Handler received MESSAGE_SCREEN"); //$NON-NLS-1$ - } - - final String screen = (String) msg.obj; - - SessionHandler.this.tagScreen(screen); - - break; - } - case MESSAGE_UPLOAD: - { - if (Constants.IS_LOGGABLE) - { - Log.d(Constants.LOG_TAG, "SessionHandler received MESSAGE_UPLOAD"); //$NON-NLS-1$ - } - - /* - * Note that callback may be null - */ - final Runnable callback = (Runnable) msg.obj; - - SessionHandler.this.upload(callback); - - break; - } - case MESSAGE_UPLOAD_COMPLETE: - { - if (Constants.IS_LOGGABLE) - { - Log.d(Constants.LOG_TAG, "Handler received MESSAGE_UPLOAD_COMPLETE"); //$NON-NLS-1$ - } - - sIsUploadingMap.put(mApiKey, Boolean.FALSE); - - break; - } - default: - { - /* - * This should never happen - */ - throw new RuntimeException("Fell through switch statement"); //$NON-NLS-1$ - } - } - } - - /** - * Initialize the handler post construction. - *

- * This method must only be called once. - *

- * Note: This method is a private implementation detail. It is only made public for unit testing purposes. The public - * interface is to send {@link #MESSAGE_INIT} to the Handler. - * - * @see #MESSAGE_INIT - */ - public void init() - { - mProvider = LocalyticsProvider.getInstance(mContext, mApiKey); - - /* - * Check whether this session key is opted out - */ - Cursor cursor = null; - try - { - cursor = mProvider.query(ApiKeysDbColumns.TABLE_NAME, new String[] - { - ApiKeysDbColumns._ID, - ApiKeysDbColumns.OPT_OUT }, String.format("%s = ?", ApiKeysDbColumns.API_KEY), new String[] //$NON-NLS-1$ - { mApiKey }, null); - - if (cursor.moveToFirst()) - { - // API key was previously created - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("Loading details for API key %s", mApiKey)); //$NON-NLS-1$ - } - - mApiKeyId = cursor.getLong(cursor.getColumnIndexOrThrow(ApiKeysDbColumns._ID)); - mIsOptedOut = cursor.getInt(cursor.getColumnIndexOrThrow(ApiKeysDbColumns.OPT_OUT)) != 0; - } - else - { - // perform first-time initialization of API key - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("Performing first-time initialization for new API key %s", mApiKey)); //$NON-NLS-1$ - } - - final ContentValues values = new ContentValues(); - values.put(ApiKeysDbColumns.API_KEY, mApiKey); - values.put(ApiKeysDbColumns.UUID, UUID.randomUUID().toString()); - values.put(ApiKeysDbColumns.OPT_OUT, Boolean.FALSE); - values.put(ApiKeysDbColumns.CREATED_TIME, Long.valueOf(System.currentTimeMillis())); - - mApiKeyId = mProvider.insert(ApiKeysDbColumns.TABLE_NAME, values); - } - } - finally - { - if (cursor != null) - { - cursor.close(); - cursor = null; - } - } - - if (!sIsUploadingMap.containsKey(mApiKey)) - { - sIsUploadingMap.put(mApiKey, Boolean.FALSE); - } - - /* - * Perform lazy initialization of the UploadHandler - */ - mUploadHandler = new UploadHandler(mContext, this, mApiKey, sUploadHandlerThread.getLooper()); - } - - /** - * Set the opt-in/out-out state for all sessions using the current API key. - *

- * This method must only be called after {@link #init()} is called. - *

- * Note: This method is a private implementation detail. It is only made package accessible for unit testing purposes. The - * public interface is to send {@link #MESSAGE_OPT_OUT} to the Handler. - * - * @param isOptingOut true if the user is opting out. False if the user is opting back in. - * @see #MESSAGE_OPT_OUT - */ - /* package */void optOut(final boolean isOptingOut) - { - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("Prior opt-out state is %b, requested opt-out state is %b", Boolean.valueOf(mIsOptedOut), Boolean.valueOf(isOptingOut))); //$NON-NLS-1$ - } - - // Do nothing if opt-out is unchanged - if (mIsOptedOut == isOptingOut) - { - return; - } - - mProvider.runBatchTransaction(new Runnable() - { - @Override - public void run() - { - final ContentValues values = new ContentValues(); - values.put(ApiKeysDbColumns.OPT_OUT, Boolean.valueOf(isOptingOut)); - mProvider.update(ApiKeysDbColumns.TABLE_NAME, values, String.format("%s = ?", ApiKeysDbColumns._ID), new String[] { Long.toString(mApiKeyId) }); //$NON-NLS-1$ - - if (!mIsSessionOpen) - { - /* - * Force a session to contain the opt event - */ - open(true); - tagEvent(isOptingOut ? OPT_OUT_EVENT : OPT_IN_EVENT, null); - close(); - } - else - { - tagEvent(isOptingOut ? OPT_OUT_EVENT : OPT_IN_EVENT, null); - } - } - }); - - /* - * Update the in-memory representation. It is important for the in-memory representation to be updated after the - * on-disk representation, just in case the database update fails. - */ - mIsOptedOut = isOptingOut; - } - - /** - * Open a session. While this method should only be called once without an intervening call to {@link #close()}, nothing - * bad will happen if it is called multiple times. - *

- * This method must only be called after {@link #init()} is called. - *

- * Note: This method is a private implementation detail. It is only made package accessible for unit testing purposes. The - * public interface is to send {@link #MESSAGE_OPEN} to the Handler. - * - * @param ignoreLimits true to ignore limits on the number of sessions. False to enforce limits. - * @see #MESSAGE_OPEN - */ - /* package */void open(final boolean ignoreLimits) - { - if (mIsSessionOpen) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Session was already open"); //$NON-NLS-1$ - } - - return; - } - - if (mIsOptedOut) - { - if (Constants.IS_LOGGABLE) - { - Log.d(Constants.LOG_TAG, "Data collection is opted out"); //$NON-NLS-1$ - } - return; - } - - /* - * There are two cases: 1. New session and 2. Re-connect to old session. There are two ways to reconnect to an old - * session. One is by the age of the close event, and the other is by the age of the open event. - */ - - long closeEventId = -1; // sentinel value - - { - Cursor eventsCursor = null; - Cursor blob_eventsCursor = null; - try - { - eventsCursor = mProvider.query(EventsDbColumns.TABLE_NAME, new String[] - { EventsDbColumns._ID }, String.format("%s = ? AND %s >= ?", EventsDbColumns.EVENT_NAME, EventsDbColumns.WALL_TIME), new String[] { CLOSE_EVENT, Long.toString(System.currentTimeMillis() - Constants.SESSION_EXPIRATION) }, EVENTS_SORT_ORDER); //$NON-NLS-1$ - blob_eventsCursor = mProvider.query(UploadBlobEventsDbColumns.TABLE_NAME, new String[] - { UploadBlobEventsDbColumns.EVENTS_KEY_REF }, null, null, UPLOAD_BLOBS_EVENTS_SORT_ORDER); - - final int idColumn = eventsCursor.getColumnIndexOrThrow(EventsDbColumns._ID); - final CursorJoiner joiner = new CursorJoiner(eventsCursor, new String[] - { EventsDbColumns._ID }, blob_eventsCursor, new String[] - { UploadBlobEventsDbColumns.EVENTS_KEY_REF }); - - for (final CursorJoiner.Result joinerResult : joiner) - { - switch (joinerResult) - { - case LEFT: - { - - if (-1 != closeEventId) - { - /* - * This should never happen - */ - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "There were multiple close events within SESSION_EXPIRATION"); //$NON-NLS-1$ - } - - long newClose = eventsCursor.getLong(eventsCursor.getColumnIndexOrThrow(EventsDbColumns._ID)); - if (newClose > closeEventId) - { - closeEventId = newClose; - } - } - - if (-1 == closeEventId) - { - closeEventId = eventsCursor.getLong(idColumn); - } - - break; - } - case BOTH: - break; - case RIGHT: - break; - } - } - /* - * Verify that the session hasn't already been flagged for upload. That could happen if - */ - } - finally - { - if (eventsCursor != null) - { - eventsCursor.close(); - } - if (blob_eventsCursor != null) - { - blob_eventsCursor.close(); - } - } - } - - if (-1 != closeEventId) - { - Log.v(Constants.LOG_TAG, "Opening old closed session and reconnecting"); //$NON-NLS-1$ - mIsSessionOpen = true; - - openClosedSession(closeEventId); - } - else - { - Cursor sessionsCursor = null; - try - { - sessionsCursor = mProvider.query(SessionsDbColumns.TABLE_NAME, new String[] - { - SessionsDbColumns._ID, - SessionsDbColumns.SESSION_START_WALL_TIME }, null, null, SessionsDbColumns._ID); - - if (sessionsCursor.moveToLast()) - { - if (sessionsCursor.getLong(sessionsCursor.getColumnIndexOrThrow(SessionsDbColumns.SESSION_START_WALL_TIME)) >= System.currentTimeMillis() - - Constants.SESSION_EXPIRATION) - { - // reconnect - Log.v(Constants.LOG_TAG, "Opening old unclosed session and reconnecting"); //$NON-NLS-1$ - mIsSessionOpen = true; - mSessionId = sessionsCursor.getLong(sessionsCursor.getColumnIndexOrThrow(SessionsDbColumns._ID)); - return; - } - - // delete empties - Cursor eventsCursor = null; - try - { - String sessionId = Long.toString(sessionsCursor.getLong(sessionsCursor.getColumnIndexOrThrow(SessionsDbColumns._ID))); - eventsCursor = mProvider.query(EventsDbColumns.TABLE_NAME, new String[] - { EventsDbColumns._ID }, String.format("%s = ?", EventsDbColumns.SESSION_KEY_REF), new String[] //$NON-NLS-1$ - { sessionId }, null); - - if (eventsCursor.getCount() == 0) - { - mProvider.delete(SessionsDbColumns.TABLE_NAME, String.format("%s = ?", SessionsDbColumns._ID), new String[] { sessionId }); //$NON-NLS-1$ - } - } - finally - { - if (null != eventsCursor) - { - eventsCursor.close(); - eventsCursor = null; - } - } - } - } - finally - { - if (null != sessionsCursor) - { - sessionsCursor.close(); - sessionsCursor = null; - } - } - - /* - * Check that the maximum number of sessions hasn't been exceeded - */ - if (!ignoreLimits) - { - Cursor cursor = null; - try - { - cursor = mProvider.query(SessionsDbColumns.TABLE_NAME, new String[] - { SessionsDbColumns._ID }, null, null, null); - - if (cursor.getCount() >= Constants.MAX_NUM_SESSIONS) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Maximum number of sessions are already on disk--not writing any new sessions until old sessions are cleared out. Try calling upload() to store more sessions."); //$NON-NLS-1$ - } - return; - } - } - finally - { - if (cursor != null) - { - cursor.close(); - cursor = null; - } - } - } - - Log.v(Constants.LOG_TAG, "Opening new session"); //$NON-NLS-1$ - mIsSessionOpen = true; - - openNewSession(); - } - } - - /** - * Opens a new session. This is a helper method to {@link #open(boolean)}. - * - * @effects Updates the database by creating a new entry in the {@link SessionsDbColumns} table. - */ - private void openNewSession() - { - final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - - final ContentValues values = new ContentValues(); - values.put(SessionsDbColumns.API_KEY_REF, Long.valueOf(mApiKeyId)); - values.put(SessionsDbColumns.SESSION_START_WALL_TIME, Long.valueOf(System.currentTimeMillis())); - values.put(SessionsDbColumns.UUID, UUID.randomUUID().toString()); - values.put(SessionsDbColumns.APP_VERSION, DatapointHelper.getAppVersion(mContext)); - values.put(SessionsDbColumns.ANDROID_SDK, Integer.valueOf(Constants.CURRENT_API_LEVEL)); - values.put(SessionsDbColumns.ANDROID_VERSION, VERSION.RELEASE); - - // Try and get the deviceId. If it is unavailable (or invalid) use the installation ID instead. - String deviceId = DatapointHelper.getAndroidIdHashOrNull(mContext); - if (deviceId == null) - { - Cursor cursor = null; - try - { - cursor = mProvider.query(ApiKeysDbColumns.TABLE_NAME, null, String.format("%s = ?", ApiKeysDbColumns.API_KEY), new String[] { mApiKey }, null); //$NON-NLS-1$ - if (cursor.moveToFirst()) - { - deviceId = cursor.getString(cursor.getColumnIndexOrThrow(ApiKeysDbColumns.UUID)); - } - } - finally - { - if (null != cursor) - { - cursor.close(); - cursor = null; - } - } - } - - values.put(SessionsDbColumns.DEVICE_ANDROID_ID_HASH, deviceId); - values.put(SessionsDbColumns.DEVICE_COUNTRY, telephonyManager.getSimCountryIso()); - values.put(SessionsDbColumns.DEVICE_MANUFACTURER, DatapointHelper.getManufacturer()); - values.put(SessionsDbColumns.DEVICE_MODEL, Build.MODEL); - values.put(SessionsDbColumns.DEVICE_SERIAL_NUMBER_HASH, DatapointHelper.getSerialNumberHashOrNull()); - values.put(SessionsDbColumns.DEVICE_TELEPHONY_ID, DatapointHelper.getTelephonyDeviceIdOrNull(mContext)); - values.put(SessionsDbColumns.DEVICE_TELEPHONY_ID_HASH, DatapointHelper.getTelephonyDeviceIdHashOrNull(mContext)); - values.put(SessionsDbColumns.LOCALE_COUNTRY, Locale.getDefault().getCountry()); - values.put(SessionsDbColumns.LOCALE_LANGUAGE, Locale.getDefault().getLanguage()); - values.put(SessionsDbColumns.LOCALYTICS_LIBRARY_VERSION, Constants.LOCALYTICS_CLIENT_LIBRARY_VERSION); - - values.putNull(SessionsDbColumns.LATITUDE); - values.putNull(SessionsDbColumns.LONGITUDE); - values.put(SessionsDbColumns.NETWORK_CARRIER, telephonyManager.getNetworkOperatorName()); - values.put(SessionsDbColumns.NETWORK_COUNTRY, telephonyManager.getNetworkCountryIso()); - values.put(SessionsDbColumns.NETWORK_TYPE, DatapointHelper.getNetworkType(mContext, telephonyManager)); - - mProvider.runBatchTransaction(new Runnable() - { - @Override - public void run() - { - mSessionId = mProvider.insert(SessionsDbColumns.TABLE_NAME, values); - if (mSessionId == -1) - { - throw new RuntimeException("session insert failed"); //$NON-NLS-1$ - } - - tagEvent(OPEN_EVENT, null); - } - - }); - - /* - * This is placed here so that the DatapointHelper has a chance to retrieve the old UUID before it is deleted. - */ - LocalyticsProvider.deleteOldFiles(mContext); - } - - /** - * Reopens a previous session. This is a helper method to {@link #open(boolean)}. - * - * @param closeEventId The last close event which is to be deleted so that the old session can be reopened - * @effects Updates the database by deleting the last close event and sets {@link #mSessionId} to the session id of the - * last close event - */ - private void openClosedSession(final long closeEventId) - { - Cursor eventCursor = null; - try - { - eventCursor = mProvider.query(EventsDbColumns.TABLE_NAME, new String[] - { EventsDbColumns.SESSION_KEY_REF }, String.format("%s = ?", EventsDbColumns._ID), new String[] { Long.toString(closeEventId) }, null); //$NON-NLS-1$ - - if (eventCursor.moveToFirst()) - { - mSessionId = eventCursor.getLong(eventCursor.getColumnIndexOrThrow(EventsDbColumns.SESSION_KEY_REF)); - - mProvider.delete(EventsDbColumns.TABLE_NAME, String.format("%s = ?", EventsDbColumns._ID), new String[] { Long.toString(closeEventId) }); //$NON-NLS-1$ - } - else - { - /* - * This should never happen - */ - - if (Constants.IS_LOGGABLE) - { - Log.e(Constants.LOG_TAG, "Event no longer exists"); //$NON-NLS-1$ - } - - openNewSession(); - } - } - finally - { - if (eventCursor != null) - { - eventCursor.close(); - } - } - } - - /** - * Close a session. While this method should only be called after {@link #open(boolean)}, nothing bad will happen if it is - * called and {@link #open(boolean)} wasn't called. Similarly, nothing bad will happen if close is called multiple times. - *

- * This method must only be called after {@link #init()} is called. - *

- * Note: This method is a private implementation detail. It is only made package accessible for unit testing purposes. The - * public interface is to send {@link #MESSAGE_CLOSE} to the Handler. - * - * @see #MESSAGE_OPEN - */ - /* package */void close() - { - if (!mIsSessionOpen) // do nothing if session is not open - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Session was not open, so close is not possible."); //$NON-NLS-1$ - } - return; - } - - tagEvent(CLOSE_EVENT, null); - - mIsSessionOpen = false; - } - - /** - * Tag an event in a session. While this method shouldn't be called unless {@link #open(boolean)} is called first, this - * method will simply do nothing if {@link #open(boolean)} hasn't been called. - *

- * This method must only be called after {@link #init()} is called. - *

- * Note: This method is a private implementation detail. It is only made package accessible for unit testing purposes. The - * public interface is to send {@link #MESSAGE_TAG_EVENT} to the Handler. - * - * @param event The name of the event which occurred. - * @param attributes The collection of attributes for this particular event. If this parameter is null, then calling this - * method has the same effect as calling {@link #tagEvent(String)}. - * @see #MESSAGE_TAG_EVENT - */ - /* package */void tagEvent(final String event, final Map attributes) - { - if (!mIsSessionOpen) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Tag not written because the session was not open"); //$NON-NLS-1$ - } - return; - } - - /* - * First insert the event - */ - final long eventId; - { - final ContentValues values = new ContentValues(); - values.put(EventsDbColumns.SESSION_KEY_REF, Long.valueOf(mSessionId)); - values.put(EventsDbColumns.UUID, UUID.randomUUID().toString()); - values.put(EventsDbColumns.EVENT_NAME, event); - values.put(EventsDbColumns.REAL_TIME, Long.valueOf(SystemClock.elapsedRealtime())); - values.put(EventsDbColumns.WALL_TIME, Long.valueOf(System.currentTimeMillis())); - - /* - * Special case for open event: keep the start time in sync with the start time put into the sessions table. - */ - if (OPEN_EVENT.equals(event)) - { - Cursor cursor = null; - try - { - cursor = mProvider.query(SessionsDbColumns.TABLE_NAME, new String[] - { SessionsDbColumns.SESSION_START_WALL_TIME }, String.format("%s = ?", SessionsDbColumns._ID), new String[] { Long.toString(mSessionId) }, null); //$NON-NLS-1$ - - if (cursor.moveToFirst()) - { - values.put(EventsDbColumns.WALL_TIME, Long.valueOf(cursor.getLong(cursor.getColumnIndexOrThrow(SessionsDbColumns.SESSION_START_WALL_TIME)))); - } - else - { - // this should never happen - throw new RuntimeException("Session didn't exist"); //$NON-NLS-1$ - } - } - finally - { - if (null != cursor) - { - cursor.close(); - } - } - } - - eventId = mProvider.insert(EventsDbColumns.TABLE_NAME, values); - - if (-1 == eventId) - { - throw new RuntimeException("Inserting event failed"); //$NON-NLS-1$ - } - } - - /* - * If attributes exist, insert them as well - */ - if (null != attributes) - { - int count = 0; - for (final Entry entry : attributes.entrySet()) - { - /* - * Note: the attributes that are skipped are deterministic, because the map is actually an instance of - * TreeMap. - */ - count++; - if (count > Constants.MAX_NUM_ATTRIBUTES) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, String.format("Map contains %s keys while the maximum number of attributes is %s. Some attributes were not written. Consider reducing the number of attributes.", Integer.valueOf(attributes.size()), Integer.valueOf(Constants.MAX_NUM_ATTRIBUTES))); //$NON-NLS-1$ - } - break; - } - - final ContentValues values = new ContentValues(); - values.put(AttributesDbColumns.EVENTS_KEY_REF, Long.valueOf(eventId)); - values.put(AttributesDbColumns.ATTRIBUTE_KEY, entry.getKey()); - values.put(AttributesDbColumns.ATTRIBUTE_VALUE, entry.getValue()); - - final long id = mProvider.insert(AttributesDbColumns.TABLE_NAME, values); - - if (-1 == id) - { - throw new RuntimeException("Inserting attribute failed"); //$NON-NLS-1$ - } - } - } - - /* - * Insert the event into the history, only for application events - */ - if (!OPEN_EVENT.equals(event) && !CLOSE_EVENT.equals(event) && !OPT_IN_EVENT.equals(event) && !OPT_OUT_EVENT.equals(event) && !FLOW_EVENT.equals(event)) - { - final ContentValues values = new ContentValues(); - values.put(EventHistoryDbColumns.NAME, event.substring(mContext.getPackageName().length() + 1, event.length())); - values.put(EventHistoryDbColumns.TYPE, Integer.valueOf(EventHistoryDbColumns.TYPE_EVENT)); - values.put(EventHistoryDbColumns.SESSION_KEY_REF, Long.valueOf(mSessionId)); - values.putNull(EventHistoryDbColumns.PROCESSED_IN_BLOB); - mProvider.insert(EventHistoryDbColumns.TABLE_NAME, values); - - conditionallyAddFlowEvent(); - } - } - - /** - * Tag a screen in a session. While this method shouldn't be called unless {@link #open(boolean)} is called first, this - * method will simply do nothing if {@link #open(boolean)} hasn't been called. - *

- * This method performs duplicate suppression, preventing multiple screens with the same value in a row within a given - * session. - *

- * This method must only be called after {@link #init()} is called. - *

- * Note: This method is a private implementation detail. It is only made public for unit testing purposes. The public - * interface is to send {@link #MESSAGE_TAG_SCREEN} to the Handler. - * - * @param screen The name of the screen which occurred. Cannot be null or empty. - * @see #MESSAGE_TAG_SCREEN - */ - /* package */void tagScreen(final String screen) - { - if (!mIsSessionOpen) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Tag not written because the session was not open"); //$NON-NLS-1$ - } - return; - } - - /* - * Do duplicate suppression - */ - Cursor cursor = null; - try - { - cursor = mProvider.query(EventHistoryDbColumns.TABLE_NAME, new String[] - { EventHistoryDbColumns.NAME }, String.format("%s = ? AND %s = ?", EventHistoryDbColumns.TYPE, EventHistoryDbColumns.SESSION_KEY_REF), new String[] { Integer.toString(EventHistoryDbColumns.TYPE_SCREEN), Long.toString(mSessionId) }, String.format("%s DESC", EventHistoryDbColumns._ID)); //$NON-NLS-1$ //$NON-NLS-2$ - - if (cursor.moveToFirst()) - { - if (screen.equals(cursor.getString(cursor.getColumnIndexOrThrow(EventHistoryDbColumns.NAME)))) - { - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("Suppressed duplicate screen %s", screen)); //$NON-NLS-1$ - } - return; - } - } - } - finally - { - if (null != cursor) - { - cursor.close(); - cursor = null; - } - } - - /* - * Write the screen to the database - */ - final ContentValues values = new ContentValues(); - values.put(EventHistoryDbColumns.NAME, screen); - values.put(EventHistoryDbColumns.TYPE, Integer.valueOf(EventHistoryDbColumns.TYPE_SCREEN)); - values.put(EventHistoryDbColumns.SESSION_KEY_REF, Long.valueOf(mSessionId)); - values.putNull(EventHistoryDbColumns.PROCESSED_IN_BLOB); - mProvider.insert(EventHistoryDbColumns.TABLE_NAME, values); - - conditionallyAddFlowEvent(); - } - - /** - * Conditionally adds a flow event if no flow event exists in the current upload blob. - */ - private void conditionallyAddFlowEvent() - { - /* - * Creating a flow "event" is required to act as a placeholder so that the uploader will know that an upload needs to - * occur. A flow event should only be created if there isn't already a flow event that hasn't been associated with an - * upload blob. - */ - boolean foundUnassociatedFlowEvent = false; - - Cursor eventsCursor = null; - Cursor blob_eventsCursor = null; - try - { - eventsCursor = mProvider.query(EventsDbColumns.TABLE_NAME, new String[] - { EventsDbColumns._ID }, String.format("%s = ?", EventsDbColumns.EVENT_NAME), new String[] //$NON-NLS-1$ - { FLOW_EVENT }, EVENTS_SORT_ORDER); - - blob_eventsCursor = mProvider.query(UploadBlobEventsDbColumns.TABLE_NAME, new String[] - { UploadBlobEventsDbColumns.EVENTS_KEY_REF }, null, null, UPLOAD_BLOBS_EVENTS_SORT_ORDER); - - final CursorJoiner joiner = new CursorJoiner(eventsCursor, new String[] - { EventsDbColumns._ID }, blob_eventsCursor, new String[] - { UploadBlobEventsDbColumns.EVENTS_KEY_REF }); - for (final CursorJoiner.Result joinerResult : joiner) - { - switch (joinerResult) - { - case LEFT: - { - foundUnassociatedFlowEvent = true; - break; - } - case BOTH: - break; - case RIGHT: - break; - } - } - } - finally - { - if (eventsCursor != null) - { - eventsCursor.close(); - eventsCursor = null; - } - - if (blob_eventsCursor != null) - { - blob_eventsCursor.close(); - blob_eventsCursor = null; - } - } - - if (!foundUnassociatedFlowEvent) - { - tagEvent(FLOW_EVENT, null); - } - } - - /** - * Builds upload blobs for all events. - * - * @effects Mutates the database by creating a new upload blob for all events that are unassociated at the time this - * method is called. - */ - /* package */void preUploadBuildBlobs() - { - /* - * Group all events that aren't part of an upload blob into a new blob. While this process is a linear algorithm that - * requires scanning two database tables, the performance won't be a problem for two reasons: 1. This process happens - * frequently so the number of events to group will always be low. 2. There is a maximum number of events, keeping the - * overall size low. Note that close events that are younger than SESSION_EXPIRATION will be skipped to allow session - * reconnects. - */ - - // temporary set of event ids that aren't in a blob - final Set eventIds = new HashSet(); - - Cursor eventsCursor = null; - Cursor blob_eventsCursor = null; - try - { - eventsCursor = mProvider.query(EventsDbColumns.TABLE_NAME, new String[] - { - EventsDbColumns._ID, - EventsDbColumns.EVENT_NAME, - EventsDbColumns.WALL_TIME }, null, null, EVENTS_SORT_ORDER); - - blob_eventsCursor = mProvider.query(UploadBlobEventsDbColumns.TABLE_NAME, new String[] - { UploadBlobEventsDbColumns.EVENTS_KEY_REF }, null, null, UPLOAD_BLOBS_EVENTS_SORT_ORDER); - - final int idColumn = eventsCursor.getColumnIndexOrThrow(EventsDbColumns._ID); - final CursorJoiner joiner = new CursorJoiner(eventsCursor, new String[] - { EventsDbColumns._ID }, blob_eventsCursor, new String[] - { UploadBlobEventsDbColumns.EVENTS_KEY_REF }); - for (final CursorJoiner.Result joinerResult : joiner) - { - switch (joinerResult) - { - case LEFT: - { - if (CLOSE_EVENT.equals(eventsCursor.getString(eventsCursor.getColumnIndexOrThrow(EventsDbColumns.EVENT_NAME)))) - { - if (System.currentTimeMillis() - eventsCursor.getLong(eventsCursor.getColumnIndexOrThrow(EventsDbColumns.WALL_TIME)) < Constants.SESSION_EXPIRATION) - { - break; - } - } - eventIds.add(Long.valueOf(eventsCursor.getLong(idColumn))); - break; - } - case BOTH: - break; - case RIGHT: - break; - } - } - } - finally - { - if (eventsCursor != null) - { - eventsCursor.close(); - } - - if (blob_eventsCursor != null) - { - blob_eventsCursor.close(); - } - } - - if (eventIds.size() > 0) - { - final long blobId; - { - final ContentValues values = new ContentValues(); - values.put(UploadBlobsDbColumns.UUID, UUID.randomUUID().toString()); - blobId = mProvider.insert(UploadBlobsDbColumns.TABLE_NAME, values); - } - - { - final ContentValues values = new ContentValues(); - for (final Long x : eventIds) - { - values.clear(); - - values.put(UploadBlobEventsDbColumns.UPLOAD_BLOBS_KEY_REF, Long.valueOf(blobId)); - values.put(UploadBlobEventsDbColumns.EVENTS_KEY_REF, x); - - mProvider.insert(UploadBlobEventsDbColumns.TABLE_NAME, values); - } - } - - final ContentValues values = new ContentValues(); - values.put(EventHistoryDbColumns.PROCESSED_IN_BLOB, Long.valueOf(blobId)); - mProvider.update(EventHistoryDbColumns.TABLE_NAME, values, String.format("%s IS NULL", EventHistoryDbColumns.PROCESSED_IN_BLOB), null); //$NON-NLS-1$ - } - } - - /** - * Initiate upload of all session data currently stored on disk. - *

- * This method must only be called after {@link #init()} is called. The session does not need to be open for an upload to - * occur. - *

- * Note: This method is a private implementation detail. It is only made package accessible for unit testing purposes. The - * public interface is to send {@link #MESSAGE_UPLOAD} to the Handler. - * - * @param callback An optional callback to perform once the upload completes. May be null for no callback. - * @see #MESSAGE_UPLOAD - */ - /* package */void upload(final Runnable callback) - { - if (sIsUploadingMap.get(mApiKey).booleanValue()) - { - if (Constants.IS_LOGGABLE) - { - Log.d(Constants.LOG_TAG, "Already uploading"); //$NON-NLS-1$ - } - - mUploadHandler.sendMessage(mUploadHandler.obtainMessage(UploadHandler.MESSAGE_RETRY_UPLOAD_REQUEST, callback)); - return; - } - - try - { - mProvider.runBatchTransaction(new Runnable() - { - @Override - public void run() - { - preUploadBuildBlobs(); - } - }); - - sIsUploadingMap.put(mApiKey, Boolean.TRUE); - mUploadHandler.sendMessage(mUploadHandler.obtainMessage(UploadHandler.MESSAGE_UPLOAD, callback)); - } - catch (final Exception e) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Error occurred during upload", e); //$NON-NLS-1$ - } - - sIsUploadingMap.put(mApiKey, Boolean.FALSE); - - // Notify the caller the upload is "complete" - if (callback != null) - { - /* - * Note that a new thread is created for the callback. This ensures that client code can't affect the - * performance of the SessionHandler's thread. - */ - new Thread(callback, UploadHandler.UPLOAD_CALLBACK_THREAD_NAME).start(); - } - } - } - } - - /** - * Helper object to the {@link SessionHandler} which helps process upload requests. - */ - /* package */static final class UploadHandler extends Handler - { - - /** - * Thread name that the upload callback runnable is executed on. - */ - private static final String UPLOAD_CALLBACK_THREAD_NAME = "upload_callback"; //$NON-NLS-1$ - - /** - * Localytics upload URL, as a format string that contains a format for the API key. - */ - private final static String ANALYTICS_URL = "http://analytics.localytics.com/api/v2/applications/%s/uploads"; //$NON-NLS-1$ - - /** - * Handler message to upload all data collected so far - *

- * {@link Message#obj} is a {@code Runnable} to execute when upload is complete. The thread that this runnable will - * executed on is undefined. - */ - public static final int MESSAGE_UPLOAD = 1; - - /** - * Handler message indicating that there is a queued upload request. When this message is processed, this handler simply - * forwards the request back to {@link LocalyticsSession#mSessionHandler} with {@link SessionHandler#MESSAGE_UPLOAD}. - *

- * {@link Message#obj} is a {@code Runnable} to execute when upload is complete. The thread that this runnable will - * executed on is undefined. - */ - public static final int MESSAGE_RETRY_UPLOAD_REQUEST = 2; - - /** - * Reference to the Localytics database - */ - private final LocalyticsProvider mProvider; - - /** - * Application context - */ - private final Context mContext; - - /** - * The Localytics API key - */ - private final String mApiKey; - - /** - * Parent session handler to notify when an upload completes. - */ - private final Handler mSessionHandler; - - /** - * Constructs a new Handler that runs on {@code looper}. - *

- * Note: This constructor may perform disk access. - * - * @param context Application context. Cannot be null. - * @param sessionHandler Parent {@link SessionHandler} object to notify when uploads are completed. Cannot be null. - * @param apiKey Localytics API key. Cannot be null. - * @param looper to run the Handler on. Cannot be null. - */ - public UploadHandler(final Context context, final Handler sessionHandler, final String apiKey, final Looper looper) - { - super(looper); - - mContext = context; - mProvider = LocalyticsProvider.getInstance(context, apiKey); - mSessionHandler = sessionHandler; - mApiKey = apiKey; - } - - @Override - public void handleMessage(final Message msg) - { - super.handleMessage(msg); - - switch (msg.what) - { - case MESSAGE_UPLOAD: - { - if (Constants.IS_LOGGABLE) - { - Log.d(Constants.LOG_TAG, "UploadHandler Received MESSAGE_UPLOAD"); //$NON-NLS-1$ - } - - /* - * Note that callback may be null - */ - final Runnable callback = (Runnable) msg.obj; - - try - { - final List toUpload = convertDatabaseToJson(); - - if (!toUpload.isEmpty()) - { - final StringBuilder builder = new StringBuilder(); - for (final JSONObject json : toUpload) - { - builder.append(json.toString()); - builder.append('\n'); - } - - if (uploadSessions(String.format(ANALYTICS_URL, mApiKey), builder.toString())) - { - mProvider.runBatchTransaction(new Runnable() - { - @Override - public void run() - { - deleteBlobsAndSessions(mProvider); - } - }); - } - } - } - finally - { - if (callback != null) - { - /* - * Execute the callback on a separate thread, to avoid exposing this thread to the client of the - * library - */ - new Thread(callback, UPLOAD_CALLBACK_THREAD_NAME).start(); - } - - mSessionHandler.sendEmptyMessage(SessionHandler.MESSAGE_UPLOAD_COMPLETE); - } - break; - } - case MESSAGE_RETRY_UPLOAD_REQUEST: - { - if (Constants.IS_LOGGABLE) - { - Log.d(Constants.LOG_TAG, "Received MESSAGE_RETRY_UPLOAD_REQUEST"); //$NON-NLS-1$ - } - - mSessionHandler.sendMessage(mSessionHandler.obtainMessage(SessionHandler.MESSAGE_UPLOAD, msg.obj)); - break; - } - default: - { - /* - * This should never happen - */ - throw new RuntimeException("Fell through switch statement"); //$NON-NLS-1$ - } - } - } - - /** - * Uploads the post Body to the webservice - * - * @param url where {@code body} will be posted to. Cannot be null. - * @param body upload body as a string. This should be a plain old string. Cannot be null. - * @return True on success, false on failure. - */ - /* package */static boolean uploadSessions(final String url, final String body) - { - if (Constants.ENABLE_PARAMETER_CHECKING) - { - if (null == url) - { - throw new IllegalArgumentException("url cannot be null"); //$NON-NLS-1$ - } - - if (null == body) - { - throw new IllegalArgumentException("body cannot be null"); //$NON-NLS-1$ - } - } - - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("Upload body before compression is: %s", body.toString())); //$NON-NLS-1$ - } - - final DefaultHttpClient client = new DefaultHttpClient(); - final HttpPost method = new HttpPost(url); - method.addHeader("Content-Type", "application/x-gzip"); //$NON-NLS-1$ //$NON-NLS-2$ - - GZIPOutputStream gos = null; - try - { - final byte[] originalBytes = body.getBytes("UTF-8"); //$NON-NLS-1$ - final ByteArrayOutputStream baos = new ByteArrayOutputStream(originalBytes.length); - gos = new GZIPOutputStream(baos); - gos.write(originalBytes); - gos.finish(); - gos.flush(); - - final ByteArrayEntity postBody = new ByteArrayEntity(baos.toByteArray()); - method.setEntity(postBody); - - final HttpResponse response = client.execute(method); - - final StatusLine status = response.getStatusLine(); - final int statusCode = status.getStatusCode(); - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("Upload complete with status %d", Integer.valueOf(statusCode))); //$NON-NLS-1$ - } - - /* - * 5xx status codes indicate a server error, so upload should be reattempted - */ - if (statusCode >= 500 && statusCode <= 599) - { - return false; - } - - return true; - } - catch (final UnsupportedEncodingException e) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "UnsupportedEncodingException", e); //$NON-NLS-1$ - } - return false; - } - catch (final ClientProtocolException e) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "ClientProtocolException", e); //$NON-NLS-1$ - } - return false; - } - catch (final IOException e) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "IOException", e); //$NON-NLS-1$ - } - return false; - } - finally - { - if (null != gos) - { - try - { - gos.close(); - } - catch (final IOException e) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Caught exception", e); //$NON-NLS-1$ - } - } - } - } - } - - /** - * Helper that converts blobs in the database into a JSON representation for upload. - * - * @return A list of JSON objecs to upload to the server - */ - /* package */List convertDatabaseToJson() - { - final List result = new LinkedList(); - Cursor cursor = null; - try - { - cursor = mProvider.query(UploadBlobsDbColumns.TABLE_NAME, null, null, null, null); - - final long creationTime = getApiKeyCreationTime(mProvider, mApiKey); - - final int idColumn = cursor.getColumnIndexOrThrow(UploadBlobsDbColumns._ID); - final int uuidColumn = cursor.getColumnIndexOrThrow(UploadBlobsDbColumns.UUID); - while (cursor.moveToNext()) - { - try - { - final JSONObject blobHeader = new JSONObject(); - - blobHeader.put(JsonObjects.BlobHeader.KEY_DATA_TYPE, BlobHeader.VALUE_DATA_TYPE); - blobHeader.put(JsonObjects.BlobHeader.KEY_PERSISTENT_STORAGE_CREATION_TIME_SECONDS, creationTime); - blobHeader.put(JsonObjects.BlobHeader.KEY_SEQUENCE_NUMBER, cursor.getLong(idColumn)); - blobHeader.put(JsonObjects.BlobHeader.KEY_UNIQUE_ID, cursor.getString(uuidColumn)); - blobHeader.put(JsonObjects.BlobHeader.KEY_ATTRIBUTES, getAttributesFromSession(mProvider, mApiKey, getSessionIdForBlobId(cursor.getLong(idColumn)))); - result.add(blobHeader); - - Cursor blobEvents = null; - try - { - blobEvents = mProvider.query(UploadBlobEventsDbColumns.TABLE_NAME, new String[] - { UploadBlobEventsDbColumns.EVENTS_KEY_REF }, String.format("%s = ?", UploadBlobEventsDbColumns.UPLOAD_BLOBS_KEY_REF), new String[] //$NON-NLS-1$ - { Long.toString(cursor.getLong(idColumn)) }, UploadBlobEventsDbColumns.EVENTS_KEY_REF); - - final int eventIdColumn = blobEvents.getColumnIndexOrThrow(UploadBlobEventsDbColumns.EVENTS_KEY_REF); - while (blobEvents.moveToNext()) - { - result.add(convertEventToJson(mProvider, mContext, blobEvents.getLong(eventIdColumn), cursor.getLong(idColumn), mApiKey)); - } - } - finally - { - if (null != blobEvents) - { - blobEvents.close(); - } - } - } - catch (final JSONException e) - { - if (Constants.IS_LOGGABLE) - { - Log.w(Constants.LOG_TAG, "Caught exception", e); //$NON-NLS-1$ - } - } - } - } - finally - { - if (cursor != null) - { - cursor.close(); - } - } - - if (Constants.IS_LOGGABLE) - { - Log.v(Constants.LOG_TAG, String.format("JSON result is %s", result.toString())); //$NON-NLS-1$ - } - - return result; - } - - /** - * Deletes all blobs and sessions/events/attributes associated with those blobs. - *

- * This should be called after a successful upload completes. - * - * @param provider Localytics database provider. Cannot be null. - */ - /* package */static void deleteBlobsAndSessions(final LocalyticsProvider provider) - { - /* - * Deletion needs to occur in a specific order due to database constraints. Specifically, blobevents need to be - * deleted first. Then blobs themselves can be deleted. Then attributes need to be deleted first. Then events. Then - * sessions. - */ - - final LinkedList sessionsToDelete = new LinkedList(); - final HashSet blobsToDelete = new HashSet(); - - Cursor blobEvents = null; - try - { - blobEvents = provider.query(UploadBlobEventsDbColumns.TABLE_NAME, new String[] - { - UploadBlobEventsDbColumns._ID, - UploadBlobEventsDbColumns.EVENTS_KEY_REF, - UploadBlobEventsDbColumns.UPLOAD_BLOBS_KEY_REF }, null, null, null); - - final int uploadBlobIdColumn = blobEvents.getColumnIndexOrThrow(UploadBlobEventsDbColumns.UPLOAD_BLOBS_KEY_REF); - final int blobEventIdColumn = blobEvents.getColumnIndexOrThrow(UploadBlobEventsDbColumns._ID); - final int eventIdColumn = blobEvents.getColumnIndexOrThrow(UploadBlobEventsDbColumns.EVENTS_KEY_REF); - while (blobEvents.moveToNext()) - { - final long blobId = blobEvents.getLong(uploadBlobIdColumn); - final long blobEventId = blobEvents.getLong(blobEventIdColumn); - final long eventId = blobEvents.getLong(eventIdColumn); - - // delete the blobevent - provider.delete(UploadBlobEventsDbColumns.TABLE_NAME, String.format("%s = ?", UploadBlobEventsDbColumns._ID), new String[] { Long.toString(blobEventId) }); //$NON-NLS-1$ - - /* - * Add the blob to the list of blobs to be deleted - */ - blobsToDelete.add(Long.valueOf(blobId)); - - // delete all attributes for the event - provider.delete(AttributesDbColumns.TABLE_NAME, String.format("%s = ?", AttributesDbColumns.EVENTS_KEY_REF), new String[] { Long.toString(eventId) }); //$NON-NLS-1$ - - /* - * Check to see if the event is a close event, indicating that the session is complete and can also be deleted - */ - Cursor eventCursor = null; - try - { - eventCursor = provider.query(EventsDbColumns.TABLE_NAME, new String[] - { EventsDbColumns.SESSION_KEY_REF }, String.format("%s = ? AND %s = ?", EventsDbColumns._ID, EventsDbColumns.EVENT_NAME), new String[] //$NON-NLS-1$ - { - Long.toString(eventId), - CLOSE_EVENT }, null); - - if (eventCursor.moveToFirst()) - { - final long sessionId = eventCursor.getLong(eventCursor.getColumnIndexOrThrow(EventsDbColumns.SESSION_KEY_REF)); - - provider.delete(EventHistoryDbColumns.TABLE_NAME, String.format("%s = ?", EventHistoryDbColumns.SESSION_KEY_REF), new String[] //$NON-NLS-1$ - { Long.toString(sessionId) }); - - sessionsToDelete.add(Long.valueOf(eventCursor.getLong(eventCursor.getColumnIndexOrThrow(EventsDbColumns.SESSION_KEY_REF)))); - } - } - finally - { - if (null != eventCursor) - { - eventCursor.close(); - } - } - - // delete the event - provider.delete(EventsDbColumns.TABLE_NAME, String.format("%s = ?", EventsDbColumns._ID), new String[] { Long.toString(eventId) }); //$NON-NLS-1$ - } - } - finally - { - if (null != blobEvents) - { - blobEvents.close(); - } - } - - // delete blobs - for (final long x : blobsToDelete) - { - provider.delete(UploadBlobsDbColumns.TABLE_NAME, String.format("%s = ?", UploadBlobsDbColumns._ID), new String[] { Long.toString(x) }); //$NON-NLS-1$ - } - - // delete sessions - for (final long x : sessionsToDelete) - { - provider.delete(SessionsDbColumns.TABLE_NAME, String.format("%s = ?", SessionsDbColumns._ID), new String[] { Long.toString(x) }); //$NON-NLS-1$ - } - - } - - /** - * Gets the creation time for an API key. - * - * @param provider Localytics database provider. Cannot be null. - * @param key Localytics API key. Cannot be null. - * @return The time in seconds since the Unix Epoch when the API key entry was created in the database. - * @throws RuntimeException if the API key entry doesn't exist in the database. - */ - /* package */static long getApiKeyCreationTime(final LocalyticsProvider provider, final String key) - { - Cursor cursor = null; - try - { - cursor = provider.query(ApiKeysDbColumns.TABLE_NAME, null, String.format("%s = ?", ApiKeysDbColumns.API_KEY), new String[] { key }, null); //$NON-NLS-1$ - - if (cursor.moveToFirst()) - { - return Math.round((float) cursor.getLong(cursor.getColumnIndexOrThrow(ApiKeysDbColumns.CREATED_TIME)) / DateUtils.SECOND_IN_MILLIS); - } - - /* - * This should never happen - */ - throw new RuntimeException("API key entry couldn't be found"); //$NON-NLS-1$ - } - finally - { - if (null != cursor) - { - cursor.close(); - } - } - } - - /** - * Helper method to generate the attributes object for a session - * - * @param provider Instance of the Localytics database provider. Cannot be null. - * @param apiKey Localytics API key. Cannot be null. - * @param sessionId The {@link SessionsDbColumns#_ID} of the session. - * @return a JSONObject representation of the session attributes - * @throws JSONException if a problem occurred converting the element to JSON. - */ - /* package */static JSONObject getAttributesFromSession(final LocalyticsProvider provider, final String apiKey, final long sessionId) throws JSONException - { - Cursor cursor = null; - try - { - cursor = provider.query(SessionsDbColumns.TABLE_NAME, null, String.format("%s = ?", SessionsDbColumns._ID), new String[] { Long.toString(sessionId) }, null); //$NON-NLS-1$ - - if (cursor.moveToFirst()) - { - final JSONObject result = new JSONObject(); - result.put(JsonObjects.BlobHeader.Attributes.KEY_CLIENT_APP_VERSION, cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.APP_VERSION))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_DATA_CONNECTION, cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.NETWORK_TYPE))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_DEVICE_ANDROID_ID_HASH, cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.DEVICE_ANDROID_ID_HASH))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_DEVICE_COUNTRY, cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.DEVICE_COUNTRY))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_DEVICE_MANUFACTURER, cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.DEVICE_MANUFACTURER))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_DEVICE_MODEL, cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.DEVICE_MODEL))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_DEVICE_OS_VERSION, cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.ANDROID_VERSION))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_DEVICE_PLATFORM, JsonObjects.BlobHeader.Attributes.VALUE_PLATFORM); - result.put(JsonObjects.BlobHeader.Attributes.KEY_DEVICE_SERIAL_HASH, cursor.isNull(cursor.getColumnIndexOrThrow(SessionsDbColumns.DEVICE_SERIAL_NUMBER_HASH)) ? JSONObject.NULL - : cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.DEVICE_SERIAL_NUMBER_HASH))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_DEVICE_SDK_LEVEL, cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.ANDROID_SDK))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_DEVICE_TELEPHONY_ID, cursor.isNull(cursor.getColumnIndexOrThrow(SessionsDbColumns.DEVICE_TELEPHONY_ID)) ? JSONObject.NULL - : cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.DEVICE_TELEPHONY_ID))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_LOCALYTICS_API_KEY, apiKey); - result.put(JsonObjects.BlobHeader.Attributes.KEY_LOCALYTICS_CLIENT_LIBRARY_VERSION, cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.LOCALYTICS_LIBRARY_VERSION))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_LOCALYTICS_DATA_TYPE, JsonObjects.BlobHeader.Attributes.VALUE_DATA_TYPE); - result.put(JsonObjects.BlobHeader.Attributes.KEY_LOCALE_COUNTRY, cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.LOCALE_COUNTRY))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_LOCALE_LANGUAGE, cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.LOCALE_LANGUAGE))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_NETWORK_CARRIER, cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.NETWORK_CARRIER))); - result.put(JsonObjects.BlobHeader.Attributes.KEY_NETWORK_COUNTRY, cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.NETWORK_COUNTRY))); - - return result; - } - - throw new RuntimeException("No session exists"); //$NON-NLS-1$ - } - finally - { - if (null != cursor) - { - cursor.close(); - } - } - } - - /** - * Converts an event into a JSON object. - *

- * There are three types of events: open, close, and application. Open and close events are Localytics events, while - * application events are generated by the app. The return value of this method will vary based on the type of event that - * is being converted. - * - * @param provider Localytics database instance. Cannot be null. - * @param context Application context. Cannot be null. - * @param eventId {@link EventsDbColumns#_ID} of the event to convert. - * @param blobId {@link UploadBlobEventsDbColumns#_ID} of the upload blob that contains this event. - * @param apiKey the Localytics API key. Cannot be null. - * @return JSON representation of the event. - * @throws JSONException if a problem occurred converting the element to JSON. - */ - /* package */static JSONObject convertEventToJson(final LocalyticsProvider provider, final Context context, final long eventId, final long blobId, final String apiKey) - throws JSONException - { - final JSONObject result = new JSONObject(); - - Cursor cursor = null; - - try - { - cursor = provider.query(EventsDbColumns.TABLE_NAME, null, String.format("%s = ?", EventsDbColumns._ID), new String[] //$NON-NLS-1$ - { Long.toString(eventId) }, EventsDbColumns._ID); - - if (cursor.moveToFirst()) - { - final String eventName = cursor.getString(cursor.getColumnIndexOrThrow(EventsDbColumns.EVENT_NAME)); - final long sessionId = getSessionIdForEventId(provider, eventId); - final String sessionUuid = getSessionUuid(provider, sessionId); - final long sessionStartTime = getSessionStartTime(provider, sessionId); - - if (OPEN_EVENT.equals(eventName)) - { - result.put(JsonObjects.SessionOpen.KEY_DATA_TYPE, JsonObjects.SessionOpen.VALUE_DATA_TYPE); - result.put(JsonObjects.SessionOpen.KEY_WALL_TIME_SECONDS, Math.round((double) cursor.getLong(cursor.getColumnIndex(EventsDbColumns.WALL_TIME)) - / DateUtils.SECOND_IN_MILLIS)); - result.put(JsonObjects.SessionOpen.KEY_EVENT_UUID, sessionUuid); - - /* - * Both the database and the web service use 1-based indexing. - */ - result.put(JsonObjects.SessionOpen.KEY_COUNT, sessionId); - } - else if (CLOSE_EVENT.equals(eventName)) - { - result.put(JsonObjects.SessionClose.KEY_DATA_TYPE, JsonObjects.SessionClose.VALUE_DATA_TYPE); - result.put(JsonObjects.SessionClose.KEY_EVENT_UUID, cursor.getString(cursor.getColumnIndexOrThrow(EventsDbColumns.UUID))); - result.put(JsonObjects.SessionClose.KEY_SESSION_UUID, sessionUuid); - result.put(JsonObjects.SessionClose.KEY_SESSION_START_TIME, Math.round((double) sessionStartTime / DateUtils.SECOND_IN_MILLIS)); - result.put(JsonObjects.SessionClose.KEY_WALL_TIME_SECONDS, Math.round((double) cursor.getLong(cursor.getColumnIndex(EventsDbColumns.WALL_TIME)) - / DateUtils.SECOND_IN_MILLIS)); - - /* - * length is a special case, as it depends on the start time embedded in the session table - */ - Cursor sessionCursor = null; - try - { - sessionCursor = provider.query(SessionsDbColumns.TABLE_NAME, new String[] - { SessionsDbColumns.SESSION_START_WALL_TIME }, String.format("%s = ?", SessionsDbColumns._ID), new String[] { Long.toString(cursor.getLong(cursor.getColumnIndexOrThrow(EventsDbColumns.SESSION_KEY_REF))) }, null); //$NON-NLS-1$ - - if (sessionCursor.moveToFirst()) - { - result.put(JsonObjects.SessionClose.KEY_SESSION_LENGTH_SECONDS, Math.round((double) cursor.getLong(cursor.getColumnIndex(EventsDbColumns.WALL_TIME)) - / DateUtils.SECOND_IN_MILLIS) - - Math.round((double) sessionCursor.getLong(sessionCursor.getColumnIndexOrThrow(SessionsDbColumns.SESSION_START_WALL_TIME)) - / DateUtils.SECOND_IN_MILLIS)); - } - else - { - // this should never happen - throw new RuntimeException("Session didn't exist"); //$NON-NLS-1$ - } - } - finally - { - if (null != sessionCursor) - { - sessionCursor.close(); - sessionCursor = null; - } - } - - /* - * The close also contains a special case element for the screens history - */ - Cursor eventHistoryCursor = null; - try - { - eventHistoryCursor = provider.query(EventHistoryDbColumns.TABLE_NAME, new String[] - { EventHistoryDbColumns.NAME }, String.format("%s = ? AND %s = ?", EventHistoryDbColumns.SESSION_KEY_REF, EventHistoryDbColumns.TYPE), new String[] { Long.toString(sessionId), Integer.toString(EventHistoryDbColumns.TYPE_SCREEN) }, EventHistoryDbColumns._ID); //$NON-NLS-1$ - - final JSONArray screens = new JSONArray(); - while (eventHistoryCursor.moveToNext()) - { - screens.put(eventHistoryCursor.getString(eventHistoryCursor.getColumnIndexOrThrow(EventHistoryDbColumns.NAME))); - } - - if (screens.length() > 0) - { - result.put(JsonObjects.SessionClose.KEY_FLOW_ARRAY, screens); - } - } - finally - { - if (null != eventHistoryCursor) - { - eventHistoryCursor.close(); - eventHistoryCursor = null; - } - } - } - else if (OPT_IN_EVENT.equals(eventName) || OPT_OUT_EVENT.equals(eventName)) - { - result.put(JsonObjects.OptEvent.KEY_DATA_TYPE, JsonObjects.OptEvent.VALUE_DATA_TYPE); - result.put(JsonObjects.OptEvent.KEY_API_KEY, apiKey); - result.put(JsonObjects.OptEvent.KEY_OPT, OPT_OUT_EVENT.equals(eventName) ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); - result.put(JsonObjects.OptEvent.KEY_WALL_TIME_SECONDS, Math.round((double) cursor.getLong(cursor.getColumnIndex(EventsDbColumns.WALL_TIME)) - / DateUtils.SECOND_IN_MILLIS)); - } - else if (FLOW_EVENT.equals(eventName)) - { - result.put(JsonObjects.EventFlow.KEY_DATA_TYPE, JsonObjects.EventFlow.VALUE_DATA_TYPE); - result.put(JsonObjects.EventFlow.KEY_EVENT_UUID, cursor.getString(cursor.getColumnIndexOrThrow(EventsDbColumns.UUID))); - result.put(JsonObjects.EventFlow.KEY_SESSION_START_TIME, Math.round((double) sessionStartTime / DateUtils.SECOND_IN_MILLIS)); - - /* - * Need to generate two objects: the old flow events and the new flow events - */ - - /* - * Default sort order is ascending by _ID, so these will be sorted chronologically. - */ - Cursor eventHistoryCursor = null; - try - { - eventHistoryCursor = provider.query(EventHistoryDbColumns.TABLE_NAME, new String[] - { - EventHistoryDbColumns.TYPE, - EventHistoryDbColumns.PROCESSED_IN_BLOB, - EventHistoryDbColumns.NAME }, String.format("%s = ? AND %s <= ?", EventHistoryDbColumns.SESSION_KEY_REF, EventHistoryDbColumns.PROCESSED_IN_BLOB), new String[] { Long.toString(sessionId), Long.toString(blobId) }, EventHistoryDbColumns._ID); //$NON-NLS-1$ - - final JSONArray newScreens = new JSONArray(); - final JSONArray oldScreens = new JSONArray(); - while (eventHistoryCursor.moveToNext()) - { - final String name = eventHistoryCursor.getString(eventHistoryCursor.getColumnIndexOrThrow(EventHistoryDbColumns.NAME)); - final String type; - if (EventHistoryDbColumns.TYPE_EVENT == eventHistoryCursor.getInt(eventHistoryCursor.getColumnIndexOrThrow(EventHistoryDbColumns.TYPE))) - { - type = JsonObjects.EventFlow.Element.TYPE_EVENT; - } - else - { - type = JsonObjects.EventFlow.Element.TYPE_SCREEN; - } - - if (blobId == eventHistoryCursor.getLong(eventHistoryCursor.getColumnIndexOrThrow(EventHistoryDbColumns.PROCESSED_IN_BLOB))) - { - newScreens.put(new JSONObject().put(type, name)); - } - else - { - oldScreens.put(new JSONObject().put(type, name)); - } - } - - result.put(JsonObjects.EventFlow.KEY_FLOW_NEW, newScreens); - result.put(JsonObjects.EventFlow.KEY_FLOW_OLD, oldScreens); - } - finally - { - if (null != eventHistoryCursor) - { - eventHistoryCursor.close(); - eventHistoryCursor = null; - } - } - } - else - { - /* - * This is a normal application event - */ - - result.put(JsonObjects.SessionEvent.KEY_DATA_TYPE, JsonObjects.SessionEvent.VALUE_DATA_TYPE); - result.put(JsonObjects.SessionEvent.KEY_WALL_TIME_SECONDS, Math.round((double) cursor.getLong(cursor.getColumnIndex(EventsDbColumns.WALL_TIME)) - / DateUtils.SECOND_IN_MILLIS)); - result.put(JsonObjects.SessionEvent.KEY_EVENT_UUID, cursor.getString(cursor.getColumnIndexOrThrow(EventsDbColumns.UUID))); - result.put(JsonObjects.SessionEvent.KEY_SESSION_UUID, sessionUuid); - result.put(JsonObjects.SessionEvent.KEY_NAME, eventName.substring(context.getPackageName().length() + 1, eventName.length())); - - final JSONObject attributes = convertAttributesToJson(provider, eventId); - - if (null != attributes) - { - result.put(JsonObjects.SessionEvent.KEY_ATTRIBUTES, attributes); - } - } - } - else - { - /* - * This should never happen - */ - throw new RuntimeException(); - } - } - finally - { - if (null != cursor) - { - cursor.close(); - } - } - - return result; - } - - /** - * Private helper to get the {@link SessionsDbColumns#_ID} for a given {@link EventsDbColumns#_ID}. - * - * @param provider Localytics database instance. Cannot be null. - * @param eventId {@link EventsDbColumns#_ID} of the event to look up - * @return The {@link SessionsDbColumns#_ID} of the session that owns the event. - */ - /* package */static long getSessionIdForEventId(final LocalyticsProvider provider, final long eventId) - { - Cursor cursor = null; - try - { - cursor = provider.query(EventsDbColumns.TABLE_NAME, new String[] - { EventsDbColumns.SESSION_KEY_REF }, String.format("%s = ?", EventsDbColumns._ID), new String[] { Long.toString(eventId) }, null); //$NON-NLS-1$ - - if (cursor.moveToFirst()) - { - return cursor.getLong(cursor.getColumnIndexOrThrow(EventsDbColumns.SESSION_KEY_REF)); - } - - /* - * This should never happen - */ - throw new RuntimeException(); - } - finally - { - if (null != cursor) - { - cursor.close(); - } - } - } - - /** - * Private helper to get the {@link SessionsDbColumns#UUID} for a given {@link SessionsDbColumns#_ID}. - * - * @param provider Localytics database instance. Cannot be null. - * @param sessionId {@link SessionsDbColumns#_ID} of the event to look up - * @return The {@link SessionsDbColumns#UUID} of the session. - */ - /* package */static String getSessionUuid(final LocalyticsProvider provider, final long sessionId) - { - Cursor cursor = null; - try - { - cursor = provider.query(SessionsDbColumns.TABLE_NAME, new String[] - { SessionsDbColumns.UUID }, String.format("%s = ?", SessionsDbColumns._ID), new String[] { Long.toString(sessionId) }, null); //$NON-NLS-1$ - - if (cursor.moveToFirst()) - { - return cursor.getString(cursor.getColumnIndexOrThrow(SessionsDbColumns.UUID)); - } - - /* - * This should never happen - */ - throw new RuntimeException(); - } - finally - { - if (null != cursor) - { - cursor.close(); - } - } - } - - /** - * Private helper to get the {@link SessionsDbColumns#SESSION_START_WALL_TIME} for a given {@link SessionsDbColumns#_ID}. - * - * @param provider Localytics database instance. Cannot be null. - * @param sessionId {@link SessionsDbColumns#_ID} of the event to look up - * @return The {@link SessionsDbColumns#SESSION_START_WALL_TIME} of the session. - */ - /* package */static long getSessionStartTime(final LocalyticsProvider provider, final long sessionId) - { - Cursor cursor = null; - try - { - cursor = provider.query(SessionsDbColumns.TABLE_NAME, new String[] - { SessionsDbColumns.SESSION_START_WALL_TIME }, String.format("%s = ?", SessionsDbColumns._ID), new String[] { Long.toString(sessionId) }, null); //$NON-NLS-1$ - - if (cursor.moveToFirst()) - { - return cursor.getLong(cursor.getColumnIndexOrThrow(SessionsDbColumns.SESSION_START_WALL_TIME)); - } - - /* - * This should never happen - */ - throw new RuntimeException(); - } - finally - { - if (null != cursor) - { - cursor.close(); - } - } - } - - /** - * Private helper to convert an event's attributes into a {@link JSONObject} representation. - * - * @param provider Localytics database instance. Cannot be null. - * @param eventId {@link EventsDbColumns#_ID} of the event whose attributes are to be loaded. - * @return {@link JSONObject} representing the attributes of the event. The order of attributes is undefined and may - * change from call to call of this method. If the event has no attributes, returns null. - * @throws JSONException if an error occurs converting the attributes to JSON - */ - /* package */static JSONObject convertAttributesToJson(final LocalyticsProvider provider, final long eventId) throws JSONException - { - Cursor cursor = null; - try - { - cursor = provider.query(AttributesDbColumns.TABLE_NAME, null, String.format("%s = ?", AttributesDbColumns.EVENTS_KEY_REF), new String[] { Long.toString(eventId) }, null); //$NON-NLS-1$ - - if (cursor.getCount() == 0) - { - return null; - } - - final JSONObject attributes = new JSONObject(); - - final int keyColumn = cursor.getColumnIndexOrThrow(AttributesDbColumns.ATTRIBUTE_KEY); - final int valueColumn = cursor.getColumnIndexOrThrow(AttributesDbColumns.ATTRIBUTE_VALUE); - while (cursor.moveToNext()) - { - attributes.put(cursor.getString(keyColumn), cursor.getString(valueColumn)); - } - - return attributes; - } - finally - { - if (null != cursor) - { - cursor.close(); - } - } - } - - /** - * Given an id of an upload blob, get the session id associated with that blob. - * - * @param blobId {@link UploadBlobsDbColumns#_ID} of the upload blob. - * @return id of the parent session. - */ - /* package */long getSessionIdForBlobId(final long blobId) - { - /* - * This implementation needs to walk up the tree of database elements. - */ - - long eventId; - { - Cursor cursor = null; - try - { - cursor = mProvider.query(UploadBlobEventsDbColumns.TABLE_NAME, new String[] - { UploadBlobEventsDbColumns.EVENTS_KEY_REF }, String.format("%s = ?", UploadBlobEventsDbColumns.UPLOAD_BLOBS_KEY_REF), new String[] //$NON-NLS-1$ - { Long.toString(blobId) }, null); - - if (cursor.moveToFirst()) - { - eventId = cursor.getLong(cursor.getColumnIndexOrThrow(UploadBlobEventsDbColumns.EVENTS_KEY_REF)); - } - else - { - /* - * This should never happen - */ - throw new RuntimeException("No events associated with blob"); //$NON-NLS-1$ - } - } - finally - { - if (null != cursor) - { - cursor.close(); - } - } - } - - long sessionId; - { - Cursor cursor = null; - try - { - cursor = mProvider.query(EventsDbColumns.TABLE_NAME, new String[] - { EventsDbColumns.SESSION_KEY_REF }, String.format("%s = ?", EventsDbColumns._ID), new String[] //$NON-NLS-1$ - { Long.toString(eventId) }, null); - - if (cursor.moveToFirst()) - { - sessionId = cursor.getLong(cursor.getColumnIndexOrThrow(EventsDbColumns.SESSION_KEY_REF)); - } - else - { - /* - * This should never happen - */ - throw new RuntimeException("No session associated with event"); //$NON-NLS-1$ - } - } - finally - { - if (null != cursor) - { - cursor.close(); - } - } - } - - return sessionId; - } - } - - /** - * Internal helper class to pass two objects to the Handler via the {@link Message#obj}. - */ - /* - * Once support for Android 1.6 is dropped, using Android's built-in Pair class would be preferable - */ - private static final class Pair - { - public final F first; - - public final S second; - - public Pair(final F first, final S second) - { - this.first = first; - this.second = second; - } - } -} diff --git a/astrid/common-src/com/localytics/android/ReflectionUtils.java b/astrid/common-src/com/localytics/android/ReflectionUtils.java deleted file mode 100644 index 202c700ac..000000000 --- a/astrid/common-src/com/localytics/android/ReflectionUtils.java +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.localytics.android; - -import java.lang.reflect.InvocationTargetException; - -/** - * Static utilities for performing reflection against newer Android SDKs. - *

- * This is not a general-purpose reflection class but is rather specifically designed for calling methods that must exist in newer - * versions of Android. - */ -public final class ReflectionUtils -{ - /** - * Private constructor prevents instantiation - * - * @throws UnsupportedOperationException because this class cannot be instantiated. - */ - private ReflectionUtils() - { - throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$ - } - - /** - * Use reflection to invoke a static method for a class object and method name - * - * @param Type that the method should return - * @param classObject Class on which to invoke {@code methodName}. Cannot be null. - * @param methodName Name of the method to invoke. Cannot be null. - * @param types explicit types for the objects. This is useful if the types are primitives, rather than objects. - * @param args arguments for the method. May be null if the method takes no arguments. - * @return The result of invoking the named method on the given class for the args - * @throws RuntimeException if the class or method doesn't exist - */ - public static T tryInvokeStatic(final Class classObject, final String methodName, final Class[] types, final Object[] args) - { - return (T) helper(null, classObject, null, methodName, types, args); - } - - /** - * Use reflection to invoke a static method for a class object and method name - * - * @param Type that the method should return - * @param className Name of the class on which to invoke {@code methodName}. Cannot be null. - * @param methodName Name of the method to invoke. Cannot be null. - * @param types explicit types for the objects. This is useful if the types are primitives, rather than objects. - * @param args arguments for the method. May be null if the method takes no arguments. - * @return The result of invoking the named method on the given class for the args - * @throws RuntimeException if the class or method doesn't exist - */ - public static T tryInvokeStatic(final String className, final String methodName, final Class[] types, final Object[] args) - { - return (T) helper(className, null, null, methodName, types, args); - } - - /** - * Use reflection to invoke a static method for a class object and method name - * - * @param Type that the method should return - * @param target Object instance on which to invoke {@code methodName}. Cannot be null. - * @param methodName Name of the method to invoke. Cannot be null. - * @param types explicit types for the objects. This is useful if the types are primitives, rather than objects. - * @param args arguments for the method. May be null if the method takes no arguments. - * @return The result of invoking the named method on the given class for the args - * @throws RuntimeException if the class or method doesn't exist - */ - public static T tryInvokeInstance(final Object target, final String methodName, final Class[] types, final Object[] args) - { - return (T) helper(target, null, null, methodName, types, args); - } - - @SuppressWarnings("unchecked") - private static T helper(final Object target, final Class classObject, final String className, final String methodName, final Class[] argTypes, final Object[] args) - { - try - { - Class cls; - if (classObject != null) - { - cls = classObject; - } - else if (target != null) - { - cls = target.getClass(); - } - else - { - cls = Class.forName(className); - } - - return (T) cls.getMethod(methodName, argTypes).invoke(target, args); - } - catch (final NoSuchMethodException e) - { - throw new RuntimeException(e); - } - catch (final IllegalAccessException e) - { - throw new RuntimeException(e); - } - catch (final InvocationTargetException e) - { - throw new RuntimeException(e); - } - catch (final ClassNotFoundException e) - { - throw new RuntimeException(e); - } - } -} diff --git a/astrid/libs/crittercism_v3_0_7_sdkonly.jar b/astrid/libs/crittercism_v3_0_7_sdkonly.jar deleted file mode 100644 index e5d4c0831..000000000 Binary files a/astrid/libs/crittercism_v3_0_7_sdkonly.jar and /dev/null differ diff --git a/astrid/libs/findbugs-annotations.jar b/astrid/libs/findbugs-annotations.jar deleted file mode 100644 index ce70abef4..000000000 Binary files a/astrid/libs/findbugs-annotations.jar and /dev/null differ diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmBackgroundService.java b/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmBackgroundService.java index 71b36794a..e64efa416 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmBackgroundService.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmBackgroundService.java @@ -9,7 +9,6 @@ import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; import com.todoroo.astrid.actfm.sync.ActFmSyncV2Provider; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.sync.SyncProviderUtilities; import com.todoroo.astrid.sync.SyncV2BackgroundService; import com.todoroo.astrid.sync.SyncV2Provider; @@ -43,12 +42,10 @@ public class ActFmBackgroundService extends SyncV2BackgroundService { @Override public void onCreate() { super.onCreate(); - StatisticsService.sessionStart(this); } @Override public void onDestroy() { - StatisticsService.sessionStop(this); super.onDestroy(); } diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmGoogleAuthActivity.java b/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmGoogleAuthActivity.java index b3db25c82..d882d953f 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmGoogleAuthActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmGoogleAuthActivity.java @@ -30,7 +30,6 @@ import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountMa import com.timsu.astrid.R; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.utility.DialogUtilities; -import com.todoroo.astrid.service.StatisticsService; /** * This activity allows users to sign in or log in to Google Tasks @@ -163,19 +162,16 @@ public class ActFmGoogleAuthActivity extends ListActivity { @Override protected void onResume() { super.onResume(); - StatisticsService.sessionStart(this); } @Override protected void onPause() { super.onPause(); - StatisticsService.sessionPause(); } @Override protected void onStop() { super.onStop(); - StatisticsService.sessionStop(this); } private static final int REQUEST_AUTHENTICATE = 0; diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmLoginActivity.java b/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmLoginActivity.java index f8a2cb0ed..bf3d3bc0b 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmLoginActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmLoginActivity.java @@ -106,7 +106,6 @@ import com.todoroo.astrid.helper.UUIDHelper; import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.service.MarketStrategy.AmazonMarketStrategy; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.SyncV2Service; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.subtasks.AstridOrderedListUpdater; @@ -232,14 +231,12 @@ public class ActFmLoginActivity extends SherlockFragmentActivity { protected void onResume() { super.onResume(); uiHelper.onResume(); - StatisticsService.sessionStart(this); } @Override protected void onPause() { super.onPause(); uiHelper.onPause(); - StatisticsService.sessionPause(); } @Override @@ -250,12 +247,10 @@ public class ActFmLoginActivity extends SherlockFragmentActivity { @Override protected void onStop() { - StatisticsService.sessionStop(this); super.onStop(); } protected void recordPageView() { - StatisticsService.reportEvent(StatisticsConstants.ACTFM_LOGIN_SHOW); } protected void setupTermsOfService(TextView tos) { @@ -331,7 +326,6 @@ public class ActFmLoginActivity extends SherlockFragmentActivity { Intent intent = new Intent(ActFmLoginActivity.this, ActFmGoogleAuthActivity.class); startActivityForResult(intent, REQUEST_CODE_GOOGLE); - StatisticsService.reportEvent(StatisticsConstants.ACTFM_LOGIN_GL_START); } }; @@ -395,7 +389,6 @@ public class ActFmLoginActivity extends SherlockFragmentActivity { AndroidUtilities.hideSoftInputForViews(ActFmLoginActivity.this, firstNameField, lastNameField, email); authenticate(email.getText().toString(), firstName, lastName, ActFmInvoker.PROVIDER_PASSWORD, generateRandomPassword()); - StatisticsService.reportEvent(StatisticsConstants.ACTFM_SIGNUP_PW); } }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override @@ -462,7 +455,6 @@ public class ActFmLoginActivity extends SherlockFragmentActivity { authenticate(email.getText().toString(), "", "", ActFmInvoker.PROVIDER_PASSWORD, //$NON-NLS-1$//$NON-NLS-2$ password.getText().toString()); - StatisticsService.reportEvent(StatisticsConstants.ACTFM_LOGIN_PW); } }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override @@ -594,7 +586,6 @@ public class ActFmLoginActivity extends SherlockFragmentActivity { final String token = actFmInvoker.getToken(); if (result.optBoolean("new")) { // Report new user statistic - StatisticsService.reportEvent(StatisticsConstants.ACTFM_NEW_USER, "provider", provider); } // Successful login, create outstanding entries String lastId = ActFmPreferenceService.userId(); //Preferences.getLong(ActFmPreferenceService.PREF_USER_ID, 0); diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmPreferences.java b/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmPreferences.java index b232379d7..5adebd9b3 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmPreferences.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/ActFmPreferences.java @@ -29,7 +29,6 @@ import com.todoroo.astrid.billing.BillingActivity; import com.todoroo.astrid.gtasks.GtasksPreferenceService; import com.todoroo.astrid.service.PremiumUnlockService; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.sync.SyncProviderPreferences; import com.todoroo.astrid.sync.SyncProviderUtilities; import com.todoroo.astrid.utility.Constants; @@ -208,7 +207,6 @@ public class ActFmPreferences extends SyncProviderPreferences { } else { Intent intent = new Intent(this, BillingActivity.class); startActivity(intent); - StatisticsService.reportEvent(StatisticsConstants.PREMIUM_PAGE_VIEWED); } } diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/CommentsFragment.java b/astrid/plugin-src/com/todoroo/astrid/actfm/CommentsFragment.java index a54a6cec5..fe1984a20 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/CommentsFragment.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/CommentsFragment.java @@ -56,7 +56,6 @@ import com.todoroo.astrid.dao.UserActivityDao; import com.todoroo.astrid.data.RemoteModel; import com.todoroo.astrid.data.UserActivity; import com.todoroo.astrid.helper.AsyncImageView; -import com.todoroo.astrid.service.StatisticsService; import edu.mit.mobile.android.imagecache.ImageCache; @@ -408,8 +407,6 @@ public abstract class CommentsFragment extends SherlockListFragment { resetPictureButton(); refreshUpdatesList(); - - StatisticsService.reportEvent(commentAddStatistic()); } @Override diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/EditPeopleControlSet.java b/astrid/plugin-src/com/todoroo/astrid/actfm/EditPeopleControlSet.java index 22bf21220..35ce01d97 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/EditPeopleControlSet.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/EditPeopleControlSet.java @@ -62,7 +62,6 @@ import com.todoroo.astrid.helper.AsyncImageView; import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TagDataService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.service.ThemeService; @@ -714,9 +713,7 @@ public class EditPeopleControlSet extends PopupControlSet { task.putTransitory(TaskService.TRANS_ASSIGNED, true); if (assignedView == assignedCustom) { - StatisticsService.reportEvent(StatisticsConstants.TASK_ASSIGNED_EMAIL); } else if (task.getValue(Task.USER_ID) != Task.USER_ID_SELF) { - StatisticsService.reportEvent(StatisticsConstants.TASK_ASSIGNED_PICKER); } return true; diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/TagSettingsActivity.java b/astrid/plugin-src/com/todoroo/astrid/actfm/TagSettingsActivity.java index 6bb882f94..818170a55 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/TagSettingsActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/TagSettingsActivity.java @@ -60,7 +60,6 @@ import com.todoroo.astrid.data.User; import com.todoroo.astrid.helper.AsyncImageView; import com.todoroo.astrid.helper.UUIDHelper; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TagDataService; import com.todoroo.astrid.service.ThemeService; import com.todoroo.astrid.tags.TagFilterExposer; @@ -376,7 +375,6 @@ public class TagSettingsActivity extends SherlockFragmentActivity { int oldMemberCount = tagData.getValue(TagData.MEMBER_COUNT); if (members.length() > oldMemberCount) { - StatisticsService.reportEvent(StatisticsConstants.ACTFM_LIST_SHARED); } tagData.setValue(TagData.MEMBER_COUNT, members.length()); tagData.setFlag(TagData.FLAGS, TagData.FLAG_SILENT, isSilent.isChecked()); diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmPreferenceService.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmPreferenceService.java index 6e0537702..aac2936f7 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmPreferenceService.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmPreferenceService.java @@ -17,7 +17,6 @@ import com.todoroo.astrid.dao.RemoteModelDao; import com.todoroo.astrid.data.RemoteModel; import com.todoroo.astrid.service.PremiumUnlockService; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.sync.SyncProviderUtilities; import com.todoroo.astrid.utility.AstridPreferences; @@ -121,7 +120,6 @@ public class ActFmPreferenceService extends SyncProviderUtilities { @Override protected void reportLastErrorImpl(String lastError, String type) { - StatisticsService.reportEvent(StatisticsConstants.ACTFM_SYNC_ERROR, "type", type); //$NON-NLS-1$ } public synchronized static JSONObject thisUser() { diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncThread.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncThread.java index 3e09af773..e28ef425a 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncThread.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncThread.java @@ -25,7 +25,6 @@ import android.net.NetworkInfo; import android.support.v4.app.NotificationCompat; import android.util.Log; -import com.crittercism.app.Crittercism; import com.timsu.astrid.R; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; @@ -381,7 +380,6 @@ public class ActFmSyncThread { } catch (Exception e) { // In the worst case, restart thread if something goes wrong Log.e(ERROR_TAG, "Unexpected sync thread exception", e); - Crittercism.logHandledException(e); thread = null; startSyncThread(); } diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/AstridNewSyncMigrator.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/AstridNewSyncMigrator.java index c5e980bbe..7c21ce9b5 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/AstridNewSyncMigrator.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/AstridNewSyncMigrator.java @@ -6,7 +6,6 @@ import java.util.Set; import android.text.TextUtils; import android.util.Log; -import com.crittercism.app.Crittercism; import com.todoroo.andlib.data.DatabaseDao; import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.TodorooCursor; @@ -109,12 +108,10 @@ public class AstridNewSyncMigrator { tagDataService.save(newTagData); } catch (Exception e) { Log.e(LOG_TAG, "Error creating tag data", e); - Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error creating tag data", e); - Crittercism.logHandledException(e); } finally { if (noTagData != null) { noTagData.close(); @@ -139,11 +136,9 @@ public class AstridNewSyncMigrator { } } catch (Exception e) { Log.e(LOG_TAG, "Error clearing emergent tags"); - Crittercism.logHandledException(e); } } } catch (Exception e){ - Crittercism.logHandledException(e); } finally { if (emergentTags != null) { emergentTags.close(); @@ -192,7 +187,6 @@ public class AstridNewSyncMigrator { }); } catch (Exception e) { Log.e(LOG_TAG, "Error asserting UUIDs", e); - Crittercism.logHandledException(e); } // -------------- @@ -207,7 +201,6 @@ public class AstridNewSyncMigrator { taskDao.update(Functions.bitwiseAnd(Task.FLAGS, Task.FLAG_PUBLIC).gt(0), template); } catch (Exception e) { Log.e(LOG_TAG, "Error clearing task flags", e); - Crittercism.logHandledException(e); } // -------------- @@ -239,12 +232,10 @@ public class AstridNewSyncMigrator { } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating recurrence", e); - Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating recurrence", e); - Crittercism.logHandledException(e); } finally { if (tasksWithRecurrence != null) { tasksWithRecurrence.close(); @@ -291,13 +282,11 @@ public class AstridNewSyncMigrator { } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating updates", e); - Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating updates", e); - Crittercism.logHandledException(e); } finally { if (updates != null) { updates.close(); @@ -313,7 +302,6 @@ public class AstridNewSyncMigrator { userDao.deleteWhere(Criterion.or(User.UUID.isNull(), User.UUID.eq(""), User.UUID.eq("0"))); } catch (Exception e) { Log.e(LOG_TAG, "Error deleting incomplete user entries", e); - Crittercism.logHandledException(e); } // -------------- @@ -369,13 +357,11 @@ public class AstridNewSyncMigrator { taskAttachmentDao.createNew(attachment); } catch (Exception e) { Log.e(LOG_TAG, "Error migrating task attachment metadata", e); - Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating task attachment metadata", e); - Crittercism.logHandledException(e); } finally { if (fmCursor != null) { fmCursor.close(); @@ -401,7 +387,6 @@ public class AstridNewSyncMigrator { } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating active tasks ordering", e); - Crittercism.logHandledException(e); } try { @@ -420,7 +405,6 @@ public class AstridNewSyncMigrator { } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating today ordering", e); - Crittercism.logHandledException(e); } TodorooCursor allTagData = null; @@ -441,12 +425,10 @@ public class AstridNewSyncMigrator { taskListMetadataDao.createNew(tlm); } catch (Exception e) { Log.e(LOG_TAG, "Error migrating tag ordering", e); - Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating tag ordering", e); - Crittercism.logHandledException(e); } finally { if (allTagData != null) { allTagData.close(); @@ -493,13 +475,11 @@ public class AstridNewSyncMigrator { } catch (Exception e) { Log.e(LOG_TAG, "Error validating task to tag metadata", e); - Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error validating task to tag metadata", e); - Crittercism.logHandledException(e); } finally { if (incompleteMetadata != null) { incompleteMetadata.close(); @@ -513,7 +493,6 @@ public class AstridNewSyncMigrator { tagDataDao.deleteWhere(Functions.bitwiseAnd(TagData.FLAGS, TagData.FLAG_FEATURED).gt(0)); } catch (Exception e) { Log.e(LOG_TAG, "Error deleting featured list data", e); - Crittercism.logHandledException(e); } @@ -549,12 +528,10 @@ public class AstridNewSyncMigrator { taskOutstandingDao.createNew(to); } catch (Exception e) { Log.e(LOG_TAG, "Error creating tag_added outstanding entries", e); - Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error creating tag_added outstanding entries", e); - Crittercism.logHandledException(e); } finally { if (tagsAdded != null) { tagsAdded.close(); @@ -602,12 +579,10 @@ public class AstridNewSyncMigrator { } } catch (Exception e) { Log.e(LOG_TAG, "Error asserting UUIDs", e); - Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error asserting UUIDs", e); - Crittercism.logHandledException(e); } finally { if (cursor != null) { cursor.close(); diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ChangesHappened.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ChangesHappened.java index 08129e69a..da4357f62 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ChangesHappened.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/messages/ChangesHappened.java @@ -14,7 +14,6 @@ import org.json.JSONObject; import android.text.TextUtils; import android.util.Log; -import com.crittercism.app.Crittercism; import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.Property.PropertyVisitor; import com.todoroo.andlib.data.TodorooCursor; @@ -186,7 +185,6 @@ public class ChangesHappened { return null; } } catch (JSONException e) { - Crittercism.logHandledException(e); return null; } } diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/BackupConstants.java b/astrid/plugin-src/com/todoroo/astrid/backup/BackupConstants.java index bb2e517fd..3ef4ff2e8 100644 --- a/astrid/plugin-src/com/todoroo/astrid/backup/BackupConstants.java +++ b/astrid/plugin-src/com/todoroo/astrid/backup/BackupConstants.java @@ -8,7 +8,6 @@ package com.todoroo.astrid.backup; import java.io.File; import android.os.Environment; -import edu.umd.cs.findbugs.annotations.CheckForNull; /** @@ -68,7 +67,6 @@ public class BackupConstants { /** * @return export directory for tasks, or null if no SD card */ - @CheckForNull public static File defaultExportDirectory() { String storageState = Environment.getExternalStorageState(); if (storageState.equals(Environment.MEDIA_MOUNTED)) { diff --git a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java index 64373c67b..776a3cd99 100644 --- a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java @@ -56,7 +56,6 @@ import com.todoroo.astrid.api.TextInputCriterion; import com.todoroo.astrid.dao.Database; import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.ThemeService; import com.todoroo.astrid.utility.AstridPreferences; @@ -277,21 +276,18 @@ public class CustomFilterActivity extends SherlockFragmentActivity { @Override protected void onStop() { - StatisticsService.sessionStop(this); super.onStop(); } @Override protected void onResume() { super.onResume(); - StatisticsService.sessionStart(this); registerReceiver(filterCriteriaReceiver, new IntentFilter(AstridApiConstants.BROADCAST_SEND_CUSTOM_FILTER_CRITERIA)); populateCriteria(); } @Override protected void onPause() { - StatisticsService.sessionPause(); super.onPause(); unregisterReceiver(filterCriteriaReceiver); } diff --git a/astrid/plugin-src/com/todoroo/astrid/gcal/GCalControlSet.java b/astrid/plugin-src/com/todoroo/astrid/gcal/GCalControlSet.java index 61571724c..6dcaf03d2 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gcal/GCalControlSet.java +++ b/astrid/plugin-src/com/todoroo/astrid/gcal/GCalControlSet.java @@ -35,7 +35,6 @@ import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.gcal.Calendars.CalendarResult; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.ThemeService; import com.todoroo.astrid.ui.PopupControlSet; @@ -149,7 +148,6 @@ public class GCalControlSet extends PopupControlSet { !Preferences.getStringValue(R.string.gcal_p_default).equals("-1"); if ((gcalCreateEventEnabled || calendarSelector.getSelectedItemPosition() != 0) && calendarUri == null) { - StatisticsService.reportEvent(StatisticsConstants.CREATE_CALENDAR_EVENT); try{ ContentResolver cr = activity.getContentResolver(); diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksBackgroundService.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksBackgroundService.java index 3b34324b8..3aed193cf 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksBackgroundService.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksBackgroundService.java @@ -8,7 +8,6 @@ package com.todoroo.astrid.gtasks; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.astrid.gtasks.sync.GtasksSyncV2Provider; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.sync.SyncProviderUtilities; import com.todoroo.astrid.sync.SyncV2BackgroundService; import com.todoroo.astrid.sync.SyncV2Provider; @@ -33,12 +32,10 @@ public class GtasksBackgroundService extends SyncV2BackgroundService { @Override public void onCreate() { super.onCreate(); - StatisticsService.sessionStart(this); } @Override public void onDestroy() { - StatisticsService.sessionStop(this); super.onDestroy(); } diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksPreferenceService.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksPreferenceService.java index 4387c387f..2ba7c1aea 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksPreferenceService.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksPreferenceService.java @@ -8,7 +8,6 @@ package com.todoroo.astrid.gtasks; import com.timsu.astrid.R; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.sync.SyncProviderUtilities; /** @@ -58,7 +57,6 @@ public class GtasksPreferenceService extends SyncProviderUtilities { @Override protected void reportLastErrorImpl(String lastError, String type) { - StatisticsService.reportEvent(StatisticsConstants.GTASKS_SYNC_ERROR, "type", type); //$NON-NLS-1$ } } diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/auth/GtasksLoginActivity.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/auth/GtasksLoginActivity.java index e271b9a77..6acdcf418 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/auth/GtasksLoginActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/auth/GtasksLoginActivity.java @@ -36,7 +36,6 @@ import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.gtasks.GtasksPreferenceService; import com.todoroo.astrid.gtasks.api.GtasksInvoker; import com.todoroo.astrid.service.AstridDependencyInjector; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.SyncV2Service; /** @@ -169,19 +168,16 @@ public class GtasksLoginActivity extends ListActivity { @Override protected void onResume() { super.onResume(); - StatisticsService.sessionStart(this); } @Override protected void onPause() { super.onPause(); - StatisticsService.sessionPause(); } @Override protected void onStop() { super.onStop(); - StatisticsService.sessionStop(this); } private static final int REQUEST_AUTHENTICATE = 0; diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncV2Provider.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncV2Provider.java index e31d3a214..62617a8e3 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncV2Provider.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncV2Provider.java @@ -55,7 +55,6 @@ import com.todoroo.astrid.gtasks.auth.GtasksTokenValidator; import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.SyncResultCallbackWrapper.WidgetUpdatingCallbackWrapper; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.sync.SyncResultCallback; @@ -377,7 +376,6 @@ public class GtasksSyncV2Provider extends SyncV2Provider { } else { mergeDates(task.task, local); if(task.task.isCompleted() && !local.isCompleted()) { - StatisticsService.reportEvent(StatisticsConstants.GTASKS_TASK_COMPLETED); } } } else { // Set default importance and reminders for remotely created tasks diff --git a/astrid/plugin-src/com/todoroo/astrid/locale/LocaleEditAlerts.java b/astrid/plugin-src/com/todoroo/astrid/locale/LocaleEditAlerts.java index f50147e7e..6dbd2b462 100644 --- a/astrid/plugin-src/com/todoroo/astrid/locale/LocaleEditAlerts.java +++ b/astrid/plugin-src/com/todoroo/astrid/locale/LocaleEditAlerts.java @@ -28,7 +28,6 @@ import com.todoroo.astrid.api.FilterCategory; import com.todoroo.astrid.api.FilterListItem; import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.twofortyfouram.SharedResources; /** @@ -200,9 +199,6 @@ public final class LocaleEditAlerts extends ListActivity { .setPositiveButton(android.R.string.ok, AddOnActivity.createAddOnClicker(LocaleEditAlerts.this, true)) .show(); - StatisticsService.reportEvent(StatisticsConstants.LOCALE_EDIT_ALERTS_NO_PLUGIN); - } else { - StatisticsService.reportEvent(StatisticsConstants.LOCALE_EDIT_ALERTS); } } @@ -281,14 +277,12 @@ public final class LocaleEditAlerts extends ListActivity { @Override protected void onResume() { super.onResume(); - StatisticsService.sessionStart(this); adapter.registerRecevier(); } @Override protected void onPause() { super.onPause(); - StatisticsService.sessionPause(); adapter.unregisterRecevier(); } @@ -300,7 +294,6 @@ public final class LocaleEditAlerts extends ListActivity { @Override protected void onStop() { super.onStop(); - StatisticsService.sessionStop(this); } /** diff --git a/astrid/plugin-src/com/todoroo/astrid/notes/EditNoteActivity.java b/astrid/plugin-src/com/todoroo/astrid/notes/EditNoteActivity.java index 9ad75c4d3..d0e111069 100644 --- a/astrid/plugin-src/com/todoroo/astrid/notes/EditNoteActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/notes/EditNoteActivity.java @@ -79,7 +79,6 @@ import com.todoroo.astrid.helper.AsyncImageView; import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.StartupService; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.timers.TimerActionControlSet.TimerActionListener; import com.todoroo.astrid.utility.ResourceDrawableCache; @@ -546,7 +545,6 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene if (pictureButton != null) { pictureButton.setImageResource(cameraButton); } - StatisticsService.reportEvent(StatisticsConstants.ACTFM_TASK_COMMENT); setUpListAdapter(); for (UpdatesChangedListener l : listeners) { diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/NotificationFragment.java b/astrid/plugin-src/com/todoroo/astrid/reminders/NotificationFragment.java index b58c2b8a1..266235293 100644 --- a/astrid/plugin-src/com/todoroo/astrid/reminders/NotificationFragment.java +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/NotificationFragment.java @@ -30,7 +30,6 @@ import com.todoroo.astrid.activity.TaskListFragment; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.repeats.RepeatControlSet; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.ui.NumberPicker; /** @@ -62,7 +61,6 @@ public class NotificationFragment extends TaskListFragment { @Override protected void onTaskCompleted(Task item) { - StatisticsService.reportEvent(StatisticsConstants.TASK_COMPLETED_NOTIFICATION); } @Override diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderDialog.java b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderDialog.java index e7878f7d6..d333aa22e 100644 --- a/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderDialog.java +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderDialog.java @@ -49,7 +49,6 @@ import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.User; import com.todoroo.astrid.helper.AsyncImageView; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.tags.TagMemberMetadata; import com.todoroo.astrid.tags.TagService; @@ -86,7 +85,6 @@ public class ReminderDialog extends Dialog { task.setValue(Task.REMINDER_SNOOZE, time); PluginServices.getTaskService().save(task); dismiss(); - StatisticsService.reportEvent(StatisticsConstants.TASK_SNOOZE); } }; final OnTimeSetListener onTimeSet = new OnTimeSetListener() { diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderPreferences.java b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderPreferences.java index 97ca9718f..f487d7af4 100644 --- a/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderPreferences.java +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderPreferences.java @@ -58,7 +58,6 @@ public class ReminderPreferences extends TodorooPreferenceActivity { int index = AndroidUtilities.indexOf(r.getStringArray(R.array.EPr_rmd_time_values), (String)value); if (index != -1 && index < r.getStringArray(R.array.EPr_rmd_time).length) { // FIXME this does not fix the underlying cause of the ArrayIndexOutofBoundsException - // https://www.crittercism.com/developers/crash-details/e0886dbfcf9e78a21d9f2e2a385c4c13e2f6ad2132ac24a3fa811144 String setting = r.getStringArray(R.array.EPr_rmd_time)[index]; preference.setSummary(r.getString(R.string.rmd_EPr_rmd_time_desc, setting)); } diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java index c20e74a4e..1bf6c47f3 100644 --- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java +++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java @@ -39,7 +39,6 @@ import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.service.ThemeService; import com.todoroo.astrid.ui.DateAndTimeDialog; @@ -341,7 +340,6 @@ public class RepeatControlSet extends PopupControlSet { result = ""; //$NON-NLS-1$ } else { if(TextUtils.isEmpty(task.getValue(Task.RECURRENCE))) { - StatisticsService.reportEvent(StatisticsConstants.REPEAT_TASK_CREATE); } RRule rrule = new RRule(); diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java index fed6a72a2..87cf2b3c2 100644 --- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java +++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java @@ -36,7 +36,6 @@ import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.gcal.GCalHelper; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.utility.Flags; @@ -73,9 +72,6 @@ public class RepeatTaskCompleteListener extends BroadcastReceiver { return; } - - StatisticsService.reportEvent(StatisticsConstants.V2_TASK_REPEAT); - long oldDueDate = task.getValue(Task.DUE_DATE); long repeatUntil = task.getValue(Task.REPEAT_UNTIL); diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedTaskListFragment.java b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedTaskListFragment.java index b28439bc5..c7c21451f 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedTaskListFragment.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedTaskListFragment.java @@ -29,7 +29,6 @@ import com.todoroo.astrid.data.TagData; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.helper.AsyncImageView; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TagDataService; import com.todoroo.astrid.tags.TagFilterExposer; import com.todoroo.astrid.tags.TagService.Tag; @@ -130,7 +129,6 @@ public class FeaturedTaskListFragment extends TagViewFragment { return; } - StatisticsService.reportEvent(StatisticsConstants.FEATURED_LIST_CLONED); final String localName = tagData.getValue(TagData.NAME) + " " + getString(R.string.actfm_feat_list_suffix); //$NON-NLS-1$ TagData clone = new TagData(); TodorooCursor existing = tagDataService.query(Query.select(TagData.PROPERTIES) diff --git a/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java b/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java index 272656906..41a6fe94f 100644 --- a/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java +++ b/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java @@ -25,7 +25,6 @@ import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.utility.Constants; public class TimerPlugin extends BroadcastReceiver { @@ -62,7 +61,6 @@ public class TimerPlugin extends BroadcastReceiver { if(start) { if(task.getValue(Task.TIMER_START) == 0) { task.setValue(Task.TIMER_START, DateUtilities.now()); - StatisticsService.reportEvent(StatisticsConstants.TIMER_START); } } else { if(task.getValue(Task.TIMER_START) > 0) { @@ -70,7 +68,6 @@ public class TimerPlugin extends BroadcastReceiver { task.setValue(Task.TIMER_START, 0L); task.setValue(Task.ELAPSED_SECONDS, task.getValue(Task.ELAPSED_SECONDS) + newElapsed); - StatisticsService.reportEvent(StatisticsConstants.TIMER_STOP); } } PluginServices.getTaskService().save(task); diff --git a/astrid/proguard.cfg b/astrid/proguard.cfg index 5d0c546b8..b9bf134da 100644 --- a/astrid/proguard.cfg +++ b/astrid/proguard.cfg @@ -41,12 +41,6 @@ -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference --keep public class com.crittercism.* - --keepclassmembers public class com.crittercism.* { - *; -} - -keep class com.facebook.** { *; } diff --git a/astrid/res/values/keys.xml b/astrid/res/values/keys.xml index 9ddc4b7a1..e8fd2ddde 100644 --- a/astrid/res/values/keys.xml +++ b/astrid/res/values/keys.xml @@ -412,11 +412,6 @@ actfm_sync_freq - - - - statistics - showed_add_task_help diff --git a/astrid/res/xml/preferences_misc.xml b/astrid/res/xml/preferences_misc.xml index 3185af34b..281cfda1b 100644 --- a/astrid/res/xml/preferences_misc.xml +++ b/astrid/res/xml/preferences_misc.xml @@ -24,11 +24,5 @@ - - - \ No newline at end of file diff --git a/astrid/src/com/todoroo/astrid/activity/AstridActivity.java b/astrid/src/com/todoroo/astrid/activity/AstridActivity.java index a534a8e66..03b396526 100644 --- a/astrid/src/com/todoroo/astrid/activity/AstridActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/AstridActivity.java @@ -44,7 +44,6 @@ import com.todoroo.astrid.data.TagData; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.service.StartupService; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.subtasks.SubtasksHelper; import com.todoroo.astrid.ui.DateChangedAlerts; import com.todoroo.astrid.ui.QuickAddBar; @@ -113,7 +112,6 @@ public class AstridActivity extends SherlockFragmentActivity DependencyInjectionService.getInstance().inject(this); super.onCreate(savedInstanceState); ContextManager.setContext(this); - StatisticsService.sessionStart(this); new StartupService().onStartupApplication(this); } @@ -132,14 +130,12 @@ public class AstridActivity extends SherlockFragmentActivity protected void onPause() { super.onPause(); - StatisticsService.sessionPause(); AndroidUtilities.tryUnregisterReceiver(this, repeatConfirmationReceiver); } @Override protected void onStop() { super.onStop(); - StatisticsService.sessionStop(this); } /** @@ -152,7 +148,6 @@ public class AstridActivity extends SherlockFragmentActivity } if (item instanceof SearchFilter) { onSearchRequested(); - StatisticsService.reportEvent(StatisticsConstants.FILTER_SEARCH); return false; } else { // If showing both fragments, directly update the tasklist-fragment @@ -169,7 +164,6 @@ public class AstridActivity extends SherlockFragmentActivity // no animation for dualpane-layout AndroidUtilities.callOverridePendingTransition(this, 0, 0); - StatisticsService.reportEvent(StatisticsConstants.FILTER_LIST); return true; } else if(item instanceof IntentFilter) { try { diff --git a/astrid/src/com/todoroo/astrid/activity/EditPreferences.java b/astrid/src/com/todoroo/astrid/activity/EditPreferences.java index 2dc68f7b9..c16e8b96e 100644 --- a/astrid/src/com/todoroo/astrid/activity/EditPreferences.java +++ b/astrid/src/com/todoroo/astrid/activity/EditPreferences.java @@ -31,7 +31,6 @@ import android.preference.PreferenceScreen; import android.text.TextUtils; import android.widget.Toast; -import com.crittercism.app.Crittercism; import com.timsu.astrid.R; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.ContextManager; @@ -56,7 +55,6 @@ import com.todoroo.astrid.service.AddOnService; import com.todoroo.astrid.service.MarketStrategy.AmazonMarketStrategy; import com.todoroo.astrid.service.StartupService; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.sync.SyncProviderPreferences; import com.todoroo.astrid.ui.ContactListAdapter; @@ -136,7 +134,6 @@ public class EditPreferences extends TodorooPreferenceActivity { @Override public void onClick(DialogInterface dialog, int which) { spec.resetDefaults(); - StatisticsService.reportEvent(statistic); setResult(RESULT_CODE_PERFORMANCE_PREF_CHANGED); finish(); } @@ -288,7 +285,6 @@ public class EditPreferences extends TodorooPreferenceActivity { boolean hasPowerPack = addOnService.hasPowerPack(); findPreference(getString(R.string.p_files_dir)).setEnabled(ActFmPreferenceService.isPremiumUser()); findPreference(getString(R.string.p_voiceRemindersEnabled)).setEnabled(hasPowerPack); - findPreference(getString(R.string.p_statistics)).setEnabled(hasPowerPack); } /** Show about dialog */ @@ -517,8 +513,6 @@ public class EditPreferences extends TodorooPreferenceActivity { @Override public boolean onPreferenceChange(Preference p, Object newValue) { String valueString = newValue.toString(); - StatisticsService.reportEvent(StatisticsConstants.PREF_CHANGED_PREFIX + "row-style", //$NON-NLS-1$ - "changed-to", valueString); //$NON-NLS-1$ Preference notes = findPreference(getString(R.string.p_showNotes)); Preference fullTitle = findPreference(getString(R.string.p_fullTaskTitle)); try { @@ -588,10 +582,6 @@ public class EditPreferences extends TodorooPreferenceActivity { dir = r.getString(R.string.p_files_dir_desc_default); } preference.setSummary(r.getString(R.string.p_files_dir_desc, dir)); - } - else if (booleanPreference(preference, value, R.string.p_statistics, - R.string.EPr_statistics_desc_disabled, R.string.EPr_statistics_desc_enabled)) { - ; } else if (booleanPreference(preference, value, R.string.p_field_missed_calls, R.string.MCA_missed_calls_pref_desc_disabled, R.string.MCA_missed_calls_pref_desc_enabled)) { ; @@ -718,28 +708,10 @@ public class EditPreferences extends TodorooPreferenceActivity { }); } - findPreference(getString(R.string.p_statistics)).setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - Boolean value = (Boolean) newValue; - try { - if (!value.booleanValue()) { - Crittercism.setOptOutStatus(true); - } else { - Crittercism.setOptOutStatus(false); - } - } catch (NullPointerException e) { - return false; - } - return true; - } - }); - findPreference(getString(R.string.p_showNotes)).setOnPreferenceChangeListener(new OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { updatePreferences(preference, newValue); - StatisticsService.reportEvent(StatisticsConstants.PREF_SHOW_NOTES_IN_ROW, "enabled", newValue.toString()); //$NON-NLS-1$ return true; } }); @@ -748,7 +720,6 @@ public class EditPreferences extends TodorooPreferenceActivity { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { updatePreferences(preference, newValue); - StatisticsService.reportEvent(StatisticsConstants.PREF_CHANGED_PREFIX + "full-title", "full-title", newValue.toString()); //$NON-NLS-1$ //$NON-NLS-2$ return true; } }); @@ -817,7 +788,6 @@ public class EditPreferences extends TodorooPreferenceActivity { @Override protected void onPause() { - StatisticsService.sessionPause(); super.onPause(); } @@ -829,12 +799,10 @@ public class EditPreferences extends TodorooPreferenceActivity { @Override protected void onResume() { super.onResume(); - StatisticsService.sessionStart(this); } @Override protected void onStop() { - StatisticsService.sessionStop(this); super.onStop(); } diff --git a/astrid/src/com/todoroo/astrid/activity/Eula.java b/astrid/src/com/todoroo/astrid/activity/Eula.java index 45a7cc848..58dec29b4 100644 --- a/astrid/src/com/todoroo/astrid/activity/Eula.java +++ b/astrid/src/com/todoroo/astrid/activity/Eula.java @@ -26,7 +26,6 @@ import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; /** @@ -110,7 +109,6 @@ public final class Eula { ((EulaCallback)activity).eulaAccepted(); } Preferences.setBoolean(PREFERENCE_EULA_ACCEPTED, true); - StatisticsService.reportEvent(StatisticsConstants.EULA_ACCEPTED); } private static void refuse(Activity activity) { diff --git a/astrid/src/com/todoroo/astrid/activity/FilterListFragment.java b/astrid/src/com/todoroo/astrid/activity/FilterListFragment.java index 22a2bf59e..f36a576c3 100644 --- a/astrid/src/com/todoroo/astrid/activity/FilterListFragment.java +++ b/astrid/src/com/todoroo/astrid/activity/FilterListFragment.java @@ -52,7 +52,6 @@ import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.FilterListItem; import com.todoroo.astrid.api.FilterWithUpdate; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.tags.TagService; import com.todoroo.astrid.tags.TagsPlugin; import com.todoroo.astrid.utility.AstridPreferences; @@ -186,14 +185,12 @@ public class FilterListFragment extends SherlockListFragment { @Override public void onStop() { - StatisticsService.sessionStop(getActivity()); super.onStop(); } @Override public void onResume() { super.onResume(); - StatisticsService.sessionStart(getActivity()); if(adapter != null) { adapter.registerRecevier(); } @@ -215,7 +212,6 @@ public class FilterListFragment extends SherlockListFragment { @Override public void onPause() { - StatisticsService.sessionPause(); super.onPause(); if(adapter != null) { adapter.unregisterRecevier(); diff --git a/astrid/src/com/todoroo/astrid/activity/FilterShortcutActivity.java b/astrid/src/com/todoroo/astrid/activity/FilterShortcutActivity.java index bee7dd4fd..048bfd4dd 100644 --- a/astrid/src/com/todoroo/astrid/activity/FilterShortcutActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/FilterShortcutActivity.java @@ -17,7 +17,6 @@ import com.timsu.astrid.R; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.astrid.adapter.FilterAdapter; import com.todoroo.astrid.api.Filter; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.ThemeService; @SuppressWarnings("nls") @@ -79,14 +78,12 @@ public class FilterShortcutActivity extends ListActivity { @Override protected void onResume() { super.onResume(); - StatisticsService.sessionStart(this); adapter.registerRecevier(); } @Override protected void onPause() { super.onPause(); - StatisticsService.sessionPause(); adapter.unregisterRecevier(); } @@ -98,7 +95,6 @@ public class FilterShortcutActivity extends ListActivity { @Override protected void onStop() { super.onStop(); - StatisticsService.sessionStop(this); } } diff --git a/astrid/src/com/todoroo/astrid/activity/ShareActivity.java b/astrid/src/com/todoroo/astrid/activity/ShareActivity.java index ec72e82ae..40e7b7f8d 100644 --- a/astrid/src/com/todoroo/astrid/activity/ShareActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/ShareActivity.java @@ -16,7 +16,6 @@ import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.view.MenuItem; import com.timsu.astrid.R; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.ThemeService; public class ShareActivity extends SherlockFragmentActivity { @@ -45,7 +44,6 @@ public class ShareActivity extends SherlockFragmentActivity { setUpTextView(google, getString(R.string.share_with_google), "https://plus.google.com/116404018347675245869", "google"); //$NON-NLS-1$ //$NON-NLS-2$ setupText(); - StatisticsService.reportEvent(StatisticsConstants.SHARE_PAGE_VIEWED); } private void setUpTextView(TextView tv, String text, final String url, final String buttonId) { @@ -53,7 +51,6 @@ public class ShareActivity extends SherlockFragmentActivity { ((View) tv.getParent()).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - StatisticsService.reportEvent(StatisticsConstants.SHARE_BUTTON_CLICKED, "button", buttonId); //$NON-NLS-1$ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(intent); } diff --git a/astrid/src/com/todoroo/astrid/activity/TaskEditFragment.java b/astrid/src/com/todoroo/astrid/activity/TaskEditFragment.java index 00428b436..3811159a1 100755 --- a/astrid/src/com/todoroo/astrid/activity/TaskEditFragment.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskEditFragment.java @@ -93,7 +93,6 @@ import com.todoroo.astrid.opencrx.OpencrxCoreUtils; import com.todoroo.astrid.reminders.Notifications; import com.todoroo.astrid.repeats.RepeatControlSet; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.service.ThemeService; import com.todoroo.astrid.tags.TagsControlSet; @@ -786,12 +785,9 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener { } if (model.getValue(Task.TITLE).length() == 0) { - StatisticsService.reportEvent(StatisticsConstants.CREATE_TASK); // set deletion date until task gets a title model.setValue(Task.DELETION_DATE, DateUtilities.now()); - } else { - StatisticsService.reportEvent(StatisticsConstants.EDIT_TASK); } setIsNewTask(model.getValue(Task.TITLE).length() == 0); @@ -1340,7 +1336,6 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener { @Override public void onPause() { super.onPause(); - StatisticsService.sessionPause(); if (shouldSaveState) { save(true); @@ -1350,7 +1345,6 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener { @Override public void onResume() { super.onResume(); - StatisticsService.sessionStart(getActivity()); populateFields(); } @@ -1414,7 +1408,6 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener { @Override public void onStop() { super.onStop(); - StatisticsService.sessionStop(getActivity()); } private void adjustInfoPopovers() { diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java index c4b038b8a..0dc2a2d47 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java @@ -59,7 +59,6 @@ import com.todoroo.astrid.data.Task; import com.todoroo.astrid.people.PeopleFilterMode; import com.todoroo.astrid.people.PersonViewFragment; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.ThemeService; import com.todoroo.astrid.service.abtesting.ABTestEventReportingService; import com.todoroo.astrid.tags.TagFilterExposer; @@ -860,22 +859,16 @@ public class TaskListActivity extends AstridActivity implements MainMenuListener switch (getIntent().getIntExtra(TOKEN_SOURCE, Constants.SOURCE_DEFAULT)) { case Constants.SOURCE_NOTIFICATION: - StatisticsService.reportEvent(StatisticsConstants.LAUNCH_FROM_NOTIFICATION); break; case Constants.SOURCE_OTHER: - StatisticsService.reportEvent(StatisticsConstants.LAUNCH_FROM_OTHER); break; case Constants.SOURCE_PPWIDGET: - StatisticsService.reportEvent(StatisticsConstants.LAUNCH_FROM_PPW); break; case Constants.SOURCE_WIDGET: - StatisticsService.reportEvent(StatisticsConstants.LAUNCH_FROM_WIDGET); break; case Constants.SOURCE_C2DM: - StatisticsService.reportEvent(StatisticsConstants.LAUNCH_FROM_C2DM); break; case Constants.SOURCE_REENGAGEMENT: - StatisticsService.reportEvent(StatisticsConstants.LAUNCH_FROM_REENGAGEMENT); } getIntent().putExtra(TOKEN_SOURCE, Constants.SOURCE_DEFAULT); // Only report source once } diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListFragment.java b/astrid/src/com/todoroo/astrid/activity/TaskListFragment.java index c9258d283..9ae480249 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListFragment.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListFragment.java @@ -97,7 +97,6 @@ import com.todoroo.astrid.service.AddOnService; import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TagDataService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.service.ThemeService; @@ -1198,9 +1197,7 @@ public class TaskListFragment extends SherlockListFragment implements OnSortSele */ protected void onTaskCompleted(Task item) { if (isInbox) { - StatisticsService.reportEvent(StatisticsConstants.TASK_COMPLETED_INBOX); } else { - StatisticsService.reportEvent(StatisticsConstants.TASK_COMPLETED_FILTER); } } @@ -1348,7 +1345,6 @@ public class TaskListFragment extends SherlockListFragment implements OnSortSele Activity activity = getActivity(); switch(id) { case MENU_SORT_ID: - StatisticsService.reportEvent(StatisticsConstants.TLA_MENU_SORT); if (activity != null) { AlertDialog dialog = SortSelectionActivity.createDialog( getActivity(), hasDraggableOption(), this, sortFlags, sortSort); @@ -1356,7 +1352,6 @@ public class TaskListFragment extends SherlockListFragment implements OnSortSele } return true; case MENU_SYNC_ID: - StatisticsService.reportEvent(StatisticsConstants.TLA_MENU_SYNC); syncActionHelper.performSyncAction(); return true; case MENU_ADDON_INTENT_ID: @@ -1466,7 +1461,6 @@ public class TaskListFragment extends SherlockListFragment implements OnSortSele return; } - StatisticsService.reportEvent(StatisticsConstants.TLA_MENU_SETTINGS); Intent intent = new Intent(activity, EditPreferences.class); startActivityForResult(intent, ACTIVITY_SETTINGS); } diff --git a/astrid/src/com/todoroo/astrid/adapter/AddOnAdapter.java b/astrid/src/com/todoroo/astrid/adapter/AddOnAdapter.java index 1b2276782..fec7d22ef 100644 --- a/astrid/src/com/todoroo/astrid/adapter/AddOnAdapter.java +++ b/astrid/src/com/todoroo/astrid/adapter/AddOnAdapter.java @@ -25,7 +25,6 @@ import android.widget.Toast; import com.timsu.astrid.R; import com.todoroo.astrid.data.AddOn; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.utility.Constants; /** @@ -59,7 +58,6 @@ public class AddOnAdapter extends ArrayAdapter { if(buttonTag != null) { try { activity.startActivity(buttonTag.intent); - StatisticsService.reportEvent("addon-" + buttonTag.event); //$NON-NLS-1$ } catch (ActivityNotFoundException e) { Toast.makeText(activity, R.string.market_unavailable, Toast.LENGTH_LONG).show(); } diff --git a/astrid/src/com/todoroo/astrid/dao/Database.java b/astrid/src/com/todoroo/astrid/dao/Database.java index 8dcc78906..0b243cb93 100644 --- a/astrid/src/com/todoroo/astrid/dao/Database.java +++ b/astrid/src/com/todoroo/astrid/dao/Database.java @@ -9,7 +9,6 @@ import android.database.sqlite.SQLiteException; import android.text.TextUtils; import android.util.Log; -import com.crittercism.app.Crittercism; import com.todoroo.andlib.data.AbstractDatabase; import com.todoroo.andlib.data.AbstractModel; import com.todoroo.andlib.data.Property; @@ -163,7 +162,6 @@ public class Database extends AbstractDatabase { } @Override - @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="SF_SWITCH_FALLTHROUGH") protected synchronized boolean onUpgrade(int oldVersion, int newVersion) { SqlConstructorVisitor visitor = new SqlConstructorVisitor(); switch(oldVersion) { @@ -416,7 +414,6 @@ public class Database extends AbstractDatabase { database.execSQL(sql); } catch (SQLiteException e) { Log.e("astrid", "SQL Error: " + sql, e); - Crittercism.logHandledException(e); } } diff --git a/astrid/src/com/todoroo/astrid/dao/MetadataDao.java b/astrid/src/com/todoroo/astrid/dao/MetadataDao.java index 6ed9ad15b..a437f4f37 100644 --- a/astrid/src/com/todoroo/astrid/dao/MetadataDao.java +++ b/astrid/src/com/todoroo/astrid/dao/MetadataDao.java @@ -33,7 +33,6 @@ import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.TaskOutstanding; import com.todoroo.astrid.provider.Astrid2TaskProvider; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.tags.TaskToTagMetadata; import com.todoroo.astrid.utility.AstridPreferences; @@ -48,7 +47,6 @@ public class MetadataDao extends DatabaseDao { @Autowired private Database database; - @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ") public MetadataDao() { super(Metadata.class); DependencyInjectionService.getInstance().inject(this); @@ -176,7 +174,6 @@ public class MetadataDao extends DatabaseDao { if(Preferences.getBoolean(AstridPreferences.P_FIRST_LIST, true)) { if (state && item.containsNonNullValue(Metadata.KEY) && item.getValue(Metadata.KEY).equals(TaskToTagMetadata.KEY)) { - StatisticsService.reportEvent(StatisticsConstants.USER_FIRST_LIST); Preferences.setBoolean(AstridPreferences.P_FIRST_LIST, false); } } diff --git a/astrid/src/com/todoroo/astrid/dao/StoreObjectDao.java b/astrid/src/com/todoroo/astrid/dao/StoreObjectDao.java index fecb3f009..5f571fa7e 100644 --- a/astrid/src/com/todoroo/astrid/dao/StoreObjectDao.java +++ b/astrid/src/com/todoroo/astrid/dao/StoreObjectDao.java @@ -22,7 +22,6 @@ public class StoreObjectDao extends DatabaseDao { @Autowired private Database database; - @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ") public StoreObjectDao() { super(StoreObject.class); DependencyInjectionService.getInstance().inject(this); diff --git a/astrid/src/com/todoroo/astrid/dao/TagDataDao.java b/astrid/src/com/todoroo/astrid/dao/TagDataDao.java index d99dc72dc..2d87df923 100644 --- a/astrid/src/com/todoroo/astrid/dao/TagDataDao.java +++ b/astrid/src/com/todoroo/astrid/dao/TagDataDao.java @@ -21,7 +21,6 @@ public class TagDataDao extends RemoteModelDao { @Autowired Database database; - @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ") public TagDataDao() { super(TagData.class); DependencyInjectionService.getInstance().inject(this); diff --git a/astrid/src/com/todoroo/astrid/dao/TagMetadataDao.java b/astrid/src/com/todoroo/astrid/dao/TagMetadataDao.java index c0b19dc1e..7cb8f7928 100644 --- a/astrid/src/com/todoroo/astrid/dao/TagMetadataDao.java +++ b/astrid/src/com/todoroo/astrid/dao/TagMetadataDao.java @@ -48,7 +48,6 @@ public class TagMetadataDao extends DatabaseDao { @Autowired private Database database; - @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ") public TagMetadataDao() { super(TagMetadata.class); DependencyInjectionService.getInstance().inject(this); diff --git a/astrid/src/com/todoroo/astrid/dao/TaskAttachmentDao.java b/astrid/src/com/todoroo/astrid/dao/TaskAttachmentDao.java index 57bc87b00..76a2790c9 100644 --- a/astrid/src/com/todoroo/astrid/dao/TaskAttachmentDao.java +++ b/astrid/src/com/todoroo/astrid/dao/TaskAttachmentDao.java @@ -33,7 +33,6 @@ public class TaskAttachmentDao extends RemoteModelDao { @Autowired Database database; - @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ") public TaskAttachmentDao() { super(TaskAttachment.class); DependencyInjectionService.getInstance().inject(this); diff --git a/astrid/src/com/todoroo/astrid/dao/TaskDao.java b/astrid/src/com/todoroo/astrid/dao/TaskDao.java index 39384f484..750f57329 100644 --- a/astrid/src/com/todoroo/astrid/dao/TaskDao.java +++ b/astrid/src/com/todoroo/astrid/dao/TaskDao.java @@ -43,7 +43,6 @@ public class TaskDao extends RemoteModelDao { @Autowired private Database database; - @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ") public TaskDao() { super(Task.class); DependencyInjectionService.getInstance().inject(this); diff --git a/astrid/src/com/todoroo/astrid/dao/TaskListMetadataDao.java b/astrid/src/com/todoroo/astrid/dao/TaskListMetadataDao.java index ad5b043a1..ab8bba620 100644 --- a/astrid/src/com/todoroo/astrid/dao/TaskListMetadataDao.java +++ b/astrid/src/com/todoroo/astrid/dao/TaskListMetadataDao.java @@ -26,7 +26,6 @@ public class TaskListMetadataDao extends RemoteModelDao { @Autowired Database database; - @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ") public TaskListMetadataDao() { super(TaskListMetadata.class); DependencyInjectionService.getInstance().inject(this); diff --git a/astrid/src/com/todoroo/astrid/dao/UpdateDao.java b/astrid/src/com/todoroo/astrid/dao/UpdateDao.java index a177ad32a..1560c232c 100644 --- a/astrid/src/com/todoroo/astrid/dao/UpdateDao.java +++ b/astrid/src/com/todoroo/astrid/dao/UpdateDao.java @@ -21,7 +21,6 @@ public class UpdateDao extends RemoteModelDao { @Autowired Database database; - @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ") public UpdateDao() { super(Update.class); DependencyInjectionService.getInstance().inject(this); diff --git a/astrid/src/com/todoroo/astrid/dao/UserDao.java b/astrid/src/com/todoroo/astrid/dao/UserDao.java index 7416ddbd3..c8065c629 100644 --- a/astrid/src/com/todoroo/astrid/dao/UserDao.java +++ b/astrid/src/com/todoroo/astrid/dao/UserDao.java @@ -13,7 +13,6 @@ import com.todoroo.astrid.data.User; public class UserDao extends RemoteModelDao { @Autowired Database database; - @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ") public UserDao() { super(User.class); DependencyInjectionService.getInstance().inject(this); diff --git a/astrid/src/com/todoroo/astrid/provider/Astrid2TaskProvider.java b/astrid/src/com/todoroo/astrid/provider/Astrid2TaskProvider.java index 2b3ab76e5..798b06bdb 100644 --- a/astrid/src/com/todoroo/astrid/provider/Astrid2TaskProvider.java +++ b/astrid/src/com/todoroo/astrid/provider/Astrid2TaskProvider.java @@ -30,7 +30,6 @@ import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.tags.TagService; import com.todoroo.astrid.tags.TagService.Tag; @@ -273,7 +272,6 @@ public class Astrid2TaskProvider extends ContentProvider { task.setValue(Task.COMPLETION_DATE, values.getAsBoolean(COMPLETED) ? DateUtilities.now() : 0); if(task.isCompleted()) { - StatisticsService.reportEvent(StatisticsConstants.TASK_COMPLETED_API2); } } diff --git a/astrid/src/com/todoroo/astrid/service/StartupService.java b/astrid/src/com/todoroo/astrid/service/StartupService.java index 672ed733b..56112d972 100644 --- a/astrid/src/com/todoroo/astrid/service/StartupService.java +++ b/astrid/src/com/todoroo/astrid/service/StartupService.java @@ -25,7 +25,6 @@ import android.media.AudioManager; import android.util.Log; import android.widget.Toast; -import com.crittercism.app.Crittercism; import com.timsu.astrid.R; import com.todoroo.andlib.data.DatabaseDao.ModelUpdateListener; import com.todoroo.andlib.data.TodorooCursor; @@ -149,10 +148,6 @@ public class StartupService { // sets up context manager ContextManager.setContext(context); - if(!StatisticsService.dontCollectStatistics()) { - Crittercism.init(context.getApplicationContext(), Constants.CRITTERCISM_APP_ID); - } - try { database.openForWriting(); checkForMissingColumns(); @@ -344,9 +339,7 @@ public class StartupService { File[] children = directory.listFiles(); AndroidUtilities.sortFilesByDateDesc(children); if(children.length > 0) { - StatisticsService.sessionStart(context); TasksXmlImporter.importTasks(context, children[0].getAbsolutePath(), null); - StatisticsService.reportEvent(StatisticsConstants.LOST_TASKS_RESTORED); } } } catch (Exception e) { @@ -359,7 +352,6 @@ public class StartupService { private void checkForSubtasksUse() { if (!Preferences.getBoolean(PREF_SUBTASKS_CHECK, false)) { if (taskService.countTasks() > 3) { - StatisticsService.reportEvent(StatisticsConstants.SUBTASKS_HAS_TASKS); checkMetadataStat(Criterion.and(MetadataCriteria.withKey(SubtasksMetadata.METADATA_KEY), SubtasksMetadata.ORDER.gt(0)), StatisticsConstants.SUBTASKS_ORDER_USED); checkMetadataStat(Criterion.and(MetadataCriteria.withKey(SubtasksMetadata.METADATA_KEY), @@ -377,7 +369,6 @@ public class StartupService { if (!Preferences.getBoolean(PREF_SWIPE_CHECK, false)) { if (Preferences.getBoolean(R.string.p_swipe_lists_enabled, false) && Preferences.getBoolean(TaskListFragmentPager.PREF_SHOWED_SWIPE_HELPER, false)) { - StatisticsService.reportEvent(StatisticsConstants.SWIPE_USED); } Preferences.setBoolean(PREF_SWIPE_CHECK, true); } @@ -388,7 +379,6 @@ public class StartupService { private void checkForVoiceRemindersUse() { if (!Preferences.getBoolean(PREF_VOICE_REMINDERS_CHECK, false)) { if (Preferences.getBoolean(R.string.p_voiceRemindersEnabled, false)) { - StatisticsService.reportEvent(StatisticsConstants.VOICE_REMINDERS_ENABLED); Preferences.setBoolean(PREF_VOICE_REMINDERS_CHECK, true); } } @@ -398,7 +388,6 @@ public class StartupService { TodorooCursor sort = metadataService.query(Query.select(Metadata.ID).where(criterion).limit(1)); try { if (sort.getCount() > 0) { - StatisticsService.reportEvent(statistic); } } finally { sort.close(); diff --git a/astrid/src/com/todoroo/astrid/service/StatisticsConstants.java b/astrid/src/com/todoroo/astrid/service/StatisticsConstants.java index 6a7fd28cf..0ab222864 100644 --- a/astrid/src/com/todoroo/astrid/service/StatisticsConstants.java +++ b/astrid/src/com/todoroo/astrid/service/StatisticsConstants.java @@ -42,7 +42,6 @@ public class StatisticsConstants { public static final String TLA_MENU_SETTINGS = "tla-menu-settings"; public static final String TLA_MENU_SORT = "tla-menu-sort"; public static final String TLA_MENU_SYNC = "tla-menu-sync"; - public static final String TLA_CRITTERCISM = "tla-crittercism"; public static final String TLA_MENU_HELP = "tla-menu-help"; public static final String V2_TASK_REPEAT = "v2-task-repeat"; public static final String TASK_COMPLETED_INBOX = "task-completed-inbox"; diff --git a/astrid/src/com/todoroo/astrid/service/StatisticsService.java b/astrid/src/com/todoroo/astrid/service/StatisticsService.java deleted file mode 100644 index 951bb71db..000000000 --- a/astrid/src/com/todoroo/astrid/service/StatisticsService.java +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ - -package com.todoroo.astrid.service; - -import java.util.HashMap; - -import android.app.Activity; -import android.content.Context; - -import com.localytics.android.LocalyticsSession; -import com.timsu.astrid.R; -import com.todoroo.andlib.utility.Preferences; -import com.todoroo.astrid.utility.Constants; - -public class StatisticsService { - - private static LocalyticsSession localyticsSession; - - /** - * Indicate session started - * - * @param context - */ - public static void sessionStart(Context context) { - if(dontCollectStatistics()) { - return; - } - - if(localyticsSession != null) { - localyticsSession.open(); // Multiple calls to open are ok, we just need to make sure it gets reopened after pause - } else { - localyticsSession = new LocalyticsSession(context.getApplicationContext(), - Constants.LOCALYTICS_KEY); - localyticsSession.open(); - localyticsSession.upload(); - } - - if (context instanceof Activity) { - localyticsSession.tagScreen(context.getClass().getSimpleName()); - } - } - - /** - * Indicate session ended - * - * @param context - */ - public static void sessionStop(Context context) { - if(dontCollectStatistics()) { - return; - } - - if(localyticsSession != null) { - localyticsSession.upload(); - } - } - - /** - * Indicate session was paused - */ - public static void sessionPause() { - if(dontCollectStatistics()) { - return; - } - - if(localyticsSession != null) { - localyticsSession.close(); - } - } - - /** - * Indicates an error occurred - * @param name - * @param message - * @param trace - */ - public static void reportError(String name, String message, String trace) { - // no reports yet - } - - /** - * Indicates an event should be reported - * @param event - */ - public static void reportEvent(String event, String... attributes) { - if(dontCollectStatistics()) { - return; - } - - if(localyticsSession != null) { - if(attributes.length > 0) { - HashMap attrMap = new HashMap(); - for(int i = 1; i < attributes.length; i += 2) { - if(attributes[i] != null) { - attrMap.put(attributes[i - 1], attributes[i]); - } - } - localyticsSession.tagEvent(event, attrMap); - } else { - localyticsSession.tagEvent(event); - } - } - } - - public static boolean dontCollectStatistics() { - return !Preferences.getBoolean(R.string.p_statistics, true); - } - -} diff --git a/astrid/src/com/todoroo/astrid/service/TaskService.java b/astrid/src/com/todoroo/astrid/service/TaskService.java index 197a83d7c..234704296 100644 --- a/astrid/src/com/todoroo/astrid/service/TaskService.java +++ b/astrid/src/com/todoroo/astrid/service/TaskService.java @@ -148,14 +148,11 @@ public class TaskService { long diff = DateUtilities.now() - reminderLast; if (diff > 0 && diff < DateUtilities.ONE_DAY) { // within one day of last reminder - StatisticsService.reportEvent(StatisticsConstants.TASK_COMPLETED_ONE_DAY, "social", socialReminder); //$NON-NLS-1$ } if (diff > 0 && diff < DateUtilities.ONE_WEEK) { // within one week of last reminder - StatisticsService.reportEvent(StatisticsConstants.TASK_COMPLETED_ONE_WEEK, "social", socialReminder); //$NON-NLS-1$ } } - StatisticsService.reportEvent(StatisticsConstants.TASK_COMPLETED_V2); } else { item.setValue(Task.COMPLETION_DATE, 0L); } diff --git a/astrid/src/com/todoroo/astrid/service/abtesting/ABTestEventReportingService.java b/astrid/src/com/todoroo/astrid/service/abtesting/ABTestEventReportingService.java index 50ef1ec63..1858615e7 100644 --- a/astrid/src/com/todoroo/astrid/service/abtesting/ABTestEventReportingService.java +++ b/astrid/src/com/todoroo/astrid/service/abtesting/ABTestEventReportingService.java @@ -25,7 +25,6 @@ import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.dao.ABTestEventDao; import com.todoroo.astrid.data.ABTestEvent; import com.todoroo.astrid.service.StartupService; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; /** @@ -89,55 +88,11 @@ public final class ABTestEventReportingService { private void pushAllUnreportedABTestEvents() { synchronized(ABTestEventReportingService.class) { - if (StatisticsService.dontCollectStatistics()) { - return; - } - final TodorooCursor unreported = abTestEventDao.query(Query.select(ABTestEvent.PROPERTIES) - .where(ABTestEvent.REPORTED.eq(0)) - .orderBy(Order.asc(ABTestEvent.TEST_NAME), Order.asc(ABTestEvent.TIME_INTERVAL))); - if (unreported.getCount() > 0) { - try { - JSONArray payload = jsonArrayFromABTestEvents(unreported); - abTestInvoker.post(ABTestInvoker.AB_RETENTION_METHOD, payload); - ABTestEvent model = new ABTestEvent(); - for (unreported.moveToFirst(); !unreported.isAfterLast(); unreported.moveToNext()) { - model.readFromCursor(unreported); - model.setValue(ABTestEvent.REPORTED, 1); - abTestEventDao.saveExisting(model); - } - } catch (JSONException e) { - handleException(e); - } catch (IOException e) { - handleException(e); - } finally { - unreported.close(); - } - } } } private void reportUserActivation() { synchronized (ABTestEventReportingService.class) { - if (StatisticsService.dontCollectStatistics()) { - return; - } - if (Preferences.getBoolean(PREF_REPORTED_ACTIVATION, false) || !taskService.getUserActivationStatus()) { - return; - } - - final TodorooCursor variants = abTestEventDao.query(Query.select(ABTestEvent.PROPERTIES) - .groupBy(ABTestEvent.TEST_NAME)); - try { - JSONArray payload = jsonArrayForActivationAnalytics(variants); - abTestInvoker.post(ABTestInvoker.AB_ACTIVATION_METHOD, payload); - Preferences.setBoolean(PREF_REPORTED_ACTIVATION, true); - } catch (JSONException e) { - handleException(e); - } catch (IOException e) { - handleException(e); - } finally { - variants.close(); - } } } diff --git a/astrid/src/com/todoroo/astrid/service/abtesting/ABTestInvoker.java b/astrid/src/com/todoroo/astrid/service/abtesting/ABTestInvoker.java index f69811aea..4a14d7292 100644 --- a/astrid/src/com/todoroo/astrid/service/abtesting/ABTestInvoker.java +++ b/astrid/src/com/todoroo/astrid/service/abtesting/ABTestInvoker.java @@ -24,7 +24,6 @@ import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.RestClient; import com.todoroo.andlib.utility.Preferences; -import com.todoroo.astrid.service.StatisticsService; /** * Invoker for communicating with the Astrid Analytics server @@ -51,21 +50,6 @@ public class ABTestInvoker { } public void reportAcquisition() { - if (!Preferences.getBoolean(PREF_REPORTED_ACQUISITION, false) && - !StatisticsService.dontCollectStatistics()) { - new Thread(new Runnable() { - @Override - public void run() { - try { - HttpEntity postData = createPostData(null); - restClient.post(URL + ACQUISITION_METHOD, postData); - Preferences.setBoolean(PREF_REPORTED_ACQUISITION, true); - } catch (IOException e) { - // Ignored - } - } - }).start(); - } } /** diff --git a/astrid/src/com/todoroo/astrid/ui/QuickAddBar.java b/astrid/src/com/todoroo/astrid/ui/QuickAddBar.java index bd07363bd..0a45b2d33 100644 --- a/astrid/src/com/todoroo/astrid/ui/QuickAddBar.java +++ b/astrid/src/com/todoroo/astrid/ui/QuickAddBar.java @@ -60,7 +60,6 @@ import com.todoroo.astrid.repeats.RepeatControlSet; import com.todoroo.astrid.service.AddOnService; import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.utility.Flags; import com.todoroo.astrid.voice.VoiceRecognizer; @@ -388,7 +387,6 @@ public class QuickAddBar extends LinearLayout { fragment.onTaskCreated(task); - StatisticsService.reportEvent(StatisticsConstants.TASK_CREATED_TASKLIST); return task; } catch (Exception e) { exceptionService.displayAndReportError(activity, diff --git a/astrid/src/com/todoroo/astrid/ui/RandomReminderControlSet.java b/astrid/src/com/todoroo/astrid/ui/RandomReminderControlSet.java index ccf61e264..22236b25a 100644 --- a/astrid/src/com/todoroo/astrid/ui/RandomReminderControlSet.java +++ b/astrid/src/com/todoroo/astrid/ui/RandomReminderControlSet.java @@ -18,7 +18,6 @@ import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.helper.TaskEditControlSet; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; /** * Control set dealing with random reminder settings @@ -101,7 +100,6 @@ public class RandomReminderControlSet extends TaskEditControlSet { int hourValue = hours[periodSpinner.getSelectedItemPosition()]; task.setValue(Task.REMINDER_PERIOD, hourValue * DateUtilities.ONE_HOUR); if (task.getSetValues().containsKey(Task.REMINDER_PERIOD.name)) { - StatisticsService.reportEvent(StatisticsConstants.RANDOM_REMINDER_SAVED); } } else { task.setValue(Task.REMINDER_PERIOD, 0L); diff --git a/astrid/src/com/todoroo/astrid/utility/Constants.java b/astrid/src/com/todoroo/astrid/utility/Constants.java index 5eca3be69..fd6e932f3 100644 --- a/astrid/src/com/todoroo/astrid/utility/Constants.java +++ b/astrid/src/com/todoroo/astrid/utility/Constants.java @@ -12,12 +12,6 @@ public final class Constants { // --- general application constants - /** - * LCL API Key - */ - public static final String LOCALYTICS_KEY_LITE = "f3a40b93823ac2024b062f2-d96a8860-4a2c-11e2-35ca-004b50a28849"; - public static final String LOCALYTICS_KEY = "ae35a010c66a997ab129ab7-3e2adf46-8bb3-11e0-fe8b-007f58cb3154"; - /** * Application Package */ @@ -78,10 +72,6 @@ public final class Constants { /** Notification Manager id for astrid.com */ public static final int NOTIFICATION_ACTFM = -5; - // --- crittercism - - public static final String CRITTERCISM_APP_ID = "4e8a796fddf5203b6f0097c5"; - // --- amazon public static final String AWS_ACCESS_KEY_ID = "AKIAJTVL4FOF4PRBKBNA"; diff --git a/astrid/src/com/todoroo/astrid/welcome/tutorial/WelcomeWalkthrough.java b/astrid/src/com/todoroo/astrid/welcome/tutorial/WelcomeWalkthrough.java index ad0a9aadf..2028d39d9 100644 --- a/astrid/src/com/todoroo/astrid/welcome/tutorial/WelcomeWalkthrough.java +++ b/astrid/src/com/todoroo/astrid/welcome/tutorial/WelcomeWalkthrough.java @@ -32,7 +32,6 @@ import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.astrid.actfm.ActFmGoogleAuthActivity; import com.todoroo.astrid.actfm.ActFmLoginActivity; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.viewpagerindicator.CirclePageIndicator; public class WelcomeWalkthrough extends ActFmLoginActivity { @@ -113,7 +112,6 @@ public class WelcomeWalkthrough extends ActFmLoginActivity { simpleLogin.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - StatisticsService.reportEvent(StatisticsConstants.ACTFM_LOGIN_SIMPLE); final ProgressDialog pd = DialogUtilities.progressDialog(WelcomeWalkthrough.this, getString(R.string.gtasks_GLA_authenticating)); pd.show(); getAuthToken(email, pd); @@ -175,7 +173,6 @@ public class WelcomeWalkthrough extends ActFmLoginActivity { rejectQuickLogin.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - StatisticsService.reportEvent(StatisticsConstants.ACTFM_LOGIN_SIMPLE_REJECTED); switchToLoginPage(); } }); diff --git a/astrid/src/com/todoroo/astrid/widget/WidgetConfigActivity.java b/astrid/src/com/todoroo/astrid/widget/WidgetConfigActivity.java index 0dfc75f10..c317841e8 100644 --- a/astrid/src/com/todoroo/astrid/widget/WidgetConfigActivity.java +++ b/astrid/src/com/todoroo/astrid/widget/WidgetConfigActivity.java @@ -23,7 +23,6 @@ import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.FilterListItem; import com.todoroo.astrid.api.FilterWithCustomIntent; import com.todoroo.astrid.service.StatisticsConstants; -import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.ThemeService; @SuppressWarnings("nls") @@ -82,8 +81,6 @@ abstract public class WidgetConfigActivity extends ListActivity { Button button = (Button)findViewById(R.id.ok); button.setOnClickListener(mOnClickListener); - - StatisticsService.reportEvent(StatisticsConstants.WIDGET_CONFIG); } View.OnClickListener mOnClickListener = new View.OnClickListener() { @@ -114,14 +111,12 @@ abstract public class WidgetConfigActivity extends ListActivity { @Override protected void onResume() { super.onResume(); - StatisticsService.sessionStart(this); adapter.registerRecevier(); } @Override protected void onPause() { super.onPause(); - StatisticsService.sessionPause(); adapter.unregisterRecevier(); } @@ -133,7 +128,6 @@ abstract public class WidgetConfigActivity extends ListActivity { @Override protected void onStop() { super.onStop(); - StatisticsService.sessionStop(this); ThemeService.setForceFilterInvert(false); }