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

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

Merge "Adding intent generation for dates and flights"

parents 23e7cc33 705b9e9a
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -49896,7 +49896,9 @@ package android.view.textclassifier {
    ctor public TextClassification.Options();
    method public int describeContents();
    method public android.os.LocaleList getDefaultLocales();
    method public java.util.Calendar getReferenceTime();
    method public android.view.textclassifier.TextClassification.Options setDefaultLocales(android.os.LocaleList);
    method public android.view.textclassifier.TextClassification.Options setReferenceTime(java.util.Calendar);
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassification.Options> CREATOR;
  }
@@ -49921,7 +49923,10 @@ package android.view.textclassifier {
    field public static final int ENTITY_PRESET_NONE = 1; // 0x1
    field public static final android.view.textclassifier.TextClassifier NO_OP;
    field public static final java.lang.String TYPE_ADDRESS = "address";
    field public static final java.lang.String TYPE_DATE = "date";
    field public static final java.lang.String TYPE_DATE_TIME = "datetime";
    field public static final java.lang.String TYPE_EMAIL = "email";
    field public static final java.lang.String TYPE_FLIGHT_NUMBER = "flight";
    field public static final java.lang.String TYPE_OTHER = "other";
    field public static final java.lang.String TYPE_PHONE = "phone";
    field public static final java.lang.String TYPE_UNKNOWN = "";
+14 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.view.textclassifier;

import android.annotation.Nullable;
import android.content.res.AssetFileDescriptor;

/**
@@ -146,11 +147,24 @@ final class SmartSelection {
        final String mCollection;
        /** float range: 0 - 1 */
        final float mScore;
        @Nullable final DatetimeParseResult mDatetime;

        ClassificationResult(String collection, float score) {
            mCollection = collection;
            mScore = score;
            mDatetime = null;
        }

        ClassificationResult(String collection, float score, DatetimeParseResult datetime) {
            mCollection = collection;
            mScore = score;
            mDatetime = datetime;
        }
    }

    /** Parsed date information for the classification result. */
    static final class DatetimeParseResult {
        long mMsSinceEpoch;
    }

    /** Represents a result of Annotate call. */
