TextClassificationSession.java revision 5a03094ebc91df1c64a2232be648ac3ed26657ce
1/* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.view.textclassifier; 18 19import android.annotation.WorkerThread; 20import android.view.textclassifier.SelectionEvent.InvocationMethod; 21 22import com.android.internal.util.Preconditions; 23 24/** 25 * Session-aware TextClassifier. 26 */ 27@WorkerThread 28final class TextClassificationSession implements TextClassifier { 29 30 /* package */ static final boolean DEBUG_LOG_ENABLED = true; 31 private static final String LOG_TAG = "TextClassificationSession"; 32 33 private final TextClassifier mDelegate; 34 private final SelectionEventHelper mEventHelper; 35 private final TextClassificationSessionId mSessionId; 36 private final TextClassificationContext mClassificationContext; 37 38 private boolean mDestroyed; 39 40 TextClassificationSession(TextClassificationContext context, TextClassifier delegate) { 41 mClassificationContext = Preconditions.checkNotNull(context); 42 mDelegate = Preconditions.checkNotNull(delegate); 43 mSessionId = new TextClassificationSessionId(); 44 mEventHelper = new SelectionEventHelper(mSessionId, mClassificationContext); 45 initializeRemoteSession(); 46 } 47 48 @Override 49 public TextSelection suggestSelection(TextSelection.Request request) { 50 checkDestroyed(); 51 return mDelegate.suggestSelection(request); 52 } 53 54 private void initializeRemoteSession() { 55 if (mDelegate instanceof SystemTextClassifier) { 56 ((SystemTextClassifier) mDelegate).initializeRemoteSession( 57 mClassificationContext, mSessionId); 58 } 59 } 60 61 @Override 62 public TextClassification classifyText(TextClassification.Request request) { 63 checkDestroyed(); 64 return mDelegate.classifyText(request); 65 } 66 67 @Override 68 public TextLinks generateLinks(TextLinks.Request request) { 69 checkDestroyed(); 70 return mDelegate.generateLinks(request); 71 } 72 73 @Override 74 public void onSelectionEvent(SelectionEvent event) { 75 checkDestroyed(); 76 Preconditions.checkNotNull(event); 77 if (mEventHelper.sanitizeEvent(event)) { 78 mDelegate.onSelectionEvent(event); 79 } 80 } 81 82 @Override 83 public void destroy() { 84 mEventHelper.endSession(); 85 mDelegate.destroy(); 86 mDestroyed = true; 87 } 88 89 @Override 90 public boolean isDestroyed() { 91 return mDestroyed; 92 } 93 94 /** 95 * @throws IllegalStateException if this TextClassification session has been destroyed. 96 * @see #isDestroyed() 97 * @see #destroy() 98 */ 99 private void checkDestroyed() { 100 if (mDestroyed) { 101 throw new IllegalStateException("This TextClassification session has been destroyed"); 102 } 103 } 104 105 /** 106 * Helper class for updating SelectionEvent fields. 107 */ 108 private static final class SelectionEventHelper { 109 110 private final TextClassificationSessionId mSessionId; 111 private final TextClassificationContext mContext; 112 113 @InvocationMethod 114 private int mInvocationMethod = SelectionEvent.INVOCATION_UNKNOWN; 115 private SelectionEvent mPrevEvent; 116 private SelectionEvent mSmartEvent; 117 private SelectionEvent mStartEvent; 118 119 SelectionEventHelper( 120 TextClassificationSessionId sessionId, TextClassificationContext context) { 121 mSessionId = Preconditions.checkNotNull(sessionId); 122 mContext = Preconditions.checkNotNull(context); 123 } 124 125 /** 126 * Updates the necessary fields in the event for the current session. 127 * 128 * @return true if the event should be reported. false if the event should be ignored 129 */ 130 boolean sanitizeEvent(SelectionEvent event) { 131 updateInvocationMethod(event); 132 modifyAutoSelectionEventType(event); 133 134 if (event.getEventType() != SelectionEvent.EVENT_SELECTION_STARTED 135 && mStartEvent == null) { 136 if (DEBUG_LOG_ENABLED) { 137 Log.d(LOG_TAG, "Selection session not yet started. Ignoring event"); 138 } 139 return false; 140 } 141 142 final long now = System.currentTimeMillis(); 143 switch (event.getEventType()) { 144 case SelectionEvent.EVENT_SELECTION_STARTED: 145 Preconditions.checkArgument( 146 event.getAbsoluteEnd() == event.getAbsoluteStart() + 1); 147 event.setSessionId(mSessionId); 148 mStartEvent = event; 149 break; 150 case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: // fall through 151 case SelectionEvent.EVENT_SMART_SELECTION_MULTI: 152 mSmartEvent = event; 153 break; 154 case SelectionEvent.EVENT_SELECTION_MODIFIED: // fall through 155 case SelectionEvent.EVENT_AUTO_SELECTION: 156 if (mPrevEvent != null 157 && mPrevEvent.getAbsoluteStart() == event.getAbsoluteStart() 158 && mPrevEvent.getAbsoluteEnd() == event.getAbsoluteEnd()) { 159 // Selection did not change. Ignore event. 160 return false; 161 } 162 break; 163 default: 164 // do nothing. 165 } 166 167 event.setEventTime(now); 168 if (mStartEvent != null) { 169 event.setSessionId(mStartEvent.getSessionId()) 170 .setDurationSinceSessionStart(now - mStartEvent.getEventTime()) 171 .setStart(event.getAbsoluteStart() - mStartEvent.getAbsoluteStart()) 172 .setEnd(event.getAbsoluteEnd() - mStartEvent.getAbsoluteStart()); 173 } 174 if (mSmartEvent != null) { 175 event.setResultId(mSmartEvent.getResultId()) 176 .setSmartStart( 177 mSmartEvent.getAbsoluteStart() - mStartEvent.getAbsoluteStart()) 178 .setSmartEnd(mSmartEvent.getAbsoluteEnd() - mStartEvent.getAbsoluteStart()); 179 } 180 if (mPrevEvent != null) { 181 event.setDurationSincePreviousEvent(now - mPrevEvent.getEventTime()) 182 .setEventIndex(mPrevEvent.getEventIndex() + 1); 183 } 184 mPrevEvent = event; 185 return true; 186 } 187 188 void endSession() { 189 mPrevEvent = null; 190 mSmartEvent = null; 191 mStartEvent = null; 192 } 193 194 private void updateInvocationMethod(SelectionEvent event) { 195 event.setTextClassificationSessionContext(mContext); 196 if (event.getInvocationMethod() == SelectionEvent.INVOCATION_UNKNOWN) { 197 event.setInvocationMethod(mInvocationMethod); 198 } else { 199 mInvocationMethod = event.getInvocationMethod(); 200 } 201 } 202 203 private void modifyAutoSelectionEventType(SelectionEvent event) { 204 switch (event.getEventType()) { 205 case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: // fall through 206 case SelectionEvent.EVENT_SMART_SELECTION_MULTI: // fall through 207 case SelectionEvent.EVENT_AUTO_SELECTION: 208 if (isPlatformLocalTextClassifierSmartSelection(event.getResultId())) { 209 if (event.getAbsoluteEnd() - event.getAbsoluteStart() > 1) { 210 event.setEventType(SelectionEvent.EVENT_SMART_SELECTION_MULTI); 211 } else { 212 event.setEventType(SelectionEvent.EVENT_SMART_SELECTION_SINGLE); 213 } 214 } else { 215 event.setEventType(SelectionEvent.EVENT_AUTO_SELECTION); 216 } 217 return; 218 default: 219 return; 220 } 221 } 222 223 private static boolean isPlatformLocalTextClassifierSmartSelection(String signature) { 224 return SelectionSessionLogger.CLASSIFIER_ID.equals( 225 SelectionSessionLogger.SignatureParser.getClassifierId(signature)); 226 } 227 } 228} 229