Merge "Abandon the idea of an Event pool and make Event immutable (B1)"
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 130fad8..625575d 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -366,6 +366,11 @@
         }
 
         @Override
+        public void cancelUpdateBatchInputTimer(final PointerTracker tracker) {
+            removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
+        }
+
+        @Override
         public void cancelAllUpdateBatchInputTimers() {
             removeMessages(MSG_UPDATE_BATCH_INPUT);
         }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 59a3c99..62e674a 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -98,6 +98,7 @@
         public boolean isInDoubleTapTimeout();
         public void cancelKeyTimers();
         public void startUpdateBatchInputTimer(PointerTracker tracker);
+        public void cancelUpdateBatchInputTimer(PointerTracker tracker);
         public void cancelAllUpdateBatchInputTimers();
 
         public static class Adapter implements TimerProxy {
@@ -124,6 +125,8 @@
             @Override
             public void startUpdateBatchInputTimer(PointerTracker tracker) {}
             @Override
+            public void cancelUpdateBatchInputTimer(PointerTracker tracker) {}
+            @Override
             public void cancelAllUpdateBatchInputTimers() {}
         }
     }
@@ -791,6 +794,7 @@
 
     private void cancelBatchInput() {
         sPointerTrackerQueue.cancelAllPointerTracker();
+        mIsDetectingGesture = false;
         if (!sInGesture) {
             return;
         }
@@ -918,6 +922,7 @@
         if (mIsDetectingGesture) {
             final boolean onValidArea = mGestureStrokeWithPreviewPoints.addPointOnKeyboard(
                     x, y, gestureTime, isMajorEvent);
+            // If the move event goes out from valid batch input area, cancel batch input.
             if (!onValidArea) {
                 cancelBatchInput();
                 return;
@@ -938,6 +943,7 @@
         if (DEBUG_MOVE_EVENT) {
             printTouchEvent("onMoveEvent:", x, y, eventTime);
         }
+        mTimerProxy.cancelUpdateBatchInputTimer(this);
         if (mIsTrackingCanceled) {
             return;
         }
@@ -1122,6 +1128,7 @@
             printTouchEvent("onUpEvent  :", x, y, eventTime);
         }
 
+        mTimerProxy.cancelUpdateBatchInputTimer(this);
         if (!sInGesture) {
             if (mCurrentKey != null && mCurrentKey.isModifier()) {
                 // Before processing an up event of modifier key, all pointers already being
@@ -1196,6 +1203,9 @@
 
     @Override
     public void cancelTracking() {
+        if (isShowingMoreKeysPanel()) {
+            return;
+        }
         mIsTrackingCanceled = true;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index a43e94a..adf2236 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -233,6 +233,10 @@
         if (lastIndex >= 0) {
             final int x = mXCoordinates.get(lastIndex);
             final int y = mYCoordinates.get(lastIndex);
+            if (DEBUG) {
+                Log.d(TAG, String.format("[%d] duplicateLastPointWith: %d,%d|%d", mPointerId,
+                        x, y, time));
+            }
             // TODO: Have appendMajorPoint()
             appendPoint(x, y, time);
             updateIncrementalRecognitionSize(x, y, time);
@@ -251,6 +255,16 @@
     }
 
     private void appendPoint(final int x, final int y, final int time) {
+        final int lastIndex = mEventTimes.getLength() - 1;
+        // The point that is created by {@link duplicateLastPointWith(int)} may have later event
+        // time than the next {@link MotionEvent}. To maintain the monotonicity of the event time,
+        // drop the successive point here.
+        if (lastIndex >= 0 && mEventTimes.get(lastIndex) > time) {
+            Log.w(TAG, String.format("[%d] drop stale event: %d,%d|%d last: %d,%d|%d", mPointerId,
+                    x, y, time, mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex),
+                    mEventTimes.get(lastIndex)));
+            return;
+        }
         mEventTimes.add(time);
         mXCoordinates.add(x);
         mYCoordinates.add(y);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 6eeee9c..3b0112b 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1717,6 +1717,11 @@
                 // If there is a selection, remove it.
                 final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
                 mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
+                // Reset mLastSelectionEnd to mLastSelectionStart. This is what is supposed to
+                // happen, and if it's wrong, the next call to onUpdateSelection will correct it,
+                // but we want to set it right away to avoid it being used with the wrong values
+                // later (typically, in a subsequent press on backspace).
+                mLastSelectionEnd = mLastSelectionStart;
                 mConnection.deleteSurroundingText(lengthToDelete, 0);
             } else {
                 // There is no selection, just delete one character.
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index c99f291..75f85b1 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -66,11 +66,15 @@
 
 ifeq ($(FLAG_DO_PROFILE), true)
     $(warning Making profiling version of native library)
-    LOCAL_CFLAGS += -DFLAG_DO_PROFILE
+    LOCAL_CFLAGS += -DFLAG_DO_PROFILE -funwind-tables
 else # FLAG_DO_PROFILE
 ifeq ($(FLAG_DBG), true)
     $(warning Making debug version of native library)
-    LOCAL_CFLAGS += -DFLAG_DBG
+    LOCAL_CFLAGS += -DFLAG_DBG -funwind-tables
+ifeq ($(FLAG_FULL_DBG), true)
+    $(warning Making full debug version of native library)
+    LOCAL_CFLAGS += -DFLAG_FULL_DBG
+endif # FLAG_FULL_DBG
 endif # FLAG_DBG
 endif # FLAG_DO_PROFILE
 
diff --git a/native/jni/jni_common.cpp b/native/jni/jni_common.cpp
index 7b97cf4..1ea2041 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -16,8 +16,6 @@
 
 #define LOG_TAG "LatinIME: jni"
 
-#include <cassert>
-
 #include "com_android_inputmethod_keyboard_ProximityInfo.h"
 #include "com_android_inputmethod_latin_BinaryDictionary.h"
 #include "com_android_inputmethod_latin_DicTraverseSession.h"
@@ -35,7 +33,7 @@
         AKLOGE("ERROR: GetEnv failed");
         return -1;
     }
-    assert(env);
+    ASSERT(env);
     if (!env) {
         AKLOGE("ERROR: JNIEnv is invalid");
         return -1;
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
index 24221c9..a0256ee 100644
--- a/native/jni/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -247,7 +247,7 @@
         if (mSkippedCount == 0 && mSkipPos < mOutputIndex) {
             if (DEBUG_DICT) {
                 // TODO: Enable this assertion.
-                //assert(mSkipPos == mOutputIndex - 1);
+                //ASSERT(mSkipPos == mOutputIndex - 1);
             }
             mSkipPos = mOutputIndex;
         }
diff --git a/native/jni/src/correction.h b/native/jni/src/correction.h
index 0469355..8c47771 100644
--- a/native/jni/src/correction.h
+++ b/native/jni/src/correction.h
@@ -17,7 +17,6 @@
 #ifndef LATINIME_CORRECTION_H
 #define LATINIME_CORRECTION_H
 
-#include <cassert>
 #include <cstring> // for memset()
 
 #include "correction_state.h"
@@ -150,7 +149,7 @@
             // Branch if multiplier == 2 for the optimization
             if (multiplier < 0) {
                 if (DEBUG_DICT) {
-                    assert(false);
+                    ASSERT(false);
                 }
                 AKLOGI("--- Invalid multiplier: %d", multiplier);
             } else if (multiplier == 0) {
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 46595d8..96abfe8 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -93,6 +93,7 @@
 #include <execinfo.h>
 #include <stdlib.h>
 
+#define DO_ASSERT_TEST
 #define ASSERT(success) do { if (!(success)) { showStackTrace(); assert(success);} } while (0)
 #define SHOW_STACK_TRACE do { showStackTrace(); } while (0)
 
@@ -111,6 +112,7 @@
 }
 #else
 #include <cassert>
+#define DO_ASSERT_TEST
 #define ASSERT(success) assert(success)
 #define SHOW_STACK_TRACE
 #endif
@@ -120,6 +122,7 @@
 #define AKLOGI(fmt, ...)
 #define DUMP_RESULT(words, frequencies, maxWordCount, maxWordLength)
 #define DUMP_WORD(word, length)
+#undef DO_ASSERT_TEST
 #define ASSERT(success)
 #define SHOW_STACK_TRACE
 #define INTS_TO_CHARS(input, length, output)
@@ -349,6 +352,9 @@
 
 #define MAX_SPACES_INTERNAL 16
 
+// TODO: Change this to MAX_WORDS, remove MAX_WORDS in Java, and stop getting it from Java
+#define MAX_WORDS_INTERNAL 18
+
 // Max Distance between point to key
 #define MAX_POINT_TO_KEY_LENGTH 10000000
 
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
index ffe12ce..8ad9c77 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <cassert>
 #include <cstring>
 
 #define LOG_TAG "LatinIME: proximity_info.cpp"
@@ -75,7 +74,7 @@
     const jsize localeCStrUtf8Length = env->GetStringUTFLength(localeJStr);
     if (localeCStrUtf8Length >= MAX_LOCALE_STRING_LENGTH) {
         AKLOGI("Locale string length too long: length=%d", localeCStrUtf8Length);
-        assert(false);
+        ASSERT(false);
     }
     memset(mLocaleStr, 0, sizeof(mLocaleStr));
     env->GetStringUTFRegion(localeJStr, 0, env->GetStringLength(localeJStr), mLocaleStr);
@@ -105,7 +104,7 @@
         if (DEBUG_DICT) {
             AKLOGI("HasSpaceProximity: Illegal coordinates (%d, %d)", x, y);
             // TODO: Enable this assertion.
-            //assert(false);
+            //ASSERT(false);
         }
         return false;
     }
@@ -180,7 +179,7 @@
                 inputCodes[insertPos++] = c;
                 if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
                     if (DEBUG_DICT) {
-                        assert(false);
+                        ASSERT(false);
                     }
                     return;
                 }
@@ -192,7 +191,7 @@
             inputCodes[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
             if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
                 if (DEBUG_DICT) {
-                    assert(false);
+                    ASSERT(false);
                 }
                 return;
             }
@@ -213,7 +212,7 @@
                 inputCodes[insertPos++] = ac;
                 if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
                     if (DEBUG_DICT) {
-                        assert(false);
+                        ASSERT(false);
                     }
                     return;
                 }
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index bd2149a..5362d69 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -123,6 +123,15 @@
                 }
             }
         }
