package android.view.textclassifier;
import android.annotation.Nullable;
import android.metrics.LogMaker;
import android.util.ArrayMap;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.Preconditions;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.UUID;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class GenerateLinksLogger {
private static final String LOG_TAG = "GenerateLinksLogger";
private static final boolean DEBUG_LOG_ENABLED = false;
private static final String ZERO = "0";
private final MetricsLogger mMetricsLogger;
private final Random mRng;
private final int mSampleRate;
public GenerateLinksLogger(int sampleRate) {
mSampleRate = sampleRate;
mRng = new Random(System.nanoTime());
mMetricsLogger = new MetricsLogger();
}
@VisibleForTesting
public GenerateLinksLogger(int sampleRate, MetricsLogger metricsLogger) {
mSampleRate = sampleRate;
mRng = new Random(System.nanoTime());
mMetricsLogger = metricsLogger;
}
public void logGenerateLinks(CharSequence text, TextLinks links, String callingPackageName,
long latencyMs) {
Preconditions.checkNotNull(text);
Preconditions.checkNotNull(links);
Preconditions.checkNotNull(callingPackageName);
if (!shouldLog()) {
return;
}
final LinkifyStats totalStats = new LinkifyStats();
final Map<String, LinkifyStats> perEntityTypeStats = new ArrayMap<>();
for (TextLinks.TextLink link : links.getLinks()) {
if (link.getEntityCount() == 0) continue;
final String entityType = link.getEntity(0);
if (entityType == null
|| TextClassifier.TYPE_OTHER.equals(entityType)
|| TextClassifier.TYPE_UNKNOWN.equals(entityType)) {
continue;
}
totalStats.countLink(link);
perEntityTypeStats.computeIfAbsent(entityType, k -> new LinkifyStats()).countLink(link);
}
final String callId = UUID.randomUUID().toString();
writeStats(callId, callingPackageName, null, totalStats, text, latencyMs);
for (Map.Entry<String, LinkifyStats> entry : perEntityTypeStats.entrySet()) {
writeStats(callId, callingPackageName, entry.getKey(), entry.getValue(), text,
latencyMs);
}
}
private boolean shouldLog() {
if (mSampleRate <= 1) {
return true;
} else {
return mRng.nextInt(mSampleRate) == 0;
}
}
private void writeStats(String callId, String callingPackageName, @Nullable String entityType,
LinkifyStats stats, CharSequence text, long latencyMs) {
final LogMaker log = new LogMaker(MetricsEvent.TEXT_CLASSIFIER_GENERATE_LINKS)
.setPackageName(callingPackageName)
.addTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID, callId)
.addTaggedData(MetricsEvent.FIELD_LINKIFY_NUM_LINKS, stats.mNumLinks)
.addTaggedData(MetricsEvent.FIELD_LINKIFY_LINK_LENGTH, stats.mNumLinksTextLength)
.addTaggedData(MetricsEvent.FIELD_LINKIFY_TEXT_LENGTH, text.length())
.addTaggedData(MetricsEvent.FIELD_LINKIFY_LATENCY, latencyMs);
if (entityType != null) {
log.addTaggedData(MetricsEvent.FIELD_LINKIFY_ENTITY_TYPE, entityType);
}
mMetricsLogger.write(log);
debugLog(log);
}
private static void debugLog(LogMaker log) {
if (!DEBUG_LOG_ENABLED) return;
final String callId = Objects.toString(
log.getTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID), "");
final String entityType = Objects.toString(
log.getTaggedData(MetricsEvent.FIELD_LINKIFY_ENTITY_TYPE), "ANY_ENTITY");
final int numLinks = Integer.parseInt(
Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_NUM_LINKS), ZERO));
final int linkLength = Integer.parseInt(
Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_LINK_LENGTH), ZERO));
final int textLength = Integer.parseInt(
Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_TEXT_LENGTH), ZERO));
final int latencyMs = Integer.parseInt(
Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_LATENCY), ZERO));
Log.d(LOG_TAG,
String.format(Locale.US, "%s:%s %d links (%d/%d chars) %dms %s", callId, entityType,
numLinks, linkLength, textLength, latencyMs, log.getPackageName()));
}
private static final class LinkifyStats {
int mNumLinks;
int mNumLinksTextLength;
void countLink(TextLinks.TextLink link) {
mNumLinks += 1;
mNumLinksTextLength += link.getEnd() - link.getStart();
}
}
}