All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.tools.analytics.UsageTracker Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
 *
 * 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 com.android.tools.analytics;

import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.utils.DateProvider;
import com.android.utils.ILogger;
import com.google.wireless.android.play.playlog.proto.ClientAnalytics;
import com.google.wireless.android.sdk.stats.AndroidStudioEvent;
import com.google.wireless.android.sdk.stats.ProductDetails;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.UUID;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * UsageTracker is an api to report usage of features. This data is used to improve
 * future versions of Android Studio and related tools.
 *
 * The tracker has an API to logDetails usage (in the form of protobuf messages).
 * A separate system called the Analytics Publisher takes the logs and sends them
 * to Google's servers for analysis.
 */
public abstract class UsageTracker implements AutoCloseable {
    private static final Object sGate = new Object();

    @VisibleForTesting static String sSessionId = UUID.randomUUID().toString();
    @VisibleForTesting public static DateProvider sDateProvider = DateProvider.SYSTEM;
    private static UsageTracker sInstance = new NullUsageTracker(new AnalyticsSettings(), null);

    private final AnalyticsSettings mAnalyticsSettings;
    private final ScheduledExecutorService mScheduler;

    private int mMaxJournalSize;
    private long mMaxJournalTime;
    private String mVersion;

    @VisibleForTesting protected long mStartTimeMs = sDateProvider.now().getTime();

    protected UsageTracker(
            AnalyticsSettings analyticsSettings, ScheduledExecutorService scheduler) {
        this.mAnalyticsSettings = analyticsSettings;
        this.mScheduler = scheduler;
    }
    /**
     * Indicates whether this UsageTracker has a maximum size at which point logs need to be flushed.
     * Zero or less indicates no maximum size at which to flush.
     */
    public int getMaxJournalSize() {
        return mMaxJournalSize;
    }

    /*
     * Sets a maximum size at which point logs need to be flushed. Zero or less indicates no
     * flushing until @{link #close()} is called.
     */
    public void setMaxJournalSize(int maxJournalSize) {
        this.mMaxJournalSize = maxJournalSize;
    }

    /**
     * Indicates whether this UsageTracker has a timeout at which point logs need to be flushed.
     * Zero or less indicates no timeout is set.
     *
     * @return timeout in nano-seconds.
     */
    public long getMaxJournalTime() {
        return mMaxJournalTime;
    }

    /**
     * Sets a timeout at which point logs need to be flushed. Zero or less indicates no timeout
     * should be used.
     */
    public void setMaxJournalTime(long duration, TimeUnit unit) {
        this.mMaxJournalTime = unit.toNanos(duration);
    }

    /**
     * Gets the version specified for this UsageTracker. This version when specified is used
     * to populate the product_details.version field of AndroidStudioEvent at time of logging
     * As the version of the product generating the event can be different of the version uploading
     * the event.
     */
    @NonNull public String getVersion() {
        return mVersion;
    }

    /**
     * Set the version specified for this UsageTracker. This version when specified is used
     * to populate the product_details.version field of AndroidStudioEvent at time of logging
     * As the version of the product generating the event can be different of the version uploading
     * the event.
     */
    public void setVersion(@NonNull String version) {
        mVersion = version;
    }

    /** Gets the analytics settings used by this tracker. */
    public AnalyticsSettings getAnalyticsSettings() {
        return mAnalyticsSettings;
    }

    /** Gets the scheduler used by this tracker. */
    public ScheduledExecutorService getScheduler() {
        return mScheduler;
    }

    /** Logs usage data provided in the @{link AndroidStudioEvent}. */
    public void log(@NonNull AndroidStudioEvent.Builder studioEvent) {
        studioEvent.setStudioSessionId(sSessionId);

        if (mVersion != null && !studioEvent.hasProductDetails()) {
            studioEvent.setProductDetails(ProductDetails.newBuilder().setVersion(mVersion));
        }

        long now = sDateProvider.now().getTime();
        try {
            logDetails(
                    ClientAnalytics.LogEvent.newBuilder()
                            .setEventTimeMs(now)
                            .setEventUptimeMs(now - mStartTimeMs)
                            .setSourceExtension(studioEvent.build().toByteString()));
        } catch (NullPointerException exception) {
            // TODO: Temporary fix for http://b.android.com/224994. We should remove this try-catch
            // block once there is a permanent fix.
            logDetails(
                    ClientAnalytics.LogEvent.newBuilder()
                            .setEventTimeMs(now)
                            .setEventUptimeMs(now - mStartTimeMs));
        }
    }

    /**
     * Logs usage data provided in the @{link ClientAnalytics.LogEvent}. Normally using {#log} is
     * preferred please talk to this code's author if you need {@link #logDetails} instead.
     */
    public abstract void logDetails(@NonNull ClientAnalytics.LogEvent.Builder logEvent);

    /**
     * Gets an instance of the {@link UsageTracker} that has been initialized correctly for this process.
     */
    @NonNull
    public static UsageTracker getInstance() {
        synchronized (sGate) {
            return sInstance;
        }
    }

    /**
     * Initializes a {@link UsageTracker} for use throughout this process based on user opt-in and
     * other settings.
     */
    public static UsageTracker initialize(
            @NonNull AnalyticsSettings analyticsSettings,
            @NonNull ScheduledExecutorService scheduler) {
        synchronized (sGate) {
            if (analyticsSettings.hasOptedIn()) {
                sInstance =
                        new JournalingUsageTracker(
                                analyticsSettings,
                                scheduler,
                                Paths.get(AnalyticsPaths.getSpoolDirectory()));
            } else {
                sInstance = new NullUsageTracker(analyticsSettings, scheduler);
            }
            return sInstance;
        }
    }

    /**
     * Sets the global instance to the provided tracker so tests can provide their own UsageTracker
     * implementation. NOTE: Should only be used from tests.
     */
    @VisibleForTesting
    public static UsageTracker setInstanceForTest(UsageTracker tracker) {
        return sInstance = tracker;
    }

    /**
     * resets the global instance to the null usage tracker, to clean state in tests. NOTE: Should
     * only be used from tests.
     */
    @VisibleForTesting
    public static void cleanAfterTesting() {
        sInstance = new NullUsageTracker(new AnalyticsSettings(), null);
    }

    public static AnalyticsSettings updateSettingsAndTracker(
            boolean optIn, @NonNull ILogger logger, @NonNull ScheduledExecutorService scheduler) {
        UsageTracker current = getInstance();
        AnalyticsSettings settings = AnalyticsSettings.getInstance(logger);

        if (optIn != settings.hasOptedIn()) {
            settings.setHasOptedIn(optIn);
            try {
                settings.saveSettings();
            } catch (IOException e) {
                logger.error(e, "Unable to save analytics settings");
            }
        }
        try {
            current.close();
        } catch (Exception e) {
            logger.error(e, "Unable to close existing analytics tracker");
        }
        initialize(settings, scheduler);
        return settings;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy