Add detectLanguage and suggestConversationActions to TCS
BUG: 111406942
BUG: 111437455
Test: atest frameworks/base/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
Change-Id: Iee9c970ebbec6590906907d67be3dd4021c1b4b2
diff --git a/Android.bp b/Android.bp
index 1012bb81..4cb330a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -334,8 +334,10 @@
"core/java/android/service/chooser/IChooserTargetResult.aidl",
"core/java/android/service/resolver/IResolverRankerService.aidl",
"core/java/android/service/resolver/IResolverRankerResult.aidl",
+ "core/java/android/service/textclassifier/IConversationActionsCallback.aidl",
"core/java/android/service/textclassifier/ITextClassificationCallback.aidl",
"core/java/android/service/textclassifier/ITextClassifierService.aidl",
+ "core/java/android/service/textclassifier/ITextLanguageCallback.aidl",
"core/java/android/service/textclassifier/ITextLinksCallback.aidl",
"core/java/android/service/textclassifier/ITextSelectionCallback.aidl",
"core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl",
diff --git a/api/system-current.txt b/api/system-current.txt
index d7265c7..4b237b5 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5171,8 +5171,10 @@
method public abstract void onClassifyText(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.TextClassification.Request, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextClassification>);
method public void onCreateTextClassificationSession(android.view.textclassifier.TextClassificationContext, android.view.textclassifier.TextClassificationSessionId);
method public void onDestroyTextClassificationSession(android.view.textclassifier.TextClassificationSessionId);
+ method public void onDetectLanguage(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.TextLanguage.Request, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLanguage>);
method public abstract void onGenerateLinks(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.TextLinks.Request, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLinks>);
method public void onSelectionEvent(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.SelectionEvent);
+ method public void onSuggestConversationActions(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.ConversationActions.Request, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.ConversationActions>);
method public abstract void onSuggestSelection(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.TextSelection.Request, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextSelection>);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.textclassifier.TextClassifierService";
}
diff --git a/core/java/android/service/textclassifier/IConversationActionsCallback.aidl b/core/java/android/service/textclassifier/IConversationActionsCallback.aidl
new file mode 100644
index 0000000..c35d424
--- /dev/null
+++ b/core/java/android/service/textclassifier/IConversationActionsCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.service.textclassifier;
+
+import android.view.textclassifier.ConversationActions;
+
+/**
+ * Callback for a ConversationActions request.
+ * @hide
+ */
+oneway interface IConversationActionsCallback {
+ void onSuccess(in ConversationActions conversationActions);
+ void onFailure();
+}
\ No newline at end of file
diff --git a/core/java/android/service/textclassifier/ITextClassifierService.aidl b/core/java/android/service/textclassifier/ITextClassifierService.aidl
index 7ac72c7..254a710 100644
--- a/core/java/android/service/textclassifier/ITextClassifierService.aidl
+++ b/core/java/android/service/textclassifier/ITextClassifierService.aidl
@@ -16,14 +16,18 @@
package android.service.textclassifier;
+import android.service.textclassifier.IConversationActionsCallback;
import android.service.textclassifier.ITextClassificationCallback;
+import android.service.textclassifier.ITextLanguageCallback;
import android.service.textclassifier.ITextLinksCallback;
import android.service.textclassifier.ITextSelectionCallback;
+import android.view.textclassifier.ConversationActions;
import android.view.textclassifier.SelectionEvent;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationContext;
import android.view.textclassifier.TextClassificationSessionId;
import android.view.textclassifier.TextLinks;
+import android.view.textclassifier.TextLanguage;
import android.view.textclassifier.TextSelection;
/**
@@ -58,4 +62,14 @@
void onDestroyTextClassificationSession(
in TextClassificationSessionId sessionId);
+
+ void onDetectLanguage(
+ in TextClassificationSessionId sessionId,
+ in TextLanguage.Request request,
+ in ITextLanguageCallback callback);
+
+ void onSuggestConversationActions(
+ in TextClassificationSessionId sessionId,
+ in ConversationActions.Request request,
+ in IConversationActionsCallback callback);
}
diff --git a/core/java/android/service/textclassifier/ITextLanguageCallback.aidl b/core/java/android/service/textclassifier/ITextLanguageCallback.aidl
new file mode 100644
index 0000000..263d99af
--- /dev/null
+++ b/core/java/android/service/textclassifier/ITextLanguageCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.service.textclassifier;
+
+import android.view.textclassifier.TextLanguage;
+
+/**
+ * Callback for a TextLanguage request.
+ * @hide
+ */
+oneway interface ITextLanguageCallback {
+ void onSuccess(in TextLanguage textLanguage);
+ void onFailure();
+}
\ No newline at end of file
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 7f1082d..d7359f1 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -32,12 +32,14 @@
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Slog;
+import android.view.textclassifier.ConversationActions;
import android.view.textclassifier.SelectionEvent;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationContext;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassificationSessionId;
import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLanguage;
import android.view.textclassifier.TextLinks;
import android.view.textclassifier.TextSelection;
@@ -92,8 +94,7 @@
@Override
public void onSuggestSelection(
TextClassificationSessionId sessionId,
- TextSelection.Request request, ITextSelectionCallback callback)
- throws RemoteException {
+ TextSelection.Request request, ITextSelectionCallback callback) {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
TextClassifierService.this.onSuggestSelection(
@@ -125,8 +126,7 @@
@Override
public void onClassifyText(
TextClassificationSessionId sessionId,
- TextClassification.Request request, ITextClassificationCallback callback)
- throws RemoteException {
+ TextClassification.Request request, ITextClassificationCallback callback) {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
TextClassifierService.this.onClassifyText(
@@ -156,8 +156,7 @@
@Override
public void onGenerateLinks(
TextClassificationSessionId sessionId,
- TextLinks.Request request, ITextLinksCallback callback)
- throws RemoteException {
+ TextLinks.Request request, ITextLinksCallback callback) {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
TextClassifierService.this.onGenerateLinks(
@@ -188,16 +187,81 @@
@Override
public void onSelectionEvent(
TextClassificationSessionId sessionId,
- SelectionEvent event) throws RemoteException {
+ SelectionEvent event) {
Preconditions.checkNotNull(event);
TextClassifierService.this.onSelectionEvent(sessionId, event);
}
/** {@inheritDoc} */
@Override
+ public void onDetectLanguage(
+ TextClassificationSessionId sessionId,
+ TextLanguage.Request request,
+ ITextLanguageCallback callback) {
+ Preconditions.checkNotNull(request);
+ Preconditions.checkNotNull(callback);
+ TextClassifierService.this.onDetectLanguage(
+ sessionId,
+ request,
+ mCancellationSignal,
+ new Callback<TextLanguage>() {
+ @Override
+ public void onSuccess(TextLanguage result) {
+ try {
+ callback.onSuccess(result);
+ } catch (RemoteException e) {
+ Slog.d(LOG_TAG, "Error calling callback");
+ }
+ }
+
+ @Override
+ public void onFailure(CharSequence error) {
+ try {
+ callback.onFailure();
+ } catch (RemoteException e) {
+ Slog.d(LOG_TAG, "Error calling callback");
+ }
+ };
+ });
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onSuggestConversationActions(
+ TextClassificationSessionId sessionId,
+ ConversationActions.Request request,
+ IConversationActionsCallback callback) {
+ Preconditions.checkNotNull(request);
+ Preconditions.checkNotNull(callback);
+ TextClassifierService.this.onSuggestConversationActions(
+ sessionId,
+ request,
+ mCancellationSignal,
+ new Callback<ConversationActions>() {
+ @Override
+ public void onSuccess(ConversationActions result) {
+ try {
+ callback.onSuccess(result);
+ } catch (RemoteException e) {
+ Slog.d(LOG_TAG, "Error calling callback");
+ }
+ }
+
+ @Override
+ public void onFailure(CharSequence error) {
+ try {
+ callback.onFailure();
+ } catch (RemoteException e) {
+ Slog.d(LOG_TAG, "Error calling callback");
+ }
+ }
+ });
+ }
+
+ /** {@inheritDoc} */
+ @Override
public void onCreateTextClassificationSession(
- TextClassificationContext context, TextClassificationSessionId sessionId)
- throws RemoteException {
+ TextClassificationContext context, TextClassificationSessionId sessionId) {
Preconditions.checkNotNull(context);
Preconditions.checkNotNull(sessionId);
TextClassifierService.this.onCreateTextClassificationSession(context, sessionId);
@@ -205,8 +269,7 @@
/** {@inheritDoc} */
@Override
- public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId)
- throws RemoteException {
+ public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId) {
TextClassifierService.this.onDestroyTextClassificationSession(sessionId);
}
};
@@ -266,6 +329,38 @@
@NonNull Callback<TextLinks> callback);
/**
+ * Detects and returns the language of the give text.
+ *
+ * @param sessionId the session id
+ * @param request the language detection request
+ * @param cancellationSignal object to watch for canceling the current operation
+ * @param callback the callback to return the result to
+ */
+ public void onDetectLanguage(
+ @Nullable TextClassificationSessionId sessionId,
+ @NonNull TextLanguage.Request request,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull Callback<TextLanguage> callback) {
+ callback.onSuccess(getLocalTextClassifier().detectLanguage(request));
+ }
+
+ /**
+ * Suggests and returns a list of actions according to the given conversation.
+ *
+ * @param sessionId the session id
+ * @param request the conversation actions request
+ * @param cancellationSignal object to watch for canceling the current operation
+ * @param callback the callback to return the result to
+ */
+ public void onSuggestConversationActions(
+ @Nullable TextClassificationSessionId sessionId,
+ @NonNull ConversationActions.Request request,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull Callback<ConversationActions> callback) {
+ callback.onSuccess(getLocalTextClassifier().suggestConversationActions(request));
+ }
+
+ /**
* Writes the selection event.
* This is called when a selection event occurs. e.g. user changed selection; or smart selection
* happened.
diff --git a/core/java/android/view/textclassifier/ConversationActions.aidl b/core/java/android/view/textclassifier/ConversationActions.aidl
new file mode 100644
index 0000000..fece939
--- /dev/null
+++ b/core/java/android/view/textclassifier/ConversationActions.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+parcelable ConversationActions;
+parcelable ConversationActions.Request;
\ No newline at end of file
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index 16eb5af..f8fce62 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -23,8 +23,10 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.service.textclassifier.IConversationActionsCallback;
import android.service.textclassifier.ITextClassificationCallback;
import android.service.textclassifier.ITextClassifierService;
+import android.service.textclassifier.ITextLanguageCallback;
import android.service.textclassifier.ITextLinksCallback;
import android.service.textclassifier.ITextSelectionCallback;
@@ -76,7 +78,7 @@
if (selection != null) {
return selection;
}
- } catch (RemoteException | InterruptedException e) {
+ } catch (RemoteException e) {
Log.e(LOG_TAG, "Error suggesting selection for text. Using fallback.", e);
}
return mFallback.suggestSelection(request);
@@ -97,7 +99,7 @@
if (classification != null) {
return classification;
}
- } catch (RemoteException | InterruptedException e) {
+ } catch (RemoteException e) {
Log.e(LOG_TAG, "Error classifying text. Using fallback.", e);
}
return mFallback.classifyText(request);
@@ -124,7 +126,7 @@
if (links != null) {
return links;
}
- } catch (RemoteException | InterruptedException e) {
+ } catch (RemoteException e) {
Log.e(LOG_TAG, "Error generating links. Using fallback.", e);
}
return mFallback.generateLinks(request);
@@ -142,6 +144,42 @@
}
}
+ @Override
+ public TextLanguage detectLanguage(TextLanguage.Request request) {
+ Preconditions.checkNotNull(request);
+ Utils.checkMainThread();
+
+ try {
+ final TextLanguageCallback callback = new TextLanguageCallback();
+ mManagerService.onDetectLanguage(mSessionId, request, callback);
+ final TextLanguage textLanguage = callback.mReceiver.get();
+ if (textLanguage != null) {
+ return textLanguage;
+ }
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Error detecting language.", e);
+ }
+ return mFallback.detectLanguage(request);
+ }
+
+ @Override
+ public ConversationActions suggestConversationActions(ConversationActions.Request request) {
+ Preconditions.checkNotNull(request);
+ Utils.checkMainThread();
+
+ try {
+ final ConversationActionsCallback callback = new ConversationActionsCallback();
+ mManagerService.onSuggestConversationActions(mSessionId, request, callback);
+ final ConversationActions conversationActions = callback.mReceiver.get();
+ if (conversationActions != null) {
+ return conversationActions;
+ }
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Error reporting selection event.", e);
+ }
+ return mFallback.suggestConversationActions(request);
+ }
+
/**
* @inheritDoc
*/
@@ -193,7 +231,7 @@
private static final class TextSelectionCallback extends ITextSelectionCallback.Stub {
- final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>();
+ final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>("textselection");
@Override
public void onSuccess(TextSelection selection) {
@@ -208,7 +246,8 @@
private static final class TextClassificationCallback extends ITextClassificationCallback.Stub {
- final ResponseReceiver<TextClassification> mReceiver = new ResponseReceiver<>();
+ final ResponseReceiver<TextClassification> mReceiver =
+ new ResponseReceiver<>("textclassification");
@Override
public void onSuccess(TextClassification classification) {
@@ -223,7 +262,7 @@
private static final class TextLinksCallback extends ITextLinksCallback.Stub {
- final ResponseReceiver<TextLinks> mReceiver = new ResponseReceiver<>();
+ final ResponseReceiver<TextLinks> mReceiver = new ResponseReceiver<>("textlinks");
@Override
public void onSuccess(TextLinks links) {
@@ -236,12 +275,48 @@
}
}
+ private static final class TextLanguageCallback extends ITextLanguageCallback.Stub {
+
+ final ResponseReceiver<TextLanguage> mReceiver = new ResponseReceiver<>("textlanguage");
+
+ @Override
+ public void onSuccess(TextLanguage textLanguage) {
+ mReceiver.onSuccess(textLanguage);
+ }
+
+ @Override
+ public void onFailure() {
+ mReceiver.onFailure();
+ }
+ }
+
+ private static final class ConversationActionsCallback
+ extends IConversationActionsCallback.Stub {
+
+ final ResponseReceiver<ConversationActions> mReceiver =
+ new ResponseReceiver<>("conversationaction");
+
+ @Override
+ public void onSuccess(ConversationActions conversationActions) {
+ mReceiver.onSuccess(conversationActions);
+ }
+
+ @Override
+ public void onFailure() {
+ mReceiver.onFailure();
+ }
+ }
+
private static final class ResponseReceiver<T> {
private final CountDownLatch mLatch = new CountDownLatch(1);
-
+ private final String mName;
private T mResponse;
+ private ResponseReceiver(String name) {
+ mName = name;
+ }
+
public void onSuccess(T response) {
mResponse = response;
mLatch.countDown();
@@ -253,13 +328,21 @@
}
@Nullable
- public T get() throws InterruptedException {
+ public T get() {
// If this is running on the main thread, do not block for a response.
// The response will unfortunately be null and the TextClassifier should depend on its
// fallback.
// NOTE that TextClassifier calls should preferably always be called on a worker thread.
if (Looper.myLooper() != Looper.getMainLooper()) {
- mLatch.await(2, TimeUnit.SECONDS);
+ try {
+ boolean success = mLatch.await(2, TimeUnit.SECONDS);
+ if (!success) {
+ Log.w(LOG_TAG, "Timeout in ResponseReceiver.get(): " + mName);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Log.e(LOG_TAG, "Interrupted during ResponseReceiver.get(): " + mName, e);
+ }
}
return mResponse;
}
diff --git a/core/java/android/view/textclassifier/TextLanguage.aidl b/core/java/android/view/textclassifier/TextLanguage.aidl
new file mode 100644
index 0000000..54e3cf9f
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextLanguage.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+parcelable TextLanguage;
+parcelable TextLanguage.Request;
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index 3a33d57..46aa5b4 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -16,12 +16,9 @@
package android.view.textclassifier;
-import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.any;
@@ -41,315 +38,24 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TextClassificationManagerTest {
private static final LocaleList LOCALES = LocaleList.forLanguageTags("en-US");
- private static final String NO_TYPE = null;
private Context mContext;
private TextClassificationManager mTcm;
- private TextClassifier mClassifier;
@Before
public void setup() {
mContext = InstrumentationRegistry.getTargetContext();
mTcm = mContext.getSystemService(TextClassificationManager.class);
- // Test with the local textClassifier only. (We only bundle "en" model by default).
- // It's hard to reliably test the results of the device's TextClassifierServiceImpl here.
- mClassifier = mTcm.getTextClassifier(TextClassifier.LOCAL);
- }
-
- @Test
- public void testSmartSelection() {
- if (isTextClassifierDisabled()) return;
-
- String text = "Contact me at droid@android.com";
- String selected = "droid";
- String suggested = "droid@android.com";
- int startIndex = text.indexOf(selected);
- int endIndex = startIndex + selected.length();
- int smartStartIndex = text.indexOf(suggested);
- int smartEndIndex = smartStartIndex + suggested.length();
- TextSelection.Request request = new TextSelection.Request.Builder(
- text, startIndex, endIndex)
- .setDefaultLocales(LOCALES)
- .build();
-
- TextSelection selection = mClassifier.suggestSelection(request);
- assertThat(selection,
- isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_EMAIL));
- }
-
- @Test
- public void testSmartSelection_url() {
- if (isTextClassifierDisabled()) return;
-
- String text = "Visit http://www.android.com for more information";
- String selected = "http";
- String suggested = "http://www.android.com";
- int startIndex = text.indexOf(selected);
- int endIndex = startIndex + selected.length();
- int smartStartIndex = text.indexOf(suggested);
- int smartEndIndex = smartStartIndex + suggested.length();
- TextSelection.Request request = new TextSelection.Request.Builder(
- text, startIndex, endIndex)
- .setDefaultLocales(LOCALES)
- .build();
-
- TextSelection selection = mClassifier.suggestSelection(request);
- assertThat(selection,
- isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_URL));
- }
-
- @Test
- public void testSmartSelection_withEmoji() {
- if (isTextClassifierDisabled()) return;
-
- String text = "\uD83D\uDE02 Hello.";
- String selected = "Hello";
- int startIndex = text.indexOf(selected);
- int endIndex = startIndex + selected.length();
- TextSelection.Request request = new TextSelection.Request.Builder(
- text, startIndex, endIndex)
- .setDefaultLocales(LOCALES)
- .build();
-
- TextSelection selection = mClassifier.suggestSelection(request);
- assertThat(selection,
- isTextSelection(startIndex, endIndex, NO_TYPE));
- }
-
- @Test
- public void testClassifyText() {
- if (isTextClassifierDisabled()) return;
-
- String text = "Contact me at droid@android.com";
- String classifiedText = "droid@android.com";
- int startIndex = text.indexOf(classifiedText);
- int endIndex = startIndex + classifiedText.length();
- TextClassification.Request request = new TextClassification.Request.Builder(
- text, startIndex, endIndex)
- .setDefaultLocales(LOCALES)
- .build();
-
- TextClassification classification = mClassifier.classifyText(request);
- assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_EMAIL));
- }
-
- @Test
- public void testTextClassifyText_url() {
- if (isTextClassifierDisabled()) return;
-
- String text = "Visit www.android.com for more information";
- String classifiedText = "www.android.com";
- int startIndex = text.indexOf(classifiedText);
- int endIndex = startIndex + classifiedText.length();
- TextClassification.Request request = new TextClassification.Request.Builder(
- text, startIndex, endIndex)
- .setDefaultLocales(LOCALES)
- .build();
-
- TextClassification classification = mClassifier.classifyText(request);
- assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_URL));
- }
-
- @Test
- public void testTextClassifyText_address() {
- if (isTextClassifierDisabled()) return;
-
- String text = "Brandschenkestrasse 110, Zürich, Switzerland";
- TextClassification.Request request = new TextClassification.Request.Builder(
- text, 0, text.length())
- .setDefaultLocales(LOCALES)
- .build();
-
- TextClassification classification = mClassifier.classifyText(request);
- assertThat(classification, isTextClassification(text, TextClassifier.TYPE_ADDRESS));
- }
-
- @Test
- public void testTextClassifyText_url_inCaps() {
- if (isTextClassifierDisabled()) return;
-
- String text = "Visit HTTP://ANDROID.COM for more information";
- String classifiedText = "HTTP://ANDROID.COM";
- int startIndex = text.indexOf(classifiedText);
- int endIndex = startIndex + classifiedText.length();
- TextClassification.Request request = new TextClassification.Request.Builder(
- text, startIndex, endIndex)
- .setDefaultLocales(LOCALES)
- .build();
-
- TextClassification classification = mClassifier.classifyText(request);
- assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_URL));
- }
-
- @Test
- public void testTextClassifyText_date() {
- if (isTextClassifierDisabled()) return;
-
- String text = "Let's meet on January 9, 2018.";
- String classifiedText = "January 9, 2018";
- int startIndex = text.indexOf(classifiedText);
- int endIndex = startIndex + classifiedText.length();
- TextClassification.Request request = new TextClassification.Request.Builder(
- text, startIndex, endIndex)
- .setDefaultLocales(LOCALES)
- .build();
-
- TextClassification classification = mClassifier.classifyText(request);
- assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_DATE));
- }
-
- @Test
- public void testTextClassifyText_datetime() {
- if (isTextClassifierDisabled()) return;
-
- String text = "Let's meet 2018/01/01 10:30:20.";
- String classifiedText = "2018/01/01 10:30:20";
- int startIndex = text.indexOf(classifiedText);
- int endIndex = startIndex + classifiedText.length();
- TextClassification.Request request = new TextClassification.Request.Builder(
- text, startIndex, endIndex)
- .setDefaultLocales(LOCALES)
- .build();
-
- TextClassification classification = mClassifier.classifyText(request);
- assertThat(classification,
- isTextClassification(classifiedText, TextClassifier.TYPE_DATE_TIME));
- }
-
- @Test
- public void testGenerateLinks_phone() {
- if (isTextClassifierDisabled()) return;
- String text = "The number is +12122537077. See you tonight!";
- TextLinks.Request request = new TextLinks.Request.Builder(text).build();
- assertThat(mClassifier.generateLinks(request),
- isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE));
- }
-
- @Test
- public void testGenerateLinks_exclude() {
- if (isTextClassifierDisabled()) return;
- String text = "You want apple@banana.com. See you tonight!";
- List<String> hints = Collections.EMPTY_LIST;
- List<String> included = Collections.EMPTY_LIST;
- List<String> excluded = Arrays.asList(TextClassifier.TYPE_EMAIL);
- TextLinks.Request request = new TextLinks.Request.Builder(text)
- .setEntityConfig(TextClassifier.EntityConfig.create(hints, included, excluded))
- .setDefaultLocales(LOCALES)
- .build();
- assertThat(mClassifier.generateLinks(request),
- not(isTextLinksContaining(text, "apple@banana.com", TextClassifier.TYPE_EMAIL)));
- }
-
- @Test
- public void testGenerateLinks_explicit_address() {
- if (isTextClassifierDisabled()) return;
- String text = "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you!";
- List<String> explicit = Arrays.asList(TextClassifier.TYPE_ADDRESS);
- TextLinks.Request request = new TextLinks.Request.Builder(text)
- .setEntityConfig(TextClassifier.EntityConfig.createWithExplicitEntityList(explicit))
- .setDefaultLocales(LOCALES)
- .build();
- assertThat(mClassifier.generateLinks(request),
- isTextLinksContaining(text, "1600 Amphitheater Parkway, Mountain View, CA",
- TextClassifier.TYPE_ADDRESS));
- }
-
- @Test
- public void testGenerateLinks_exclude_override() {
- if (isTextClassifierDisabled()) return;
- String text = "You want apple@banana.com. See you tonight!";
- List<String> hints = Collections.EMPTY_LIST;
- List<String> included = Arrays.asList(TextClassifier.TYPE_EMAIL);
- List<String> excluded = Arrays.asList(TextClassifier.TYPE_EMAIL);
- TextLinks.Request request = new TextLinks.Request.Builder(text)
- .setEntityConfig(TextClassifier.EntityConfig.create(hints, included, excluded))
- .setDefaultLocales(LOCALES)
- .build();
- assertThat(mClassifier.generateLinks(request),
- not(isTextLinksContaining(text, "apple@banana.com", TextClassifier.TYPE_EMAIL)));
- }
-
- @Test
- public void testGenerateLinks_maxLength() {
- if (isTextClassifierDisabled()) return;
- char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength()];
- Arrays.fill(manySpaces, ' ');
- TextLinks.Request request = new TextLinks.Request.Builder(new String(manySpaces)).build();
- TextLinks links = mClassifier.generateLinks(request);
- assertTrue(links.getLinks().isEmpty());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testGenerateLinks_tooLong() {
- if (isTextClassifierDisabled()) {
- throw new IllegalArgumentException("pass if disabled");
- }
- char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength() + 1];
- Arrays.fill(manySpaces, ' ');
- TextLinks.Request request = new TextLinks.Request.Builder(new String(manySpaces)).build();
- mClassifier.generateLinks(request);
- }
-
- @Test
- public void testDetectLanguage() {
- if (isTextClassifierDisabled()) return;
- String text = "This is English text";
- TextLanguage.Request request = new TextLanguage.Request.Builder(text).build();
- TextLanguage textLanguage = mClassifier.detectLanguage(request);
- assertThat(textLanguage, isTextLanguage("en"));
- }
-
- @Test
- public void testDetectLanguage_japanese() {
- if (isTextClassifierDisabled()) return;
- String text = "これは日本語のテキストです";
- TextLanguage.Request request = new TextLanguage.Request.Builder(text).build();
- TextLanguage textLanguage = mClassifier.detectLanguage(request);
- assertThat(textLanguage, isTextLanguage("ja"));
- }
-
- @Test
- public void testSuggestConversationActions_textReplyOnly_maxThree() {
- if (isTextClassifierDisabled()) return;
- ConversationActions.Message message =
- new ConversationActions.Message.Builder().setText("Hello").build();
- ConversationActions.TypeConfig typeConfig =
- new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false)
- .setIncludedTypes(
- Collections.singletonList(ConversationActions.TYPE_TEXT_REPLY))
- .build();
- ConversationActions.Request request =
- new ConversationActions.Request.Builder(Collections.singletonList(message))
- .setMaxSuggestions(1)
- .setTypeConfig(typeConfig)
- .build();
-
- ConversationActions conversationActions = mClassifier.suggestConversationActions(request);
- assertTrue(conversationActions.getConversationActions().size() <= 1);
- for (ConversationActions.ConversationAction conversationAction :
- conversationActions.getConversationActions()) {
- assertEquals(conversationAction.getType(), ConversationActions.TYPE_TEXT_REPLY);
- assertNotNull(conversationAction.getTextReply());
- assertTrue(conversationAction.getConfidenceScore() > 0);
- assertTrue(conversationAction.getConfidenceScore() <= 1);
- }
}
@Test
@@ -411,102 +117,4 @@
assertFalse(result.getActions().isEmpty());
assertNotSame(result, fallbackResult);
}
-
- private boolean isTextClassifierDisabled() {
- return mClassifier == TextClassifier.NO_OP;
- }
-
- private static Matcher<TextSelection> isTextSelection(
- final int startIndex, final int endIndex, final String type) {
- return new BaseMatcher<TextSelection>() {
- @Override
- public boolean matches(Object o) {
- if (o instanceof TextSelection) {
- TextSelection selection = (TextSelection) o;
- return startIndex == selection.getSelectionStartIndex()
- && endIndex == selection.getSelectionEndIndex()
- && typeMatches(selection, type);
- }
- return false;
- }
-
- private boolean typeMatches(TextSelection selection, String type) {
- return type == null
- || (selection.getEntityCount() > 0
- && type.trim().equalsIgnoreCase(selection.getEntity(0)));
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendValue(
- String.format("%d, %d, %s", startIndex, endIndex, type));
- }
- };
- }
-
- private static Matcher<TextLinks> isTextLinksContaining(
- final String text, final String substring, final String type) {
- return new BaseMatcher<TextLinks>() {
-
- @Override
- public void describeTo(Description description) {
- description.appendText("text=").appendValue(text)
- .appendText(", substring=").appendValue(substring)
- .appendText(", type=").appendValue(type);
- }
-
- @Override
- public boolean matches(Object o) {
- if (o instanceof TextLinks) {
- for (TextLinks.TextLink link : ((TextLinks) o).getLinks()) {
- if (text.subSequence(link.getStart(), link.getEnd()).equals(substring)) {
- return type.equals(link.getEntity(0));
- }
- }
- }
- return false;
- }
- };
- }
-
- private static Matcher<TextClassification> isTextClassification(
- final String text, final String type) {
- return new BaseMatcher<TextClassification>() {
- @Override
- public boolean matches(Object o) {
- if (o instanceof TextClassification) {
- TextClassification result = (TextClassification) o;
- return text.equals(result.getText())
- && result.getEntityCount() > 0
- && type.equals(result.getEntity(0));
- }
- return false;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("text=").appendValue(text)
- .appendText(", type=").appendValue(type);
- }
- };
- }
-
- private static Matcher<TextLanguage> isTextLanguage(final String languageTag) {
- return new BaseMatcher<TextLanguage>() {
- @Override
- public boolean matches(Object o) {
- if (o instanceof TextLanguage) {
- TextLanguage result = (TextLanguage) o;
- return result.getLocaleHypothesisCount() > 0
- && languageTag.equals(result.getLocale(0).toLanguageTag());
- }
- return false;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("locale=").appendValue(languageTag);
- }
- };
- }
}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
new file mode 100644
index 0000000..06ba15e
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -0,0 +1,454 @@
+/*
+ * 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 org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.LocaleList;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Testing {@link TextClassifierTest} APIs on local and system textclassifier.
+ * <p>
+ * Tests are skipped if such a textclassifier does not exist.
+ */
+@SmallTest
+@RunWith(Parameterized.class)
+public class TextClassifierTest {
+ private static final String LOCAL = "local";
+ private static final String SYSTEM = "system";
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Iterable<Object> textClassifierTypes() {
+ return Arrays.asList(LOCAL, SYSTEM);
+ }
+
+ @Parameterized.Parameter
+ public String mTextClassifierType;
+
+ private static final LocaleList LOCALES = LocaleList.forLanguageTags("en-US");
+ private static final String NO_TYPE = null;
+
+ private Context mContext;
+ private TextClassificationManager mTcm;
+ private TextClassifier mClassifier;
+
+ @Before
+ public void setup() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mTcm = mContext.getSystemService(TextClassificationManager.class);
+ mClassifier = mTcm.getTextClassifier(
+ mTextClassifierType.equals(LOCAL) ? TextClassifier.LOCAL : TextClassifier.SYSTEM);
+ }
+
+ @Test
+ public void testSmartSelection() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Contact me at droid@android.com";
+ String selected = "droid";
+ String suggested = "droid@android.com";
+ int startIndex = text.indexOf(selected);
+ int endIndex = startIndex + selected.length();
+ int smartStartIndex = text.indexOf(suggested);
+ int smartEndIndex = smartStartIndex + suggested.length();
+ TextSelection.Request request = new TextSelection.Request.Builder(
+ text, startIndex, endIndex)
+ .setDefaultLocales(LOCALES)
+ .build();
+
+ TextSelection selection = mClassifier.suggestSelection(request);
+ assertThat(selection,
+ isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_EMAIL));
+ }
+
+ @Test
+ public void testSmartSelection_url() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Visit http://www.android.com for more information";
+ String selected = "http";
+ String suggested = "http://www.android.com";
+ int startIndex = text.indexOf(selected);
+ int endIndex = startIndex + selected.length();
+ int smartStartIndex = text.indexOf(suggested);
+ int smartEndIndex = smartStartIndex + suggested.length();
+ TextSelection.Request request = new TextSelection.Request.Builder(
+ text, startIndex, endIndex)
+ .setDefaultLocales(LOCALES)
+ .build();
+
+ TextSelection selection = mClassifier.suggestSelection(request);
+ assertThat(selection,
+ isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_URL));
+ }
+
+ @Test
+ public void testSmartSelection_withEmoji() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "\uD83D\uDE02 Hello.";
+ String selected = "Hello";
+ int startIndex = text.indexOf(selected);
+ int endIndex = startIndex + selected.length();
+ TextSelection.Request request = new TextSelection.Request.Builder(
+ text, startIndex, endIndex)
+ .setDefaultLocales(LOCALES)
+ .build();
+
+ TextSelection selection = mClassifier.suggestSelection(request);
+ assertThat(selection,
+ isTextSelection(startIndex, endIndex, NO_TYPE));
+ }
+
+ @Test
+ public void testClassifyText() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Contact me at droid@android.com";
+ String classifiedText = "droid@android.com";
+ int startIndex = text.indexOf(classifiedText);
+ int endIndex = startIndex + classifiedText.length();
+ TextClassification.Request request = new TextClassification.Request.Builder(
+ text, startIndex, endIndex)
+ .setDefaultLocales(LOCALES)
+ .build();
+
+ TextClassification classification = mClassifier.classifyText(request);
+ assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_EMAIL));
+ }
+
+ @Test
+ public void testTextClassifyText_url() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Visit www.android.com for more information";
+ String classifiedText = "www.android.com";
+ int startIndex = text.indexOf(classifiedText);
+ int endIndex = startIndex + classifiedText.length();
+ TextClassification.Request request = new TextClassification.Request.Builder(
+ text, startIndex, endIndex)
+ .setDefaultLocales(LOCALES)
+ .build();
+
+ TextClassification classification = mClassifier.classifyText(request);
+ assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_URL));
+ }
+
+ @Test
+ public void testTextClassifyText_address() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Brandschenkestrasse 110, Zürich, Switzerland";
+ TextClassification.Request request = new TextClassification.Request.Builder(
+ text, 0, text.length())
+ .setDefaultLocales(LOCALES)
+ .build();
+
+ TextClassification classification = mClassifier.classifyText(request);
+ assertThat(classification, isTextClassification(text, TextClassifier.TYPE_ADDRESS));
+ }
+
+ @Test
+ public void testTextClassifyText_url_inCaps() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Visit HTTP://ANDROID.COM for more information";
+ String classifiedText = "HTTP://ANDROID.COM";
+ int startIndex = text.indexOf(classifiedText);
+ int endIndex = startIndex + classifiedText.length();
+ TextClassification.Request request = new TextClassification.Request.Builder(
+ text, startIndex, endIndex)
+ .setDefaultLocales(LOCALES)
+ .build();
+
+ TextClassification classification = mClassifier.classifyText(request);
+ assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_URL));
+ }
+
+ @Test
+ public void testTextClassifyText_date() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Let's meet on January 9, 2018.";
+ String classifiedText = "January 9, 2018";
+ int startIndex = text.indexOf(classifiedText);
+ int endIndex = startIndex + classifiedText.length();
+ TextClassification.Request request = new TextClassification.Request.Builder(
+ text, startIndex, endIndex)
+ .setDefaultLocales(LOCALES)
+ .build();
+
+ TextClassification classification = mClassifier.classifyText(request);
+ assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_DATE));
+ }
+
+ @Test
+ public void testTextClassifyText_datetime() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Let's meet 2018/01/01 10:30:20.";
+ String classifiedText = "2018/01/01 10:30:20";
+ int startIndex = text.indexOf(classifiedText);
+ int endIndex = startIndex + classifiedText.length();
+ TextClassification.Request request = new TextClassification.Request.Builder(
+ text, startIndex, endIndex)
+ .setDefaultLocales(LOCALES)
+ .build();
+
+ TextClassification classification = mClassifier.classifyText(request);
+ assertThat(classification,
+ isTextClassification(classifiedText, TextClassifier.TYPE_DATE_TIME));
+ }
+
+ @Test
+ public void testGenerateLinks_phone() {
+ if (isTextClassifierDisabled()) return;
+ String text = "The number is +12122537077. See you tonight!";
+ TextLinks.Request request = new TextLinks.Request.Builder(text).build();
+ assertThat(mClassifier.generateLinks(request),
+ isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE));
+ }
+
+ @Test
+ public void testGenerateLinks_exclude() {
+ if (isTextClassifierDisabled()) return;
+ String text = "You want apple@banana.com. See you tonight!";
+ List<String> hints = Collections.EMPTY_LIST;
+ List<String> included = Collections.EMPTY_LIST;
+ List<String> excluded = Arrays.asList(TextClassifier.TYPE_EMAIL);
+ TextLinks.Request request = new TextLinks.Request.Builder(text)
+ .setEntityConfig(TextClassifier.EntityConfig.create(hints, included, excluded))
+ .setDefaultLocales(LOCALES)
+ .build();
+ assertThat(mClassifier.generateLinks(request),
+ not(isTextLinksContaining(text, "apple@banana.com", TextClassifier.TYPE_EMAIL)));
+ }
+
+ @Test
+ public void testGenerateLinks_explicit_address() {
+ if (isTextClassifierDisabled()) return;
+ String text = "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you!";
+ List<String> explicit = Arrays.asList(TextClassifier.TYPE_ADDRESS);
+ TextLinks.Request request = new TextLinks.Request.Builder(text)
+ .setEntityConfig(TextClassifier.EntityConfig.createWithExplicitEntityList(explicit))
+ .setDefaultLocales(LOCALES)
+ .build();
+ assertThat(mClassifier.generateLinks(request),
+ isTextLinksContaining(text, "1600 Amphitheater Parkway, Mountain View, CA",
+ TextClassifier.TYPE_ADDRESS));
+ }
+
+ @Test
+ public void testGenerateLinks_exclude_override() {
+ if (isTextClassifierDisabled()) return;
+ String text = "You want apple@banana.com. See you tonight!";
+ List<String> hints = Collections.EMPTY_LIST;
+ List<String> included = Arrays.asList(TextClassifier.TYPE_EMAIL);
+ List<String> excluded = Arrays.asList(TextClassifier.TYPE_EMAIL);
+ TextLinks.Request request = new TextLinks.Request.Builder(text)
+ .setEntityConfig(TextClassifier.EntityConfig.create(hints, included, excluded))
+ .setDefaultLocales(LOCALES)
+ .build();
+ assertThat(mClassifier.generateLinks(request),
+ not(isTextLinksContaining(text, "apple@banana.com", TextClassifier.TYPE_EMAIL)));
+ }
+
+ @Test
+ public void testGenerateLinks_maxLength() {
+ if (isTextClassifierDisabled()) return;
+ char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength()];
+ Arrays.fill(manySpaces, ' ');
+ TextLinks.Request request = new TextLinks.Request.Builder(new String(manySpaces)).build();
+ TextLinks links = mClassifier.generateLinks(request);
+ assertTrue(links.getLinks().isEmpty());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGenerateLinks_tooLong() {
+ if (isTextClassifierDisabled()) {
+ throw new IllegalArgumentException("pass if disabled");
+ }
+ char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength() + 1];
+ Arrays.fill(manySpaces, ' ');
+ TextLinks.Request request = new TextLinks.Request.Builder(new String(manySpaces)).build();
+ mClassifier.generateLinks(request);
+ }
+
+ @Test
+ public void testDetectLanguage() {
+ if (isTextClassifierDisabled()) return;
+ String text = "This is English text";
+ TextLanguage.Request request = new TextLanguage.Request.Builder(text).build();
+ TextLanguage textLanguage = mClassifier.detectLanguage(request);
+ assertThat(textLanguage, isTextLanguage("en"));
+ }
+
+ @Test
+ public void testDetectLanguage_japanese() {
+ if (isTextClassifierDisabled()) return;
+ String text = "これは日本語のテキストです";
+ TextLanguage.Request request = new TextLanguage.Request.Builder(text).build();
+ TextLanguage textLanguage = mClassifier.detectLanguage(request);
+ assertThat(textLanguage, isTextLanguage("ja"));
+ }
+
+ @Test
+ public void testSuggestConversationActions_textReplyOnly_maxThree() {
+ if (isTextClassifierDisabled()) return;
+ ConversationActions.Message message =
+ new ConversationActions.Message.Builder().setText("Hello").build();
+ ConversationActions.TypeConfig typeConfig =
+ new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false)
+ .setIncludedTypes(
+ Collections.singletonList(ConversationActions.TYPE_TEXT_REPLY))
+ .build();
+ ConversationActions.Request request =
+ new ConversationActions.Request.Builder(Collections.singletonList(message))
+ .setMaxSuggestions(3)
+ .setTypeConfig(typeConfig)
+ .build();
+
+ ConversationActions conversationActions = mClassifier.suggestConversationActions(request);
+ assertTrue(conversationActions.getConversationActions().size() > 0);
+ assertTrue(conversationActions.getConversationActions().size() <= 3);
+ for (ConversationActions.ConversationAction conversationAction :
+ conversationActions.getConversationActions()) {
+ assertEquals(conversationAction.getType(), ConversationActions.TYPE_TEXT_REPLY);
+ assertNotNull(conversationAction.getTextReply());
+ assertTrue(conversationAction.getConfidenceScore() > 0);
+ assertTrue(conversationAction.getConfidenceScore() <= 1);
+ }
+ }
+
+
+ private boolean isTextClassifierDisabled() {
+ return mClassifier == null || mClassifier == TextClassifier.NO_OP;
+ }
+
+ private static Matcher<TextSelection> isTextSelection(
+ final int startIndex, final int endIndex, final String type) {
+ return new BaseMatcher<TextSelection>() {
+ @Override
+ public boolean matches(Object o) {
+ if (o instanceof TextSelection) {
+ TextSelection selection = (TextSelection) o;
+ return startIndex == selection.getSelectionStartIndex()
+ && endIndex == selection.getSelectionEndIndex()
+ && typeMatches(selection, type);
+ }
+ return false;
+ }
+
+ private boolean typeMatches(TextSelection selection, String type) {
+ return type == null
+ || (selection.getEntityCount() > 0
+ && type.trim().equalsIgnoreCase(selection.getEntity(0)));
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendValue(
+ String.format("%d, %d, %s", startIndex, endIndex, type));
+ }
+ };
+ }
+
+ private static Matcher<TextLinks> isTextLinksContaining(
+ final String text, final String substring, final String type) {
+ return new BaseMatcher<TextLinks>() {
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("text=").appendValue(text)
+ .appendText(", substring=").appendValue(substring)
+ .appendText(", type=").appendValue(type);
+ }
+
+ @Override
+ public boolean matches(Object o) {
+ if (o instanceof TextLinks) {
+ for (TextLinks.TextLink link : ((TextLinks) o).getLinks()) {
+ if (text.subSequence(link.getStart(), link.getEnd()).equals(substring)) {
+ return type.equals(link.getEntity(0));
+ }
+ }
+ }
+ return false;
+ }
+ };
+ }
+
+ private static Matcher<TextClassification> isTextClassification(
+ final String text, final String type) {
+ return new BaseMatcher<TextClassification>() {
+ @Override
+ public boolean matches(Object o) {
+ if (o instanceof TextClassification) {
+ TextClassification result = (TextClassification) o;
+ return text.equals(result.getText())
+ && result.getEntityCount() > 0
+ && type.equals(result.getEntity(0));
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("text=").appendValue(text)
+ .appendText(", type=").appendValue(type);
+ }
+ };
+ }
+
+ private static Matcher<TextLanguage> isTextLanguage(final String languageTag) {
+ return new BaseMatcher<TextLanguage>() {
+ @Override
+ public boolean matches(Object o) {
+ if (o instanceof TextLanguage) {
+ TextLanguage result = (TextLanguage) o;
+ return result.getLocaleHypothesisCount() > 0
+ && languageTag.equals(result.getLocale(0).toLanguageTag());
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("locale=").appendValue(languageTag);
+ }
+ };
+ }
+}
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 5ce8145..8d27d1e 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -28,18 +28,22 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.service.textclassifier.IConversationActionsCallback;
import android.service.textclassifier.ITextClassificationCallback;
import android.service.textclassifier.ITextClassifierService;
+import android.service.textclassifier.ITextLanguageCallback;
import android.service.textclassifier.ITextLinksCallback;
import android.service.textclassifier.ITextSelectionCallback;
import android.service.textclassifier.TextClassifierService;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.textclassifier.ConversationActions;
import android.view.textclassifier.SelectionEvent;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationContext;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassificationSessionId;
+import android.view.textclassifier.TextLanguage;
import android.view.textclassifier.TextLinks;
import android.view.textclassifier.TextSelection;
@@ -210,6 +214,50 @@
}
@Override
+ public void onDetectLanguage(
+ TextClassificationSessionId sessionId,
+ TextLanguage.Request request,
+ ITextLanguageCallback callback) throws RemoteException {
+ Preconditions.checkNotNull(request);
+ Preconditions.checkNotNull(callback);
+
+ synchronized (mLock) {
+ UserState userState = getCallingUserStateLocked();
+ if (!userState.bindLocked()) {
+ callback.onFailure();
+ } else if (userState.isBoundLocked()) {
+ userState.mService.onDetectLanguage(sessionId, request, callback);
+ } else {
+ userState.mPendingRequests.add(new PendingRequest(
+ () -> onDetectLanguage(sessionId, request, callback),
+ callback::onFailure, callback.asBinder(), this, userState));
+ }
+ }
+ }
+
+ @Override
+ public void onSuggestConversationActions(
+ TextClassificationSessionId sessionId,
+ ConversationActions.Request request,
+ IConversationActionsCallback callback) throws RemoteException {
+ Preconditions.checkNotNull(request);
+ Preconditions.checkNotNull(callback);
+
+ synchronized (mLock) {
+ UserState userState = getCallingUserStateLocked();
+ if (!userState.bindLocked()) {
+ callback.onFailure();
+ } else if (userState.isBoundLocked()) {
+ userState.mService.onSuggestConversationActions(sessionId, request, callback);
+ } else {
+ userState.mPendingRequests.add(new PendingRequest(
+ () -> onSuggestConversationActions(sessionId, request, callback),
+ callback::onFailure, callback.asBinder(), this, userState));
+ }
+ }
+ }
+
+ @Override
public void onCreateTextClassificationSession(
TextClassificationContext classificationContext, TextClassificationSessionId sessionId)
throws RemoteException {