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

Commit e94d4b04 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Remove local text classifier and related tests. am: 293bdf36

Change-Id: I466b3f5c6838c87d84f078ba24d7dd19aaf8d015
parents 5735fef3 293bdf36
Loading
Loading
Loading
Loading
+0 −4
Original line number Original line Diff line number Diff line
@@ -791,10 +791,6 @@ java_library {
        "libphonenumber-platform",
        "libphonenumber-platform",
        "tagsoup",
        "tagsoup",
        "rappor",
        "rappor",
        "libtextclassifier-java",
    ],
    required: [
        "libtextclassifier",
    ],
    ],
    dxflags: ["--core-library"],
    dxflags: ["--core-library"],
}
}
+0 −2
Original line number Original line Diff line number Diff line
@@ -77,7 +77,6 @@ public class TextClassificationManagerPerfTest {
        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        while (state.keepRunning()) {
        while (state.keepRunning()) {
            textClassificationManager.getTextClassifier();
            textClassificationManager.getTextClassifier();
            textClassificationManager.invalidateForTesting();
        }
        }
    }
    }


@@ -90,7 +89,6 @@ public class TextClassificationManagerPerfTest {
        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        while (state.keepRunning()) {
        while (state.keepRunning()) {
            textClassificationManager.getTextClassifier();
            textClassificationManager.getTextClassifier();
            textClassificationManager.invalidateForTesting();
        }
        }
    }
    }


+10 −19
Original line number Original line Diff line number Diff line
@@ -41,7 +41,6 @@ import android.util.Slog;
import android.view.textclassifier.ConversationActions;
import android.view.textclassifier.ConversationActions;
import android.view.textclassifier.SelectionEvent;
import android.view.textclassifier.SelectionEvent;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationConstants;
import android.view.textclassifier.TextClassificationContext;
import android.view.textclassifier.TextClassificationContext;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassificationSessionId;
import android.view.textclassifier.TextClassificationSessionId;
@@ -405,13 +404,6 @@ public abstract class TextClassifierService extends Service {
     */
     */
    @NonNull
    @NonNull
    public static TextClassifier getDefaultTextClassifierImplementation(@NonNull Context context) {
    public static TextClassifier getDefaultTextClassifierImplementation(@NonNull Context context) {
        final TextClassificationManager tcm =
                context.getSystemService(TextClassificationManager.class);
        if (tcm == null) {
            return TextClassifier.NO_OP;
        }
        TextClassificationConstants settings = new TextClassificationConstants();
        if (settings.getUseDefaultTextClassifierAsDefaultImplementation()) {
        final String defaultTextClassifierPackageName =
        final String defaultTextClassifierPackageName =
                context.getPackageManager().getDefaultTextClassifierPackageName();
                context.getPackageManager().getDefaultTextClassifierPackageName();
        if (TextUtils.isEmpty(defaultTextClassifierPackageName)) {
        if (TextUtils.isEmpty(defaultTextClassifierPackageName)) {
@@ -422,10 +414,9 @@ public abstract class TextClassifierService extends Service {
                    "The default text classifier itself should not call the"
                    "The default text classifier itself should not call the"
                            + "getDefaultTextClassifierImplementation() method.");
                            + "getDefaultTextClassifierImplementation() method.");
        }
        }
            return tcm.getTextClassifier(TextClassifier.DEFAULT_SERVICE);
        final TextClassificationManager tcm =
        } else {
                context.getSystemService(TextClassificationManager.class);
            return tcm.getTextClassifier(TextClassifier.LOCAL);
        return tcm.getTextClassifier(TextClassifier.DEFAULT_SYSTEM);
        }
    }
    }


    /** @hide **/
    /** @hide **/
+0 −210
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2019 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.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Base64;
import android.util.KeyValueListParser;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;

import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.function.Supplier;

/**
 * Parses the {@link Settings.Global#TEXT_CLASSIFIER_ACTION_MODEL_PARAMS} flag.
 *
 * @hide
 */
