Merge "Add detectLanguage and suggestConversationActions to TCS"
This commit is contained in:
committed by
Android (Google) Code Review
commit
c61363e7a2
@@ -335,8 +335,10 @@ java_defaults {
|
||||
"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",
|
||||
|
||||
@@ -5191,8 +5191,10 @@ package android.service.textclassifier {
|
||||
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";
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 @@ oneway interface ITextClassifierService {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -32,12 +32,14 @@ import android.os.IBinder;
|
||||
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 @@ public abstract class TextClassifierService extends Service {
|
||||
@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 @@ public abstract class TextClassifierService extends Service {
|
||||
@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 @@ public abstract class TextClassifierService extends Service {
|
||||
@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 @@ public abstract class TextClassifierService extends Service {
|
||||
@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 @@ public abstract class TextClassifierService extends Service {
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId)
|
||||
throws RemoteException {
|
||||
public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId) {
|
||||
TextClassifierService.this.onDestroyTextClassificationSession(sessionId);
|
||||
}
|
||||
};
|
||||
@@ -265,6 +328,38 @@ public abstract class TextClassifierService extends Service {
|
||||
@NonNull CancellationSignal cancellationSignal,
|
||||
@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
|
||||
|
||||
@@ -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;
|
||||
@@ -23,8 +23,10 @@ import android.content.Context;
|
||||
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 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
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 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
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 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
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 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
}
|
||||
}
|
||||
|
||||
@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 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
|
||||
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 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
|
||||
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 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
|
||||
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 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public final class SystemTextClassifier implements TextClassifier {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
20
core/java/android/view/textclassifier/TextLanguage.aidl
Normal file
20
core/java/android/view/textclassifier/TextLanguage.aidl
Normal file
@@ -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;
|
||||
@@ -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.InstrumentationRegistry;
|
||||
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 @@ public class TextClassificationManagerTest {
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -28,18 +28,22 @@ import android.os.Binder;
|
||||
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;
|
||||
|
||||
@@ -209,6 +213,50 @@ public final class TextClassificationManagerService extends ITextClassifierServi
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
Reference in New Issue
Block a user