blob: 4c6419855f7ced84127252029d189a791d4b7cf3 [file] [log] [blame]
/*
* 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.
*/
@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 SelectionSessionLogger.CLASSIFIER_ID.equals(
SelectionSessionLogger.SignatureParser.getClassifierId(signature));
}
}
}