public final class ActionsModelParamsSupplier implements
        Supplier<ActionsModelParamsSupplier.ActionsModelParams> {
    private static final String TAG = TextClassifier.DEFAULT_LOG_TAG;

    @VisibleForTesting
    static final String KEY_REQUIRED_MODEL_VERSION = "required_model_version";
    @VisibleForTesting
    static final String KEY_REQUIRED_LOCALES = "required_locales";
    @VisibleForTesting
    static final String KEY_SERIALIZED_PRECONDITIONS = "serialized_preconditions";

    private final Context mAppContext;
    private final SettingsObserver mSettingsObserver;

    private final Object mLock = new Object();
    private final Runnable mOnChangedListener;
    @Nullable
    @GuardedBy("mLock")
    private ActionsModelParams mActionsModelParams;
    @GuardedBy("mLock")
    private boolean mParsed = true;

    public ActionsModelParamsSupplier(Context context, @Nullable Runnable onChangedListener) {
        final Context appContext = Preconditions.checkNotNull(context).getApplicationContext();
        // Some contexts don't have an app context.
        mAppContext = appContext != null ? appContext : context;
        mOnChangedListener = onChangedListener == null ? () -> {} : onChangedListener;
        mSettingsObserver = new SettingsObserver(mAppContext, () -> {
            synchronized (mLock) {
                Log.v(TAG, "Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS is updated");
                mParsed = true;
                mOnChangedListener.run();
            }
        });
    }

    /**
     * Returns the parsed actions params or {@link ActionsModelParams#INVALID} if the value is
     * invalid.
     */
    @Override
    public ActionsModelParams get() {
        synchronized (mLock) {
            if (mParsed) {
                mActionsModelParams = parse(mAppContext.getContentResolver());
                mParsed = false;
            }
        }
        return mActionsModelParams;
    }

    private ActionsModelParams parse(ContentResolver contentResolver) {
        String settingStr = Settings.Global.getString(contentResolver,
                Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS);
        if (TextUtils.isEmpty(settingStr)) {
            return ActionsModelParams.INVALID;
        }
        try {
            KeyValueListParser keyValueListParser = new KeyValueListParser(',');
            keyValueListParser.setString(settingStr);
            int version = keyValueListParser.getInt(KEY_REQUIRED_MODEL_VERSION, -1);
            if (version == -1) {
                Log.w(TAG, "ActionsModelParams.Parse, invalid model version");
                return ActionsModelParams.INVALID;
            }
            String locales = keyValueListParser.getString(KEY_REQUIRED_LOCALES, null);
            if (locales == null) {
                Log.w(TAG, "ActionsModelParams.Parse, invalid locales");
                return ActionsModelParams.INVALID;
            }
            String serializedPreconditionsStr =
                    keyValueListParser.getString(KEY_SERIALIZED_PRECONDITIONS, null);
            if (serializedPreconditionsStr == null) {
                Log.w(TAG, "ActionsModelParams.Parse, invalid preconditions");
                return ActionsModelParams.INVALID;
            }
            byte[] serializedPreconditions =
                    Base64.decode(serializedPreconditionsStr, Base64.NO_WRAP);
            return new ActionsModelParams(version, locales, serializedPreconditions);
        } catch (Throwable t) {
            Log.e(TAG, "Invalid TEXT_CLASSIFIER_ACTION_MODEL_PARAMS, ignore", t);
        }
        return ActionsModelParams.INVALID;
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            mAppContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
        } finally {
            super.finalize();
        }
    }

    /**
     * Represents the parsed result.
     */
    public static final class ActionsModelParams {

        public static final ActionsModelParams INVALID =
                new ActionsModelParams(-1, "", new byte[0]);

        /**
         * The required model version to apply {@code mSerializedPreconditions}.
         */
        private final int mRequiredModelVersion;

        /**
         * The required model locales to apply {@code mSerializedPreconditions}.
         */
        private final String mRequiredModelLocales;

        /**
         * The serialized params that will be applied to the model file, if all requirements are
         * met. Do not modify.
         */
        private final byte[] mSerializedPreconditions;

        public ActionsModelParams(int requiredModelVersion, String requiredModelLocales,
                byte[] serializedPreconditions) {
            mRequiredModelVersion = requiredModelVersion;
            mRequiredModelLocales = Preconditions.checkNotNull(requiredModelLocales);
            mSerializedPreconditions = Preconditions.checkNotNull(serializedPreconditions);
        }

        /**
         * Returns the serialized preconditions. Returns {@code null} if the the model in use does
         * not meet all the requirements listed in the {@code ActionsModelParams} or the params
         * are invalid.
         */
        @Nullable
        public byte[] getSerializedPreconditions(ModelFileManager.ModelFile modelInUse) {
            if (this == INVALID) {
                return null;
            }
            if (modelInUse.getVersion() != mRequiredModelVersion) {
                Log.w(TAG, String.format(
                        "Not applying mSerializedPreconditions, required version=%d, actual=%d",
                        mRequiredModelVersion, modelInUse.getVersion()));
                return null;
            }
            if (!Objects.equals(modelInUse.getSupportedLocalesStr(), mRequiredModelLocales)) {
                Log.w(TAG, String.format(
                        "Not applying mSerializedPreconditions, required locales=%s, actual=%s",
                        mRequiredModelLocales, modelInUse.getSupportedLocalesStr()));
                return null;
            }
            return mSerializedPreconditions;
        }
    }

    private static final class SettingsObserver extends ContentObserver {

        private final WeakReference<Runnable> mOnChangedListener;

        SettingsObserver(Context appContext, Runnable listener) {
            super(null);
            mOnChangedListener = new WeakReference<>(listener);
            appContext.getContentResolver().registerContentObserver(
                    Settings.Global.getUriFor(Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS),
                    false /* notifyForDescendants */,
                    this);
        }

        public void onChange(boolean selfChange) {
            if (mOnChangedListener.get() != null) {
                mOnChangedListener.get().run();
            }
        }
    }
}
+0 −234
Original line number Original line 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 android.annotation.Nullable;
import android.app.Person;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Pair;
import android.view.textclassifier.intent.LabeledIntent;
import android.view.textclassifier.intent.TemplateIntentFactory;

