Merge "Update IntentFactory to construct intents using RemoteActionTemplate... objects that are returned by the model"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 32d139f..9721ab1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11766,6 +11766,7 @@
* entity_list_not_editable (String[])
* entity_list_editable (String[])
* lang_id_threshold_override (float)
+ * template_intent_factory_enabled (boolean)
* </pre>
*
* <p>
diff --git a/core/java/android/view/textclassifier/IntentFactory.java b/core/java/android/view/textclassifier/IntentFactory.java
new file mode 100644
index 0000000..d9c03c8
--- /dev/null
+++ b/core/java/android/view/textclassifier/IntentFactory.java
@@ -0,0 +1,56 @@
+/*
+ * 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.Context;
+import android.content.Intent;
+
+import com.google.android.textclassifier.AnnotatorModel;
+
+import java.time.Instant;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public interface IntentFactory {
+
+ /**
+ * Return a list of LabeledIntent from the classification result.
+ */
+ List<TextClassifierImpl.LabeledIntent> create(
+ Context context,
+ String text,
+ boolean foreignText,
+ @Nullable Instant referenceTime,
+ @Nullable AnnotatorModel.ClassificationResult classification);
+
+ /**
+ * 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(
+ context.getString(com.android.internal.R.string.translate),
+ context.getString(com.android.internal.R.string.translate_desc),
+ new Intent(Intent.ACTION_TRANSLATE)
+ // TODO: Probably better to introduce a "translate" scheme instead of
+ // using EXTRA_TEXT.
+ .putExtra(Intent.EXTRA_TEXT, text),
+ text.hashCode()));
+ }
+}
diff --git a/core/java/android/view/textclassifier/LegacyIntentFactory.java b/core/java/android/view/textclassifier/LegacyIntentFactory.java
new file mode 100644
index 0000000..b6e5b3e2
--- /dev/null
+++ b/core/java/android/view/textclassifier/LegacyIntentFactory.java
@@ -0,0 +1,260 @@
+/*
+ * 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 static java.time.temporal.ChronoUnit.MILLIS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.SearchManager;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+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;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Creates intents based on the classification type.
+ * @hide
+ */
+public final class LegacyIntentFactory implements IntentFactory {
+
+ private static final String TAG = "LegacyIntentFactory";
+ private static final long MIN_EVENT_FUTURE_MILLIS = TimeUnit.MINUTES.toMillis(5);
+ private static final long DEFAULT_EVENT_DURATION = TimeUnit.HOURS.toMillis(1);
+
+ public LegacyIntentFactory() {}
+
+ @NonNull
+ @Override
+ public List<LabeledIntent> create(Context context, String text, boolean foreignText,
+ @Nullable Instant referenceTime,
+ AnnotatorModel.ClassificationResult classification) {
+ final String type = classification != null
+ ? classification.getCollection().trim().toLowerCase(Locale.ENGLISH)
+ : "";
+ text = text.trim();
+ final List<LabeledIntent> actions;
+ switch (type) {
+ case TextClassifier.TYPE_EMAIL:
+ actions = createForEmail(context, text);
+ break;
+ case TextClassifier.TYPE_PHONE:
+ actions = createForPhone(context, text);
+ break;
+ case TextClassifier.TYPE_ADDRESS:
+ actions = createForAddress(context, text);
+ break;
+ case TextClassifier.TYPE_URL:
+ actions = createForUrl(context, text);
+ break;
+ case TextClassifier.TYPE_DATE: // fall through
+ case TextClassifier.TYPE_DATE_TIME:
+ if (classification.getDatetimeResult() != null) {
+ final Instant parsedTime = Instant.ofEpochMilli(
+ classification.getDatetimeResult().getTimeMsUtc());
+ actions = createForDatetime(context, type, referenceTime, parsedTime);
+ } else {
+ actions = new ArrayList<>();
+ }
+ break;
+ case TextClassifier.TYPE_FLIGHT_NUMBER:
+ actions = createForFlight(context, text);
+ break;
+ case TextClassifier.TYPE_DICTIONARY:
+ actions = createForDictionary(context, text);
+ break;
+ default:
+ actions = new ArrayList<>();
+ break;
+ }
+ if (foreignText) {
+ IntentFactory.insertTranslateAction(actions, context, text);
+ }
+ actions.forEach(
+ action -> action.getIntent()
+ .putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true));
+ return actions;
+ }
+
+ @NonNull
+ private static List<LabeledIntent> createForEmail(Context context, String text) {
+ final List<LabeledIntent> actions = new ArrayList<>();
+ actions.add(new LabeledIntent(
+ context.getString(com.android.internal.R.string.email),
+ 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),
+ context.getString(com.android.internal.R.string.add_contact_desc),
+ new Intent(Intent.ACTION_INSERT_OR_EDIT)
+ .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+ .putExtra(ContactsContract.Intents.Insert.EMAIL, text),
+ text.hashCode()));
+ return actions;
+ }
+
+ @NonNull
+ private static List<LabeledIntent> createForPhone(Context context, String text) {
+ final List<LabeledIntent> actions = new ArrayList<>();
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ final Bundle userRestrictions = userManager != null
+ ? userManager.getUserRestrictions() : new Bundle();
+ if (!userRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS, false)) {
+ actions.add(new LabeledIntent(
+ context.getString(com.android.internal.R.string.dial),
+ context.getString(com.android.internal.R.string.dial_desc),
+ new Intent(Intent.ACTION_DIAL).setData(
+ Uri.parse(String.format("tel:%s", text))),
+ LabeledIntent.DEFAULT_REQUEST_CODE));
+ }
+ actions.add(new LabeledIntent(
+ context.getString(com.android.internal.R.string.add_contact),
+ context.getString(com.android.internal.R.string.add_contact_desc),
+ new Intent(Intent.ACTION_INSERT_OR_EDIT)
+ .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+ .putExtra(ContactsContract.Intents.Insert.PHONE, text),
+ text.hashCode()));
+ if (!userRestrictions.getBoolean(UserManager.DISALLOW_SMS, false)) {
+ actions.add(new LabeledIntent(
+ context.getString(com.android.internal.R.string.sms),
+ context.getString(com.android.internal.R.string.sms_desc),
+ new Intent(Intent.ACTION_SENDTO)
+ .setData(Uri.parse(String.format("smsto:%s", text))),
+ LabeledIntent.DEFAULT_REQUEST_CODE));
+ }
+ return actions;
+ }
+
+ @NonNull
+ private static List<LabeledIntent> createForAddress(Context context, String text) {
+ final List<LabeledIntent> actions = new ArrayList<>();
+ try {
+ final String encText = URLEncoder.encode(text, "UTF-8");
+ actions.add(new LabeledIntent(
+ context.getString(com.android.internal.R.string.map),
+ 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))),
+ LabeledIntent.DEFAULT_REQUEST_CODE));
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "Could not encode address", e);
+ }
+ return actions;
+ }
+
+ @NonNull
+ private static List<LabeledIntent> createForUrl(Context context, String text) {
+ if (Uri.parse(text).getScheme() == null) {
+ text = "http://" + text;
+ }
+ final List<LabeledIntent> actions = new ArrayList<>();
+ actions.add(new LabeledIntent(
+ context.getString(com.android.internal.R.string.browse),
+ context.getString(com.android.internal.R.string.browse_desc),
+ new Intent(Intent.ACTION_VIEW, Uri.parse(text))
+ .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()),
+ LabeledIntent.DEFAULT_REQUEST_CODE));
+ return actions;
+ }
+
+ @NonNull
+ private static List<LabeledIntent> createForDatetime(
+ Context context, String type, @Nullable Instant referenceTime,
+ Instant parsedTime) {
+ if (referenceTime == null) {
+ // If no reference time was given, use now.
+ referenceTime = Instant.now();
+ }
+ List<LabeledIntent> actions = new ArrayList<>();
+ actions.add(createCalendarViewIntent(context, parsedTime));
+ final long millisUntilEvent = referenceTime.until(parsedTime, MILLIS);
+ if (millisUntilEvent > MIN_EVENT_FUTURE_MILLIS) {
+ actions.add(createCalendarCreateEventIntent(context, parsedTime, type));
+ }
+ return actions;
+ }
+
+ @NonNull
+ private static List<LabeledIntent> createForFlight(Context context, String text) {
+ final List<LabeledIntent> actions = new ArrayList<>();
+ actions.add(new LabeledIntent(
+ context.getString(com.android.internal.R.string.view_flight),
+ context.getString(com.android.internal.R.string.view_flight_desc),
+ new Intent(Intent.ACTION_WEB_SEARCH)
+ .putExtra(SearchManager.QUERY, text),
+ text.hashCode()));
+ return actions;
+ }
+
+ @NonNull
+ private static LabeledIntent createCalendarViewIntent(Context context, Instant parsedTime) {
+ Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
+ builder.appendPath("time");
+ ContentUris.appendId(builder, parsedTime.toEpochMilli());
+ return new LabeledIntent(
+ context.getString(com.android.internal.R.string.view_calendar),
+ context.getString(com.android.internal.R.string.view_calendar_desc),
+ new Intent(Intent.ACTION_VIEW).setData(builder.build()),
+ LabeledIntent.DEFAULT_REQUEST_CODE);
+ }
+
+ @NonNull
+ private static LabeledIntent createCalendarCreateEventIntent(
+ Context context, Instant parsedTime, @TextClassifier.EntityType String type) {
+ final boolean isAllDay = TextClassifier.TYPE_DATE.equals(type);
+ return new LabeledIntent(
+ context.getString(com.android.internal.R.string.add_calendar_event),
+ context.getString(com.android.internal.R.string.add_calendar_event_desc),
+ new Intent(Intent.ACTION_INSERT)
+ .setData(CalendarContract.Events.CONTENT_URI)
+ .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay)
+ .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME,
+ parsedTime.toEpochMilli())
+ .putExtra(CalendarContract.EXTRA_EVENT_END_TIME,
+ parsedTime.toEpochMilli() + DEFAULT_EVENT_DURATION),
+ parsedTime.hashCode());
+ }
+
+ @NonNull
+ private static List<LabeledIntent> createForDictionary(Context context, String text) {
+ final List<LabeledIntent> actions = new ArrayList<>();
+ actions.add(new LabeledIntent(
+ context.getString(com.android.internal.R.string.define),
+ context.getString(com.android.internal.R.string.define_desc),
+ new Intent(Intent.ACTION_DEFINE)
+ .putExtra(Intent.EXTRA_TEXT, text),
+ text.hashCode()));
+ return actions;
+ }
+}
diff --git a/core/java/android/view/textclassifier/TemplateIntentFactory.java b/core/java/android/view/textclassifier/TemplateIntentFactory.java
new file mode 100644
index 0000000..97e11bb
--- /dev/null
+++ b/core/java/android/view/textclassifier/TemplateIntentFactory.java
@@ -0,0 +1,167 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import com.google.android.textclassifier.AnnotatorModel;
+import com.google.android.textclassifier.NamedVariant;
+import com.google.android.textclassifier.RemoteActionTemplate;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Creates intents based on {@link RemoteActionTemplate} objects.
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class TemplateIntentFactory implements IntentFactory {
+ private static final String TAG = TextClassifier.DEFAULT_LOG_TAG;
+ private final IntentFactory mFallback;
+
+ public TemplateIntentFactory(IntentFactory fallback) {
+ mFallback = Preconditions.checkNotNull(fallback);
+ }
+
+ /**
+ * Returns a list of {@link android.view.textclassifier.TextClassifierImpl.LabeledIntent}
+ * that are constructed from the classification result.
+ */
+ @NonNull
+ @Override
+ public List<TextClassifierImpl.LabeledIntent> create(
+ Context context,
+ String text,
+ boolean foreignText,
+ @Nullable Instant referenceTime,
+ @Nullable AnnotatorModel.ClassificationResult classification) {
+ if (classification == null) {
+ return Collections.emptyList();
+ }
+ RemoteActionTemplate[] remoteActionTemplates = classification.getRemoteActionTemplates();
+ if (ArrayUtils.isEmpty(remoteActionTemplates)) {
+ // RemoteActionTemplate is missing, fallback.
+ Log.w(TAG, "RemoteActionTemplate is missing, fallback to LegacyIntentFactory.");
+ return mFallback.create(context, text, foreignText, referenceTime, classification);
+ }
+ final List<TextClassifierImpl.LabeledIntent> labeledIntents =
+ new ArrayList<>(createFromRemoteActionTemplates(remoteActionTemplates));
+ if (foreignText) {
+ IntentFactory.insertTranslateAction(labeledIntents, context, text.trim());
+ }
+ labeledIntents.forEach(
+ action -> action.getIntent()
+ .putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true));
+ return labeledIntents;
+ }
+
+ private static List<TextClassifierImpl.LabeledIntent> createFromRemoteActionTemplates(
+ RemoteActionTemplate[] remoteActionTemplates) {
+ final List<TextClassifierImpl.LabeledIntent> labeledIntents = new ArrayList<>();
+ for (RemoteActionTemplate remoteActionTemplate : remoteActionTemplates) {
+ Intent intent = createIntent(remoteActionTemplate);
+ if (intent == null) {
+ continue;
+ }
+ TextClassifierImpl.LabeledIntent
+ labeledIntent = new TextClassifierImpl.LabeledIntent(
+ remoteActionTemplate.title,
+ remoteActionTemplate.description,
+ intent,
+ remoteActionTemplate.requestCode == null
+ ? TextClassifierImpl.LabeledIntent.DEFAULT_REQUEST_CODE
+ : remoteActionTemplate.requestCode
+ );
+ labeledIntents.add(labeledIntent);
+ }
+ return labeledIntents;
+ }
+
+ @Nullable
+ private static Intent createIntent(RemoteActionTemplate remoteActionTemplate) {
+ Intent intent = new Intent();
+ if (!TextUtils.isEmpty(remoteActionTemplate.packageName)) {
+ Log.w(TAG, "A RemoteActionTemplate is skipped as package name is set.");
+ return null;
+ }
+ if (!TextUtils.isEmpty(remoteActionTemplate.action)) {
+ intent.setAction(remoteActionTemplate.action);
+ }
+ Uri data = null;
+ if (!TextUtils.isEmpty(remoteActionTemplate.data)) {
+ data = Uri.parse(remoteActionTemplate.data);
+ }
+ if (data != null || !TextUtils.isEmpty(remoteActionTemplate.type)) {
+ intent.setDataAndType(data, remoteActionTemplate.type);
+ }
+ if (remoteActionTemplate.flags != null) {
+ intent.setFlags(remoteActionTemplate.flags);
+ }
+ if (remoteActionTemplate.category != null) {
+ for (String category : remoteActionTemplate.category) {
+ intent.addCategory(category);
+ }
+ }
+ intent.putExtras(createExtras(remoteActionTemplate.extras));
+ return intent;
+ }
+
+ private static Bundle createExtras(NamedVariant[] namedVariants) {
+ if (namedVariants == null) {
+ return Bundle.EMPTY;
+ }
+ Bundle bundle = new Bundle();
+ for (NamedVariant namedVariant : namedVariants) {
+ switch (namedVariant.getType()) {
+ case NamedVariant.TYPE_INT:
+ bundle.putInt(namedVariant.getName(), namedVariant.getInt());
+ break;
+ case NamedVariant.TYPE_LONG:
+ bundle.putLong(namedVariant.getName(), namedVariant.getLong());
+ break;
+ case NamedVariant.TYPE_FLOAT:
+ bundle.putFloat(namedVariant.getName(), namedVariant.getFloat());
+ break;
+ case NamedVariant.TYPE_DOUBLE:
+ bundle.putDouble(namedVariant.getName(), namedVariant.getDouble());
+ break;
+ case NamedVariant.TYPE_BOOL:
+ bundle.putBoolean(namedVariant.getName(), namedVariant.getBool());
+ break;
+ case NamedVariant.TYPE_STRING:
+ bundle.putString(namedVariant.getName(), namedVariant.getString());
+ break;
+ default:
+ Log.w(TAG,
+ "Unsupported type found in createExtras : " + namedVariant.getType());
+ }
+ }
+ return bundle;
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java
index 7f928f7..ee9e04e 100644
--- a/core/java/android/view/textclassifier/TextClassificationConstants.java
+++ b/core/java/android/view/textclassifier/TextClassificationConstants.java
@@ -47,6 +47,7 @@
* entity_list_not_editable (String[])
* entity_list_editable (String[])
* lang_id_threshold_override (float)
+ * template_intent_factory_enabled (boolean)
* </pre>
*
* <p>
@@ -97,6 +98,7 @@
"notification_conversation_action_types_default";
private static final String LANG_ID_THRESHOLD_OVERRIDE =
"lang_id_threshold_override";
+ private static final String TEMPLATE_INTENT_FACTORY_ENABLED = "template_intent_factory_enabled";
private static final boolean LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
@@ -137,6 +139,7 @@
* @see EntityConfidence
*/
private static final float LANG_ID_THRESHOLD_OVERRIDE_DEFAULT = -1f;
+ private static final boolean TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT = true;
private final boolean mSystemTextClassifierEnabled;
private final boolean mLocalTextClassifierEnabled;
@@ -155,6 +158,7 @@
private final List<String> mInAppConversationActionTypesDefault;
private final List<String> mNotificationConversationActionTypesDefault;
private final float mLangIdThresholdOverride;
+ private final boolean mTemplateIntentFactoryEnabled;
private TextClassificationConstants(@Nullable String settings) {
final KeyValueListParser parser = new KeyValueListParser(',');
@@ -215,6 +219,8 @@
mLangIdThresholdOverride = parser.getFloat(
LANG_ID_THRESHOLD_OVERRIDE,
LANG_ID_THRESHOLD_OVERRIDE_DEFAULT);
+ mTemplateIntentFactoryEnabled = parser.getBoolean(
+ TEMPLATE_INTENT_FACTORY_ENABLED, TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT);
}
/** Load from a settings string. */
@@ -290,6 +296,10 @@
return mLangIdThresholdOverride;
}
+ public boolean isTemplateIntentFactoryEnabled() {
+ return mTemplateIntentFactoryEnabled;
+ }
+
private static List<String> parseStringList(String listStr) {
return Collections.unmodifiableList(Arrays.asList(listStr.split(STRING_LIST_DELIMITER)));
}
@@ -315,6 +325,7 @@
pw.printPair("getNotificationConversationActionTypes",
mNotificationConversationActionTypesDefault);
pw.printPair("getLangIdThresholdOverride", mLangIdThresholdOverride);
+ pw.printPair("isTemplateIntentFactoryEnabled", mTemplateIntentFactoryEnabled);
pw.decreaseIndent();
pw.println();
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 7782079..c297928 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -16,30 +16,21 @@
package android.view.textclassifier;
-import static java.time.temporal.ChronoUnit.MILLIS;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.PendingIntent;
import android.app.RemoteAction;
-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;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Icon;
import android.icu.util.ULocale;
-import android.net.Uri;
import android.os.Bundle;
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
-import android.os.UserManager;
-import android.provider.Browser;
-import android.provider.CalendarContract;
-import android.provider.ContactsContract;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -53,19 +44,15 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
-import java.util.concurrent.TimeUnit;
/**
* Default implementation of the {@link TextClassifier} interface.
@@ -128,6 +115,8 @@
private final ModelFileManager mLangIdModelFileManager;
private final ModelFileManager mActionsModelFileManager;
+ private final IntentFactory mIntentFactory;
+
public TextClassifierImpl(
Context context, TextClassificationConstants settings, TextClassifier fallback) {
mContext = Preconditions.checkNotNull(context);
@@ -155,6 +144,10 @@
UPDATED_ACTIONS_MODEL,
ActionsSuggestionsModel::getVersion,
ActionsSuggestionsModel::getLocales));
+
+ mIntentFactory = mSettings.isTemplateIntentFactoryEnabled()
+ ? new TemplateIntentFactory(new LegacyIntentFactory())
+ : new LegacyIntentFactory();
}
public TextClassifierImpl(Context context, TextClassificationConstants settings) {
@@ -198,7 +191,8 @@
new AnnotatorModel.ClassificationOptions(
refTime.toInstant().toEpochMilli(),
refTime.getZone().getId(),
- localesString));
+ localesString),
+ mContext);
final int size = results.length;
for (int i = 0; i < size; i++) {
tsBuilder.setEntityType(results[i].getCollection(), results[i].getScore());
@@ -241,7 +235,8 @@
new AnnotatorModel.ClassificationOptions(
refTime.toInstant().toEpochMilli(),
refTime.getZone().getId(),
- localesString));
+ localesString),
+ mContext);
if (results.length > 0) {
return createClassificationResult(
results, string,
@@ -560,8 +555,9 @@
AnnotatorModel.ClassificationResult highestScoringResult =
typeCount > 0 ? classifications[0] : null;
for (int i = 0; i < typeCount; i++) {
- builder.setEntityType(classifications[i].getCollection(),
- classifications[i].getScore());
+ builder.setEntityType(
+ classifications[i].getCollection(),
+ classifications[i].getScore());
if (classifications[i].getScore() > highestScoringResult.getScore()) {
highestScoringResult = classifications[i];
}
@@ -572,9 +568,13 @@
: 0.5f /* TODO: Load this from the langId model. */;
boolean isPrimaryAction = true;
final ArrayList<Intent> sourceIntents = new ArrayList<>();
- for (LabeledIntent labeledIntent : IntentFactory.create(
- mContext, classifiedText, isForeignText(classifiedText, foreignTextThreshold),
- referenceTime, highestScoringResult)) {
+ List<LabeledIntent> labeledIntents = mIntentFactory.create(
+ mContext,
+ classifiedText,
+ isForeignText(classifiedText, foreignTextThreshold),
+ referenceTime,
+ highestScoringResult);
+ for (LabeledIntent labeledIntent : labeledIntents) {
final RemoteAction action = labeledIntent.asRemoteAction(mContext);
if (action == null) {
continue;
@@ -720,11 +720,13 @@
mRequestCode = requestCode;
}
- String getTitle() {
+ @VisibleForTesting
+ public String getTitle() {
return mTitle;
}
- String getDescription() {
+ @VisibleForTesting
+ public String getDescription() {
return mDescription;
}
@@ -733,7 +735,8 @@
return mIntent;
}
- int getRequestCode() {
+ @VisibleForTesting
+ public int getRequestCode() {
return mRequestCode;
}
@@ -769,233 +772,4 @@
return action;
}
}
-
- /**
- * Creates intents based on the classification type.
- */
- @VisibleForTesting
- public 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<LabeledIntent> create(
- Context context,
- String text,
- boolean foreignText,
- @Nullable Instant referenceTime,
- @Nullable AnnotatorModel.ClassificationResult classification) {
- final String type = classification != null
- ? classification.getCollection().trim().toLowerCase(Locale.ENGLISH)
- : "";
- text = text.trim();
- final List<LabeledIntent> actions;
- switch (type) {
- case TextClassifier.TYPE_EMAIL:
- actions = createForEmail(context, text);
- break;
- case TextClassifier.TYPE_PHONE:
- actions = createForPhone(context, text);
- break;
- case TextClassifier.TYPE_ADDRESS:
- actions = createForAddress(context, text);
- break;
- case TextClassifier.TYPE_URL:
- actions = createForUrl(context, text);
- break;
- case TextClassifier.TYPE_DATE: // fall through
- case TextClassifier.TYPE_DATE_TIME:
- if (classification.getDatetimeResult() != null) {
- final Instant parsedTime = Instant.ofEpochMilli(
- classification.getDatetimeResult().getTimeMsUtc());
- actions = createForDatetime(context, type, referenceTime, parsedTime);
- } else {
- actions = new ArrayList<>();
- }
- break;
- case TextClassifier.TYPE_FLIGHT_NUMBER:
- actions = createForFlight(context, text);
- break;
- case TextClassifier.TYPE_DICTIONARY:
- actions = createForDictionary(context, text);
- break;
- default:
- actions = new ArrayList<>();
- break;
- }
- if (foreignText) {
- insertTranslateAction(actions, context, text);
- }
- actions.forEach(
- action -> action.getIntent()
- .putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true));
- return actions;
- }
-
- @NonNull
- private static List<LabeledIntent> createForEmail(Context context, String text) {
- final List<LabeledIntent> actions = new ArrayList<>();
- actions.add(new LabeledIntent(
- context.getString(com.android.internal.R.string.email),
- 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),
- context.getString(com.android.internal.R.string.add_contact_desc),
- new Intent(Intent.ACTION_INSERT_OR_EDIT)
- .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
- .putExtra(ContactsContract.Intents.Insert.EMAIL, text),
- text.hashCode()));
- return actions;
- }
-
- @NonNull
- private static List<LabeledIntent> createForPhone(Context context, String text) {
- final List<LabeledIntent> actions = new ArrayList<>();
- final UserManager userManager = context.getSystemService(UserManager.class);
- final Bundle userRestrictions = userManager != null
- ? userManager.getUserRestrictions() : new Bundle();
- if (!userRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS, false)) {
- actions.add(new LabeledIntent(
- context.getString(com.android.internal.R.string.dial),
- context.getString(com.android.internal.R.string.dial_desc),
- new Intent(Intent.ACTION_DIAL).setData(
- Uri.parse(String.format("tel:%s", text))),
- LabeledIntent.DEFAULT_REQUEST_CODE));
- }
- actions.add(new LabeledIntent(
- context.getString(com.android.internal.R.string.add_contact),
- context.getString(com.android.internal.R.string.add_contact_desc),
- new Intent(Intent.ACTION_INSERT_OR_EDIT)
- .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
- .putExtra(ContactsContract.Intents.Insert.PHONE, text),
- text.hashCode()));
- if (!userRestrictions.getBoolean(UserManager.DISALLOW_SMS, false)) {
- actions.add(new LabeledIntent(
- context.getString(com.android.internal.R.string.sms),
- context.getString(com.android.internal.R.string.sms_desc),
- new Intent(Intent.ACTION_SENDTO)
- .setData(Uri.parse(String.format("smsto:%s", text))),
- LabeledIntent.DEFAULT_REQUEST_CODE));
- }
- return actions;
- }
-
- @NonNull
- private static List<LabeledIntent> createForAddress(Context context, String text) {
- final List<LabeledIntent> actions = new ArrayList<>();
- try {
- final String encText = URLEncoder.encode(text, "UTF-8");
- actions.add(new LabeledIntent(
- context.getString(com.android.internal.R.string.map),
- 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))),
- LabeledIntent.DEFAULT_REQUEST_CODE));
- } catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Could not encode address", e);
- }
- return actions;
- }
-
- @NonNull
- private static List<LabeledIntent> createForUrl(Context context, String text) {
- if (Uri.parse(text).getScheme() == null) {
- text = "http://" + text;
- }
- final List<LabeledIntent> actions = new ArrayList<>();
- actions.add(new LabeledIntent(
- context.getString(com.android.internal.R.string.browse),
- context.getString(com.android.internal.R.string.browse_desc),
- new Intent(Intent.ACTION_VIEW, Uri.parse(text))
- .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()),
- LabeledIntent.DEFAULT_REQUEST_CODE));
- return actions;
- }
-
- @NonNull
- private static List<LabeledIntent> createForDatetime(
- Context context, String type, @Nullable Instant referenceTime,
- Instant parsedTime) {
- if (referenceTime == null) {
- // If no reference time was given, use now.
- referenceTime = Instant.now();
- }
- List<LabeledIntent> actions = new ArrayList<>();
- actions.add(createCalendarViewIntent(context, parsedTime));
- final long millisUntilEvent = referenceTime.until(parsedTime, MILLIS);
- if (millisUntilEvent > MIN_EVENT_FUTURE_MILLIS) {
- actions.add(createCalendarCreateEventIntent(context, parsedTime, type));
- }
- return actions;
- }
-
- @NonNull
- private static List<LabeledIntent> createForFlight(Context context, String text) {
- final List<LabeledIntent> actions = new ArrayList<>();
- actions.add(new LabeledIntent(
- context.getString(com.android.internal.R.string.view_flight),
- context.getString(com.android.internal.R.string.view_flight_desc),
- new Intent(Intent.ACTION_WEB_SEARCH)
- .putExtra(SearchManager.QUERY, text),
- text.hashCode()));
- return actions;
- }
-
- @NonNull
- private static LabeledIntent createCalendarViewIntent(Context context, Instant parsedTime) {
- Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
- builder.appendPath("time");
- ContentUris.appendId(builder, parsedTime.toEpochMilli());
- return new LabeledIntent(
- context.getString(com.android.internal.R.string.view_calendar),
- context.getString(com.android.internal.R.string.view_calendar_desc),
- new Intent(Intent.ACTION_VIEW).setData(builder.build()),
- LabeledIntent.DEFAULT_REQUEST_CODE);
- }
-
- @NonNull
- private static LabeledIntent createCalendarCreateEventIntent(
- Context context, Instant parsedTime, @EntityType String type) {
- final boolean isAllDay = TextClassifier.TYPE_DATE.equals(type);
- return new LabeledIntent(
- context.getString(com.android.internal.R.string.add_calendar_event),
- context.getString(com.android.internal.R.string.add_calendar_event_desc),
- new Intent(Intent.ACTION_INSERT)
- .setData(CalendarContract.Events.CONTENT_URI)
- .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay)
- .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME,
- parsedTime.toEpochMilli())
- .putExtra(CalendarContract.EXTRA_EVENT_END_TIME,
- parsedTime.toEpochMilli() + DEFAULT_EVENT_DURATION),
- parsedTime.hashCode());
- }
-
- private static void insertTranslateAction(
- List<LabeledIntent> actions, Context context, String text) {
- actions.add(new LabeledIntent(
- context.getString(com.android.internal.R.string.translate),
- context.getString(com.android.internal.R.string.translate_desc),
- new Intent(Intent.ACTION_TRANSLATE)
- // TODO: Probably better to introduce a "translate" scheme instead of
- // using EXTRA_TEXT.
- .putExtra(Intent.EXTRA_TEXT, text),
- text.hashCode()));
- }
-
- @NonNull
- private static List<LabeledIntent> createForDictionary(Context context, String text) {
- return Arrays.asList(new LabeledIntent(
- context.getString(com.android.internal.R.string.define),
- context.getString(com.android.internal.R.string.define_desc),
- new Intent(Intent.ACTION_DEFINE)
- .putExtra(Intent.EXTRA_TEXT, text),
- text.hashCode()));
- }
- }
}
diff --git a/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java
deleted file mode 100644
index 3fc8e4c..0000000
--- a/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.google.common.truth.Truth.assertThat;
-
-import android.content.Intent;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.google.android.textclassifier.AnnotatorModel;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class IntentFactoryTest {
-
- private static final String TEXT = "text";
-
- @Test
- public void create_typeDictionary() {
- AnnotatorModel.ClassificationResult classificationResult =
- new AnnotatorModel.ClassificationResult(
- TextClassifier.TYPE_DICTIONARY,
- 1.0f,
- null,
- null);
-
- List<TextClassifierImpl.LabeledIntent> intents = TextClassifierImpl.IntentFactory.create(
- InstrumentationRegistry.getContext(),
- TEXT,
- false,
- null,
- classificationResult);
-
- assertThat(intents).hasSize(1);
- TextClassifierImpl.LabeledIntent labeledIntent = intents.get(0);
- Intent intent = labeledIntent.getIntent();
- assertThat(intent.getAction()).isEqualTo(Intent.ACTION_DEFINE);
- assertThat(intent.getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(TEXT);
- assertThat(
- intent.getBooleanExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, false)).isTrue();
- }
-}
diff --git a/core/tests/coretests/src/android/view/textclassifier/LegacyIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/LegacyIntentFactoryTest.java
new file mode 100644
index 0000000..73d3eec
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/LegacyIntentFactoryTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.android.textclassifier.AnnotatorModel;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LegacyIntentFactoryTest {
+
+ private static final String TEXT = "text";
+
+ private LegacyIntentFactory mLegacyIntentFactory;
+
+ @Before
+ public void setup() {
+ mLegacyIntentFactory = new LegacyIntentFactory();
+ }
+
+ @Test
+ public void create_typeDictionary() {
+ AnnotatorModel.ClassificationResult classificationResult =
+ new AnnotatorModel.ClassificationResult(
+ TextClassifier.TYPE_DICTIONARY,
+ 1.0f,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null);
+
+ List<TextClassifierImpl.LabeledIntent> intents = mLegacyIntentFactory.create(
+ InstrumentationRegistry.getContext(),
+ TEXT,
+ /* foreignText */ false,
+ null,
+ classificationResult);
+
+ assertThat(intents).hasSize(1);
+ TextClassifierImpl.LabeledIntent labeledIntent = intents.get(0);
+ Intent intent = labeledIntent.getIntent();
+ assertThat(intent.getAction()).isEqualTo(Intent.ACTION_DEFINE);
+ assertThat(intent.getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(TEXT);
+ assertThat(
+ intent.getBooleanExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, false)).isTrue();
+ }
+
+ @Test
+ public void create_translateAndDictionary() {
+ AnnotatorModel.ClassificationResult classificationResult =
+ new AnnotatorModel.ClassificationResult(
+ TextClassifier.TYPE_DICTIONARY,
+ 1.0f,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null);
+
+ List<TextClassifierImpl.LabeledIntent> intents = mLegacyIntentFactory.create(
+ InstrumentationRegistry.getContext(),
+ TEXT,
+ /* foreignText */ true,
+ null,
+ classificationResult);
+
+ assertThat(intents).hasSize(2);
+ assertThat(intents.get(0).getIntent().getAction()).isEqualTo(Intent.ACTION_DEFINE);
+ assertThat(intents.get(1).getIntent().getAction()).isEqualTo(Intent.ACTION_TRANSLATE);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TemplateIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/TemplateIntentFactoryTest.java
new file mode 100644
index 0000000..0fcf359
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/TemplateIntentFactoryTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+import android.net.Uri;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.android.textclassifier.AnnotatorModel;
+import com.google.android.textclassifier.NamedVariant;
+import com.google.android.textclassifier.RemoteActionTemplate;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TemplateIntentFactoryTest {
+
+ private static final String TEXT = "text";
+ private static final String TITLE = "Map";
+ private static final String DESCRIPTION = "Check the map";
+ private static final String ACTION = Intent.ACTION_VIEW;
+ private static final String DATA = Uri.parse("http://www.android.com").toString();
+ private static final String TYPE = "text/html";
+ private static final Integer FLAG = Intent.FLAG_ACTIVITY_NEW_TASK;
+ private static final String[] CATEGORY =
+ new String[]{Intent.CATEGORY_DEFAULT, Intent.CATEGORY_APP_BROWSER};
+ private static final String PACKAGE_NAME = "pkg.name";
+ private static final String KEY_ONE = "key1";
+ private static final String VALUE_ONE = "value1";
+ private static final String KEY_TWO = "key2";
+ private static final int VALUE_TWO = 42;
+
+ private static final NamedVariant[] NAMED_VARIANTS = new NamedVariant[]{
+ new NamedVariant(KEY_ONE, VALUE_ONE),
+ new NamedVariant(KEY_TWO, VALUE_TWO)
+ };
+ private static final Integer REQUEST_CODE = 10;
+
+ @Mock
+ private IntentFactory mFallback;
+ private TemplateIntentFactory mTemplateIntentFactory;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTemplateIntentFactory = new TemplateIntentFactory(mFallback);
+ }
+
+ @Test
+ public void create_full() {
+ RemoteActionTemplate remoteActionTemplate = new RemoteActionTemplate(
+ TITLE,
+ DESCRIPTION,
+ ACTION,
+ DATA,
+ TYPE,
+ FLAG,
+ CATEGORY,
+ /* packageName */ null,
+ NAMED_VARIANTS,
+ REQUEST_CODE
+ );
+
+ AnnotatorModel.ClassificationResult classificationResult =
+ new AnnotatorModel.ClassificationResult(
+ TextClassifier.TYPE_ADDRESS,
+ 1.0f,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ new RemoteActionTemplate[]{remoteActionTemplate});
+
+ List<TextClassifierImpl.LabeledIntent> intents = mTemplateIntentFactory.create(
+ InstrumentationRegistry.getContext(),
+ TEXT,
+ false,
+ null,
+ classificationResult);
+
+ assertThat(intents).hasSize(1);
+ TextClassifierImpl.LabeledIntent labeledIntent = intents.get(0);
+ assertThat(labeledIntent.getTitle()).isEqualTo(TITLE);
+ assertThat(labeledIntent.getDescription()).isEqualTo(DESCRIPTION);
+ assertThat(labeledIntent.getRequestCode()).isEqualTo(REQUEST_CODE);
+ Intent intent = labeledIntent.getIntent();
+ assertThat(intent.getAction()).isEqualTo(ACTION);
+ assertThat(intent.getData().toString()).isEqualTo(DATA);
+ assertThat(intent.getType()).isEqualTo(TYPE);
+ assertThat(intent.getFlags()).isEqualTo(FLAG);
+ assertThat(intent.getCategories()).containsExactly((Object[]) CATEGORY);
+ assertThat(intent.getPackage()).isNull();
+ assertThat(
+ intent.getStringExtra(KEY_ONE)).isEqualTo(VALUE_ONE);
+ assertThat(intent.getIntExtra(KEY_TWO, 0)).isEqualTo(VALUE_TWO);
+ assertThat(
+ intent.getBooleanExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, false)).isTrue();
+ }
+
+ @Test
+ public void create_pacakgeIsNotNull() {
+ RemoteActionTemplate remoteActionTemplate = new RemoteActionTemplate(
+ TITLE,
+ DESCRIPTION,
+ ACTION,
+ DATA,
+ TYPE,
+ FLAG,
+ CATEGORY,
+ PACKAGE_NAME,
+ NAMED_VARIANTS,
+ REQUEST_CODE
+ );
+
+ AnnotatorModel.ClassificationResult classificationResult =
+ new AnnotatorModel.ClassificationResult(
+ TextClassifier.TYPE_ADDRESS,
+ 1.0f,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ new RemoteActionTemplate[]{remoteActionTemplate});
+
+ List<TextClassifierImpl.LabeledIntent> intents = mTemplateIntentFactory.create(
+ InstrumentationRegistry.getContext(),
+ TEXT,
+ false,
+ null,
+ classificationResult);
+
+ assertThat(intents).hasSize(0);
+ }
+
+ @Test
+ public void create_minimal() {
+ RemoteActionTemplate remoteActionTemplate = new RemoteActionTemplate(
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ );
+
+ AnnotatorModel.ClassificationResult classificationResult =
+ new AnnotatorModel.ClassificationResult(
+ TextClassifier.TYPE_ADDRESS,
+ 1.0f,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ new RemoteActionTemplate[]{remoteActionTemplate});
+
+ List<TextClassifierImpl.LabeledIntent> intents = mTemplateIntentFactory.create(
+ InstrumentationRegistry.getContext(),
+ TEXT,
+ false,
+ null,
+ classificationResult);
+
+ assertThat(intents).hasSize(1);
+ TextClassifierImpl.LabeledIntent labeledIntent = intents.get(0);
+ assertThat(labeledIntent.getTitle()).isNull();
+ assertThat(labeledIntent.getDescription()).isNull();
+ assertThat(labeledIntent.getRequestCode()).isEqualTo(
+ TextClassifierImpl.LabeledIntent.DEFAULT_REQUEST_CODE);
+ Intent intent = labeledIntent.getIntent();
+ assertThat(intent.getAction()).isNull();
+ assertThat(intent.getData()).isNull();
+ assertThat(intent.getType()).isNull();
+ assertThat(intent.getFlags()).isEqualTo(0);
+ assertThat(intent.getCategories()).isNull();
+ assertThat(intent.getPackage()).isNull();
+ assertThat(
+ intent.getBooleanExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, false)).isTrue();
+ }
+}