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

Commit 5a03094e authored by Jan Althaus's avatar Jan Althaus
Browse files

Remove legacy logger

Migrate DefaultLogger implementation to SelectionSessionLogger.
This cleans up after the API refactor and fixes two bugs:
- All events are currently logged twice.
- Interfaces accept a null signature, but it currently crashes the legacy logger.

Bug: 73392698
Bug: 77659305
Test: atest FrameworksCoreTests:TextClassificationManagerTest
Test: atest FrameworksCoreTests:TextClassificationTest
Test: atest CtsViewTestCases:TextClassificationManagerTest
Test: atest CtsViewTestCases:TextClassifierValueObjectsTest
Test: atest CtsWidgetTestCases:TextViewTest
Test: atest CtsWidgetTestCases:EditTextTest
Test: Manually examined logs
Change-Id: I0d2b925abf5cab12d71fc2cc0fa527530c86ab10
parent 4d289593
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -2231,7 +2231,6 @@ Landroid/view/SurfaceView;->mRequestedFormat:I
Landroid/view/SurfaceView;->mSurfaceHolder:Landroid/view/SurfaceHolder;
Landroid/view/SurfaceView;->surfacePositionLost_uiRtSync(J)V
Landroid/view/SurfaceView;->updateSurfacePosition_renderWorker(JIIII)V
Landroid/view/textclassifier/Logger;->DISABLED:Landroid/view/textclassifier/Logger;
Landroid/view/textclassifier/logging/SmartSelectionEventTracker;-><init>(Landroid/content/Context;I)V
Landroid/view/textclassifier/logging/SmartSelectionEventTracker;->logEvent(Landroid/view/textclassifier/logging/SmartSelectionEventTracker$SelectionEvent;)V
Landroid/view/textclassifier/logging/SmartSelectionEventTracker$SelectionEvent;->selectionAction(III)Landroid/view/textclassifier/logging/SmartSelectionEventTracker$SelectionEvent;
+6 −4
Original line number Diff line number Diff line
@@ -19,13 +19,13 @@ package android.view.textclassifier;
import android.annotation.Nullable;
import android.metrics.LogMaker;
import android.util.ArrayMap;
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.Map;
import java.util.Objects;
import java.util.Random;
@@ -39,6 +39,7 @@ import java.util.UUID;
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;
@@ -127,7 +128,7 @@ public final class GenerateLinksLogger {
    }

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

        final String callId = Objects.toString(
                log.getTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID), "");
@@ -142,7 +143,8 @@ public final class GenerateLinksLogger {
        final int latencyMs = Integer.parseInt(
                Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_LATENCY), ZERO));

        Log.d(LOG_TAG, String.format("%s:%s %d links (%d/%d chars) %dms %s", callId, entityType,
        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()));
    }

+0 −397
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;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;

import com.android.internal.util.Preconditions;

import java.text.BreakIterator;
import java.util.Locale;
import java.util.Objects;

/**
 * A helper for logging TextClassifier related events.
 * @hide
 */
public abstract class Logger {

    private static final String LOG_TAG = "Logger";
    /* package */ static final boolean DEBUG_LOG_ENABLED = true;

    private @SelectionEvent.InvocationMethod int mInvocationMethod;
    private SelectionEvent mPrevEvent;
    private SelectionEvent mSmartEvent;
    private SelectionEvent mStartEvent;

    /**
     * Logger that does not log anything.
     * @hide
     */
    public static final Logger DISABLED = new Logger() {
        @Override
        public void writeEvent(SelectionEvent event) {}
    };

    @Nullable
    private final Config mConfig;

    public Logger(Config config) {
        mConfig = Preconditions.checkNotNull(config);
    }

    private Logger() {
        mConfig = null;
    }

    /**
     * Writes the selection event to a log.
     */
    public abstract void writeEvent(@NonNull SelectionEvent event);

    /**
     * Returns true if the resultId matches that of a smart selection event (i.e.
     * {@link SelectionEvent#EVENT_SMART_SELECTION_SINGLE} or
     * {@link SelectionEvent#EVENT_SMART_SELECTION_MULTI}).
     * Returns false otherwise.
     */
    public boolean isSmartSelection(@NonNull String resultId) {
        return false;
    }

    /**
     * Returns a token iterator for tokenizing text for logging purposes.
     */
    public BreakIterator getTokenIterator(@NonNull Locale locale) {
        return BreakIterator.getWordInstance(Preconditions.checkNotNull(locale));
    }

