public final class

AnalyticsBasedUsageTracker

extends java.lang.Object

implements UsageTracker

 java.lang.Object

↳androidx.test.internal.runner.tracker.AnalyticsBasedUsageTracker

Gradle dependencies

compile group: 'androidx.test', name: 'runner', version: '1.5.0-alpha03'

  • groupId: androidx.test
  • artifactId: runner
  • version: 1.5.0-alpha03

Artifact androidx.test:runner:1.5.0-alpha03 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.test:runner com.android.support.test:runner

Overview

Creates a usage tracker that pings google analytics when infra bits get used.

Summary

Methods
public voidsendUsages()

public voidtrackUsage(java.lang.String usageType, java.lang.String version)

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

public void trackUsage(java.lang.String usageType, java.lang.String version)

public void sendUsages()

Source

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.test.internal.runner.tracker;

import static androidx.test.internal.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.util.Log;
import android.view.Display;
import android.view.WindowManager;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * Creates a usage tracker that pings google analytics when infra bits get used.
 *
 * @deprecated obsolete
 */
@Deprecated
public 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 SCREEN_NAME_PARAM = "&cd=";
  private static final String APP_VERSION_PARAM = "&av=";
  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 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 final Map<String, String> usageTypeToVersion = new HashMap<>();

  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);
  }

  /** Builder for AnalyticsBasedUsageTracker. */
  public static class Builder {
    private final Context targetContext;
    private Uri analyticsUri =
        new Uri.Builder()
            .scheme("https")
            .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;
    private boolean hashed;

    public Builder(Context targetContext) {
      if (targetContext == null) {
        throw new NullPointerException("Context null!?");
      }
      this.targetContext = targetContext;
    }

    public Builder withTrackingId(String trackingId) {
      this.trackingId = trackingId;
      return this;
    }

    public Builder withAnalyticsUri(Uri analyticsUri) {
      checkNotNull(analyticsUri);
      this.analyticsUri = analyticsUri;
      return this;
    }

    public Builder withApiLevel(String apiLevel) {
      this.apiLevel = apiLevel;
      return this;
    }

    public Builder withScreenResolution(String resolutionVal) {
      this.screenResolution = resolutionVal;
      return this;
    }

    public Builder withUserId(String userId) {
      this.userId = userId;
      return this;
    }

    public Builder withModel(String model) {
      this.model = model;
      return this;
    }

    public Builder withTargetPackage(String targetPackage) {
      hashed = false;
      this.targetPackage = targetPackage;
      return this;
    }

    public UsageTracker buildIfPossible() {
      if (!hasInternetPermission()) {
        Log.d(TAG, "Tracking disabled due to lack of internet permissions");
        return null;
      }

      if (null == targetPackage) {
        withTargetPackage(targetContext.getPackageName());
      }

      if (targetPackage.contains("com.google.analytics")) {
        Log.d(TAG, "Refusing to use analytics while testing analytics.");
        return null;
      }

      try {
        if (targetPackage.startsWith("com.google.")
            || targetPackage.startsWith("com.android.")
            || targetPackage.startsWith("android.support.")) {
          // track usage of google owned packages...
        } else {
          if (!hashed) {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            digest.reset();
            digest.update(targetPackage.getBytes(UTF_8));
            BigInteger hashedPackage = new BigInteger(digest.digest());
            targetPackage = "sha256-" + hashedPackage.toString(16);
          }
          hashed = true;
        }
      } catch (NoSuchAlgorithmException nsae) {
        Log.d(TAG, "Cannot hash package name.", nsae);
        return null;
      } catch (UnsupportedEncodingException uee) {
        Log.d(TAG, "Impossible - no utf-8 encoding?", uee);
        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();
        // Headless devices don't have a Display.
        if (null == display) {
          screenResolution = "0x0";
        } else {
          screenResolution =
              new StringBuilder()
                  .append(display.getWidth())
                  .append("x")
                  .append(display.getHeight())
                  .toString();
        }
      }

      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, String version) {
    synchronized (usageTypeToVersion) {
      usageTypeToVersion.put(usageType, version);
    }
  }

  @Override
  public void sendUsages() {
    Map<String, String> myUsages;
    synchronized (usageTypeToVersion) {
      if (usageTypeToVersion.isEmpty()) {
        return;
      }
      myUsages = new HashMap<>(usageTypeToVersion);
      usageTypeToVersion.clear();
    }

    String baseBody = null;
    try {
      baseBody =
          new StringBuilder()
              .append(APP_NAME_PARAM)
              .append(encode(targetPackage, UTF_8))
              .append(TRACKER_ID_PARAM)
              .append(encode(trackingId, UTF_8))
              .append("&v=1") // Protocol Version.
              .append("&z=") // Cache Buster (optional)
              .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") // Hit type
              .append("&sc=start") // Session Control
              .toString();
    } catch (IOException ioe) {
      Log.w(TAG, "Impossible error happened. analytics disabled.", ioe);
    }

    for (Map.Entry<String, String> usage : myUsages.entrySet()) {
      HttpURLConnection analyticsConnection = null;
      try {
        analyticsConnection = (HttpURLConnection) analyticsURI.openConnection();

        byte[] body =
            new StringBuilder()
                .append(baseBody)
                .append(SCREEN_NAME_PARAM)
                .append(encode(usage.getKey(), UTF_8))
                .append(APP_VERSION_PARAM)
                .append(encode(usage.getValue(), UTF_8))
                .toString()
                // j5 compatibility. this is utf8.
                .getBytes();

        analyticsConnection.setConnectTimeout(3000); // milliseconds
        analyticsConnection.setReadTimeout(5000); // milliseconds
        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();
        }
      }
    }
  }
}