@Override
  public void identify(IdentifyPayload identify) {
    if (sendUserId) {
      String userId = identify.userId();
      tracker.set(USER_ID_KEY, userId);
      logger.verbose("tracker.set(%s, %s);", USER_ID_KEY, userId);
    }

    // Set traits, custom dimensions, and custom metrics on the shared tracker.
    for (Map.Entry<String, Object> entry : identify.traits().entrySet()) {
      String trait = entry.getKey();
      if (customDimensions.containsKey(trait)) {
        String dimension =
            customDimensions.getString(trait).replace(DIMENSION_PREFIX, DIMENSION_PREFIX_KEY);
        String value = String.valueOf(entry.getValue());
        tracker.set(dimension, value);
        logger.verbose("tracker.set(%s, %s);", dimension, value);
      }
      if (customMetrics.containsKey(trait)) {
        String metric = customMetrics.getString(trait).replace(METRIC_PREFIX, METRIC_PREFIX_KEY);
        String value = String.valueOf(entry.getValue());
        tracker.set(metric, value);
        logger.verbose("tracker.set(%s, %s);", metric, value);
      }
    }
  }
  GoogleAnalyticsIntegration(
      Context context, GoogleAnalytics googleAnalytics, ValueMap settings, Logger logger) {
    this.googleAnalytics = googleAnalytics;
    this.logger = logger;

    String mobileTrackingId = settings.getString("mobileTrackingId");
    tracker = googleAnalytics.newTracker(mobileTrackingId);
    logger.verbose("GoogleAnalytics.getInstance(context).newTracker(%s);", mobileTrackingId);

    boolean anonymizeIp = settings.getBoolean("anonymizeIp", false);
    tracker.setAnonymizeIp(anonymizeIp);
    logger.verbose("tracker.setAnonymizeIp(%s);", anonymizeIp);

    boolean reportUncaughtExceptions = settings.getBoolean("reportUncaughtExceptions", false);
    if (reportUncaughtExceptions) {
      tracker.setUncaughtExceptionReporter(context);
      logger.verbose("Thread.setDefaultUncaughtExceptionHandler(new ExceptionReporter(...));");
    }

    sendUserId = settings.getBoolean("sendUserId", false);
    customDimensions = settings.getValueMap("dimensions");
    if (isNullOrEmpty(customDimensions)) customDimensions = EMPTY;
    customMetrics = settings.getValueMap("metrics");
    if (isNullOrEmpty(customMetrics)) customMetrics = EMPTY;
  }
  @Override
  public void track(TrackPayload track) {
    Properties properties = track.properties();
    String event = track.event();
    String category = properties.category();

    sendProductEvent(event, category, properties);

    if (COMPLETED_ORDER_PATTERN.matcher(event).matches()) {
      List<Product> products = properties.products();
      if (!isNullOrEmpty(products)) {
        for (int i = 0; i < products.size(); i++) {
          Product product = products.get(i);
          ItemHitBuilder hitBuilder = new ItemHitBuilder();
          hitBuilder
              .setTransactionId(properties.orderId())
              .setName(product.name())
              .setSku(product.sku())
              .setPrice(product.price())
              .setQuantity(product.getLong(QUANTITY_KEY, 0))
              .build();
          attachCustomDimensionsAndMetrics(hitBuilder, properties);
          Map<String, String> hit = hitBuilder.build();
          tracker.send(hit);
          logger.verbose("tracker.send(%s);", hit);
        }
      }
      TransactionBuilder transactionBuilder = new TransactionBuilder();
      transactionBuilder
          .setTransactionId(properties.orderId())
          .setCurrencyCode(properties.currency())
          .setRevenue(properties.total())
          .setTax(properties.tax())
          .setShipping(properties.shipping());
      Map<String, String> transaction = transactionBuilder.build();
      tracker.send(transaction);
      logger.verbose("tracker.send(%s);", transaction);
    }

    String label = properties.getString(LABEL_KEY);
    EventHitBuilder eventHitBuilder = new EventHitBuilder();
    eventHitBuilder
        .setAction(event)
        .setCategory(isNullOrEmpty(category) ? DEFAULT_CATEGORY : category)
        .setLabel(label)
        .setValue((int) properties.value());
    attachCustomDimensionsAndMetrics(eventHitBuilder, properties);
    Map<String, String> eventHit = eventHitBuilder.build();
    tracker.send(eventHit);
    logger.verbose("tracker.send(%s);", eventHit);
  }
  @Override
  public void screen(ScreenPayload screen) {
    Properties properties = screen.properties();
    String screenName = screen.event();

    sendProductEvent(screenName, screen.category(), properties);

    tracker.setScreenName(screenName);
    logger.verbose("tracker.setScreenName(%s);", screenName);

    ScreenViewHitBuilder hitBuilder = new ScreenViewHitBuilder();
    attachCustomDimensionsAndMetrics(hitBuilder, properties);
    Map<String, String> hit = hitBuilder.build();
    tracker.send(hit);
    logger.verbose("tracker.send(%s);", hit);
  }
        @Override
        public Integration<?> create(ValueMap settings, Analytics analytics) {
          Logger logger = analytics.logger(GOOGLE_ANALYTICS_KEY);
          if (!hasPermission(
              analytics.getApplication(), Manifest.permission.ACCESS_NETWORK_STATE)) {
            logger.debug("ACCESS_NETWORK_STATE is required for Google Analytics.");
            return null;
          }
          String mobileTrackingId = settings.getString("mobileTrackingId");
          if (isNullOrEmpty(mobileTrackingId)) {
            logger.debug("mobileTrackingId is required for Google Analytics.");
            return null;
          }

          Context context = analytics.getApplication();
          com.google.android.gms.analytics.GoogleAnalytics ga =
              com.google.android.gms.analytics.GoogleAnalytics.getInstance(context);

          GoogleAnalytics googleAnalytics = new DefaultGoogleAnalytics(ga);
          return new GoogleAnalyticsIntegration(context, googleAnalytics, settings, logger);
        }
  @Before
  public void setUp() throws IOException {
    Analytics.INSTANCES.clear();

    initMocks(this);
    application = mockApplication();
    traits = Traits.create();
    when(traitsCache.get()).thenReturn(traits);
    analyticsContext = createContext(traits);
    factory =
        new Integration.Factory() {
          @Override
          public Integration<?> create(ValueMap settings, Analytics analytics) {
            return integration;
          }

          @Override
          public String key() {
            return "test";
          }
        };
    when(projectSettingsCache.get())
        .thenReturn(ProjectSettings.create(Cartographer.INSTANCE.fromJson(SETTINGS)));

    analytics =
        new Analytics(
            application,
            networkExecutor,
            stats,
            traitsCache,
            analyticsContext,
            defaultOptions,
            Logger.with(NONE),
            "qaz",
            Collections.singletonList(factory),
            client,
            Cartographer.INSTANCE,
            projectSettingsCache,
            "foo",
            DEFAULT_FLUSH_QUEUE_SIZE,
            DEFAULT_FLUSH_INTERVAL,
            analyticsExecutor);

    // Used by singleton tests
    grantPermission(RuntimeEnvironment.application, Manifest.permission.INTERNET);
  }
  /** Send a product event. */
  void sendProductEvent(String event, String category, Properties properties) {
    if (!PRODUCT_EVENT_NAME_PATTERN.matcher(event).matches()) {
      return;
    }

    ItemHitBuilder itemHitBuilder = new ItemHitBuilder();
    itemHitBuilder
        .setTransactionId(properties.orderId())
        .setCurrencyCode(properties.currency())
        .setName(properties.name())
        .setSku(properties.sku())
        .setCategory(isNullOrEmpty(category) ? DEFAULT_CATEGORY : category)
        .setPrice(properties.price())
        .setQuantity(properties.getLong(QUANTITY_KEY, 0))
        .build();
    attachCustomDimensionsAndMetrics(itemHitBuilder, properties);
    Map<String, String> itemHit = itemHitBuilder.build();
    tracker.send(itemHit);
    logger.verbose("tracker.send(%s);", itemHit);
  }
 @Override
 public void flush() {
   googleAnalytics.dispatchLocalHits();
   logger.verbose("GoogleAnalytics.getInstance(context).dispatchLocalHits();");
 }
 @Override
 public void onActivityStopped(Activity activity) {
   super.onActivityStopped(activity);
   googleAnalytics.reportActivityStop(activity);
   logger.verbose("GoogleAnalytics.getInstance(context).reportActivityStop(activity);");
 }