+#ifdef DO_ASSERT_TEST
+        if (times) {
+            for (int i = 0; i < inputSize; ++i) {
+                if (i > 0) {
+                    ASSERT(times[i] >= times[i - 1]);
+                }
+            }
+        }
+#endif
         const bool proximityOnly = !isGeometric && (xCoordinates[0] < 0 || yCoordinates[0] < 0);
         int lastInputIndex = pushTouchPointStartIndex;
         for (int i = lastInputIndex; i < inputSize; ++i) {
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
index ebeef13..0a14425 100644
--- a/native/jni/src/unigram_dictionary.cpp
+++ b/native/jni/src/unigram_dictionary.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <cassert>
 #include <cstring>
 
 #define LOG_TAG "LatinIME: unigram_dictionary.cpp"
@@ -100,9 +99,9 @@
         const int codesRemain, const int currentDepth, int *codesDest, Correction *correction,
         WordsPriorityQueuePool *queuePool,
         const digraph_t *const digraphs, const unsigned int digraphsSize) const {
-    assert(sizeof(codesDest[0]) == sizeof(codesSrc[0]));
-    assert(sizeof(xCoordinatesBuffer[0]) == sizeof(xcoordinates[0]));
-    assert(sizeof(yCoordinatesBuffer[0]) == sizeof(ycoordinates[0]));
+    ASSERT(sizeof(codesDest[0]) == sizeof(codesSrc[0]));
+    ASSERT(sizeof(xCoordinatesBuffer[0]) == sizeof(xcoordinates[0]));
+    ASSERT(sizeof(yCoordinatesBuffer[0]) == sizeof(ycoordinates[0]));
 
     const int startIndex = static_cast<int>(codesDest - codesBuffer);
     if (currentDepth < MAX_DIGRAPH_SEARCH_DEPTH) {
@@ -894,7 +893,7 @@
     // else if MASK_GROUP_ADDRESS_TYPE is not NONE: the children address
     // Note that you can't have a node that both is not a terminal and has no children.
     int c = BinaryFormat::getCodePointAndForwardPointer(DICT_ROOT, &pos);
-    assert(NOT_A_CODE_POINT != c);
+    ASSERT(NOT_A_CODE_POINT != c);
 
     // We are going to loop through each character and make it look like it's a different
     // node each time. To do that, we will process characters in this node in order until
@@ -987,7 +986,7 @@
 
     // Now we finished processing this node, and we want to traverse children. If there are no
     // children, we can't come here.
-    assert(BinaryFormat::hasChildrenInFlags(flags));
+    ASSERT(BinaryFormat::hasChildrenInFlags(flags));
 
     // If this node was a terminal it still has the frequency under the pointer (it may have been
     // read, but not skipped - see readFrequencyWithoutMovingPointer).
diff --git a/native/jni/src/words_priority_queue_pool.h b/native/jni/src/words_priority_queue_pool.h
index c14afa0..f7c08fb 100644
--- a/native/jni/src/words_priority_queue_pool.h
+++ b/native/jni/src/words_priority_queue_pool.h
@@ -17,7 +17,7 @@
 #ifndef LATINIME_WORDS_PRIORITY_QUEUE_POOL_H
 #define LATINIME_WORDS_PRIORITY_QUEUE_POOL_H
 
-#include <cassert>
+#include "defines.h"
 #include "words_priority_queue.h"
 
 namespace latinime {
@@ -55,7 +55,7 @@
         }
         if (inputWordLength < 0 || inputWordLength >= SUB_QUEUE_MAX_COUNT) {
             if (DEBUG_WORDS_PRIORITY_QUEUE) {
-                assert(false);
+                ASSERT(false);
             }
             return 0;
         }
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index 38f5305..6412a9d 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -102,6 +102,27 @@
         assertEquals("delete selection", EXPECTED_RESULT, mTextView.getText().toString());
     }
 
+    public void testDeleteSelectionTwice() {
+        final String STRING_TO_TYPE = "some text delete me some text";
+        final int typedLength = STRING_TO_TYPE.length();
+        final int SELECTION_START = 10;
+        final int SELECTION_END = 19;
+        final String EXPECTED_RESULT = "some text some text";
+        type(STRING_TO_TYPE);
+        // There is no IMF to call onUpdateSelection for us so we must do it by hand.
+        // Send once to simulate the cursor actually responding to the move caused by typing.
+        // This is necessary because LatinIME is bookkeeping to avoid confusing a real cursor
+        // move with a move triggered by LatinIME inputting stuff.
+        mLatinIME.onUpdateSelection(0, 0, typedLength, typedLength, -1, -1);
+        mInputConnection.setSelection(SELECTION_START, SELECTION_END);
+        // And now we simulate the user actually selecting some text.
+        mLatinIME.onUpdateSelection(typedLength, typedLength,
+                SELECTION_START, SELECTION_END, -1, -1);
+        type(Constants.CODE_DELETE);
+        type(Constants.CODE_DELETE);
+        assertEquals("delete selection twice", EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
     public void testAutoCorrect() {
         final String STRING_TO_TYPE = "tgis ";
         final String EXPECTED_RESULT = "this ";