Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3bb44361 authored by Abodunrinwa Toki's avatar Abodunrinwa Toki
Browse files

Implement TextClassifier.getLogger API

 - Introduces getLogger() API.
 - A logger should run in the client's process. This helps us manage
   sessions specific to a client.
 - The logger exposes a tokenizer that clients may use to tokenize
   strings for logging purposes.
 - Logger subclasses need to provide a writeEvent() implementation.
 - SelectionEvent is serializable over IPC.
 - Logger takes care of the session management. It writes session
   specific information into the SelectionEvent.
 - We still keep the SmartSelectionEventTracker for now so clients
   can slowly move off of it. The plan is to delete it.
 - The plan is to include support other event types. e.g. link events.

Bug: 64914512
Bug: 67609167
Test: See topic
Change-Id: Ic9470cf8f969add8a4c6570f78603d0b118956cd
parent 7c691c60
Loading
Loading
Loading
Loading
+70 −0
Original line number Diff line number Diff line
@@ -50096,6 +50096,7 @@ package android.view.textclassifier {
    method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options);
    method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence);
    method public default java.util.Collection<java.lang.String> getEntitiesForPreset(int);
    method public default android.view.textclassifier.logging.Logger getLogger(android.view.textclassifier.logging.Logger.Config);
    method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
    method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
    method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
