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

Commit 5a5f0d52 authored by Tony Mak's avatar Tony Mak
Browse files

Introduce TextClassifierEventTronLogger

1. SelectionEvent will be still logged via SelectionSessionLogger
   to make sure we don't break existing logs.

2. New features including language detection and conversation actions
   are logged via TextClassifierEventTronLogger.

3. Added TYPE_ACTIONS_GENERATED to log when actions are generated.
   This is used to calcuate the recall, i.e. among all the requests,
   how many of them TextClassifier returns something.

Test: atest TextClassifierEventTronLoggerTest
Test: Turn on the DEBUG flag and observe the logging.

BUG: 120803809
BUG: 120828422

Change-Id: I33f2ce58885d90bc35316f54abcd42b137b42a13
parent e5050980
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -53114,6 +53114,7 @@ package android.view.textclassifier {
    field public static final int CATEGORY_SELECTION = 1; // 0x1
    field public static final int CATEGORY_UNDEFINED = 0; // 0x0
    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassifierEvent> CREATOR;
    field public static final int TYPE_ACTIONS_GENERATED = 20; // 0x14
    field public static final int TYPE_ACTIONS_SHOWN = 6; // 0x6
    field public static final int TYPE_AUTO_SELECTION = 5; // 0x5
    field public static final int TYPE_COPY_ACTION = 9; // 0x9
+2 −0
Original line number Diff line number Diff line
@@ -116,6 +116,8 @@ public final class TextClassifierEvent implements Parcelable {
    public static final int TYPE_SELECTION_RESET = 18;
    /** User composed a reply. */
    public static final int TYPE_MANUAL_REPLY = 19;
    /** TextClassifier generated some actions */
    public static final int TYPE_ACTIONS_GENERATED = 20;

    @Category private final int mEventCategory;
    @Type private final int mEventType;
+152 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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 static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_SESSION_ID;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_VERSION;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME;

import android.metrics.LogMaker;

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;


/**
 * Log {@link TextClassifierEvent} by using Tron, only support language detection and
 * conversation actions.
 *
 * @hide
 */
public final class TextClassifierEventTronLogger {

    private static final String TAG = "TCEventTronLogger";
    private static final boolean DEBUG_LOG_ENABLED = false;

    private final MetricsLogger mMetricsLogger;

    public TextClassifierEventTronLogger() {
        mMetricsLogger = new MetricsLogger();
    }

    @VisibleForTesting
    public TextClassifierEventTronLogger(MetricsLogger metricsLogger) {
        mMetricsLogger = Preconditions.checkNotNull(metricsLogger);
    }

    /** Emits a text classifier event to the logs. */
    public void writeEvent(TextClassifierEvent event) {
        Preconditions.checkNotNull(event);
        int category = getCategory(event);
        if (category == -1) {
            Log.w(TAG, "Unknown category: " + event.getEventCategory());
            return;
        }
        final LogMaker log = new LogMaker(category)
                .setType(getLogType(event))
                .addTaggedData(FIELD_SELECTION_SESSION_ID, event.getResultId())
                .addTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME, event.getEventTime())
                .addTaggedData(FIELD_TEXTCLASSIFIER_MODEL,
                        SelectionSessionLogger.SignatureParser.getModelName(event.getResultId()))
                .addTaggedData(FIELD_SELECTION_ENTITY_TYPE, event.getEntityType());
        TextClassificationContext eventContext = event.getEventContext();
        if (eventContext != null) {
            log.addTaggedData(FIELD_SELECTION_WIDGET_TYPE, eventContext.getWidgetType());
            log.addTaggedData(FIELD_SELECTION_WIDGET_VERSION, eventContext.getWidgetVersion());
            log.setPackageName(eventContext.getPackageName());
        }
        mMetricsLogger.write(log);
        debugLog(log);
    }

    private static int getCategory(TextClassifierEvent event) {
        switch (event.getEventCategory()) {
            case TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS:
                return MetricsEvent.CONVERSATION_ACTIONS;
            case TextClassifierEvent.CATEGORY_LANGUAGE_DETECTION:
                return MetricsEvent.LANGUAGE_DETECTION;
        }
        return -1;
    }

    private static int getLogType(TextClassifierEvent event) {
        switch (event.getEventType()) {
            case TextClassifierEvent.TYPE_SMART_ACTION:
                return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
            case TextClassifierEvent.TYPE_ACTIONS_SHOWN:
                return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN;
            case TextClassifierEvent.TYPE_MANUAL_REPLY:
                return MetricsEvent.ACTION_TEXT_CLASSIFIER_MANUAL_REPLY;
            default:
                return MetricsEvent.VIEW_UNKNOWN;
        }
    }

    private String toCategoryName(int category) {
        switch (category) {
            case MetricsEvent.CONVERSATION_ACTIONS:
                return "conversation_actions";
            case MetricsEvent.LANGUAGE_DETECTION:
                return "language_detection";
        }
        return "unknown";
    }

    private String toEventName(int logType) {
        switch (logType) {
            case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE:
                return "smart_share";
            case MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN:
                return "actions_shown";
            case MetricsEvent.ACTION_TEXT_CLASSIFIER_MANUAL_REPLY:
                return "manual_reply";
            case MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED:
                return "actions_generated";
        }
        return "unknown";
    }

    private void debugLog(LogMaker log) {
        if (!DEBUG_LOG_ENABLED) {
            return;
        }
        final String id = String.valueOf(log.getTaggedData(FIELD_SELECTION_SESSION_ID));
        final String categoryName = toCategoryName(log.getCategory());
        final String eventName = toEventName(log.getType());
        final String widgetType = String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_TYPE));
        final String widgetVersion =
                String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_VERSION));
        final String model = String.valueOf(log.getTaggedData(FIELD_TEXTCLASSIFIER_MODEL));
        final String entityType = String.valueOf(log.getTaggedData(FIELD_SELECTION_ENTITY_TYPE));

        StringBuilder builder = new StringBuilder();
        builder.append("writeEvent: ");
        builder.append("id=").append(id);
        builder.append(", category=").append(categoryName);
        builder.append(", eventName=").append(eventName);
        builder.append(", widgetType=").append(widgetType);
        builder.append(", widgetVersion=").append(widgetVersion);
        builder.append(", model=").append(model);
        builder.append(", entityType=").append(entityType);

        Log.d(TAG, builder.toString());
    }
}
+5 −9
Original line number Diff line number Diff line
@@ -115,9 +115,9 @@ public final class TextClassifierImpl implements TextClassifier {
    @GuardedBy("mLock") // Do not access outside this lock.
    private ActionsSuggestionsModel mActionsImpl;

    private final Object mLoggerLock = new Object();
    @GuardedBy("mLoggerLock") // Do not access outside this lock.
    private SelectionSessionLogger mSessionLogger;
    private final SelectionSessionLogger mSessionLogger = new SelectionSessionLogger();
    private final TextClassifierEventTronLogger mTextClassifierEventTronLogger =
            new TextClassifierEventTronLogger();

    private final TextClassificationConstants mSettings;

@@ -337,19 +337,15 @@ public final class TextClassifierImpl implements TextClassifier {
    @Override
    public void onSelectionEvent(SelectionEvent event) {
        Preconditions.checkNotNull(event);
        synchronized (mLoggerLock) {
            if (mSessionLogger == null) {
                mSessionLogger = new SelectionSessionLogger();
            }
        mSessionLogger.writeEvent(event);
    }
    }

    @Override
    public void onTextClassifierEvent(TextClassifierEvent event) {
        if (DEBUG) {
            Log.d(DEFAULT_LOG_TAG, "onTextClassifierEvent() called with: event = [" + event + "]");
        }
        mTextClassifierEventTronLogger.writeEvent(event);
    }

    /** @inheritDoc */
+107 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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 static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.CONVERSATION_ACTIONS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME;

import static com.google.common.truth.Truth.assertThat;

import android.metrics.LogMaker;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.textclassifier.ConversationActions;
import android.view.textclassifier.TextClassificationContext;
import android.view.textclassifier.TextClassifierEvent;
import android.view.textclassifier.TextClassifierEventTronLogger;

import com.android.internal.logging.MetricsLogger;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class TextClassifierEventTronLoggerTest {
    private static final String WIDGET_TYPE = "notification";
    private static final String PACKAGE_NAME = "pkg";
    private static final long EVENT_TIME = System.currentTimeMillis();


    @Mock
    private MetricsLogger mMetricsLogger;
    private TextClassifierEventTronLogger mTextClassifierEventTronLogger;


    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mTextClassifierEventTronLogger = new TextClassifierEventTronLogger(mMetricsLogger);
    }

    @Test
    public void testWriteEvent() {
        TextClassificationContext textClassificationContext =
                new TextClassificationContext.Builder(PACKAGE_NAME, WIDGET_TYPE)
                        .build();
        TextClassifierEvent textClassifierEvent =
                new TextClassifierEvent.Builder(
                        TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS,
                        TextClassifierEvent.TYPE_SMART_ACTION)
                        .setEntityType(ConversationActions.TYPE_CALL_PHONE)
                        .setEventTime(EVENT_TIME)
                        .setEventContext(textClassificationContext)
                        .build();

        mTextClassifierEventTronLogger.writeEvent(textClassifierEvent);

        ArgumentCaptor<LogMaker> captor = ArgumentCaptor.forClass(LogMaker.class);
        Mockito.verify(mMetricsLogger).write(captor.capture());
        LogMaker logMaker = captor.getValue();
        assertThat(logMaker.getCategory()).isEqualTo(
                CONVERSATION_ACTIONS);
        assertThat(logMaker.getType()).isEqualTo(
                ACTION_TEXT_SELECTION_SMART_SHARE);
        assertThat(logMaker.getTaggedData(FIELD_SELECTION_ENTITY_TYPE))
                .isEqualTo(ConversationActions.TYPE_CALL_PHONE);
        assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME))
                .isEqualTo(EVENT_TIME);
        assertThat(logMaker.getPackageName()).isEqualTo(PACKAGE_NAME);
        assertThat(logMaker.getTaggedData(FIELD_SELECTION_WIDGET_TYPE))
                .isEqualTo(WIDGET_TYPE);
    }

    @Test
    public void testWriteEvent_unsupportedCategory() {
        TextClassifierEvent textClassifierEvent =
                new TextClassifierEvent.Builder(
                        TextClassifierEvent.CATEGORY_SELECTION,
                        TextClassifierEvent.TYPE_SMART_ACTION)
                        .build();

        mTextClassifierEventTronLogger.writeEvent(textClassifierEvent);

        Mockito.verify(mMetricsLogger, Mockito.never()).write(Mockito.any(LogMaker.class));
    }
}
Loading