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
17package com.android.inputmethod.keyboard.internal;
18
19import com.android.inputmethod.latin.common.Constants;
20import com.android.inputmethod.latin.common.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 */
28public class BatchInputArbiter {
29    public interface BatchInputArbiterListener {
30        public void onStartBatchInput();
31        public void onUpdateBatchInput(
32                final InputPointers aggregatedPointers, final long moveEventTime);
33        public void onStartUpdateBatchInputTimer();
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
47    public BatchInputArbiter(final int pointerId, final GestureStrokeRecognitionParams params) {
48        mRecognitionPoints = new GestureStrokeRecognitionPoints(pointerId, params);
49    }
50
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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