package com.percolate.foam; import android.content.Context; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.lang.reflect.Type; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; import retrofit.Callback; import retrofit.RestAdapter; import retrofit.client.Response; import retrofit.converter.ConversionException; import retrofit.converter.Converter; import retrofit.http.Body; import retrofit.http.POST; import retrofit.mime.TypedInput; import retrofit.mime.TypedOutput; import retrofit.mime.TypedString; /** * {@inheritDoc} */ class GoogleAnalytics extends ServiceImpl implements EventTrackingService { String trackingId; GoogleAnalyticsService googleAnalyticsService; GoogleAnalytics(Context context) { super(context); } /** * {@inheritDoc} */ @Override public void enable(String trackingId) { this.trackingId = trackingId; } /** * {@inheritDoc} */ @Override public boolean isEnabled() { return utils.isNotBlank(trackingId); } /** * {@inheritDoc} */ @Override public ServiceType getServiceType() { return ServiceType.GOOGLE_ANALYTICS; } /** * {@inheritDoc} */ public void logEvent(Context context, String event){ try { String payload = createPayloadData(event); createService().createEvent(payload, new NoOpCallback()); } catch (Exception ex) { utils.logIssue("Could not send google analytics data", ex); } } /** * Lazy load instance of {@link GoogleAnalyticsService} * @return Instance of {@link GoogleAnalyticsService}. Never null. */ GoogleAnalyticsService createService() { if(googleAnalyticsService == null) { RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint("https://www.google-analytics.com") .setConverter(new StringConverter()) .build(); googleAnalyticsService = restAdapter.create(GoogleAnalyticsService.class); } return googleAnalyticsService; } /** * See: https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide * @param event Event to tack * @return String of data in the format expected by google analytics. */ String createPayloadData(String event){ String payload = null; try { String androidId = utils.getAndroidId(context); UUID deviceUUID = UUID.nameUUIDFromBytes(androidId.getBytes("utf8")); Map<String, String> data = new LinkedHashMap<String, String>(); data.put("v", "1"); // Version. data.put("tid", this.trackingId); // Tracking ID / Property ID. data.put("cid", deviceUUID.toString()); // Anonymous Client ID. data.put("t", "screenview"); // Hit Type. data.put("an", utils.getApplicationName(context)); // App name. data.put("av", utils.getVersionName(context)); // App version. data.put("aid", utils.getApplicationPackageName(context)); // App Id. data.put("cd", event); // Screen name / content description. StringBuilder sb = new StringBuilder(8192); //8192 bytes max for (Map.Entry<String, String> entry : data.entrySet()) { sb.append(entry.getKey()); sb.append("="); sb.append(entry.getValue()); sb.append("&"); } sb.deleteCharAt(sb.length() - 1);// Remove last "&" payload = new String(sb.toString().getBytes(), "UTF-8"); } catch (UnsupportedEncodingException ex) { utils.logIssue("Encoding exception", ex); } catch(Exception ex){ utils.logIssue("Error creating google analytics payload data", ex); } return payload; } /** * Retrofit Service */ interface GoogleAnalyticsService { @POST("/collect") //@POST("/debug/collect") void createEvent(@Body String payload, Callback<Response> callback); } /** * Custom Retrofit Converter that parses data as simple Strings. */ static class StringConverter implements Converter { @Override public Object fromBody(TypedInput typedInput, Type type) throws ConversionException { String text = null; try { typedInput.in(); BufferedReader reader = new BufferedReader(new InputStreamReader(typedInput.in())); StringBuilder out = new StringBuilder(); String newLine = System.getProperty("line.separator"); String line; while ((line = reader.readLine()) != null) { out.append(line); out.append(newLine); } text = out.toString(); } catch (IOException ignored) {/*NOP*/ } return text; } @Override public TypedOutput toBody(Object output) { return new TypedString(output.toString()); } } }