+28 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.view.textclassifier.TextClassifier.EntityType;
import com.android.internal.util.Preconditions;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -592,6 +593,7 @@ public final class TextClassification {
    public static final class Options implements Parcelable {

        private @Nullable LocaleList mDefaultLocales;
        private @Nullable Calendar mReferenceTime;

        public Options() {}

@@ -605,6 +607,16 @@ public final class TextClassification {
            return this;
        }

        /**
         * @param referenceTime reference time based on which relative dates (e.g. "tomorrow" should
         *      be interpreted. This should usually be the time when the text was originally
         *      composed. If no reference time is set, now is used.
         */
        public Options setReferenceTime(Calendar referenceTime) {
            mReferenceTime = referenceTime;
            return this;
        }

        /**
         * @return ordered list of locale preferences that can be used to disambiguate
         *      the provided text.
@@ -614,6 +626,15 @@ public final class TextClassification {
            return mDefaultLocales;
        }

        /**
         * @return reference time based on which relative dates (e.g. "tomorrow") should be
         *      interpreted.
         */
        @Nullable
        public Calendar getReferenceTime() {
            return mReferenceTime;
        }

        @Override
        public int describeContents() {
            return 0;
@@ -625,6 +646,10 @@ public final class TextClassification {
            if (mDefaultLocales != null) {
                mDefaultLocales.writeToParcel(dest, flags);
            }
            dest.writeInt(mReferenceTime != null ? 1 : 0);
            if (mReferenceTime != null) {
                dest.writeSerializable(mReferenceTime);
            }
        }

        public static final Parcelable.Creator<Options> CREATOR =
@@ -644,6 +669,9 @@ public final class TextClassification {
            if (in.readInt() > 0) {
                mDefaultLocales = LocaleList.CREATOR.createFromParcel(in);
            }
            if (in.readInt() > 0) {
                mReferenceTime = (Calendar) in.readSerializable();
            }
        }
    }

+17 −0
Original line number Diff line number Diff line
@@ -47,12 +47,26 @@ public interface TextClassifier {
    /** @hide */
    String DEFAULT_LOG_TAG = "androidtc";

    /** The TextClassifier failed to run. */
    String TYPE_UNKNOWN = "";
    /** The classifier ran, but didn't recognize a known entity. */
    String TYPE_OTHER = "other";
    /** E-mail address (e.g. "noreply@android.com"). */
    String TYPE_EMAIL = "email";
    /** Phone number (e.g. "555-123 456"). */
    String TYPE_PHONE = "phone";
    /** Physical address. */
    String TYPE_ADDRESS = "address";
    /** Web URL. */
    String TYPE_URL = "url";
    /** Time reference that is no more specific than a date. May be absolute such as "01/01/2000" or
     * relative like "tomorrow". **/
    String TYPE_DATE = "date";
    /** Time reference that includes a specific time. May be absolute such as "01/01/2000 5:30pm" or
     * relative like "tomorrow at 5:30pm". **/
    String TYPE_DATE_TIME = "datetime";
    /** Flight number in IATA format. */
    String TYPE_FLIGHT_NUMBER = "flight";

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
@@ -63,6 +77,9 @@ public interface TextClassifier {
            TYPE_PHONE,
            TYPE_ADDRESS,
            TYPE_URL,
            TYPE_DATE,
            TYPE_DATE_TIME,
            TYPE_FLIGHT_NUMBER,
    })
    @interface EntityType {}

+145 −64
Original line number Diff line number Diff line
@@ -18,7 +18,9 @@ package android.view.textclassifier;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.SearchManager;
import android.content.ComponentName;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -28,6 +30,7 @@ import android.net.Uri;
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.provider.Browser;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.text.util.Linkify;
@@ -42,6 +45,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -49,6 +53,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@@ -73,7 +78,10 @@ final class TextClassifierImpl implements TextClassifier {
                    TextClassifier.TYPE_ADDRESS,
                    TextClassifier.TYPE_EMAIL,
                    TextClassifier.TYPE_PHONE,
                    TextClassifier.TYPE_URL));
                    TextClassifier.TYPE_URL,
                    TextClassifier.TYPE_DATE,
                    TextClassifier.TYPE_DATE_TIME,
                    TextClassifier.TYPE_FLIGHT_NUMBER));
    private static final List<String> ENTITY_TYPES_BASE =
            Collections.unmodifiableList(Arrays.asList(
                    TextClassifier.TYPE_ADDRESS,
@@ -167,9 +175,8 @@ final class TextClassifierImpl implements TextClassifier {
                        .classifyText(string, startIndex, endIndex,
                                getHintFlags(string, startIndex, endIndex));
                if (results.length > 0) {
                    final TextClassification classificationResult =
                            createClassificationResult(results, string, startIndex, endIndex);
                    return classificationResult;
                    return createClassificationResult(
                            results, string, startIndex, endIndex, options.getReferenceTime());
                }
            }
        } catch (Throwable t) {
@@ -410,18 +417,24 @@ final class TextClassifierImpl implements TextClassifier {

    private TextClassification createClassificationResult(
            SmartSelection.ClassificationResult[] classifications,
            String text, int start, int end) {
            String text, int start, int end, @Nullable Calendar referenceTime) {
        final String classifiedText = text.substring(start, end);
        final TextClassification.Builder builder = new TextClassification.Builder()
                .setText(classifiedText);

        final int size = classifications.length;
        SmartSelection.ClassificationResult highestScoringResult = null;
        float highestScore = Float.MIN_VALUE;
        for (int i = 0; i < size; i++) {
            builder.setEntityType(classifications[i].mCollection, classifications[i].mScore);
            if (classifications[i].mScore > highestScore) {
                highestScoringResult = classifications[i];
                highestScore = classifications[i].mScore;
            }
        }

        final String type = getHighestScoringType(classifications);
        addActions(builder, IntentFactory.create(mContext, type, classifiedText));
        addActions(builder, IntentFactory.create(
                mContext, referenceTime, highestScoringResult, classifiedText));

        return builder.setSignature(getSignature(text, start, end)).build();
    }
@@ -441,11 +454,10 @@ final class TextClassifierImpl implements TextClassifier {
            }
            if (resolveInfo != null && resolveInfo.activityInfo != null) {
                final String packageName = resolveInfo.activityInfo.packageName;
                CharSequence label;
                final String label = IntentFactory.getLabel(mContext, intent);
                Drawable icon;
                if ("android".equals(packageName)) {
                    // Requires the chooser to find an activity to handle the intent.
                    label = IntentFactory.getLabel(mContext, intent);
                    icon = null;
                } else {
                    // A default activity will handle the intent.
@@ -455,16 +467,11 @@ final class TextClassifierImpl implements TextClassifier {
                    if (icon == null) {
                        icon = resolveInfo.loadIcon(pm);
                    }
                    label = resolveInfo.activityInfo.loadLabel(pm);
                    if (label == null) {
                        label = resolveInfo.loadLabel(pm);
                    }
                }
                final String labelString = (label != null) ? label.toString() : null;
                if (i == 0) {
                    builder.setPrimaryAction(intent, labelString, icon);
                    builder.setPrimaryAction(intent, label, icon);
                } else {
                    builder.addSecondaryAction(intent, labelString, icon);
                    builder.addSecondaryAction(intent, label, icon);
                }
            }
        }
@@ -483,23 +490,6 @@ final class TextClassifierImpl implements TextClassifier {
        return flag;
    }

    private static String getHighestScoringType(SmartSelection.ClassificationResult[] types) {
        if (types.length < 1) {
            return "";
        }

        String type = types[0].mCollection;
        float highestScore = types[0].mScore;
        final int size = types.length;
        for (int i = 1; i < size; i++) {
            if (types[i].mScore > highestScore) {
                type = types[i].mCollection;
                highestScore = types[i].mScore;
            }
        }
        return type;
    }

    /**
     * Closes the ParcelFileDescriptor and logs any errors that occur.
     */
@@ -514,37 +504,76 @@ final class TextClassifierImpl implements TextClassifier {
    /**
     * Creates intents based on the classification type.
     */
    private static final class IntentFactory {
    static final class IntentFactory {

        private static final long MIN_EVENT_FUTURE_MILLIS = TimeUnit.MINUTES.toMillis(5);
        private static final long DEFAULT_EVENT_DURATION = TimeUnit.HOURS.toMillis(1);

        private IntentFactory() {}

        @NonNull
        public static List<Intent> create(Context context, String type, String text) {
            final List<Intent> intents = new ArrayList<>();
            type = type.trim().toLowerCase(Locale.ENGLISH);
        public static List<Intent> create(
                Context context,
                @Nullable Calendar referenceTime,
                SmartSelection.ClassificationResult classification,
                String text) {
            final String type = classification.mCollection.trim().toLowerCase(Locale.ENGLISH);
            text = text.trim();
            switch (type) {
                case TextClassifier.TYPE_EMAIL:
                    intents.add(new Intent(Intent.ACTION_SENDTO)
                            .setData(Uri.parse(String.format("mailto:%s", text))));
                    intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
                    return createForEmail(text);
                case TextClassifier.TYPE_PHONE:
                    return createForPhone(text);
                case TextClassifier.TYPE_ADDRESS:
                    return createForAddress(text);
                case TextClassifier.TYPE_URL:
                    return createForUrl(context, text);
                case TextClassifier.TYPE_DATE:
                case TextClassifier.TYPE_DATE_TIME:
                    if (classification.mDatetime != null) {
                        Calendar eventTime = Calendar.getInstance();
                        eventTime.setTimeInMillis(classification.mDatetime.mMsSinceEpoch);
                        return createForDatetime(type, referenceTime, eventTime);
                    } else {
                        return new ArrayList<>();
                    }
                case TextClassifier.TYPE_FLIGHT_NUMBER:
                    return createForFlight(text);
                default:
                    return new ArrayList<>();
            }
        }

        @NonNull
        private static List<Intent> createForEmail(String text) {
            return Arrays.asList(
                    new Intent(Intent.ACTION_SENDTO)
                            .setData(Uri.parse(String.format("mailto:%s", text))),
                    new Intent(Intent.ACTION_INSERT_OR_EDIT)
                            .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
                            .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
                    break;
                case TextClassifier.TYPE_PHONE:
                    intents.add(new Intent(Intent.ACTION_DIAL)
                            .setData(Uri.parse(String.format("tel:%s", text))));
                    intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
        }

        @NonNull
        private static List<Intent> createForPhone(String text) {
            return Arrays.asList(
                    new Intent(Intent.ACTION_DIAL)
                            .setData(Uri.parse(String.format("tel:%s", text))),
                    new Intent(Intent.ACTION_INSERT_OR_EDIT)
                            .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
                            .putExtra(ContactsContract.Intents.Insert.PHONE, text));
                    intents.add(new Intent(Intent.ACTION_SENDTO)
                            .putExtra(ContactsContract.Intents.Insert.PHONE, text),
                    new Intent(Intent.ACTION_SENDTO)
                            .setData(Uri.parse(String.format("smsto:%s", text))));
                    break;
                case TextClassifier.TYPE_ADDRESS:
                    intents.add(new Intent(Intent.ACTION_VIEW)
        }

        @NonNull
        private static List<Intent> createForAddress(String text) {
            return Arrays.asList(new Intent(Intent.ACTION_VIEW)
                    .setData(Uri.parse(String.format("geo:0,0?q=%s", text))));
                    break;
                case TextClassifier.TYPE_URL:
        }

        @NonNull
        private static List<Intent> createForUrl(Context context, String text) {
            final String httpPrefix = "http://";
            final String httpsPrefix = "https://";
            if (text.toLowerCase().startsWith(httpPrefix)) {
@@ -554,18 +583,60 @@ final class TextClassifierImpl implements TextClassifier {
            } else {
                text = httpPrefix + text;
            }
                    intents.add(new Intent(Intent.ACTION_VIEW, Uri.parse(text))
            return Arrays.asList(new Intent(Intent.ACTION_VIEW, Uri.parse(text))
                    .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()));
                    break;
        }

        @NonNull
        private static List<Intent> createForDatetime(
                String type, @Nullable Calendar referenceTime, Calendar eventTime) {
            if (referenceTime == null) {
                // If no reference time was given, use now.
                referenceTime = Calendar.getInstance();
            }
            List<Intent> intents = new ArrayList<>();
            intents.add(createCalendarViewIntent(eventTime));
            final long millisSinceReference =
                    eventTime.getTimeInMillis() - referenceTime.getTimeInMillis();
            if (millisSinceReference > MIN_EVENT_FUTURE_MILLIS) {
                intents.add(createCalendarCreateEventIntent(eventTime, type));
            }
            return intents;
        }

        @NonNull
        private static List<Intent> createForFlight(String text) {
            return Arrays.asList(new Intent(Intent.ACTION_WEB_SEARCH)
                    .putExtra(SearchManager.QUERY, text));
        }

        @NonNull
        private static Intent createCalendarViewIntent(Calendar eventTime) {
            Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
            builder.appendPath("time");
            ContentUris.appendId(builder, eventTime.getTimeInMillis());
            return new Intent(Intent.ACTION_VIEW).setData(builder.build());
        }

        @NonNull
        private static Intent createCalendarCreateEventIntent(
                Calendar eventTime, @EntityType String type) {
            final boolean isAllDay = TextClassifier.TYPE_DATE.equals(type);
            return new Intent(Intent.ACTION_INSERT)
                    .setData(CalendarContract.Events.CONTENT_URI)
                    .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay)
                    .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, eventTime.getTimeInMillis())
                    .putExtra(CalendarContract.EXTRA_EVENT_END_TIME,
                            eventTime.getTimeInMillis() + DEFAULT_EVENT_DURATION);
        }

        @Nullable
        public static String getLabel(Context context, @Nullable Intent intent) {
            if (intent == null || intent.getAction() == null) {
                return null;
            }
            final String authority =
                    intent.getData() == null ? null : intent.getData().getAuthority();
            switch (intent.getAction()) {
                case Intent.ACTION_DIAL:
                    return context.getString(com.android.internal.R.string.dial);
@@ -578,6 +649,11 @@ final class TextClassifierImpl implements TextClassifier {
                        default:
                            return null;
                    }
                case Intent.ACTION_INSERT:
                    if (CalendarContract.AUTHORITY.equals(authority)) {
                        return context.getString(com.android.internal.R.string.add_calendar_event);
                    }
                    return null;
                case Intent.ACTION_INSERT_OR_EDIT:
                    switch (intent.getDataString()) {
                        case ContactsContract.Contacts.CONTENT_ITEM_TYPE:
@@ -586,6 +662,9 @@ final class TextClassifierImpl implements TextClassifier {
                            return null;
                    }
                case Intent.ACTION_VIEW:
                    if (CalendarContract.AUTHORITY.equals(authority)) {
                        return context.getString(com.android.internal.R.string.view_calendar);
                    }
                    switch (intent.getScheme()) {
                        case "geo":
                            return context.getString(com.android.internal.R.string.map);
@@ -595,6 +674,8 @@ final class TextClassifierImpl implements TextClassifier {
                        default:
                            return null;
                    }
                case Intent.ACTION_WEB_SEARCH:
                    return context.getString(com.android.internal.R.string.view_flight);
                default:
                    return null;
            }
Loading