package com.google.android.apps.common.testing.testrunner; import static com.google.android.apps.common.testing.testrunner.util.Checks.checkNotNull; import static java.net.URLEncoder.encode; import android.content.Context; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.SystemClock; import android.provider.Settings; import android.util.Log; import android.view.Display; import android.view.WindowManager; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.UUID; import javax.annotation.Nullable; /** * Creates a usage tracker that pings google analytics when infra bits get used. */ final class AnalyticsBasedUsageTracker implements UsageTracker { private static final String TAG = "InfraTrack"; private static final String UTF_8 = "UTF_8"; private static final String APP_NAME_PARAM = "an="; private static final String CONTENT_DESCRIPT_PARAM = "&cd="; private static final String TRACKER_ID_PARAM = "&tid="; private static final String CLIENT_ID_PARAM = "&cid="; private static final String SCREEN_RESOLUTION_PARAM = "&sr="; private static final String HOST_TYPE_PARAM = "&cd1="; private static final String API_LEVEL_PARAM = "&cd2="; private static final String MODEL_NAME_PARAM = "&cd3="; private final String trackingId; private final String targetPackage; private final URL analyticsURI; private final String screenResolution; private final String apiLevel; private final String model; private final String userId; private List<String> usages = new ArrayList<String>(); private AnalyticsBasedUsageTracker(Builder builder) { this.trackingId = checkNotNull(builder.trackingId); this.targetPackage = checkNotNull(builder.targetPackage); this.analyticsURI = checkNotNull(builder.analyticsURI); this.apiLevel = checkNotNull(builder.apiLevel); this.model = checkNotNull(builder.model); this.screenResolution = checkNotNull(builder.screenResolution); this.userId = checkNotNull(builder.userId); } public static class Builder { private final Context targetContext; private Uri analyticsUri = new Uri.Builder() .scheme("http") .authority("www.google-analytics.com") .path("collect") .build(); private String trackingId = "UA-36650409-3"; private String apiLevel = String.valueOf(Build.VERSION.SDK_INT); private String model = Build.MODEL; private String targetPackage; private URL analyticsURI; private String screenResolution; private String userId; public Builder(Context targetContext) { if (targetContext == null) { throw new NullPointerException("Context null!?"); } this.targetContext = targetContext; } public Builder withTrackingId(@Nullable String trackingId) { this.trackingId = trackingId; return this; } public Builder withAnalyticsUri(Uri analyticsUri) { checkNotNull(analyticsUri); this.analyticsUri = analyticsUri; return this; } public Builder withApiLevel(@Nullable String apiLevel) { this.apiLevel = apiLevel; return this; } public Builder withScreenResolution(@Nullable String resolutionVal) { this.screenResolution = resolutionVal; return this; } public Builder withUserId(@Nullable String userId) { this.userId = userId; return this; } public Builder withModel(@Nullable String model) { this.model = model; return this; } public Builder withTargetPackage(@Nullable String targetPackage) { this.targetPackage = targetPackage; return this; } public UsageTracker buildIfPossible() { if (!hasInternetPermission()) { return null; } if (null == targetPackage) { targetPackage = targetContext.getPackageName(); } if (targetPackage.contains("com.google.analytics")) { Log.d(TAG, "Refusing to use analytics while testing analytics."); return null; } try { analyticsURI = new URL(analyticsUri.toString()); } catch (MalformedURLException mule) { Log.w(TAG, "Tracking disabled bad url: " + analyticsUri.toString(), mule); return null; } if (null == screenResolution) { Display display = ((WindowManager) targetContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); screenResolution = new StringBuilder() .append(display.getWidth()) .append("x") .append(display.getHeight()) .toString(); } if (null == userId) { userId = Settings.Secure.getString( targetContext.getContentResolver(), Settings.Secure.ANDROID_ID); if (null == userId) { userId = UUID.randomUUID().toString(); } } return new AnalyticsBasedUsageTracker(this); } private boolean hasInternetPermission() { return PackageManager.PERMISSION_GRANTED == targetContext.checkCallingOrSelfPermission( "android.permission.INTERNET"); } } @Override public void trackUsage(String usageType) { synchronized (usages) { usages.add(usageType); } } @Override public void sendUsages() { List<String> myUsages = null; synchronized (usages) { if (usages.isEmpty()) { return; } myUsages = new ArrayList<String>(usages); usages.clear(); } String baseBody = null; try { baseBody = new StringBuilder() .append(APP_NAME_PARAM) .append(encode(targetPackage, UTF_8)) .append(TRACKER_ID_PARAM) .append(encode(trackingId)) .append("&v=1") .append("&z=") .append(SystemClock.uptimeMillis()) .append(CLIENT_ID_PARAM) .append(encode(userId, UTF_8)) .append(SCREEN_RESOLUTION_PARAM) .append(encode(screenResolution, UTF_8)) .append(API_LEVEL_PARAM) .append(encode(apiLevel, UTF_8)) .append(MODEL_NAME_PARAM) .append(encode(model, UTF_8)) .append("&t=appview") .toString(); } catch (IOException ioe) { Log.w(TAG, "Impossible error happened. analytics disabled.", ioe); } for (String usage : myUsages) { HttpURLConnection analyticsConnection = null; try { analyticsConnection = (HttpURLConnection) analyticsURI.openConnection(); byte[] body = new StringBuilder() .append(baseBody) .append(CONTENT_DESCRIPT_PARAM) .append(encode(usage, UTF_8)) .toString() // j5 compatibility. this is utf8. .getBytes(); analyticsConnection.setDoOutput(true); analyticsConnection.setFixedLengthStreamingMode(body.length); analyticsConnection.getOutputStream().write(body); int status = analyticsConnection.getResponseCode(); if (status / 100 != 2) { Log.w(TAG, "Analytics post: " + usage + " failed. code: " + analyticsConnection.getResponseCode() + " - " + analyticsConnection.getResponseMessage()); } } catch (IOException ioe) { Log.w(TAG, "Analytics post: " + usage + " failed. ", ioe); } finally { if (null != analyticsConnection) { analyticsConnection.disconnect(); } } } } }