@@ -50191,6 +50192,75 @@ package android.view.textclassifier {
}
package android.view.textclassifier.logging {
  public abstract class Logger {
    ctor public Logger(android.view.textclassifier.logging.Logger.Config);
    method public java.text.BreakIterator getTokenIterator(java.util.Locale);
    method public boolean isSmartSelection(java.lang.String);
    method public final void logSelectionActionEvent(int, int, int);
    method public final void logSelectionActionEvent(int, int, int, android.view.textclassifier.TextClassification);
    method public final void logSelectionModifiedEvent(int, int);
    method public final void logSelectionModifiedEvent(int, int, android.view.textclassifier.TextClassification);
    method public final void logSelectionModifiedEvent(int, int, android.view.textclassifier.TextSelection);
    method public final void logSelectionStartedEvent(int);
    method public abstract void writeEvent(android.view.textclassifier.logging.SelectionEvent);
    field public static final int OUT_OF_BOUNDS = 2147483647; // 0x7fffffff
    field public static final int OUT_OF_BOUNDS_NEGATIVE = -2147483648; // 0x80000000
    field public static final java.lang.String WIDGET_CUSTOM_EDITTEXT = "customedit";
    field public static final java.lang.String WIDGET_CUSTOM_TEXTVIEW = "customview";
    field public static final java.lang.String WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
    field public static final java.lang.String WIDGET_EDITTEXT = "edittext";
    field public static final java.lang.String WIDGET_EDIT_WEBVIEW = "edit-webview";
    field public static final java.lang.String WIDGET_TEXTVIEW = "textview";
    field public static final java.lang.String WIDGET_UNKNOWN = "unknown";
    field public static final java.lang.String WIDGET_UNSELECTABLE_TEXTVIEW = "nosel-textview";
    field public static final java.lang.String WIDGET_WEBVIEW = "webview";
  }
  public static final class Logger.Config {
    ctor public Logger.Config(android.content.Context, java.lang.String, java.lang.String);
    method public java.lang.String getPackageName();
    method public java.lang.String getWidgetType();
    method public java.lang.String getWidgetVersion();
  }
  public final class SelectionEvent {
    method public long getDurationSincePreviousEvent();
    method public long getDurationSinceSessionStart();
    method public int getEnd();
    method public java.lang.String getEntityType();
    method public int getEventIndex();
    method public long getEventTime();
    method public int getEventType();
    method public java.lang.String getPackageName();
    method public java.lang.String getSessionId();
    method public java.lang.String getSignature();
    method public int getSmartEnd();
    method public int getSmartStart();
    method public int getStart();
    method public java.lang.String getWidgetType();
    method public java.lang.String getWidgetVersion();
    field public static final int ACTION_ABANDON = 107; // 0x6b
    field public static final int ACTION_COPY = 101; // 0x65
    field public static final int ACTION_CUT = 103; // 0x67
    field public static final int ACTION_DRAG = 106; // 0x6a
    field public static final int ACTION_OTHER = 108; // 0x6c
    field public static final int ACTION_OVERTYPE = 100; // 0x64
    field public static final int ACTION_PASTE = 102; // 0x66
    field public static final int ACTION_RESET = 201; // 0xc9
    field public static final int ACTION_SELECT_ALL = 200; // 0xc8
    field public static final int ACTION_SHARE = 104; // 0x68
    field public static final int ACTION_SMART_SHARE = 105; // 0x69
    field public static final int EVENT_AUTO_SELECTION = 5; // 0x5
    field public static final int EVENT_SELECTION_MODIFIED = 2; // 0x2
    field public static final int EVENT_SELECTION_STARTED = 1; // 0x1
    field public static final int EVENT_SMART_SELECTION_MULTI = 4; // 0x4
    field public static final int EVENT_SMART_SELECTION_SINGLE = 3; // 0x3
  }
}
package android.view.textservice {
  public final class SentenceSuggestionsInfo implements android.os.Parcelable {
+7 −5
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
import android.util.Slog;
import android.view.textclassifier.logging.Logger;

import com.android.internal.util.Preconditions;

@@ -319,14 +320,15 @@ public interface TextClassifier {
    }

    /**
     * Logs a TextClassifier event.
     * Returns a helper for logging TextClassifier related events.
     *
     * @param source the text classifier used to generate this event
     * @param event the text classifier related event
     * @hide
     * @param config logger configuration
     */
    @WorkerThread
    default void logEvent(String source, String event) {}
    default Logger getLogger(@NonNull Logger.Config config) {
        Preconditions.checkNotNull(config);
        return Logger.DISABLED;
    }

    /**
     * Returns this TextClassifier's settings.
+20 −11
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ import android.provider.ContactsContract;
import android.provider.Settings;
import android.text.util.Linkify;
import android.util.Patterns;
import android.view.textclassifier.logging.DefaultLogger;
import android.view.textclassifier.logging.Logger;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
@@ -43,6 +45,7 @@ import com.android.internal.util.Preconditions;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -104,6 +107,12 @@ public final class TextClassifierImpl implements TextClassifier {
    @GuardedBy("mLock") // Do not access outside this lock.
    private SmartSelection mSmartSelection;

    private final Object mLoggerLock = new Object();
    @GuardedBy("mLoggerLock") // Do not access outside this lock.
    private WeakReference<Logger.Config> mLoggerConfig = new WeakReference<>(null);
    @GuardedBy("mLoggerLock") // Do not access outside this lock.
    private Logger mLogger;  // Should never be null if mLoggerConfig.get() is not null.

    private TextClassifierConstants mSettings;

    public TextClassifierImpl(Context context) {
@@ -245,11 +254,15 @@ public final class TextClassifierImpl implements TextClassifier {
        }
    }

    /** @hide */
    @Override
    public void logEvent(String source, String event) {
        if (LOG_TAG.equals(source)) {
            mMetricsLogger.count(event, 1);
    public Logger getLogger(@NonNull Logger.Config config) {
        Preconditions.checkNotNull(config);
        synchronized (mLoggerLock) {
            if (mLoggerConfig.get() == null || !mLoggerConfig.get().equals(config)) {
                mLoggerConfig = new WeakReference<>(config);
                mLogger = new DefaultLogger(config);
            }
            return mLogger;
        }
    }

@@ -285,11 +298,7 @@ public final class TextClassifierImpl implements TextClassifier {

    private String getSignature(String text, int start, int end) {
        synchronized (mLock) {
            final String versionInfo = (mLocale != null)
                    ? String.format(Locale.US, "%s_v%d", mLocale.toLanguageTag(), mVersion)
                    : "";
            final int hash = Objects.hash(text, start, end, mContext.getPackageName());
            return String.format(Locale.US, "%s|%s|%d", LOG_TAG, versionInfo, hash);
            return DefaultLogger.createSignature(text, start, end, mContext, mVersion, mLocale);
        }
    }

@@ -328,7 +337,7 @@ public final class TextClassifierImpl implements TextClassifier {
                return factoryFd;
            } else {
                throw new FileNotFoundException(
                        String.format("No model file found for %s", locale));
                        String.format(Locale.US, "No model file found for %s", locale));
            }
        }

@@ -342,7 +351,7 @@ public final class TextClassifierImpl implements TextClassifier {
            } else {
                closeAndLogError(updateFd);
                throw new FileNotFoundException(
                        String.format("No model file found for %s", locale));
                        String.format(Locale.US, "No model file found for %s", locale));
            }
        }

+263 −0
Original line number Diff line number Diff line
/*
 * 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 android.view.textclassifier.logging;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.metrics.LogMaker;
import android.util.Log;

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.Objects;

/**
 * Default Logger.
 * Used internally by TextClassifierImpl.
 * @hide
 */
public final class DefaultLogger extends Logger {

    private static final String LOG_TAG = "DefaultLogger";
    private static final String CLASSIFIER_ID = "androidtc";

    private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
    private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS;
    private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX;
    private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
    private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION;
    private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
    private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
    private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START;
    private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END;
    private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START;
    private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END;
    private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID;

    private static final String ZERO = "0";
    private static final String UNKNOWN = "unknown";

    private final MetricsLogger mMetricsLogger;

    public DefaultLogger(@NonNull Config config) {
        super(config);
        mMetricsLogger = new MetricsLogger();
    }

    @VisibleForTesting
    public DefaultLogger(@NonNull Config config, @NonNull MetricsLogger metricsLogger) {
        super(config);
        mMetricsLogger = Preconditions.checkNotNull(metricsLogger);
    }

    @Override
    public boolean isSmartSelection(@NonNull String signature) {
        return CLASSIFIER_ID.equals(SignatureParser.getClassifierId(signature));
    }

    @Override
    public void writeEvent(@NonNull SelectionEvent event) {
        Preconditions.checkNotNull(event);
        final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
                .setType(getLogType(event))
                .setSubtype(MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL)
                .setPackageName(event.getPackageName())
                .addTaggedData(START_EVENT_DELTA, event.getDurationSinceSessionStart())
                .addTaggedData(PREV_EVENT_DELTA, event.getDurationSincePreviousEvent())
                .addTaggedData(INDEX, event.getEventIndex())
                .addTaggedData(WIDGET_TYPE, event.getWidgetType())
                .addTaggedData(WIDGET_VERSION, event.getWidgetVersion())
                .addTaggedData(MODEL_NAME, SignatureParser.getModelName(event.getSignature()))
                .addTaggedData(ENTITY_TYPE, event.getEntityType())
                .addTaggedData(SMART_START, event.getSmartStart())
                .addTaggedData(SMART_END, event.getSmartEnd())
                .addTaggedData(EVENT_START, event.getStart())
                .addTaggedData(EVENT_END, event.getEnd())
                .addTaggedData(SESSION_ID, event.getSessionId());
        mMetricsLogger.write(log);
        debugLog(log);
    }

    private static int getLogType(SelectionEvent event) {
        switch (event.getEventType()) {
            case SelectionEvent.ACTION_OVERTYPE:
                return MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE;
            case SelectionEvent.ACTION_COPY:
                return MetricsEvent.ACTION_TEXT_SELECTION_COPY;
            case SelectionEvent.ACTION_PASTE:
                return MetricsEvent.ACTION_TEXT_SELECTION_PASTE;
            case SelectionEvent.ACTION_CUT:
                return MetricsEvent.ACTION_TEXT_SELECTION_CUT;
            case SelectionEvent.ACTION_SHARE:
                return MetricsEvent.ACTION_TEXT_SELECTION_SHARE;
            case SelectionEvent.ACTION_SMART_SHARE:
                return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
            case SelectionEvent.ACTION_DRAG:
                return MetricsEvent.ACTION_TEXT_SELECTION_DRAG;
            case SelectionEvent.ACTION_ABANDON:
                return MetricsEvent.ACTION_TEXT_SELECTION_ABANDON;
            case SelectionEvent.ACTION_OTHER:
                return MetricsEvent.ACTION_TEXT_SELECTION_OTHER;
            case SelectionEvent.ACTION_SELECT_ALL:
                return MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL;
            case SelectionEvent.ACTION_RESET:
                return MetricsEvent.ACTION_TEXT_SELECTION_RESET;
            case SelectionEvent.EVENT_SELECTION_STARTED:
                return MetricsEvent.ACTION_TEXT_SELECTION_START;
            case SelectionEvent.EVENT_SELECTION_MODIFIED:
                return MetricsEvent.ACTION_TEXT_SELECTION_MODIFY;
            case SelectionEvent.EVENT_SMART_SELECTION_SINGLE:
                return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE;
            case SelectionEvent.EVENT_SMART_SELECTION_MULTI:
                return MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI;
            case SelectionEvent.EVENT_AUTO_SELECTION:
                return MetricsEvent.ACTION_TEXT_SELECTION_AUTO;
            default:
                return MetricsEvent.VIEW_UNKNOWN;
        }
    }

    private static String getLogTypeString(int logType) {
        switch (logType) {
            case MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE:
                return "OVERTYPE";
            case MetricsEvent.ACTION_TEXT_SELECTION_COPY:
                return "COPY";
            case MetricsEvent.ACTION_TEXT_SELECTION_PASTE:
                return "PASTE";
            case MetricsEvent.ACTION_TEXT_SELECTION_CUT:
                return "CUT";
            case MetricsEvent.ACTION_TEXT_SELECTION_SHARE:
                return "SHARE";
            case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE:
                return "SMART_SHARE";
            case MetricsEvent.ACTION_TEXT_SELECTION_DRAG:
                return "DRAG";
            case MetricsEvent.ACTION_TEXT_SELECTION_ABANDON:
                return "ABANDON";
            case MetricsEvent.ACTION_TEXT_SELECTION_OTHER:
                return "OTHER";
            case MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL:
                return "SELECT_ALL";
            case MetricsEvent.ACTION_TEXT_SELECTION_RESET:
                return "RESET";
            case MetricsEvent.ACTION_TEXT_SELECTION_START:
                return "SELECTION_STARTED";
            case MetricsEvent.ACTION_TEXT_SELECTION_MODIFY:
                return "SELECTION_MODIFIED";
            case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE:
                return "SMART_SELECTION_SINGLE";
            case MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI:
                return "SMART_SELECTION_MULTI";
            case MetricsEvent.ACTION_TEXT_SELECTION_AUTO:
                return "AUTO_SELECTION";
            default:
                return UNKNOWN;
        }
    }

    private static void debugLog(LogMaker log) {
        if (!DEBUG_LOG_ENABLED) return;

        final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN);
        final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), "");
        final String widget = widgetVersion.isEmpty()
                ? widgetType : widgetType + "-" + widgetVersion;
        final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO));
        if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) {
            String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), "");
            sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1);
            Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId));
        }

        final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN);
        final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN);
        final String type = getLogTypeString(log.getType());
        final int smartStart = Integer.parseInt(
                Objects.toString(log.getTaggedData(SMART_START), ZERO));
        final int smartEnd = Integer.parseInt(
                Objects.toString(log.getTaggedData(SMART_END), ZERO));
        final int eventStart = Integer.parseInt(
                Objects.toString(log.getTaggedData(EVENT_START), ZERO));
        final int eventEnd = Integer.parseInt(
                Objects.toString(log.getTaggedData(EVENT_END), ZERO));

        Log.d(LOG_TAG, String.format("%2d: %s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)",
                index, type, entity, eventStart, eventEnd, smartStart, smartEnd, widget, model));
    }

    /**
     * Creates a signature string that may be used to tag TextClassifier results.
     */
    public static String createSignature(
            String text, int start, int end, Context context, int modelVersion,
            @Nullable Locale locale) {
        Preconditions.checkNotNull(text);
        Preconditions.checkNotNull(context);
        final String modelName = (locale != null)
                ? String.format(Locale.US, "%s_v%d", locale.toLanguageTag(), modelVersion)
                : "";
        final int hash = Objects.hash(text, start, end, context.getPackageName());
        return SignatureParser.createSignature(CLASSIFIER_ID, modelName, hash);
    }

    /**
     * Helper for creating and parsing signature strings for
     * {@link android.view.textclassifier.TextClassifierImpl}.
     */
    @VisibleForTesting
    public static final class SignatureParser {

        static String createSignature(String classifierId, String modelName, int hash) {
            return String.format(Locale.US, "%s|%s|%d", classifierId, modelName, hash);
        }

        static String getClassifierId(String signature) {
            Preconditions.checkNotNull(signature);
            final int end = signature.indexOf("|");
            if (end >= 0) {
                return signature.substring(0, end);
            }
            return "";
        }

        static String getModelName(String signature) {
            Preconditions.checkNotNull(signature);
            final int start = signature.indexOf("|");
            final int end = signature.indexOf("|", start);
            if (start >= 0 && end >= start) {
                return signature.substring(start, end);
            }
            return "";
        }

        static int getHash(String signature) {
            Preconditions.checkNotNull(signature);
            final int index1 = signature.indexOf("|");
            final int index2 = signature.indexOf("|", index1);
            if (index2 > 0) {
                return Integer.parseInt(signature.substring(index2));
            }
            return 0;
        }
    }
}
+429 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading