/*
 * 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.SelectionEvent.InvocationMethod;

import com.android.internal.util.Preconditions;

Session-aware TextClassifier.
/** * 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 Also:
/** * @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.
/** * 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.
Returns:true if the event should be reported. false if the event should be ignored
/** * 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 SelectionSessionLogger.CLASSIFIER_ID.equals( SelectionSessionLogger.SignatureParser.getClassifierId(signature)); } } }