1 /*
2  * Copyright (C) 2013 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 com.android.inputmethod.keyboard.internal;
18 
19 import com.android.inputmethod.latin.Constants;
20 import com.android.inputmethod.latin.InputPointers;
21 
22 /**
23  * This class arbitrates batch input.
24  * An instance of this class holds a {@link GestureStrokeRecognitionPoints}.
25  * And it arbitrates multiple strokes gestured by multiple fingers and aggregates those gesture
26  * points into one batch input.
27  */
28 public class BatchInputArbiter {
29     public interface BatchInputArbiterListener {
onStartBatchInput()30         public void onStartBatchInput();
onUpdateBatchInput( final InputPointers aggregatedPointers, final long moveEventTime)31         public void onUpdateBatchInput(
32                 final InputPointers aggregatedPointers, final long moveEventTime);
onStartUpdateBatchInputTimer()33         public void onStartUpdateBatchInputTimer();
onEndBatchInput(final InputPointers aggregatedPointers, final long upEventTime)34         public void onEndBatchInput(final InputPointers aggregatedPointers, final long upEventTime);
35     }
36 
37     // The starting time of the first stroke of a gesture input.
38     private static long sGestureFirstDownTime;
39     // The {@link InputPointers} that includes all events of a gesture input.
40     private static final InputPointers sAggregatedPointers = new InputPointers(
41             Constants.DEFAULT_GESTURE_POINTS_CAPACITY);
42     private static int sLastRecognitionPointSize = 0; // synchronized using sAggregatedPointers
43     private static long sLastRecognitionTime = 0; // synchronized using sAggregatedPointers
44 
45     private final GestureStrokeRecognitionPoints mRecognitionPoints;
46 
BatchInputArbiter(final int pointerId, final GestureStrokeRecognitionParams params)47     public BatchInputArbiter(final int pointerId, final GestureStrokeRecognitionParams params) {
48         mRecognitionPoints = new GestureStrokeRecognitionPoints(pointerId, params);
49     }
50 
setKeyboardGeometry(final int keyWidth, final int keyboardHeight)51     public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) {
52         mRecognitionPoints.setKeyboardGeometry(keyWidth, keyboardHeight);
53     }
54 
55     /**
56      * Calculate elapsed time since the first gesture down.
57      * @param eventTime the time of this event.
58      * @return the elapsed time in millisecond from the first gesture down.
59      */
getElapsedTimeSinceFirstDown(final long eventTime)60     public int getElapsedTimeSinceFirstDown(final long eventTime) {
61         return (int)(eventTime - sGestureFirstDownTime);
62     }
63 
64     /**
65      * Add a down event point.
66      * @param x the x-coordinate of this down event.
67      * @param y the y-coordinate of this down event.
68      * @param downEventTime the time of this down event.
69      * @param lastLetterTypingTime the last typing input time.
70      * @param activePointerCount the number of active pointers when this pointer down event occurs.
71      */
addDownEventPoint(final int x, final int y, final long downEventTime, final long lastLetterTypingTime, final int activePointerCount)72     public void addDownEventPoint(final int x, final int y, final long downEventTime,
73             final long lastLetterTypingTime, final int activePointerCount) {
74         if (activePointerCount == 1) {
75             sGestureFirstDownTime = downEventTime;
76         }
77         final int elapsedTimeSinceFirstDown = getElapsedTimeSinceFirstDown(downEventTime);
78         final int elapsedTimeSinceLastTyping = (int)(downEventTime - lastLetterTypingTime);
79         mRecognitionPoints.addDownEventPoint(
80                 x, y, elapsedTimeSinceFirstDown, elapsedTimeSinceLastTyping);
81     }
82 
83     /**
84      * Add a move event point.
85      * @param x the x-coordinate of this move event.
86      * @param y the y-coordinate of this move event.
87      * @param moveEventTime the time of this move event.
88      * @param isMajorEvent false if this is a historical move event.
89      * @param listener {@link BatchInputArbiterListener#onStartUpdateBatchInputTimer()} of this
90      *     <code>listener</code> may be called if enough move points have been added.
91      * @return true if this move event occurs on the valid gesture area.
92      */
addMoveEventPoint(final int x, final int y, final long moveEventTime, final boolean isMajorEvent, final BatchInputArbiterListener listener)93     public boolean addMoveEventPoint(final int x, final int y, final long moveEventTime,
94             final boolean isMajorEvent, final BatchInputArbiterListener listener) {
95         final int beforeLength = mRecognitionPoints.getLength();
96         final boolean onValidArea = mRecognitionPoints.addEventPoint(
97                 x, y, getElapsedTimeSinceFirstDown(moveEventTime), isMajorEvent);
98         if (mRecognitionPoints.getLength() > beforeLength) {
99             listener.onStartUpdateBatchInputTimer();
100         }
101         return onValidArea;
102     }
103 
104     /**
105      * Determine whether the batch input has started or not.
106      * @param listener {@link BatchInputArbiterListener#onStartBatchInput()} of this
107      *     <code>listener</code> will be called when the batch input has started successfully.
108      * @return true if the batch input has started successfully.
109      */
mayStartBatchInput(final BatchInputArbiterListener listener)110     public boolean mayStartBatchInput(final BatchInputArbiterListener listener) {
111         if (!mRecognitionPoints.isStartOfAGesture()) {
112             return false;
113         }
114         synchronized (sAggregatedPointers) {
115             sAggregatedPointers.reset();
116             sLastRecognitionPointSize = 0;
117             sLastRecognitionTime = 0;
118             listener.onStartBatchInput();
119         }
120         return true;
121     }
122 
123     /**
124      * Add synthetic move event point. After adding the point,
125      * {@link #updateBatchInput(long,BatchInputArbiterListener)} will be called internally.
126      * @param syntheticMoveEventTime the synthetic move event time.
127      * @param listener the listener to be passed to
128      *     {@link #updateBatchInput(long,BatchInputArbiterListener)}.
129      */
updateBatchInputByTimer(final long syntheticMoveEventTime, final BatchInputArbiterListener listener)130     public void updateBatchInputByTimer(final long syntheticMoveEventTime,
131             final BatchInputArbiterListener listener) {
132         mRecognitionPoints.duplicateLastPointWith(
133                 getElapsedTimeSinceFirstDown(syntheticMoveEventTime));
134         updateBatchInput(syntheticMoveEventTime, listener);
135     }
136 
137     /**
138      * Determine whether we have enough gesture points to lookup dictionary.
139      * @param moveEventTime the time of this move event.
140      * @param listener {@link BatchInputArbiterListener#onUpdateBatchInput(InputPointers,long)} of
141      *     this <code>listener</code> will be called when enough event points we have. Also
142      *     {@link BatchInputArbiterListener#onStartUpdateBatchInputTimer()} will be called to have
143      *     possible future synthetic move event.
144      */
updateBatchInput(final long moveEventTime, final BatchInputArbiterListener listener)145     public void updateBatchInput(final long moveEventTime,
146             final BatchInputArbiterListener listener) {
147         synchronized (sAggregatedPointers) {
148             mRecognitionPoints.appendIncrementalBatchPoints(sAggregatedPointers);
149             final int size = sAggregatedPointers.getPointerSize();
150             if (size > sLastRecognitionPointSize && mRecognitionPoints.hasRecognitionTimePast(
151                     moveEventTime, sLastRecognitionTime)) {
152                 listener.onUpdateBatchInput(sAggregatedPointers, moveEventTime);
153                 listener.onStartUpdateBatchInputTimer();
154                 // The listener may change the size of the pointers (when auto-committing
155                 // for example), so we need to get the size from the pointers again.
156                 sLastRecognitionPointSize = sAggregatedPointers.getPointerSize();
157                 sLastRecognitionTime = moveEventTime;
158             }
159         }
160     }
161 
162     /**
163      * Determine whether the batch input has ended successfully or continues.
164      * @param upEventTime the time of this up event.
165      * @param activePointerCount the number of active pointers when this pointer up event occurs.
166      * @param listener {@link BatchInputArbiterListener#onEndBatchInput(InputPointers,long)} of this
167      *     <code>listener</code> will be called when the batch input has started successfully.
168      * @return true if the batch input has ended successfully.
169      */
mayEndBatchInput(final long upEventTime, final int activePointerCount, final BatchInputArbiterListener listener)170     public boolean mayEndBatchInput(final long upEventTime, final int activePointerCount,
171             final BatchInputArbiterListener listener) {
172         synchronized (sAggregatedPointers) {
173             mRecognitionPoints.appendAllBatchPoints(sAggregatedPointers);
174             if (activePointerCount == 1) {
175                 listener.onEndBatchInput(sAggregatedPointers, upEventTime);
176                 return true;
177             }
178         }
179         return false;
180     }
181 }
182