blob: e0254e7ce8be4401e26edb8756772f8f3233c801 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content.browser.selection;
import android.os.Build;
import android.support.annotation.IntDef;
import android.text.TextUtils;
import android.view.textclassifier.TextClassifier;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.content_public.browser.SelectionClient;
import org.chromium.content_public.browser.SelectionMetricsLogger;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.WindowAndroid;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* A class that controls Smart Text selection. Smart Text selection automatically augments the
* selected boundaries and classifies the selected text based on the context.
* This class requests the selection together with its surrounding text from the focused frame and
* sends it to SmartSelectionProvider which does the classification itself.
*/
@JNINamespace("content")
public class SmartSelectionClient implements SelectionClient {
@IntDef({RequestType.CLASSIFY, RequestType.SUGGEST_AND_CLASSIFY})
@Retention(RetentionPolicy.SOURCE)
private @interface RequestType {
// Request to obtain the type (e.g. phone number, e-mail address) and the most
// appropriate operation for the selected text.
int CLASSIFY = 0;
// Request to obtain the type (e.g. phone number, e-mail address), the most
// appropriate operation for the selected text and a better selection boundaries.
int SUGGEST_AND_CLASSIFY = 1;
}
// The maximal number of characters on the left and on the right from the current selection.
// Used for surrounding text request.
private static final int NUM_EXTRA_CHARS = 240;
private long mNativeSmartSelectionClient;
private SmartSelectionProvider mProvider;
private ResultCallback mCallback;
private SmartSelectionMetricsLogger mSmartSelectionMetricLogger;
/**
* Creates the SmartSelectionClient. Returns null in case SmartSelectionProvider does not exist
* in the system.
*/
public static SmartSelectionClient create(ResultCallback callback, WebContents webContents) {
WindowAndroid windowAndroid = webContents.getTopLevelNativeWindow();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || windowAndroid == null) return null;
return new SmartSelectionClient(callback, webContents, windowAndroid);
}
private SmartSelectionClient(
ResultCallback callback, WebContents webContents, WindowAndroid windowAndroid) {
assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
mProvider = new SmartSelectionProvider(callback, windowAndroid);
mCallback = callback;
mSmartSelectionMetricLogger =
SmartSelectionMetricsLogger.create(windowAndroid.getContext().get());
mNativeSmartSelectionClient = nativeInit(webContents);
}
@CalledByNative
private void onNativeSideDestroyed(long nativeSmartSelectionClient) {
assert nativeSmartSelectionClient == mNativeSmartSelectionClient;
mNativeSmartSelectionClient = 0;
mProvider.cancelAllRequests();
}
// SelectionClient implementation
@Override
public void onSelectionChanged(String selection) {}
@Override
public void onSelectionEvent(int eventType, float posXPix, float posYPix) {}
@Override
public void selectWordAroundCaretAck(boolean didSelect, int startAdjust, int endAdjust) {}
@Override
public boolean requestSelectionPopupUpdates(boolean shouldSuggest) {
requestSurroundingText(
shouldSuggest ? RequestType.SUGGEST_AND_CLASSIFY : RequestType.CLASSIFY);
return true;
}
@Override
public void cancelAllRequests() {
if (mNativeSmartSelectionClient != 0) {
nativeCancelAllRequests(mNativeSmartSelectionClient);
}
mProvider.cancelAllRequests();
}
@Override
public SelectionMetricsLogger getSelectionMetricsLogger() {
return mSmartSelectionMetricLogger;
}
@Override
public void setTextClassifier(TextClassifier textClassifier) {
mProvider.setTextClassifier(textClassifier);
}
@Override
public TextClassifier getTextClassifier() {
return mProvider.getTextClassifier();
}
@Override
public TextClassifier getCustomTextClassifier() {
return mProvider.getCustomTextClassifier();
}
private void requestSurroundingText(@RequestType int callbackData) {
if (mNativeSmartSelectionClient == 0) {
onSurroundingTextReceived(callbackData, "", 0, 0);
return;
}
nativeRequestSurroundingText(mNativeSmartSelectionClient, NUM_EXTRA_CHARS, callbackData);
}
@CalledByNative
private void onSurroundingTextReceived(
@RequestType int callbackData, String text, int start, int end) {
if (!textHasValidSelection(text, start, end)) {
mCallback.onClassified(new Result());
return;
}
switch (callbackData) {
case RequestType.SUGGEST_AND_CLASSIFY:
mProvider.sendSuggestAndClassifyRequest(text, start, end, null);
break;
case RequestType.CLASSIFY:
mProvider.sendClassifyRequest(text, start, end, null);
break;
default:
assert false : "Unexpected callback data";
break;
}
}
private boolean textHasValidSelection(String text, int start, int end) {
return !TextUtils.isEmpty(text) && 0 <= start && start < end && end <= text.length();
}
private native long nativeInit(WebContents webContents);
private native void nativeRequestSurroundingText(
long nativeSmartSelectionClient, int numExtraCharacters, int callbackData);
private native void nativeCancelAllRequests(long nativeSmartSelectionClient);
}