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.common.Constants; 20 import 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 */ 28 public 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