Home | History | Annotate | Download | only in textclassifier
      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 
     17 package android.view.textclassifier;
     18 
     19 import android.annotation.WorkerThread;
     20 import android.view.textclassifier.SelectionEvent.InvocationMethod;
     21 
     22 import com.android.internal.util.Preconditions;
     23 
     24 /**
     25  * Session-aware TextClassifier.
     26  */
     27 @WorkerThread
     28 final 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