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