summaryrefslogtreecommitdiff
path: root/android/view/textclassifier/TextClassificationSession.java
diff options
context:
space:
mode:
Diffstat (limited to 'android/view/textclassifier/TextClassificationSession.java')
-rw-r--r--android/view/textclassifier/TextClassificationSession.java228
1 files changed, 228 insertions, 0 deletions
diff --git a/android/view/textclassifier/TextClassificationSession.java b/android/view/textclassifier/TextClassificationSession.java
new file mode 100644
index 00000000..e8e300a9
--- /dev/null
+++ b/android/view/textclassifier/TextClassificationSession.java
@@ -0,0 +1,228 @@
+/*
+ * 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 android.annotation.WorkerThread;
+import android.view.textclassifier.DefaultLogger.SignatureParser;
+import android.view.textclassifier.SelectionEvent.InvocationMethod;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Session-aware TextClassifier.
+ */
+@WorkerThread
+final class TextClassificationSession implements TextClassifier {
+
+ /* package */ static final boolean DEBUG_LOG_ENABLED = true;
+ private static final String LOG_TAG = "TextClassificationSession";
+
+ private final TextClassifier mDelegate;
+ private final SelectionEventHelper mEventHelper;
+ private final TextClassificationSessionId mSessionId;
+ private final TextClassificationContext mClassificationContext;
+
+ private boolean mDestroyed;
+
+ TextClassificationSession(TextClassificationContext context, TextClassifier delegate) {
+ mClassificationContext = Preconditions.checkNotNull(context);
+ mDelegate = Preconditions.checkNotNull(delegate);
+ mSessionId = new TextClassificationSessionId();
+ mEventHelper = new SelectionEventHelper(mSessionId, mClassificationContext);
+ initializeRemoteSession();
+ }
+
+ @Override
+ public TextSelection suggestSelection(TextSelection.Request request) {
+ checkDestroyed();
+ return mDelegate.suggestSelection(request);
+ }
+
+ private void initializeRemoteSession() {
+ if (mDelegate instanceof SystemTextClassifier) {
+ ((SystemTextClassifier) mDelegate).initializeRemoteSession(
+ mClassificationContext, mSessionId);
+ }
+ }
+
+ @Override
+ public TextClassification classifyText(TextClassification.Request request) {
+ checkDestroyed();
+ return mDelegate.classifyText(request);
+ }
+
+ @Override
+ public TextLinks generateLinks(TextLinks.Request request) {
+ checkDestroyed();
+ return mDelegate.generateLinks(request);
+ }
+
+ @Override
+ public void onSelectionEvent(SelectionEvent event) {
+ checkDestroyed();
+ Preconditions.checkNotNull(event);
+ if (mEventHelper.sanitizeEvent(event)) {
+ mDelegate.onSelectionEvent(event);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ mEventHelper.endSession();
+ mDelegate.destroy();
+ mDestroyed = true;
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return mDestroyed;
+ }
+
+ /**
+ * @throws IllegalStateException if this TextClassification session has been destroyed.
+ * @see #isDestroyed()
+ * @see #destroy()
+ */
+ private void checkDestroyed() {
+ if (mDestroyed) {
+ throw new IllegalStateException("This TextClassification session has been destroyed");
+ }
+ }
+
+ /**
+ * Helper class for updating SelectionEvent fields.
+ */
+ private static final class SelectionEventHelper {
+
+ private final TextClassificationSessionId mSessionId;
+ private final TextClassificationContext mContext;
+
+ @InvocationMethod
+ private int mInvocationMethod = SelectionEvent.INVOCATION_UNKNOWN;
+ private SelectionEvent mPrevEvent;
+ private SelectionEvent mSmartEvent;
+ private SelectionEvent mStartEvent;
+
+ SelectionEventHelper(
+ TextClassificationSessionId sessionId, TextClassificationContext context) {
+ mSessionId = Preconditions.checkNotNull(sessionId);
+ mContext = Preconditions.checkNotNull(context);
+ }
+
+ /**
+ * Updates the necessary fields in the event for the current session.
+ *
+ * @return true if the event should be reported. false if the event should be ignored
+ */
+ boolean sanitizeEvent(SelectionEvent event) {
+ updateInvocationMethod(event);
+ modifyAutoSelectionEventType(event);
+
+ if (event.getEventType() != SelectionEvent.EVENT_SELECTION_STARTED
+ && mStartEvent == null) {
+ if (DEBUG_LOG_ENABLED) {
+ Log.d(LOG_TAG, "Selection session not yet started. Ignoring event");
+ }
+ return false;
+ }
+
+ final long now = System.currentTimeMillis();
+ switch (event.getEventType()) {
+ case SelectionEvent.EVENT_SELECTION_STARTED:
+ Preconditions.checkArgument(
+ event.getAbsoluteEnd() == event.getAbsoluteStart() + 1);
+ event.setSessionId(mSessionId);
+ mStartEvent = event;
+ break;
+ case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: // fall through
+ case SelectionEvent.EVENT_SMART_SELECTION_MULTI:
+ mSmartEvent = event;
+ break;
+ case SelectionEvent.EVENT_SELECTION_MODIFIED: // fall through
+ case SelectionEvent.EVENT_AUTO_SELECTION:
+ if (mPrevEvent != null
+ && mPrevEvent.getAbsoluteStart() == event.getAbsoluteStart()
+ && mPrevEvent.getAbsoluteEnd() == event.getAbsoluteEnd()) {
+ // Selection did not change. Ignore event.
+ return false;
+ }
+ break;
+ default:
+ // do nothing.
+ }
+
+ event.setEventTime(now);
+ if (mStartEvent != null) {
+ event.setSessionId(mStartEvent.getSessionId())
+ .setDurationSinceSessionStart(now - mStartEvent.getEventTime())
+ .setStart(event.getAbsoluteStart() - mStartEvent.getAbsoluteStart())
+ .setEnd(event.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
+ }
+ if (mSmartEvent != null) {
+ event.setResultId(mSmartEvent.getResultId())
+ .setSmartStart(
+ mSmartEvent.getAbsoluteStart() - mStartEvent.getAbsoluteStart())
+ .setSmartEnd(mSmartEvent.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
+ }
+ if (mPrevEvent != null) {
+ event.setDurationSincePreviousEvent(now - mPrevEvent.getEventTime())
+ .setEventIndex(mPrevEvent.getEventIndex() + 1);
+ }
+ mPrevEvent = event;
+ return true;
+ }
+
+ void endSession() {
+ mPrevEvent = null;
+ mSmartEvent = null;
+ mStartEvent = null;
+ }
+
+ private void updateInvocationMethod(SelectionEvent event) {
+ event.setTextClassificationSessionContext(mContext);
+ if (event.getInvocationMethod() == SelectionEvent.INVOCATION_UNKNOWN) {
+ event.setInvocationMethod(mInvocationMethod);
+ } else {
+ mInvocationMethod = event.getInvocationMethod();
+ }
+ }
+
+ private void modifyAutoSelectionEventType(SelectionEvent event) {
+ switch (event.getEventType()) {
+ case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: // fall through
+ case SelectionEvent.EVENT_SMART_SELECTION_MULTI: // fall through
+ case SelectionEvent.EVENT_AUTO_SELECTION:
+ if (isPlatformLocalTextClassifierSmartSelection(event.getResultId())) {
+ if (event.getAbsoluteEnd() - event.getAbsoluteStart() > 1) {
+ event.setEventType(SelectionEvent.EVENT_SMART_SELECTION_MULTI);
+ } else {
+ event.setEventType(SelectionEvent.EVENT_SMART_SELECTION_SINGLE);
+ }
+ } else {
+ event.setEventType(SelectionEvent.EVENT_AUTO_SELECTION);
+ }
+ return;
+ default:
+ return;
+ }
+ }
+
+ private static boolean isPlatformLocalTextClassifierSmartSelection(String signature) {
+ return DefaultLogger.CLASSIFIER_ID.equals(SignatureParser.getClassifierId(signature));
+ }
+ }
+}