    /**
     * Logs a "selection started" event.
     *
     * @param invocationMethod  the way the selection was triggered
     * @param start  the token index of the selected token
     */
    public final void logSelectionStartedEvent(
            @SelectionEvent.InvocationMethod int invocationMethod, int start) {
        if (mConfig == null) {
            return;
        }

        mInvocationMethod = invocationMethod;
        logEvent(new SelectionEvent(
                start, start + 1, SelectionEvent.EVENT_SELECTION_STARTED,
                TextClassifier.TYPE_UNKNOWN, mInvocationMethod, null, mConfig));
    }

    /**
     * Logs a "selection modified" event.
     * Use when the user modifies the selection.
     *
     * @param start  the start token (inclusive) index of the selection
     * @param end  the end token (exclusive) index of the selection
     */
    public final void logSelectionModifiedEvent(int start, int end) {
        Preconditions.checkArgument(end >= start, "end cannot be less than start");

        if (mConfig == null) {
            return;
        }

        logEvent(new SelectionEvent(
                start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
                TextClassifier.TYPE_UNKNOWN, mInvocationMethod, null, mConfig));
    }

    /**
     * Logs a "selection modified" event.
     * Use when the user modifies the selection and the selection's entity type is known.
     *
     * @param start  the start token (inclusive) index of the selection
     * @param end  the end token (exclusive) index of the selection
     * @param classification  the TextClassification object returned by the TextClassifier that
     *      classified the selected text
     */
    public final void logSelectionModifiedEvent(
            int start, int end, @NonNull TextClassification classification) {
        Preconditions.checkArgument(end >= start, "end cannot be less than start");
        Preconditions.checkNotNull(classification);

        if (mConfig == null) {
            return;
        }

        final String entityType = classification.getEntityCount() > 0
                ? classification.getEntity(0)
                : TextClassifier.TYPE_UNKNOWN;
        logEvent(new SelectionEvent(
                start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
                entityType, mInvocationMethod, classification.getId(), mConfig));
    }

    /**
     * Logs a "selection modified" event.
     * Use when a TextClassifier modifies the selection.
     *
     * @param start  the start token (inclusive) index of the selection
     * @param end  the end token (exclusive) index of the selection
     * @param selection  the TextSelection object returned by the TextClassifier for the
     *      specified selection
     */
    public final void logSelectionModifiedEvent(
            int start, int end, @NonNull TextSelection selection) {
        Preconditions.checkArgument(end >= start, "end cannot be less than start");
        Preconditions.checkNotNull(selection);

        if (mConfig == null) {
            return;
        }

        final int eventType;
        if (isSmartSelection(selection.getId())) {
            eventType = end - start > 1
                    ? SelectionEvent.EVENT_SMART_SELECTION_MULTI
                    : SelectionEvent.EVENT_SMART_SELECTION_SINGLE;

        } else {
            eventType = SelectionEvent.EVENT_AUTO_SELECTION;
        }
        final String entityType = selection.getEntityCount() > 0
                ? selection.getEntity(0)
                : TextClassifier.TYPE_UNKNOWN;
        logEvent(new SelectionEvent(start, end, eventType, entityType, mInvocationMethod,
                selection.getId(), mConfig));
    }

    /**
     * Logs an event specifying an action taken on a selection.
     * Use when the user clicks on an action to act on the selected text.
     *
     * @param start  the start token (inclusive) index of the selection
     * @param end  the end token (exclusive) index of the selection
     * @param actionType  the action that was performed on the selection
     */
    public final void logSelectionActionEvent(
            int start, int end, @SelectionEvent.ActionType int actionType) {
        Preconditions.checkArgument(end >= start, "end cannot be less than start");
        checkActionType(actionType);

        if (mConfig == null) {
            return;
        }

        logEvent(new SelectionEvent(
                start, end, actionType, TextClassifier.TYPE_UNKNOWN, mInvocationMethod,
                null, mConfig));
    }

    /**
     * Logs an event specifying an action taken on a selection.
     * Use when the user clicks on an action to act on the selected text and the selection's
     * entity type is known.
     *
     * @param start  the start token (inclusive) index of the selection
     * @param end  the end token (exclusive) index of the selection
     * @param actionType  the action that was performed on the selection
     * @param classification  the TextClassification object returned by the TextClassifier that
     *      classified the selected text
     *
     * @throws IllegalArgumentException If actionType is not a valid SelectionEvent actionType
     */
    public final void logSelectionActionEvent(
            int start, int end, @SelectionEvent.ActionType int actionType,
            @NonNull TextClassification classification) {
        Preconditions.checkArgument(end >= start, "end cannot be less than start");
        Preconditions.checkNotNull(classification);
        checkActionType(actionType);

        if (mConfig == null) {
            return;
        }

        final String entityType = classification.getEntityCount() > 0
                ? classification.getEntity(0)
                : TextClassifier.TYPE_UNKNOWN;
        logEvent(new SelectionEvent(start, end, actionType, entityType, mInvocationMethod,
                classification.getId(), mConfig));
    }

