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