diff options
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java')
-rw-r--r-- | platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java | 1354 |
1 files changed, 704 insertions, 650 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java index 22624677c5..6deb961584 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java @@ -21,13 +21,11 @@ import android.support.annotation.NonNull; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.DisplayMetrics; -import timber.log.Timber; import android.view.WindowManager; import com.mapbox.mapboxsdk.BuildConfig; import com.mapbox.mapboxsdk.MapboxAccountManager; import com.mapbox.mapboxsdk.constants.GeoConstants; -import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.exceptions.TelemetryServiceNotConfiguredException; import com.mapbox.mapboxsdk.location.LocationServices; import com.mapbox.mapboxsdk.utils.MathUtils; @@ -53,6 +51,46 @@ import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.internal.Util; +import timber.log.Timber; + +import static com.mapbox.mapboxsdk.constants.MapboxConstants.KEY_META_DATA_STAGING_ACCESS_TOKEN; +import static com.mapbox.mapboxsdk.constants.MapboxConstants.KEY_META_DATA_STAGING_SERVER; +import static com.mapbox.mapboxsdk.constants.MapboxConstants.MAPBOX_LOCALE; +import static com.mapbox.mapboxsdk.constants.MapboxConstants.MAPBOX_SHARED_PREFERENCES_FILE; +import static com.mapbox.mapboxsdk.constants.MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_ENABLED; +import static com.mapbox.mapboxsdk.constants.MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_STAGING_ACCESS_TOKEN; +import static com.mapbox.mapboxsdk.constants.MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_STAGING_URL; +import static com.mapbox.mapboxsdk.constants.MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_VENDORID; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_ACCESSIBILITY_FONT_SCALE; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_APPLICATION_STATE; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_BATTERY_LEVEL; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_CARRIER; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_CELLULAR_NETWORK_TYPE; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_CREATED; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_ENABLED_TELEMETRY; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_EVENT; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_MODEL; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_OPERATING_SYSTEM; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_ORIENTATION; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_PLUGGED_IN; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_RESOLUTION; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_SESSION_ID; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_SOURCE; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_USERID; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.ATTRIBUTE_WIFI; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.KEY_ALTITUDE; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.KEY_GESTURE_ID; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.KEY_HORIZONTAL_ACCURACY; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.KEY_LATITUDE; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.KEY_LONGITUDE; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.KEY_ZOOM; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.MAPBOX_EVENTS_BASE_URL; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.SOURCE_MAPBOX; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.TYPE_LOCATION; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.TYPE_MAP_CLICK; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.TYPE_MAP_DRAGEND; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.TYPE_MAP_LOAD; +import static com.mapbox.mapboxsdk.telemetry.MapboxEvent.TYPE_TURNSTILE; /** * Singleton control center for managing Telemetry Data. @@ -60,733 +98,749 @@ import okhttp3.internal.Util; */ public class MapboxEventManager { - private static MapboxEventManager mapboxEventManager = null; + private static MapboxEventManager mapboxEventManager = null; - private boolean initialized = false; - private boolean stagingEnv; - private boolean telemetryEnabled; + private boolean initialized = false; + private boolean stagingEnv; + private boolean telemetryEnabled; - private final Vector<Hashtable<String, Object>> events = new Vector<>(); - private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", MapboxConstants.MAPBOX_LOCALE); + private final Vector<Hashtable<String, Object>> events = new Vector<>(); + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", MAPBOX_LOCALE); - private Context context = null; - private String accessToken = null; - private String eventsURL = MapboxEvent.MAPBOX_EVENTS_BASE_URL; + private Context context = null; + private String accessToken = null; + private String eventsURL = MAPBOX_EVENTS_BASE_URL; - private String userAgent = BuildConfig.MAPBOX_EVENTS_USER_AGENT_BASE; + private String userAgent = BuildConfig.MAPBOX_EVENTS_USER_AGENT_BASE; - private Intent batteryStatus = null; - private final String operatingSystem = "Android - " + Build.VERSION.RELEASE; + private Intent batteryStatus = null; + private final String operatingSystem = "Android - " + Build.VERSION.RELEASE; - private DisplayMetrics displayMetrics = null; + private DisplayMetrics displayMetrics = null; - private String mapboxVendorId = null; + private String mapboxVendorId = null; - private String mapboxSessionId = null; - private long mapboxSessionIdLastSet = 0; - private static long hourInMillis = 1000 * 60 * 60; - private static long flushDelayInitialInMillis = 1000 * 10; // 10 Seconds - private static long flushDelayInMillis = 1000 * 60 * 3; // 3 Minutes - private static final int SESSION_ID_ROTATION_HOURS = 24; + private String mapboxSessionId = null; + private long mapboxSessionIdLastSet = 0; + private static long hourInMillis = 1000 * 60 * 60; + private static long flushDelayInitialInMillis = 1000 * 10; // 10 Seconds + private static long flushDelayInMillis = 1000 * 60 * 3; // 3 Minutes + private static final int SESSION_ID_ROTATION_HOURS = 24; - private static final int FLUSH_EVENTS_CAP = 1000; + private static final int FLUSH_EVENTS_CAP = 1000; - private static MessageDigest messageDigest = null; + private static MessageDigest messageDigest = null; - private static final double locationEventAccuracy = 10000000; + private static final double locationEventAccuracy = 10000000; - private Timer timer = null; + private Timer timer = null; - /** - * Private Constructor for configuring the single instance per app. - */ - private MapboxEventManager() { - super(); - } - - /** - * Internal setup of MapboxEventsManager. It needs to be called once before @link MapboxEventManager#getMapboxEventManager - * - * This allows for a cleaner getMapboxEventManager() that doesn't require context and accessToken - * - * @param context The context associated with MapView - * @param accessToken The accessToken to load MapView - */ - public void initialize(@NonNull Context context, @NonNull String accessToken) { + /** + * Private Constructor for configuring the single instance per app. + */ + private MapboxEventManager() { + super(); + } - Timber.i("Telemetry initialize() called..."); - - if (initialized) { - Timber.i("Mapbox Telemetry has already been initialized."); - return; - } - - this.context = context.getApplicationContext(); - this.accessToken = accessToken; - - validateTelemetryServiceConfigured(); - - // Setup Message Digest - try { - messageDigest = MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - Timber.w("Error getting Encryption Algorithm: " + e); - } + /** + * Internal setup of MapboxEventsManager. It needs to be called once before + * {@link MapboxEventManager#getMapboxEventManager} + * <p> + * This allows for a cleaner getMapboxEventManager() that doesn't require context and accessToken + * + * @param context The context associated with MapView + * @param accessToken The accessToken to load MapView + */ + public void initialize(@NonNull Context context, @NonNull String accessToken) { - // Create Initial Session Id - rotateSessionId(); + Timber.i("Telemetry initialize() called..."); - SharedPreferences prefs = context.getSharedPreferences(MapboxConstants.MAPBOX_SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE); + if (initialized) { + Timber.i("Mapbox Telemetry has already been initialized."); + return; + } - // Determine if Telemetry Should Be Enabled - Timber.i("Right before Telemetry set enabled in initialized()"); - setTelemetryEnabled(prefs.getBoolean(MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_ENABLED, true)); + this.context = context.getApplicationContext(); + this.accessToken = accessToken; - // Load / Create Vendor Id - if (prefs.contains(MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_VENDORID)) { - mapboxVendorId = prefs.getString(MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_VENDORID, ""); - } - if (TextUtils.isEmpty(mapboxVendorId)) { - String vendorId = UUID.randomUUID().toString(); - mapboxVendorId = encodeString(vendorId); - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_VENDORID, mapboxVendorId); - editor.apply(); - editor.commit(); - } + validateTelemetryServiceConfigured(); - // Get DisplayMetrics Setup - displayMetrics = new DisplayMetrics(); - ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(displayMetrics); + // Setup Message Digest + try { + messageDigest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException noSuchAlgorithmException) { + Timber.w("Error getting Encryption Algorithm: " + noSuchAlgorithmException); + } - // Check for Staging Server Information - try { - ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); - String stagingURL = appInfo.metaData.getString(MapboxConstants.KEY_META_DATA_STAGING_SERVER); - String stagingAccessToken = appInfo.metaData.getString(MapboxConstants.KEY_META_DATA_STAGING_ACCESS_TOKEN); + // Create Initial Session Id + rotateSessionId(); - if (TextUtils.isEmpty(stagingURL) || TextUtils.isEmpty(stagingAccessToken)) { - Timber.d("Looking in SharedPreferences for Staging Credentials"); - stagingURL = prefs.getString(MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_STAGING_URL, null); - stagingAccessToken = prefs.getString(MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_STAGING_ACCESS_TOKEN, null); - } + SharedPreferences prefs = context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES_FILE, + Context.MODE_PRIVATE); - if (!TextUtils.isEmpty(stagingURL) && !TextUtils.isEmpty(stagingAccessToken)) { - eventsURL = stagingURL; - this.accessToken = accessToken; - stagingEnv = true; - } + // Determine if Telemetry Should Be Enabled + Timber.i("Right before Telemetry set enabled in initialized()"); + setTelemetryEnabled(prefs.getBoolean(MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_ENABLED, true)); - // Build User Agent - String appIdentifier = getApplicationIdentifier(); - if (TextUtils.equals(userAgent, BuildConfig.MAPBOX_EVENTS_USER_AGENT_BASE) && !TextUtils.isEmpty(appIdentifier)) { - userAgent = Util.toHumanReadableAscii(String.format(MapboxConstants.MAPBOX_LOCALE, "%s %s", appIdentifier, userAgent)); - } + // Load / Create Vendor Id + if (prefs.contains(MAPBOX_SHARED_PREFERENCE_KEY_VENDORID)) { + mapboxVendorId = prefs.getString(MAPBOX_SHARED_PREFERENCE_KEY_VENDORID, ""); + } + if (TextUtils.isEmpty(mapboxVendorId)) { + String vendorId = UUID.randomUUID().toString(); + mapboxVendorId = encodeString(vendorId); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(MAPBOX_SHARED_PREFERENCE_KEY_VENDORID, mapboxVendorId); + editor.apply(); + editor.commit(); + } - } catch (Exception e) { - Timber.e("Error Trying to load Staging Credentials: " + e.toString()); - } + // Get DisplayMetrics Setup + displayMetrics = new DisplayMetrics(); + ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(displayMetrics); + + // Check for Staging Server Information + try { + ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), + PackageManager.GET_META_DATA); + String stagingURL = appInfo.metaData.getString(KEY_META_DATA_STAGING_SERVER); + String stagingAccessToken = appInfo.metaData.getString(KEY_META_DATA_STAGING_ACCESS_TOKEN); + + if (TextUtils.isEmpty(stagingURL) || TextUtils.isEmpty(stagingAccessToken)) { + Timber.d("Looking in SharedPreferences for Staging Credentials"); + stagingURL = prefs.getString(MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_STAGING_URL, null); + stagingAccessToken = prefs.getString(MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_STAGING_ACCESS_TOKEN, null); + } + + if (!TextUtils.isEmpty(stagingURL) && !TextUtils.isEmpty(stagingAccessToken)) { + eventsURL = stagingURL; + this.accessToken = accessToken; + stagingEnv = true; + } - // Register for battery updates - IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); - batteryStatus = context.registerReceiver(null, iFilter); + // Build User Agent + String appIdentifier = getApplicationIdentifier(); + if (TextUtils.equals(userAgent, BuildConfig.MAPBOX_EVENTS_USER_AGENT_BASE) && !TextUtils.isEmpty(appIdentifier)) { + userAgent = Util.toHumanReadableAscii(String.format(MAPBOX_LOCALE, "%s %s", appIdentifier, userAgent)); + } - initialized = true; + } catch (Exception exception) { + Timber.e("Error Trying to load Staging Credentials: " + exception.toString()); } - /** - * Primary Access method using Singleton pattern - * - * @return MapboxEventManager - */ - public static MapboxEventManager getMapboxEventManager() { - if (mapboxEventManager == null) { - mapboxEventManager = new MapboxEventManager(); - } - return mapboxEventManager; + // Register for battery updates + IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + batteryStatus = context.registerReceiver(null, iFilter); + + initialized = true; + } + + /** + * Primary Access method using Singleton pattern + * + * @return MapboxEventManager + */ + public static MapboxEventManager getMapboxEventManager() { + if (mapboxEventManager == null) { + mapboxEventManager = new MapboxEventManager(); } - - // Checks that TelemetryService has been configured by developer - private void validateTelemetryServiceConfigured() { - try { - // Check Implementing app's AndroidManifest.xml - PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SERVICES); - if (packageInfo.services != null) { - for (ServiceInfo service : packageInfo.services) { - if (TextUtils.equals("com.mapbox.mapboxsdk.telemetry.TelemetryService", service.name)) { - return; - } - } - } - } catch (Exception e) { - Timber.w("Error checking for Telemetry Service Config: " + e); + return mapboxEventManager; + } + + // Checks that TelemetryService has been configured by developer + private void validateTelemetryServiceConfigured() { + try { + // Check Implementing app's AndroidManifest.xml + PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), + PackageManager.GET_SERVICES); + if (packageInfo.services != null) { + for (ServiceInfo service : packageInfo.services) { + if (TextUtils.equals("com.mapbox.mapboxsdk.telemetry.TelemetryService", service.name)) { + return; + } } - throw new TelemetryServiceNotConfiguredException(); + } + } catch (Exception exception) { + Timber.w("Error checking for Telemetry Service Config: " + exception); } - - public static String generateCreateDate() { - return dateFormat.format(new Date()); + throw new TelemetryServiceNotConfiguredException(); + } + + public static String generateCreateDate() { + return dateFormat.format(new Date()); + } + + public boolean isTelemetryEnabled() { + return telemetryEnabled; + } + + /** + * Enables / Disables Telemetry + * + * @param telemetryEnabled True to start telemetry, false to stop it + */ + public void setTelemetryEnabled(boolean telemetryEnabled) { + Timber.i("setTelemetryEnabled(); this.telemetryEnabled = " + this.telemetryEnabled + "; telemetryEnabled = " + + telemetryEnabled); + if (this.telemetryEnabled == telemetryEnabled) { + Timber.d("No need to start / stop telemetry as it's already in that state."); + return; } - public boolean isTelemetryEnabled() { - return telemetryEnabled; - } + if (telemetryEnabled) { + Timber.d("Starting Telemetry Up!"); + // Start It Up + context.startService(new Intent(context, TelemetryService.class)); - /** - * Enables / Disables Telemetry - * @param telemetryEnabled True to start telemetry, false to stop it - */ - public void setTelemetryEnabled(boolean telemetryEnabled) { - Timber.i("setTelemetryEnabled(); this.telemetryEnabled = " + this.telemetryEnabled + "; telemetryEnabled = " + telemetryEnabled); - if (this.telemetryEnabled == telemetryEnabled) { - Timber.d("No need to start / stop telemetry as it's already in that state."); - return; + // Make sure Ambient Mode is started at a minimum + if (LocationServices.getLocationServices(context).areLocationPermissionsGranted()) { + Timber.i("Permissions are good, see if GPS is enabled and if not then setup Ambient."); + if (LocationServices.getLocationServices(context).isGpsEnabled()) { + LocationServices.getLocationServices(context).toggleGPS(false); } - - if (telemetryEnabled) { - Timber.d("Starting Telemetry Up!"); - // Start It Up - context.startService(new Intent(context, TelemetryService.class)); - - // Make sure Ambient Mode is started at a minimum + } else { + // Start timer that checks for Permissions + Timber.i("Permissions are not good. Need to do some looping to check on stuff."); + + final Handler permsHandler = new Handler(); + Runnable runnable = new Runnable() { + @Override + public void run() { if (LocationServices.getLocationServices(context).areLocationPermissionsGranted()) { - Timber.i("Permissions are good, see if GPS is enabled and if not then setup Ambient."); - if (LocationServices.getLocationServices(context).isGPSEnabled()) { - LocationServices.getLocationServices(context).toggleGPS(false); - } + Timber.i("Permissions finally granted, so starting Ambient if GPS isn't already enabled"); + // Start Ambient + if (LocationServices.getLocationServices(context).isGpsEnabled()) { + LocationServices.getLocationServices(context).toggleGPS(false); + } } else { - // Start timer that checks for Permissions - Timber.i("Permissions are not good. Need to do some looping to check on stuff."); - - final Handler permsHandler = new Handler(); - Runnable runnable = new Runnable() { - @Override - public void run() { - if (LocationServices.getLocationServices(context).areLocationPermissionsGranted()) { - Timber.i("Permissions finally granted, so starting Ambient if GPS isn't already enabled"); - // Start Ambient - if (LocationServices.getLocationServices(context).isGPSEnabled()) { - LocationServices.getLocationServices(context).toggleGPS(false); - } - } else { - // Restart Handler - Timber.i("Permissions not granted yet... let's try again in 30 seconds"); - permsHandler.postDelayed(this, 1000 * 30); - } - } - }; - permsHandler.postDelayed(runnable, 1000 * 10); + // Restart Handler + Timber.i("Permissions not granted yet... let's try again in 30 seconds"); + permsHandler.postDelayed(this, 1000 * 30); } - - // Manage Timer Flush - timer = new Timer(); - timer.schedule(new FlushEventsTimerTask(), flushDelayInitialInMillis, flushDelayInMillis); - } else { - Timber.d("Shutting Telemetry Down"); - // Shut It Down - events.removeAllElements(); - context.stopService(new Intent(context, TelemetryService.class)); - - if (timer != null) { - timer.cancel(); - timer = null; - } - } - - // Persist - this.telemetryEnabled = telemetryEnabled; - SharedPreferences prefs = context.getSharedPreferences(MapboxConstants.MAPBOX_SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_ENABLED, telemetryEnabled); - editor.apply(); - editor.commit(); + } + }; + permsHandler.postDelayed(runnable, 1000 * 10); + } + + // Manage Timer Flush + timer = new Timer(); + timer.schedule(new FlushEventsTimerTask(), flushDelayInitialInMillis, flushDelayInMillis); + } else { + Timber.d("Shutting Telemetry Down"); + // Shut It Down + events.removeAllElements(); + context.stopService(new Intent(context, TelemetryService.class)); + + if (timer != null) { + timer.cancel(); + timer = null; + } } - /** - * Immediately attempt to send all events data in the queue to the server. - * - * NOTE: Permission set to package private to enable only telemetry code to use this. - */ - void flushEventsQueueImmediately() { - Timber.i("flushEventsQueueImmediately() called..."); - new FlushTheEventsTask().execute(); + // Persist + this.telemetryEnabled = telemetryEnabled; + SharedPreferences prefs = context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_ENABLED, telemetryEnabled); + editor.apply(); + editor.commit(); + } + + /** + * Immediately attempt to send all events data in the queue to the server. + * <p> + * NOTE: Permission set to package private to enable only telemetry code to use this. + */ + void flushEventsQueueImmediately() { + Timber.i("flushEventsQueueImmediately() called..."); + new FlushTheEventsTask().execute(); + } + + /** + * Centralized method for adding populated event to the queue allowing for cap size checking + * + * @param event Event to add to the Events Queue + */ + private void putEventOnQueue(@NonNull Hashtable<String, Object> event) { + if (event == null) { + return; } - - /** - * Centralized method for adding populated event to the queue allowing for cap size checking - * @param event Event to add to the Events Queue - */ - private void putEventOnQueue(@NonNull Hashtable<String, Object> event) { - if (event == null) { - return; - } - events.add(event); - if (events.size() == FLUSH_EVENTS_CAP) { - Timber.d("eventsSize == flushCap so send data."); - flushEventsQueueImmediately(); - } + events.add(event); + if (events.size() == FLUSH_EVENTS_CAP) { + Timber.d("eventsSize == flushCap so send data."); + flushEventsQueueImmediately(); } - - /** - * Adds a Location Event to the system for processing - * - * @param location Location event - */ - public void addLocationEvent(Location location) { - - // NaN and Infinite checks to prevent JSON errors at send to server time - if (Double.isNaN(location.getLatitude()) || Double.isNaN(location.getLongitude()) || Double.isNaN(location.getAltitude()) || Float.isNaN(location.getAccuracy())) { - return; - } - - if (Double.isInfinite(location.getLatitude()) || Double.isInfinite(location.getLongitude()) || Double.isInfinite(location.getAltitude()) || Float.isInfinite(location.getAccuracy())) { - return; - } - - // Add Location even to queue - Hashtable<String, Object> event = new Hashtable<>(); - event.put(MapboxEvent.ATTRIBUTE_EVENT, MapboxEvent.TYPE_LOCATION); - event.put(MapboxEvent.ATTRIBUTE_CREATED, generateCreateDate()); - event.put(MapboxEvent.ATTRIBUTE_SOURCE, MapboxEvent.SOURCE_MAPBOX); - event.put(MapboxEvent.ATTRIBUTE_SESSION_ID, encodeString(mapboxSessionId)); - event.put(MapboxEvent.KEY_LATITUDE, Math.floor(location.getLatitude() * locationEventAccuracy) / locationEventAccuracy); - event.put(MapboxEvent.KEY_LONGITUDE, Math.floor(location.getLongitude() * locationEventAccuracy) / locationEventAccuracy); - event.put(MapboxEvent.KEY_ALTITUDE, location.getAltitude()); - event.put(MapboxEvent.KEY_HORIZONTAL_ACCURACY, Math.round(location.getAccuracy())); - event.put(MapboxEvent.ATTRIBUTE_OPERATING_SYSTEM, operatingSystem); - event.put(MapboxEvent.ATTRIBUTE_APPLICATION_STATE, getApplicationState()); - - putEventOnQueue(event); - - rotateSessionId(); + } + + /** + * Adds a Location Event to the system for processing + * + * @param location Location event + */ + public void addLocationEvent(Location location) { + + // NaN and Infinite checks to prevent JSON errors at send to server time + if (Double.isNaN(location.getLatitude()) || Double.isNaN(location.getLongitude()) + || Double.isNaN(location.getAltitude()) || Float.isNaN(location.getAccuracy())) { + return; } - /** - * Push Interactive Events to the system for processing - * - * @param eventWithAttributes Event with attributes - */ - public void pushEvent(Hashtable<String, Object> eventWithAttributes) { - if (context == null || accessToken == null) { - return; - } - - if (eventWithAttributes == null) { - return; - } - - String eventType = (String) eventWithAttributes.get(MapboxEvent.ATTRIBUTE_EVENT); - if (TextUtils.isEmpty(eventType)) { - return; - } - - if (eventType.equalsIgnoreCase(MapboxEvent.TYPE_MAP_LOAD)) { - // Map Load Data Model - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_USERID, mapboxVendorId); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_MODEL, Build.MODEL); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_OPERATING_SYSTEM, operatingSystem); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_RESOLUTION, displayMetrics.density); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_ACCESSIBILITY_FONT_SCALE, getAccesibilityFontScaleSize()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_ORIENTATION, getOrientation()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_BATTERY_LEVEL, getBatteryLevel()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_PLUGGED_IN, isPluggedIn()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_CARRIER, getCellularCarrier()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_CELLULAR_NETWORK_TYPE, getCellularNetworkType()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_WIFI, getConnectedToWifi()); - - // Put Map Load on events before Turnstile clears it - putEventOnQueue(eventWithAttributes); - - // Turnstile - pushTurnstileEvent(); - - // Return immediately to avoid double adding of event - return; - - } else if (eventType.equalsIgnoreCase(MapboxEvent.TYPE_MAP_CLICK)) { - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_ORIENTATION, getOrientation()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_BATTERY_LEVEL, getBatteryLevel()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_PLUGGED_IN, isPluggedIn()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_CARRIER, getCellularCarrier()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_CELLULAR_NETWORK_TYPE, getCellularNetworkType()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_WIFI, getConnectedToWifi()); - } else if (eventType.equalsIgnoreCase(MapboxEvent.TYPE_MAP_DRAGEND)) { - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_ORIENTATION, getOrientation()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_BATTERY_LEVEL, getBatteryLevel()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_PLUGGED_IN, isPluggedIn()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_CARRIER, getCellularCarrier()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_CELLULAR_NETWORK_TYPE, getCellularNetworkType()); - eventWithAttributes.put(MapboxEvent.ATTRIBUTE_WIFI, getConnectedToWifi()); - } else { - Timber.w("This is not an event type in the Events Data Model."); - return; - } - - putEventOnQueue(eventWithAttributes); + if (Double.isInfinite(location.getLatitude()) || Double.isInfinite(location.getLongitude()) + || Double.isInfinite(location.getAltitude()) || Float.isInfinite(location.getAccuracy())) { + return; } - /** - * Pushes turnstile event for internal billing purposes - */ - private void pushTurnstileEvent() { - - Hashtable<String, Object> event = new Hashtable<>(); - event.put(MapboxEvent.ATTRIBUTE_EVENT, MapboxEvent.TYPE_TURNSTILE); - event.put(MapboxEvent.ATTRIBUTE_CREATED, generateCreateDate()); - event.put(MapboxEvent.ATTRIBUTE_USERID, mapboxVendorId); - event.put(MapboxEvent.ATTRIBUTE_ENABLED_TELEMETRY, telemetryEnabled); - - events.add(event); - - // Send to Server Immediately - flushEventsQueueImmediately(); - Timber.d("turnstile event pushed."); + // Add Location even to queue + Hashtable<String, Object> event = new Hashtable<>(); + event.put(ATTRIBUTE_EVENT, TYPE_LOCATION); + event.put(ATTRIBUTE_CREATED, generateCreateDate()); + event.put(ATTRIBUTE_SOURCE, SOURCE_MAPBOX); + event.put(ATTRIBUTE_SESSION_ID, encodeString(mapboxSessionId)); + event.put(KEY_LATITUDE, Math.floor(location.getLatitude() * locationEventAccuracy) + / locationEventAccuracy); + event.put(KEY_LONGITUDE, Math.floor(location.getLongitude() * locationEventAccuracy) + / locationEventAccuracy); + event.put(KEY_ALTITUDE, location.getAltitude()); + event.put(KEY_HORIZONTAL_ACCURACY, Math.round(location.getAccuracy())); + event.put(ATTRIBUTE_OPERATING_SYSTEM, operatingSystem); + event.put(ATTRIBUTE_APPLICATION_STATE, getApplicationState()); + + putEventOnQueue(event); + + rotateSessionId(); + } + + /** + * Push Interactive Events to the system for processing + * + * @param eventWithAttributes Event with attributes + */ + public void pushEvent(Hashtable<String, Object> eventWithAttributes) { + if (context == null || accessToken == null) { + return; } - /** - * SHA-1 Encoding for strings - * @param string String to encode - * @return String encoded if no error, original string if error - */ - private String encodeString(String string) { - try { - if (messageDigest != null) { - messageDigest.reset(); - messageDigest.update(string.getBytes("UTF-8")); - byte[] bytes = messageDigest.digest(); - - // Get the Hex version of the digest - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append( String.format("%02X", b) ); - } - String hex = sb.toString(); - - return hex; - } - } catch (Exception e) { - Timber.w("Error encoding string, will return in original form." + e); - } - return string; + if (eventWithAttributes == null) { + return; } - /** - * Changes Session Id based on time boundary - */ - private void rotateSessionId() { - long now = System.currentTimeMillis(); - if ((TextUtils.isEmpty(mapboxSessionId)) || (now - mapboxSessionIdLastSet > (SESSION_ID_ROTATION_HOURS * hourInMillis))) { - mapboxSessionId = UUID.randomUUID().toString(); - mapboxSessionIdLastSet = System.currentTimeMillis(); - } + String eventType = (String) eventWithAttributes.get(ATTRIBUTE_EVENT); + if (TextUtils.isEmpty(eventType)) { + return; } - private String getOrientation() { - switch (context.getResources().getConfiguration().orientation) { - case Configuration.ORIENTATION_LANDSCAPE: - return "Landscape"; - case Configuration.ORIENTATION_PORTRAIT: - return "Portrait"; - default: - return ""; - } + if (eventType.equalsIgnoreCase(TYPE_MAP_LOAD)) { + // Map Load Data Model + eventWithAttributes.put(ATTRIBUTE_USERID, mapboxVendorId); + eventWithAttributes.put(ATTRIBUTE_MODEL, Build.MODEL); + eventWithAttributes.put(ATTRIBUTE_OPERATING_SYSTEM, operatingSystem); + eventWithAttributes.put(ATTRIBUTE_RESOLUTION, displayMetrics.density); + eventWithAttributes.put(ATTRIBUTE_ACCESSIBILITY_FONT_SCALE, getAccesibilityFontScaleSize()); + eventWithAttributes.put(ATTRIBUTE_ORIENTATION, getOrientation()); + eventWithAttributes.put(ATTRIBUTE_BATTERY_LEVEL, getBatteryLevel()); + eventWithAttributes.put(ATTRIBUTE_PLUGGED_IN, isPluggedIn()); + eventWithAttributes.put(ATTRIBUTE_CARRIER, getCellularCarrier()); + eventWithAttributes.put(ATTRIBUTE_CELLULAR_NETWORK_TYPE, getCellularNetworkType()); + eventWithAttributes.put(ATTRIBUTE_WIFI, getConnectedToWifi()); + + // Put Map Load on events before Turnstile clears it + putEventOnQueue(eventWithAttributes); + + // Turnstile + pushTurnstileEvent(); + + // Return immediately to avoid double adding of event + return; + + } else if (eventType.equalsIgnoreCase(TYPE_MAP_CLICK)) { + eventWithAttributes.put(ATTRIBUTE_ORIENTATION, getOrientation()); + eventWithAttributes.put(ATTRIBUTE_BATTERY_LEVEL, getBatteryLevel()); + eventWithAttributes.put(ATTRIBUTE_PLUGGED_IN, isPluggedIn()); + eventWithAttributes.put(ATTRIBUTE_CARRIER, getCellularCarrier()); + eventWithAttributes.put(ATTRIBUTE_CELLULAR_NETWORK_TYPE, getCellularNetworkType()); + eventWithAttributes.put(ATTRIBUTE_WIFI, getConnectedToWifi()); + } else if (eventType.equalsIgnoreCase(TYPE_MAP_DRAGEND)) { + eventWithAttributes.put(ATTRIBUTE_ORIENTATION, getOrientation()); + eventWithAttributes.put(ATTRIBUTE_BATTERY_LEVEL, getBatteryLevel()); + eventWithAttributes.put(ATTRIBUTE_PLUGGED_IN, isPluggedIn()); + eventWithAttributes.put(ATTRIBUTE_CARRIER, getCellularCarrier()); + eventWithAttributes.put(ATTRIBUTE_CELLULAR_NETWORK_TYPE, getCellularNetworkType()); + eventWithAttributes.put(ATTRIBUTE_WIFI, getConnectedToWifi()); + } else { + Timber.w("This is not an event type in the Events Data Model."); + return; } - private int getBatteryLevel() { - int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); - int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); + putEventOnQueue(eventWithAttributes); + } + + /** + * Pushes turnstile event for internal billing purposes + */ + private void pushTurnstileEvent() { + + Hashtable<String, Object> event = new Hashtable<>(); + event.put(ATTRIBUTE_EVENT, TYPE_TURNSTILE); + event.put(ATTRIBUTE_CREATED, generateCreateDate()); + event.put(ATTRIBUTE_USERID, mapboxVendorId); + event.put(ATTRIBUTE_ENABLED_TELEMETRY, telemetryEnabled); + + events.add(event); + + // Send to Server Immediately + flushEventsQueueImmediately(); + Timber.d("turnstile event pushed."); + } + + /** + * SHA-1 Encoding for strings + * + * @param string String to encode + * @return String encoded if no error, original string if error + */ + private String encodeString(String string) { + try { + if (messageDigest != null) { + messageDigest.reset(); + messageDigest.update(string.getBytes("UTF-8")); + byte[] bytes = messageDigest.digest(); + + // Get the Hex version of the digest + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02X", b)); + } + String hex = sb.toString(); - return Math.round((level / (float)scale) * 100); + return hex; + } + } catch (Exception exception) { + Timber.w("Error encoding string, will return in original form." + exception); + } + return string; + } + + /** + * Changes Session Id based on time boundary + */ + private void rotateSessionId() { + long now = System.currentTimeMillis(); + if ((TextUtils.isEmpty(mapboxSessionId)) || (now - mapboxSessionIdLastSet + > (SESSION_ID_ROTATION_HOURS * hourInMillis))) { + mapboxSessionId = UUID.randomUUID().toString(); + mapboxSessionIdLastSet = System.currentTimeMillis(); + } + } + + private String getOrientation() { + switch (context.getResources().getConfiguration().orientation) { + case Configuration.ORIENTATION_LANDSCAPE: + return "Landscape"; + case Configuration.ORIENTATION_PORTRAIT: + return "Portrait"; + default: + return ""; + } + } + + private int getBatteryLevel() { + int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); + + return Math.round((level / (float) scale) * 100); + } + + /** + * Determine if device is plugged in to power via USB or AC or not. + * <p> + * http://developer.android.com/reference/android/os/BatteryManager.html#EXTRA_PLUGGED + * + * @return true if plugged in, false if not + */ + private boolean isPluggedIn() { + + int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + if (chargePlug == BatteryManager.BATTERY_PLUGGED_USB || chargePlug == BatteryManager.BATTERY_PLUGGED_AC) { + return true; } - /** - * Determine if device is plugged in to power via USB or AC or not. - * - * http://developer.android.com/reference/android/os/BatteryManager.html#EXTRA_PLUGGED - * @return true if plugged in, false if not - */ - private boolean isPluggedIn() { + return false; + } - int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); - if (chargePlug == BatteryManager.BATTERY_PLUGGED_USB || chargePlug == BatteryManager.BATTERY_PLUGGED_AC) { - return true; - } + private String getApplicationState() { - return false; + ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses(); + if (appProcesses == null) { + return ""; } - - private String getApplicationState() { - - ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses(); - if (appProcesses == null) { - return ""; - } - final String packageName = context.getPackageName(); - for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { - if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName.equals(packageName)) { - return "Foreground"; - } - } - return "Background"; + final String packageName = context.getPackageName(); + for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { + if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND + && appProcess.processName.equals(packageName)) { + return "Foreground"; + } } - - private float getAccesibilityFontScaleSize() { - // Values - // Small = 0.85 - // Normal = 1.0 - // Large = 1.15 - // Huge = 1.3 - - return context.getResources().getConfiguration().fontScale; + return "Background"; + } + + private float getAccesibilityFontScaleSize() { + // Values + // Small = 0.85 + // Normal = 1.0 + // Large = 1.15 + // Huge = 1.3 + + return context.getResources().getConfiguration().fontScale; + } + + private String getCellularCarrier() { + TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + String carrierName = manager.getNetworkOperatorName(); + if (TextUtils.isEmpty(carrierName)) { + carrierName = ""; } - - private String getCellularCarrier() { - TelephonyManager manager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); - String carrierName = manager.getNetworkOperatorName(); - if (TextUtils.isEmpty(carrierName)) { - carrierName = ""; - } - return carrierName; + return carrierName; + } + + private String getCellularNetworkType() { + TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + switch (manager.getNetworkType()) { + case TelephonyManager.NETWORK_TYPE_1xRTT: + return "1xRTT"; + case TelephonyManager.NETWORK_TYPE_CDMA: + return "CDMA"; + case TelephonyManager.NETWORK_TYPE_EDGE: + return "EDGE"; + case TelephonyManager.NETWORK_TYPE_EHRPD: + return "EHRPD"; + case TelephonyManager.NETWORK_TYPE_EVDO_0: + return "EVDO_0"; + case TelephonyManager.NETWORK_TYPE_EVDO_A: + return "EVDO_A"; + case TelephonyManager.NETWORK_TYPE_EVDO_B: + return "EVDO_B"; + case TelephonyManager.NETWORK_TYPE_GPRS: + return "GPRS"; + case TelephonyManager.NETWORK_TYPE_HSDPA: + return "HSDPA"; + case TelephonyManager.NETWORK_TYPE_HSPA: + return "HSPA"; + case TelephonyManager.NETWORK_TYPE_HSPAP: + return "HSPAP"; + case TelephonyManager.NETWORK_TYPE_HSUPA: + return "HSUPA"; + case TelephonyManager.NETWORK_TYPE_IDEN: + return "IDEN"; + case TelephonyManager.NETWORK_TYPE_LTE: + return "LTE"; + case TelephonyManager.NETWORK_TYPE_UMTS: + return "UMTS"; + case TelephonyManager.NETWORK_TYPE_UNKNOWN: + return "Unknown"; + default: + return ""; } + } - private String getCellularNetworkType () { - TelephonyManager manager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); - switch (manager.getNetworkType()) { - case TelephonyManager.NETWORK_TYPE_1xRTT: - return "1xRTT"; - case TelephonyManager.NETWORK_TYPE_CDMA: - return "CDMA"; - case TelephonyManager.NETWORK_TYPE_EDGE: - return "EDGE"; - case TelephonyManager.NETWORK_TYPE_EHRPD: - return "EHRPD"; - case TelephonyManager.NETWORK_TYPE_EVDO_0: - return "EVDO_0"; - case TelephonyManager.NETWORK_TYPE_EVDO_A: - return "EVDO_A"; - case TelephonyManager.NETWORK_TYPE_EVDO_B: - return "EVDO_B"; - case TelephonyManager.NETWORK_TYPE_GPRS: - return "GPRS"; - case TelephonyManager.NETWORK_TYPE_HSDPA: - return "HSDPA"; - case TelephonyManager.NETWORK_TYPE_HSPA: - return "HSPA"; - case TelephonyManager.NETWORK_TYPE_HSPAP: - return "HSPAP"; - case TelephonyManager.NETWORK_TYPE_HSUPA: - return "HSUPA"; - case TelephonyManager.NETWORK_TYPE_IDEN: - return "IDEN"; - case TelephonyManager.NETWORK_TYPE_LTE: - return "LTE"; - case TelephonyManager.NETWORK_TYPE_UMTS: - return "UMTS"; - case TelephonyManager.NETWORK_TYPE_UNKNOWN: - return "Unknown"; - default: - return ""; - } - } + public Boolean getConnectedToWifi() { - public Boolean getConnectedToWifi() { - - Boolean status = false; - WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - if (wifiMgr.isWifiEnabled()) { - try { - WifiInfo wifiInfo = wifiMgr.getConnectionInfo(); - if (wifiInfo.getNetworkId() != -1){ - status = true; - } - } catch (Exception e) { - Timber.w("Error getting Wifi Connection Status: " + e); - status = false; - } + Boolean status = false; + WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + if (wifiMgr.isWifiEnabled()) { + try { + WifiInfo wifiInfo = wifiMgr.getConnectionInfo(); + if (wifiInfo.getNetworkId() != -1) { + status = true; } - - return status; + } catch (Exception exception) { + Timber.w("Error getting Wifi Connection Status: " + exception); + status = false; + } } - - /** - * Task responsible for converting stored events and sending them to the server - */ - private class FlushTheEventsTask extends AsyncTask<Void, Void, Void> { - - @Override - protected Void doInBackground(Void... voids) { - - if (events.isEmpty()) { - Timber.d("No events in the queue to send so returning."); - return null; + return status; + } + + + /** + * Task responsible for converting stored events and sending them to the server + */ + private class FlushTheEventsTask extends AsyncTask<Void, Void, Void> { + + @Override + protected Void doInBackground(Void... voids) { + + if (events.isEmpty()) { + Timber.d("No events in the queue to send so returning."); + return null; + } + + // Check for NetworkConnectivity + if (!MapboxAccountManager.getInstance().isConnected()) { + Timber.w("Not connected to network, so empty events cache and return without attempting to send events"); + // Make sure that events don't pile up when Offline + // and thus impact available memory over time. + events.removeAllElements(); + return null; + } + + Response response = null; + + try { + // Send data + // ========= + JSONArray jsonArray = new JSONArray(); + + Vector<Hashtable<String, Object>> eventsClone = (Vector<Hashtable<String, Object>>) events.clone(); + + for (Hashtable<String, Object> evt : eventsClone) { + JSONObject jsonObject = new JSONObject(); + + // Build the JSON but only if there's a value for it in the evt + jsonObject.putOpt(ATTRIBUTE_EVENT, evt.get(ATTRIBUTE_EVENT)); + jsonObject.putOpt(ATTRIBUTE_CREATED, evt.get(ATTRIBUTE_CREATED)); + jsonObject.putOpt(ATTRIBUTE_USERID, evt.get(ATTRIBUTE_USERID)); + jsonObject.putOpt(ATTRIBUTE_ENABLED_TELEMETRY, evt.get(ATTRIBUTE_ENABLED_TELEMETRY)); + jsonObject.putOpt(ATTRIBUTE_SOURCE, evt.get(ATTRIBUTE_SOURCE)); + jsonObject.putOpt(ATTRIBUTE_SESSION_ID, evt.get(ATTRIBUTE_SESSION_ID)); + jsonObject.putOpt(KEY_LATITUDE, evt.get(KEY_LATITUDE)); + + // Make sure Longitude Is Wrapped + if (evt.containsKey(KEY_LONGITUDE)) { + double lon = (double) evt.get(KEY_LONGITUDE); + if ((lon < GeoConstants.MIN_LONGITUDE) || (lon > GeoConstants.MAX_LONGITUDE)) { + lon = MathUtils.wrap(lon, GeoConstants.MIN_LONGITUDE, GeoConstants.MAX_LONGITUDE); } - - // Check for NetworkConnectivity - if (!MapboxAccountManager.getInstance().isConnected()) { - Timber.w("Not connected to network, so empty events cache and return without attempting to send events"); - // Make sure that events don't pile up when Offline - // and thus impact available memory over time. - events.removeAllElements(); - return null; + jsonObject.put(KEY_LONGITUDE, lon); + } + + jsonObject.putOpt(KEY_ALTITUDE, evt.get(KEY_ALTITUDE)); + jsonObject.putOpt(KEY_ZOOM, evt.get(KEY_ZOOM)); + jsonObject.putOpt(ATTRIBUTE_OPERATING_SYSTEM, evt.get(ATTRIBUTE_OPERATING_SYSTEM)); + jsonObject.putOpt(ATTRIBUTE_USERID, evt.get(ATTRIBUTE_USERID)); + jsonObject.putOpt(ATTRIBUTE_MODEL, evt.get(ATTRIBUTE_MODEL)); + jsonObject.putOpt(ATTRIBUTE_RESOLUTION, evt.get(ATTRIBUTE_RESOLUTION)); + jsonObject.putOpt(ATTRIBUTE_ACCESSIBILITY_FONT_SCALE, evt.get(ATTRIBUTE_ACCESSIBILITY_FONT_SCALE)); + jsonObject.putOpt(ATTRIBUTE_BATTERY_LEVEL, evt.get(ATTRIBUTE_BATTERY_LEVEL)); + jsonObject.putOpt(ATTRIBUTE_PLUGGED_IN, evt.get(ATTRIBUTE_PLUGGED_IN)); + jsonObject.putOpt(ATTRIBUTE_WIFI, evt.get(ATTRIBUTE_WIFI)); + + // Special Cases where empty string is denoting null and therefore should not be sent at all + // This arises as thread safe Hashtable does not accept null values (nor keys) + if (evt.containsKey(ATTRIBUTE_ORIENTATION)) { + String orientation = (String) evt.get(ATTRIBUTE_ORIENTATION); + if (!TextUtils.isEmpty(orientation)) { + jsonObject.putOpt(ATTRIBUTE_ORIENTATION, orientation); } - - Response response = null; - - try { - // Send data - // ========= - JSONArray jsonArray = new JSONArray(); - - Vector<Hashtable<String, Object>> eventsClone = (Vector<Hashtable<String, Object>>) events.clone(); - - for (Hashtable<String, Object> evt : eventsClone) { - JSONObject jsonObject = new JSONObject(); - - // Build the JSON but only if there's a value for it in the evt - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_EVENT, evt.get(MapboxEvent.ATTRIBUTE_EVENT)); - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_CREATED, evt.get(MapboxEvent.ATTRIBUTE_CREATED)); - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_USERID, evt.get(MapboxEvent.ATTRIBUTE_USERID)); - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_ENABLED_TELEMETRY, evt.get(MapboxEvent.ATTRIBUTE_ENABLED_TELEMETRY)); - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_SOURCE, evt.get(MapboxEvent.ATTRIBUTE_SOURCE)); - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_SESSION_ID, evt.get(MapboxEvent.ATTRIBUTE_SESSION_ID)); - jsonObject.putOpt(MapboxEvent.KEY_LATITUDE, evt.get(MapboxEvent.KEY_LATITUDE)); - - // Make sure Longitude Is Wrapped - if (evt.containsKey(MapboxEvent.KEY_LONGITUDE)) { - double lon = (double)evt.get(MapboxEvent.KEY_LONGITUDE); - if ((lon < GeoConstants.MIN_LONGITUDE) || (lon > GeoConstants.MAX_LONGITUDE)) { - lon = MathUtils.wrap(lon, GeoConstants.MIN_LONGITUDE, GeoConstants.MAX_LONGITUDE); - } - jsonObject.put(MapboxEvent.KEY_LONGITUDE, lon); - } - - jsonObject.putOpt(MapboxEvent.KEY_ALTITUDE, evt.get(MapboxEvent.KEY_ALTITUDE)); - jsonObject.putOpt(MapboxEvent.KEY_ZOOM, evt.get(MapboxEvent.KEY_ZOOM)); - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_OPERATING_SYSTEM, evt.get(MapboxEvent.ATTRIBUTE_OPERATING_SYSTEM)); - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_USERID, evt.get(MapboxEvent.ATTRIBUTE_USERID)); - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_MODEL, evt.get(MapboxEvent.ATTRIBUTE_MODEL)); - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_RESOLUTION, evt.get(MapboxEvent.ATTRIBUTE_RESOLUTION)); - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_ACCESSIBILITY_FONT_SCALE, evt.get(MapboxEvent.ATTRIBUTE_ACCESSIBILITY_FONT_SCALE)); - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_BATTERY_LEVEL, evt.get(MapboxEvent.ATTRIBUTE_BATTERY_LEVEL)); - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_PLUGGED_IN, evt.get(MapboxEvent.ATTRIBUTE_PLUGGED_IN)); - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_WIFI, evt.get(MapboxEvent.ATTRIBUTE_WIFI)); - - // Special Cases where empty string is denoting null and therefore should not be sent at all - // This arises as thread safe Hashtable does not accept null values (nor keys) - if (evt.containsKey(MapboxEvent.ATTRIBUTE_ORIENTATION)) { - String orientation = (String)evt.get(MapboxEvent.ATTRIBUTE_ORIENTATION); - if (!TextUtils.isEmpty(orientation)) { - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_ORIENTATION, orientation); - } - } - if (evt.containsKey(MapboxEvent.ATTRIBUTE_CARRIER)) { - String carrier = (String)evt.get(MapboxEvent.ATTRIBUTE_CARRIER); - if (!TextUtils.isEmpty(carrier)) { - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_CARRIER, carrier); - } - } - if (evt.containsKey(MapboxEvent.ATTRIBUTE_APPLICATION_STATE)) { - String appState = (String)evt.get(MapboxEvent.ATTRIBUTE_APPLICATION_STATE); - if (!TextUtils.isEmpty(appState)) { - jsonObject.putOpt(MapboxEvent.ATTRIBUTE_APPLICATION_STATE, evt.get(MapboxEvent.ATTRIBUTE_APPLICATION_STATE)); - } - } - - // Special Cases where null has to be passed if no value exists - // Requires using put() instead of putOpt() - String eventType = (String)evt.get(MapboxEvent.ATTRIBUTE_EVENT); - if (!TextUtils.isEmpty(eventType) && eventType.equalsIgnoreCase(MapboxEvent.TYPE_MAP_CLICK)) { - jsonObject.put(MapboxEvent.KEY_GESTURE_ID, evt.get(MapboxEvent.KEY_GESTURE_ID)); - } - if (evt.containsKey(MapboxEvent.ATTRIBUTE_CELLULAR_NETWORK_TYPE)) { - String cellularNetworkType = (String)evt.get(MapboxEvent.ATTRIBUTE_CELLULAR_NETWORK_TYPE); - if (TextUtils.isEmpty(cellularNetworkType)) { - jsonObject.put(MapboxEvent.ATTRIBUTE_CELLULAR_NETWORK_TYPE, null); - } else { - jsonObject.put(MapboxEvent.ATTRIBUTE_CELLULAR_NETWORK_TYPE, evt.get(MapboxEvent.ATTRIBUTE_CELLULAR_NETWORK_TYPE)); - } - } - - jsonArray.put(jsonObject); - } - - // Based on http://square.github.io/okhttp/3.x/okhttp/okhttp3/CertificatePinner.html - CertificatePinner.Builder certificatePinnerBuilder = new CertificatePinner.Builder(); - if(stagingEnv){ - // Staging - Geotrust - certificatePinnerBuilder - .add("cloudfront-staging.tilestream.net", "sha256/3euxrJOrEZI15R4104UsiAkDqe007EPyZ6eTL/XxdAY=") - .add("cloudfront-staging.tilestream.net", "sha256/5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=") - .add("cloudfront-staging.tilestream.net", "sha256/r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="); - }else{ - certificatePinnerBuilder - // Prod - Geotrust - .add("events.mapbox.com", "sha256/BhynraKizavqoC5U26qgYuxLZst6pCu9J5stfL6RSYY=") - .add("events.mapbox.com", "sha256/owrR9U9FWDWtrFF+myoRIu75JwU4sJwzvhCNLZoY37g=") - .add("events.mapbox.com", "sha256/SQVGZiOrQXi+kqxcvWWE96HhfydlLVqFr4lQTqI5qqo=") - // Prod - DigiCert - .add("events.mapbox.com", "sha256/Tb0uHZ/KQjWh8N9+CZFLc4zx36LONQ55l6laDi1qtT4=") - .add("events.mapbox.com", "sha256/RRM1dGqnDFsCJXBTHky16vi1obOlCgFFn/yOhI/y+ho=") - .add("events.mapbox.com", "sha256/WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="); - } - - OkHttpClient client = new OkHttpClient.Builder() - .certificatePinner(certificatePinnerBuilder.build()) - .addInterceptor(new GzipRequestInterceptor()) - .build(); - RequestBody body = RequestBody.create(JSON, jsonArray.toString()); - - String url = eventsURL + "/events/v2?access_token=" + accessToken; - - Request request = new Request.Builder() - .url(url) - .header("User-Agent", userAgent) - .post(body) - .build(); - response = client.newCall(request).execute(); - Timber.d("response code = " + response.code() + " for events " + events.size()); - - } catch (Exception e) { - Timber.e("FlushTheEventsTask borked: " + e); - e.printStackTrace(); - } finally { - if (response != null && response.body() != null) { - response.body().close(); - } - // Reset Events - // ============ - events.removeAllElements(); + } + if (evt.containsKey(ATTRIBUTE_CARRIER)) { + String carrier = (String) evt.get(ATTRIBUTE_CARRIER); + if (!TextUtils.isEmpty(carrier)) { + jsonObject.putOpt(ATTRIBUTE_CARRIER, carrier); } + } + if (evt.containsKey(ATTRIBUTE_APPLICATION_STATE)) { + String appState = (String) evt.get(ATTRIBUTE_APPLICATION_STATE); + if (!TextUtils.isEmpty(appState)) { + jsonObject.putOpt(ATTRIBUTE_APPLICATION_STATE, evt.get(ATTRIBUTE_APPLICATION_STATE)); + } + } + + // Special Cases where null has to be passed if no value exists + // Requires using put() instead of putOpt() + String eventType = (String) evt.get(ATTRIBUTE_EVENT); + if (!TextUtils.isEmpty(eventType) && eventType.equalsIgnoreCase(TYPE_MAP_CLICK)) { + jsonObject.put(KEY_GESTURE_ID, evt.get(KEY_GESTURE_ID)); + } + if (evt.containsKey(ATTRIBUTE_CELLULAR_NETWORK_TYPE)) { + String cellularNetworkType = (String) evt.get(ATTRIBUTE_CELLULAR_NETWORK_TYPE); + if (TextUtils.isEmpty(cellularNetworkType)) { + jsonObject.put(ATTRIBUTE_CELLULAR_NETWORK_TYPE, null); + } else { + jsonObject.put(ATTRIBUTE_CELLULAR_NETWORK_TYPE, evt.get(ATTRIBUTE_CELLULAR_NETWORK_TYPE)); + } + } + + jsonArray.put(jsonObject); + } + + // Based on http://square.github.io/okhttp/3.x/okhttp/okhttp3/CertificatePinner.html + CertificatePinner.Builder certificatePinnerBuilder = new CertificatePinner.Builder(); + if (stagingEnv) { + // Staging - Geotrust + certificatePinnerBuilder + .add("cloudfront-staging.tilestream.net", "sha256/3euxrJOrEZI15R4104UsiAkDqe007EPyZ6eTL/XxdAY=") + .add("cloudfront-staging.tilestream.net", "sha256/5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=") + .add("cloudfront-staging.tilestream.net", "sha256/r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="); + } else { + certificatePinnerBuilder + // Prod - Geotrust + .add("events.mapbox.com", "sha256/BhynraKizavqoC5U26qgYuxLZst6pCu9J5stfL6RSYY=") + .add("events.mapbox.com", "sha256/owrR9U9FWDWtrFF+myoRIu75JwU4sJwzvhCNLZoY37g=") + .add("events.mapbox.com", "sha256/SQVGZiOrQXi+kqxcvWWE96HhfydlLVqFr4lQTqI5qqo=") + // Prod - DigiCert + .add("events.mapbox.com", "sha256/Tb0uHZ/KQjWh8N9+CZFLc4zx36LONQ55l6laDi1qtT4=") + .add("events.mapbox.com", "sha256/RRM1dGqnDFsCJXBTHky16vi1obOlCgFFn/yOhI/y+ho=") + .add("events.mapbox.com", "sha256/WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="); + } - return null; + OkHttpClient client = new OkHttpClient.Builder() + .certificatePinner(certificatePinnerBuilder.build()) + .addInterceptor(new GzipRequestInterceptor()) + .build(); + RequestBody body = RequestBody.create(JSON, jsonArray.toString()); + + String url = eventsURL + "/events/v2?access_token=" + accessToken; + + Request request = new Request.Builder() + .url(url) + .header("User-Agent", userAgent) + .post(body) + .build(); + response = client.newCall(request).execute(); + Timber.d("response code = " + response.code() + " for events " + events.size()); + + } catch (Exception exception) { + Timber.e("FlushTheEventsTask borked: " + exception); + exception.printStackTrace(); + } finally { + if (response != null && response.body() != null) { + response.body().close(); } + // Reset Events + // ============ + events.removeAllElements(); + } + return null; } + } + + /** + * TimerTask responsible for sending event data to server + */ + private class FlushEventsTimerTask extends TimerTask { /** - * TimerTask responsible for sending event data to server + * The task to run should be specified in the implementation of the {@code run()} + * method. */ - private class FlushEventsTimerTask extends TimerTask { - /** - * The task to run should be specified in the implementation of the {@code run()} - * method. - */ - @Override - public void run() { - new FlushTheEventsTask().execute(); - } + @Override + public void run() { + new FlushTheEventsTask().execute(); } - - private String getApplicationIdentifier() { - try { - PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); - return String.format(MapboxConstants.MAPBOX_LOCALE, "%s/%s/%s", context.getPackageName(), packageInfo.versionName, packageInfo.versionCode); - } catch (Exception e) { - return ""; - } + } + + private String getApplicationIdentifier() { + try { + PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + return String.format(MAPBOX_LOCALE, "%s/%s/%s", context.getPackageName(), packageInfo.versionName, + packageInfo.versionCode); + } catch (Exception exception) { + return ""; } + } } |