    private void logEvent(@NonNull SelectionEvent event) {
        Preconditions.checkNotNull(event);

        if (event.getEventType() != SelectionEvent.EVENT_SELECTION_STARTED
                && mStartEvent == null) {
            if (DEBUG_LOG_ENABLED) {
                Log.d(LOG_TAG, "Selection session not yet started. Ignoring event");
            }
            return;
        }

        final long now = System.currentTimeMillis();
        switch (event.getEventType()) {
            case SelectionEvent.EVENT_SELECTION_STARTED:
                Preconditions.checkArgument(event.getAbsoluteEnd() == event.getAbsoluteStart() + 1);
                event.setSessionId(startNewSession());
                mStartEvent = event;
                break;
            case SelectionEvent.EVENT_SMART_SELECTION_SINGLE:  // fall through
            case SelectionEvent.EVENT_SMART_SELECTION_MULTI:
                mSmartEvent = event;
                break;
            case SelectionEvent.EVENT_SELECTION_MODIFIED:  // fall through
            case SelectionEvent.EVENT_AUTO_SELECTION:
                if (mPrevEvent != null
                        && mPrevEvent.getAbsoluteStart() == event.getAbsoluteStart()
                        && mPrevEvent.getAbsoluteEnd() == event.getAbsoluteEnd()) {
                    // Selection did not change. Ignore event.
                    return;
                }
                break;
            default:
                // do nothing.
        }

        event.setEventTime(now);
        if (mStartEvent != null) {
            event.setSessionId(mStartEvent.getSessionId())
                    .setDurationSinceSessionStart(now - mStartEvent.getEventTime())
                    .setStart(event.getAbsoluteStart() - mStartEvent.getAbsoluteStart())
                    .setEnd(event.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
        }
        if (mSmartEvent != null) {
            event.setResultId(mSmartEvent.getResultId())
                    .setSmartStart(mSmartEvent.getAbsoluteStart() - mStartEvent.getAbsoluteStart())
                    .setSmartEnd(mSmartEvent.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
        }
        if (mPrevEvent != null) {
            event.setDurationSincePreviousEvent(now - mPrevEvent.getEventTime())
                    .setEventIndex(mPrevEvent.getEventIndex() + 1);
        }
        writeEvent(event);
        mPrevEvent = event;

        if (event.isTerminal()) {
            endSession();
        }
    }

    private TextClassificationSessionId startNewSession() {
        endSession();
        return new TextClassificationSessionId();
    }

    private void endSession() {
        mPrevEvent = null;
        mSmartEvent = null;
        mStartEvent = null;
    }

    /**
     * @throws IllegalArgumentException If eventType is not an {@link SelectionEvent.ActionType}
     */
    private static void checkActionType(@SelectionEvent.EventType int eventType)
            throws IllegalArgumentException {
        switch (eventType) {
            case SelectionEvent.ACTION_OVERTYPE:  // fall through
            case SelectionEvent.ACTION_COPY:  // fall through
            case SelectionEvent.ACTION_PASTE:  // fall through
            case SelectionEvent.ACTION_CUT:  // fall through
            case SelectionEvent.ACTION_SHARE:  // fall through
            case SelectionEvent.ACTION_SMART_SHARE:  // fall through
            case SelectionEvent.ACTION_DRAG:  // fall through
            case SelectionEvent.ACTION_ABANDON:  // fall through
            case SelectionEvent.ACTION_SELECT_ALL:  // fall through
            case SelectionEvent.ACTION_RESET:  // fall through
                return;
            default:
                throw new IllegalArgumentException(
                        String.format(Locale.US, "%d is not an eventType", eventType));
        }
    }


    /**
     * A Logger config.
     */
    public static final class Config {

        private final String mPackageName;
        private final String mWidgetType;
        @Nullable private final String mWidgetVersion;

        /**
         * @param context Context of the widget the logger logs for
         * @param widgetType a name for the widget being logged for. e.g.
         *      {@link TextClassifier#WIDGET_TYPE_TEXTVIEW}
         * @param widgetVersion a string version info for the widget the logger logs for
         */
        public Config(
                @NonNull Context context,
                @TextClassifier.WidgetType String widgetType,
                @Nullable String widgetVersion) {
            mPackageName = Preconditions.checkNotNull(context).getPackageName();
            mWidgetType = widgetType;
            mWidgetVersion = widgetVersion;
        }

        /**
         * Returns the package name of the application the logger logs for.
         */
        public String getPackageName() {
            return mPackageName;
        }

        /**
         * Returns the name for the widget being logged for. e.g.
         * {@link TextClassifier#WIDGET_TYPE_TEXTVIEW}.
         */
        public String getWidgetType() {
            return mWidgetType;
        }

        /**
         * Returns string version info for the logger. This is specific to the text classifier.
         */
        @Nullable
        public String getWidgetVersion() {
            return mWidgetVersion;
        }

        @Override
        public int hashCode() {
            return Objects.hash(mPackageName, mWidgetType, mWidgetVersion);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }

            if (!(obj instanceof Config)) {
                return false;
            }

            final Config other = (Config) obj;
            return Objects.equals(mPackageName, other.mPackageName)
                    && Objects.equals(mWidgetType, other.mWidgetType)
                    && Objects.equals(mWidgetVersion, other.mWidgetType);
        }
    }
}
+0 −14
Original line number Diff line number Diff line
@@ -150,20 +150,6 @@ public final class SelectionEvent implements Parcelable {
        mInvocationMethod = invocationMethod;
    }

