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

Commit 7a13eb21 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Support title_with_entity and title_without_entity"

parents 47a8e71e ac9b4d8d
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.view.textclassifier;

import android.annotation.Nullable;
import android.app.Person;
import android.content.Context;
import android.text.TextUtils;
@@ -110,6 +111,19 @@ public final class ActionsSuggestionsHelper {
                SelectionSessionLogger.CLASSIFIER_ID, modelName, hash);
    }

    /**
     * Returns a {@link android.view.textclassifier.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) -> resolveInfo.handleAllWebDataURI
                    ? labeledIntent.titleWithEntity : labeledIntent.titleWithoutEntity;
        }
        return null;
    }

    private static final class PersonEncoder {
        private final Map<Person, Integer> mMapping = new ArrayMap<>();
        private int mNextUserId = FIRST_NON_LOCAL_USER;
+4 −3
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ public interface IntentFactory {
    /**
     * Return a list of LabeledIntent from the classification result.
     */
    List<TextClassifierImpl.LabeledIntent> create(
    List<LabeledIntent> create(
            Context context,
            String text,
            boolean foreignText,
@@ -43,9 +43,10 @@ public interface IntentFactory {
     * Inserts translate action to the list if it is a foreign text.
     */
    static void insertTranslateAction(
            List<TextClassifierImpl.LabeledIntent> actions, Context context, String text) {
        actions.add(new TextClassifierImpl.LabeledIntent(
            List<LabeledIntent> actions, Context context, String text) {
        actions.add(new LabeledIntent(
                context.getString(com.android.internal.R.string.translate),
                /* titleWithEntity */ null,
                context.getString(com.android.internal.R.string.translate_desc),
                new Intent(Intent.ACTION_TRANSLATE)
                        // TODO: Probably better to introduce a "translate" scheme instead of
+157 −0
Original line number 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.app.PendingIntent;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Icon;
import android.text.TextUtils;

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

/**
 * Helper class to store the information from which RemoteActions are built.
 *
 * @hide
 */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class LabeledIntent {
    private static final String TAG = "LabeledIntent";
    public static final int DEFAULT_REQUEST_CODE = 0;
    private static final TitleChooser DEFAULT_TITLE_CHOOSER =
            (labeledIntent, resolveInfo) -> {
                if (!TextUtils.isEmpty(labeledIntent.titleWithEntity)) {
                    return labeledIntent.titleWithEntity;
                }
                return labeledIntent.titleWithoutEntity;
            };

    @Nullable
    public final String titleWithoutEntity;
    @Nullable
    public final String titleWithEntity;
    public final String description;
    // Do not update this intent.
    public final Intent intent;
    public final int requestCode;

    /**
     * Initializes a LabeledIntent.
     *
     * <p>NOTE: {@code requestCode} is required to not be {@link #DEFAULT_REQUEST_CODE}
     * if distinguishing info (e.g. the classified text) is represented in intent extras only.
     * In such circumstances, the request code should represent the distinguishing info
     * (e.g. by generating a hashcode) so that the generated PendingIntent is (somewhat)
     * unique. To be correct, the PendingIntent should be definitely unique but we try a
     * best effort approach that avoids spamming the system with PendingIntents.
     */
    // TODO: Fix the issue mentioned above so the behaviour is correct.
    public LabeledIntent(
            @Nullable String titleWithoutEntity,
            @Nullable String titleWithEntity,
            String description,
            Intent intent,
            int requestCode) {
        if (TextUtils.isEmpty(titleWithEntity) && TextUtils.isEmpty(titleWithoutEntity)) {
            throw new IllegalArgumentException(
                    "titleWithEntity and titleWithoutEntity should not be both null");
        }
        this.titleWithoutEntity = titleWithoutEntity;
        this.titleWithEntity = titleWithEntity;
        this.description = Preconditions.checkNotNull(description);
        this.intent = Preconditions.checkNotNull(intent);
        this.requestCode = requestCode;
    }

    /**
     * Return the resolved result.
     */
    @Nullable
    public Result resolve(
            Context context, @Nullable TitleChooser titleChooser) {
        final PackageManager pm = context.getPackageManager();
        final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
        final String packageName = resolveInfo != null && resolveInfo.activityInfo != null
                ? resolveInfo.activityInfo.packageName : null;
        Icon icon = null;
        Intent resolvedIntent = new Intent(intent);
        boolean shouldShowIcon = false;
        if (packageName != null && !"android".equals(packageName)) {
            // There is a default activity handling the intent.
            resolvedIntent.setComponent(
                    new ComponentName(packageName, resolveInfo.activityInfo.name));
            if (resolveInfo.activityInfo.getIconResource() != 0) {
                icon = Icon.createWithResource(
                        packageName, resolveInfo.activityInfo.getIconResource());
                shouldShowIcon = true;
            }
        }
        if (icon == null) {
            // RemoteAction requires that there be an icon.
            icon = Icon.createWithResource("android",
                    com.android.internal.R.drawable.ic_more_items);
        }
        final PendingIntent pendingIntent =
                TextClassification.createPendingIntent(context, resolvedIntent, requestCode);
        if (pendingIntent == null) {
            return null;
        }
        if (titleChooser == null) {
            titleChooser = DEFAULT_TITLE_CHOOSER;
        }
        CharSequence title = titleChooser.chooseTitle(this, resolveInfo);
        if (TextUtils.isEmpty(title)) {
            Log.w(TAG, "Custom titleChooser return null, fallback to the default titleChooser");
            title = DEFAULT_TITLE_CHOOSER.chooseTitle(this, resolveInfo);
        }
        final RemoteAction action =
                new RemoteAction(icon, title, description, pendingIntent);
        action.setShouldShowIcon(shouldShowIcon);
        return new Result(resolvedIntent, action);
    }

    /**
     * Data class that holds the result.
     */
    public static final class Result {
        public final Intent resolvedIntent;
        public final RemoteAction remoteAction;

        public Result(Intent resolvedIntent, RemoteAction remoteAction) {
            this.resolvedIntent = Preconditions.checkNotNull(resolvedIntent);
            this.remoteAction = Preconditions.checkNotNull(remoteAction);
        }
    }

    /**
     * An object to choose a title from resolved info.  If {@code null} is returned,
     * {@link #titleWithEntity} will be used if it exists, {@link #titleWithoutEntity} otherwise.
     */
    public interface TitleChooser {
        /**
         * Picks a title from a {@link LabeledIntent} by looking into resolved info.
         */
        @Nullable
        CharSequence chooseTitle(LabeledIntent labeledIntent, ResolveInfo resolveInfo);
    }
}
+12 −3
Original line number Diff line number Diff line
@@ -29,7 +29,6 @@ import android.os.UserManager;
import android.provider.Browser;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.view.textclassifier.TextClassifierImpl.LabeledIntent;

import com.google.android.textclassifier.AnnotatorModel;

@@ -100,8 +99,7 @@ public final class LegacyIntentFactory implements IntentFactory {
            IntentFactory.insertTranslateAction(actions, context, text);
        }
        actions.forEach(
                action -> action.getIntent()
                        .putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true));
                action -> action.intent.putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true));
        return actions;
    }

@@ -110,12 +108,14 @@ public final class LegacyIntentFactory implements IntentFactory {
        final List<LabeledIntent> actions = new ArrayList<>();
        actions.add(new LabeledIntent(
                context.getString(com.android.internal.R.string.email),
                /* titleWithEntity */ null,
                context.getString(com.android.internal.R.string.email_desc),
                new Intent(Intent.ACTION_SENDTO)
                        .setData(Uri.parse(String.format("mailto:%s", text))),
                LabeledIntent.DEFAULT_REQUEST_CODE));
        actions.add(new LabeledIntent(
                context.getString(com.android.internal.R.string.add_contact),
                /* titleWithEntity */ null,
                context.getString(com.android.internal.R.string.add_contact_desc),
                new Intent(Intent.ACTION_INSERT_OR_EDIT)
                        .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
@@ -133,6 +133,7 @@ public final class LegacyIntentFactory implements IntentFactory {
        if (!userRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS, false)) {
            actions.add(new LabeledIntent(
                    context.getString(com.android.internal.R.string.dial),
                    /* titleWithEntity */ null,
                    context.getString(com.android.internal.R.string.dial_desc),
                    new Intent(Intent.ACTION_DIAL).setData(
                            Uri.parse(String.format("tel:%s", text))),
@@ -140,6 +141,7 @@ public final class LegacyIntentFactory implements IntentFactory {
        }
        actions.add(new LabeledIntent(
                context.getString(com.android.internal.R.string.add_contact),
                /* titleWithEntity */ null,
                context.getString(com.android.internal.R.string.add_contact_desc),
                new Intent(Intent.ACTION_INSERT_OR_EDIT)
                        .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
@@ -148,6 +150,7 @@ public final class LegacyIntentFactory implements IntentFactory {
        if (!userRestrictions.getBoolean(UserManager.DISALLOW_SMS, false)) {
            actions.add(new LabeledIntent(
                    context.getString(com.android.internal.R.string.sms),
                    /* titleWithEntity */ null,
                    context.getString(com.android.internal.R.string.sms_desc),
                    new Intent(Intent.ACTION_SENDTO)
                            .setData(Uri.parse(String.format("smsto:%s", text))),
@@ -163,6 +166,7 @@ public final class LegacyIntentFactory implements IntentFactory {
            final String encText = URLEncoder.encode(text, "UTF-8");
            actions.add(new LabeledIntent(
                    context.getString(com.android.internal.R.string.map),
                    /* titleWithEntity */ null,
                    context.getString(com.android.internal.R.string.map_desc),
                    new Intent(Intent.ACTION_VIEW)
                            .setData(Uri.parse(String.format("geo:0,0?q=%s", encText))),
@@ -181,6 +185,7 @@ public final class LegacyIntentFactory implements IntentFactory {
        final List<LabeledIntent> actions = new ArrayList<>();
        actions.add(new LabeledIntent(
                context.getString(com.android.internal.R.string.browse),
                /* titleWithEntity */ null,
                context.getString(com.android.internal.R.string.browse_desc),
                new Intent(Intent.ACTION_VIEW)
                        .setDataAndNormalize(Uri.parse(text))
@@ -211,6 +216,7 @@ public final class LegacyIntentFactory implements IntentFactory {
        final List<LabeledIntent> actions = new ArrayList<>();
        actions.add(new LabeledIntent(
                context.getString(com.android.internal.R.string.view_flight),
                /* titleWithEntity */ null,
                context.getString(com.android.internal.R.string.view_flight_desc),
                new Intent(Intent.ACTION_WEB_SEARCH)
                        .putExtra(SearchManager.QUERY, text),
@@ -225,6 +231,7 @@ public final class LegacyIntentFactory implements IntentFactory {
        ContentUris.appendId(builder, parsedTime.toEpochMilli());
        return new LabeledIntent(
                context.getString(com.android.internal.R.string.view_calendar),
                /* titleWithEntity */ null,
                context.getString(com.android.internal.R.string.view_calendar_desc),
                new Intent(Intent.ACTION_VIEW).setData(builder.build()),
                LabeledIntent.DEFAULT_REQUEST_CODE);
@@ -236,6 +243,7 @@ public final class LegacyIntentFactory implements IntentFactory {
        final boolean isAllDay = TextClassifier.TYPE_DATE.equals(type);
        return new LabeledIntent(
                context.getString(com.android.internal.R.string.add_calendar_event),
                /* titleWithEntity */ null,
                context.getString(com.android.internal.R.string.add_calendar_event_desc),
                new Intent(Intent.ACTION_INSERT)
                        .setData(CalendarContract.Events.CONTENT_URI)
@@ -252,6 +260,7 @@ public final class LegacyIntentFactory implements IntentFactory {
        final List<LabeledIntent> actions = new ArrayList<>();
        actions.add(new LabeledIntent(
                context.getString(com.android.internal.R.string.define),
                /* titleWithEntity */ null,
                context.getString(com.android.internal.R.string.define_desc),
                new Intent(Intent.ACTION_DEFINE)
                        .putExtra(Intent.EXTRA_TEXT, text),
+3 −3
Original line number Diff line number Diff line
@@ -48,12 +48,12 @@ public final class TemplateClassificationIntentFactory implements IntentFactory
    }

    /**
     * Returns a list of {@link android.view.textclassifier.TextClassifierImpl.LabeledIntent}
     * Returns a list of {@link android.view.textclassifier.LabeledIntent}
     * that are constructed from the classification result.
     */
    @NonNull
    @Override
    public List<TextClassifierImpl.LabeledIntent> create(
    public List<LabeledIntent> create(
            Context context,
            String text,
            boolean foreignText,
@@ -68,7 +68,7 @@ public final class TemplateClassificationIntentFactory implements IntentFactory
            Log.w(TAG, "RemoteActionTemplate is missing, fallback to LegacyIntentFactory.");
            return mFallback.create(context, text, foreignText, referenceTime, classification);
        }
        final List<TextClassifierImpl.LabeledIntent> labeledIntents =
        final List<LabeledIntent> labeledIntents =
                mTemplateIntentFactory.create(remoteActionTemplates);
        if (foreignText) {
            IntentFactory.insertTranslateAction(labeledIntents, context, text.trim());
Loading