/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.inputmethod.keyboard.internal; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.InputPointers; /** * This class arbitrates batch input. * An instance of this class holds a {@link GestureStrokeRecognitionPoints}. * And it arbitrates multiple strokes gestured by multiple fingers and aggregates those gesture * points into one batch input. */ public class BatchInputArbiter { public interface BatchInputArbiterListener { public void onStartBatchInput(); public void onUpdateBatchInput( final InputPointers aggregatedPointers, final long moveEventTime); public void onStartUpdateBatchInputTimer(); public void onEndBatchInput(final InputPointers aggregatedPointers, final long upEventTime); } // The starting time of the first stroke of a gesture input. private static long sGestureFirstDownTime; // The {@link InputPointers} that includes all events of a gesture input. private static final InputPointers sAggregatedPointers = new InputPointers( Constants.DEFAULT_GESTURE_POINTS_CAPACITY); private static int sLastRecognitionPointSize = 0; // synchronized using sAggregatedPointers private static long sLastRecognitionTime = 0; // synchronized using sAggregatedPointers private final GestureStrokeRecognitionPoints mRecognitionPoints; public BatchInputArbiter(final int pointerId, final GestureStrokeRecognitionParams params) { mRecognitionPoints = new GestureStrokeRecognitionPoints(pointerId, params); } public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { mRecognitionPoints.setKeyboardGeometry(keyWidth, keyboardHeight); } /** * Calculate elapsed time since the first gesture down. * @param eventTime the time of this event. * @return the elapsed time in millisecond from the first gesture down. */ public int getElapsedTimeSinceFirstDown(final long eventTime) { return (int)(eventTime - sGestureFirstDownTime); } /** * Add a down event point. * @param x the x-coordinate of this down event. * @param y the y-coordinate of this down event. * @param downEventTime the time of this down event. * @param lastLetterTypingTime the last typing input time. * @param activePointerCount the number of active pointers when this pointer down event occurs. */ public void addDownEventPoint(final int x, final int y, final long downEventTime, final long lastLetterTypingTime, final int activePointerCount) { if (activePointerCount == 1) { sGestureFirstDownTime = downEventTime; } final int elapsedTimeSinceFirstDown = getElapsedTimeSinceFirstDown(downEventTime); final int elapsedTimeSinceLastTyping = (int)(downEventTime - lastLetterTypingTime); mRecognitionPoints.addDownEventPoint( x, y, elapsedTimeSinceFirstDown, elapsedTimeSinceLastTyping); } /** * Add a move event point. * @param x the x-coordinate of this move event. * @param y the y-coordinate of this move event. * @param moveEventTime the time of this move event. * @param isMajorEvent false if this is a historical move event. * @param listener {@link BatchInputArbiterListener#onStartUpdateBatchInputTimer()} of this * listener may be called if enough move points have been added. * @return true if this move event occurs on the valid gesture area. */ public boolean addMoveEventPoint(final int x, final int y, final long moveEventTime, final boolean isMajorEvent, final BatchInputArbiterListener listener) { final int beforeLength = mRecognitionPoints.getLength(); final boolean onValidArea = mRecognitionPoints.addEventPoint( x, y, getElapsedTimeSinceFirstDown(moveEventTime), isMajorEvent); if (mRecognitionPoints.getLength() > beforeLength) { listener.onStartUpdateBatchInputTimer(); } return onValidArea; } /** * Determine whether the batch input has started or not. * @param listener {@link BatchInputArbiterListener#onStartBatchInput()} of this * listener will be called when the batch input has started successfully. * @return true if the batch input has started successfully. */ public boolean mayStartBatchInput(final BatchInputArbiterListener listener) { if (!mRecognitionPoints.isStartOfAGesture()) { return false; } synchronized (sAggregatedPointers) { sAggregatedPointers.reset(); sLastRecognitionPointSize = 0; sLastRecognitionTime = 0; listener.onStartBatchInput(); } return true; } /** * Add synthetic move event point. After adding the point, * {@link #updateBatchInput(long,BatchInputArbiterListener)} will be called internally. * @param syntheticMoveEventTime the synthetic move event time. * @param listener the listener to be passed to * {@link #updateBatchInput(long,BatchInputArbiterListener)}. */ public void updateBatchInputByTimer(final long syntheticMoveEventTime, final BatchInputArbiterListener listener) { mRecognitionPoints.duplicateLastPointWith( getElapsedTimeSinceFirstDown(syntheticMoveEventTime)); updateBatchInput(syntheticMoveEventTime, listener); } /** * Determine whether we have enough gesture points to lookup dictionary. * @param moveEventTime the time of this move event. * @param listener {@link BatchInputArbiterListener#onUpdateBatchInput(InputPointers,long)} of * this listener will be called when enough event points we have. Also * {@link BatchInputArbiterListener#onStartUpdateBatchInputTimer()} will be called to have * possible future synthetic move event. */ public void updateBatchInput(final long moveEventTime, final BatchInputArbiterListener listener) { synchronized (sAggregatedPointers) { mRecognitionPoints.appendIncrementalBatchPoints(sAggregatedPointers); final int size = sAggregatedPointers.getPointerSize(); if (size > sLastRecognitionPointSize && mRecognitionPoints.hasRecognitionTimePast( moveEventTime, sLastRecognitionTime)) { listener.onUpdateBatchInput(sAggregatedPointers, moveEventTime); listener.onStartUpdateBatchInputTimer(); // The listener may change the size of the pointers (when auto-committing // for example), so we need to get the size from the pointers again. sLastRecognitionPointSize = sAggregatedPointers.getPointerSize(); sLastRecognitionTime = moveEventTime; } } } /** * Determine whether the batch input has ended successfully or continues. * @param upEventTime the time of this up event. * @param activePointerCount the number of active pointers when this pointer up event occurs. * @param listener {@link BatchInputArbiterListener#onEndBatchInput(InputPointers,long)} of this * listener will be called when the batch input has started successfully. * @return true if the batch input has ended successfully. */ public boolean mayEndBatchInput(final long upEventTime, final int activePointerCount, final BatchInputArbiterListener listener) { synchronized (sAggregatedPointers) { mRecognitionPoints.appendAllBatchPoints(sAggregatedPointers); if (activePointerCount == 1) { listener.onEndBatchInput(sAggregatedPointers, upEventTime); return true; } } return false; } }