    SelectionEvent(
            int start, int end,
            @EventType int eventType, @EntityType String entityType,
            @InvocationMethod int invocationMethod, @Nullable String resultId,
            Logger.Config config) {
        this(start, end, eventType, entityType, invocationMethod, resultId);
        Preconditions.checkNotNull(config);
        setTextClassificationSessionContext(
                new TextClassificationContext.Builder(
                        config.getPackageName(), config.getWidgetType())
                        .setWidgetVersion(config.getWidgetVersion())
                        .build());
    }

    private SelectionEvent(Parcel in) {
        mAbsoluteStart = in.readInt();
        mAbsoluteEnd = in.readInt();
+33 −25
Original line number Diff line number Diff line
@@ -17,28 +17,29 @@
package android.view.textclassifier;

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.text.BreakIterator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.StringJoiner;

/**
 * Default Logger.
 * Used internally by TextClassifierImpl.
 * A helper for logging selection session events.
 * @hide
 */
public final class DefaultLogger extends Logger {
public final class SelectionSessionLogger {

    private static final String LOG_TAG = "DefaultLogger";
    private static final String LOG_TAG = "SelectionSessionLogger";
    private static final boolean DEBUG_LOG_ENABLED = false;
    static final String CLASSIFIER_ID = "androidtc";

    private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
@@ -59,23 +60,16 @@ public final class DefaultLogger extends Logger {

    private final MetricsLogger mMetricsLogger;

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

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

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

    @Override
    /** Emits a selection event to the logs. */
    public void writeEvent(@NonNull SelectionEvent event) {
        Preconditions.checkNotNull(event);
        final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
@@ -93,7 +87,7 @@ public final class DefaultLogger extends Logger {
                .addTaggedData(SMART_END, event.getSmartEnd())
                .addTaggedData(EVENT_START, event.getStart())
                .addTaggedData(EVENT_END, event.getEnd())
                .addTaggedData(SESSION_ID, event.getSessionId());
                .addTaggedData(SESSION_ID, event.getSessionId().flattenToString());
        mMetricsLogger.write(log);
        debugLog(log);
    }
@@ -225,9 +219,17 @@ public final class DefaultLogger extends Logger {
        final int eventEnd = Integer.parseInt(
                Objects.toString(log.getTaggedData(EVENT_END), ZERO));

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

    /**
     * Returns a token iterator for tokenizing text for logging purposes.
     */
    public static BreakIterator getTokenIterator(@NonNull Locale locale) {
        return BreakIterator.getWordInstance(Preconditions.checkNotNull(locale));
    }

    /**
@@ -260,8 +262,10 @@ public final class DefaultLogger extends Logger {
            return String.format(Locale.US, "%s|%s|%d", classifierId, modelName, hash);
        }

        static String getClassifierId(String signature) {
            Preconditions.checkNotNull(signature);
        static String getClassifierId(@Nullable String signature) {
            if (signature == null) {
                return "";
            }
            final int end = signature.indexOf("|");
            if (end >= 0) {
                return signature.substring(0, end);
@@ -269,8 +273,10 @@ public final class DefaultLogger extends Logger {
            return "";
        }

        static String getModelName(String signature) {
            Preconditions.checkNotNull(signature);
        static String getModelName(@Nullable String signature) {
            if (signature == null) {
                return "";
            }
            final int start = signature.indexOf("|") + 1;
            final int end = signature.indexOf("|", start);
            if (start >= 1 && end >= start) {
@@ -279,8 +285,10 @@ public final class DefaultLogger extends Logger {
            return "";
        }

        static int getHash(String signature) {
            Preconditions.checkNotNull(signature);
        static int getHash(@Nullable String signature) {
            if (signature == null) {
                return 0;
            }
            final int index1 = signature.indexOf("|");
            final int index2 = signature.indexOf("|", index1);
            if (index2 > 0) {
Loading