import com.android.internal.annotations.VisibleForTesting;

import com.google.android.textclassifier.ActionsSuggestionsModel;
import com.google.android.textclassifier.RemoteActionTemplate;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Helper class for action suggestions.
 *
 * @hide
 */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class ActionsSuggestionsHelper {
    private static final String TAG = "ActionsSuggestions";
    private static final int USER_LOCAL = 0;
    private static final int FIRST_NON_LOCAL_USER = 1;

    private ActionsSuggestionsHelper() {}

    /**
     * Converts the messages to a list of native messages object that the model can understand.
     * <p>
     * User id encoding - local user is represented as 0, Other users are numbered according to
     * how far before they spoke last time in the conversation. For example, considering this
     * conversation:
     * <ul>
     * <li> User A: xxx
     * <li> Local user: yyy
     * <li> User B: zzz
     * </ul>
     * User A will be encoded as 2, user B will be encoded as 1 and local user will be encoded as 0.
     */
    public static ActionsSuggestionsModel.ConversationMessage[] toNativeMessages(
            List<ConversationActions.Message> messages,
            Function<CharSequence, String> languageDetector) {
        List<ConversationActions.Message> messagesWithText =
                messages.stream()
                        .filter(message -> !TextUtils.isEmpty(message.getText()))
                        .collect(Collectors.toCollection(ArrayList::new));
        if (messagesWithText.isEmpty()) {
            return new ActionsSuggestionsModel.ConversationMessage[0];
        }
        Deque<ActionsSuggestionsModel.ConversationMessage> nativeMessages = new ArrayDeque<>();
        PersonEncoder personEncoder = new PersonEncoder();
        int size = messagesWithText.size();
        for (int i = size - 1; i >= 0; i--) {
            ConversationActions.Message message = messagesWithText.get(i);
            long referenceTime = message.getReferenceTime() == null
                    ? 0
                    : message.getReferenceTime().toInstant().toEpochMilli();
            String timeZone = message.getReferenceTime() == null
                    ? null
                    : message.getReferenceTime().getZone().getId();
            nativeMessages.push(new ActionsSuggestionsModel.ConversationMessage(
                    personEncoder.encode(message.getAuthor()),
                    message.getText().toString(), referenceTime, timeZone,
                    languageDetector.apply(message.getText())));
        }
        return nativeMessages.toArray(
                new ActionsSuggestionsModel.ConversationMessage[nativeMessages.size()]);
    }

    /**
     * Returns the result id for logging.
     */
    public static String createResultId(
            Context context,
            List<ConversationActions.Message> messages,
            int modelVersion,
            List<Locale> modelLocales) {
        final StringJoiner localesJoiner = new StringJoiner(",");
        for (Locale locale : modelLocales) {
            localesJoiner.add(locale.toLanguageTag());
        }
        final String modelName = String.format(
                Locale.US, "%s_v%d", localesJoiner.toString(), modelVersion);
        final int hash = Objects.hash(
                messages.stream().mapToInt(ActionsSuggestionsHelper::hashMessage),
                context.getPackageName(),
                System.currentTimeMillis());
        return SelectionSessionLogger.SignatureParser.createSignature(
                SelectionSessionLogger.CLASSIFIER_ID, modelName, hash);
    }

    /**
     * Generated labeled intent from an action suggestion and return the resolved result.
     */
    @Nullable
    public static LabeledIntent.Result createLabeledIntentResult(
            Context context,
            TemplateIntentFactory templateIntentFactory,
            ActionsSuggestionsModel.ActionSuggestion nativeSuggestion) {
        RemoteActionTemplate[] remoteActionTemplates =
                nativeSuggestion.getRemoteActionTemplates();
        if (remoteActionTemplates == null) {
            Log.w(TAG, "createRemoteAction: Missing template for type "
                    + nativeSuggestion.getActionType());
            return null;
        }
        List<LabeledIntent> labeledIntents = templateIntentFactory.create(remoteActionTemplates);
        if (labeledIntents.isEmpty()) {
            return null;
        }
        // Given that we only support implicit intent here, we should expect there is just one
        // intent for each action type.
        LabeledIntent.TitleChooser titleChooser =
                ActionsSuggestionsHelper.createTitleChooser(nativeSuggestion.getActionType());
        return labeledIntents.get(0).resolve(context, titleChooser, null);
    }

    /**
     * Returns a {@link LabeledIntent.TitleChooser} for conversation actions use case.
     */
    @Nullable
    public static LabeledIntent.TitleChooser createTitleChooser(String actionType) {
        if (ConversationAction.TYPE_OPEN_URL.equals(actionType)) {
            return (labeledIntent, resolveInfo) -> {
                if (resolveInfo.handleAllWebDataURI) {
                    return labeledIntent.titleWithEntity;
                }
                if ("android".equals(resolveInfo.activityInfo.packageName)) {
                    return labeledIntent.titleWithEntity;
                }
                return labeledIntent.titleWithoutEntity;
            };
        }
        return null;
    }

    /**
     * Returns a list of {@link ConversationAction}s that have 0 duplicates. Two actions are
     * duplicates if they may look the same to users. This function assumes every
     * ConversationActions with a non-null RemoteAction also have a non-null intent in the extras.
     */
    public static List<ConversationAction> removeActionsWithDuplicates(
            List<ConversationAction> conversationActions) {
        // Ideally, we should compare title and icon here, but comparing icon is expensive and thus
        // we use the component name of the target handler as the heuristic.
        Map<Pair<String, String>, Integer> counter = new ArrayMap<>();
        for (ConversationAction conversationAction : conversationActions) {
            Pair<String, String> representation = getRepresentation(conversationAction);
            if (representation == null) {
                continue;
            }
            Integer existingCount = counter.getOrDefault(representation, 0);
            counter.put(representation, existingCount + 1);
        }
        List<ConversationAction> result = new ArrayList<>();
        for (ConversationAction conversationAction : conversationActions) {
            Pair<String, String> representation = getRepresentation(conversationAction);
            if (representation == null || counter.getOrDefault(representation, 0) == 1) {
                result.add(conversationAction);
            }
        }
        return result;
    }

    @Nullable
    private static Pair<String, String> getRepresentation(
            ConversationAction conversationAction) {
        RemoteAction remoteAction = conversationAction.getAction();
        if (remoteAction == null) {
            return null;
        }
        Intent actionIntent = ExtrasUtils.getActionIntent(conversationAction.getExtras());
        ComponentName componentName = actionIntent.getComponent();
        // Action without a component name will be considered as from the same app.
        String packageName = componentName == null ? null : componentName.getPackageName();
        return new Pair<>(
                conversationAction.getAction().getTitle().toString(), packageName);
    }

    private static final class PersonEncoder {
        private final Map<Person, Integer> mMapping = new ArrayMap<>();
        private int mNextUserId = FIRST_NON_LOCAL_USER;

        private int encode(Person person) {
            if (ConversationActions.Message.PERSON_USER_SELF.equals(person)) {
                return USER_LOCAL;
            }
            Integer result = mMapping.get(person);
            if (result == null) {
                mMapping.put(person, mNextUserId);
                result = mNextUserId;
                mNextUserId++;
            }
            return result;
        }
    }

    private static int hashMessage(ConversationActions.Message message) {
        return Objects.hash(message.getAuthor(), message.getText(), message.getReferenceTime());
    }
}
Loading