diff --git a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
index 4f3f4d25f17e434f1f41fd8d48c25f6bfd317e73..88c70630d455e3096d4a9d97f7039112f441f4f1 100644
--- a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
+++ b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
@@ -17,10 +17,12 @@
 package com.android.inputmethod.event;
 
 import android.text.TextUtils;
+import android.util.SparseIntArray;
 import android.view.KeyCharacterMap;
 
 import com.android.inputmethod.latin.Constants;
 
+import java.text.Normalizer;
 import java.util.ArrayList;
 
 import javax.annotation.Nonnull;
@@ -29,9 +31,209 @@ import javax.annotation.Nonnull;
  * A combiner that handles dead keys.
  */
 public class DeadKeyCombiner implements Combiner {
+
+    private static class Data {
+        // This class data taken from KeyCharacterMap.java.
+
+        /* Characters used to display placeholders for dead keys. */
+        private static final int ACCENT_ACUTE = '\u00B4';
+        private static final int ACCENT_BREVE = '\u02D8';
+        private static final int ACCENT_CARON = '\u02C7';
+        private static final int ACCENT_CEDILLA = '\u00B8';
+        private static final int ACCENT_CIRCUMFLEX = '\u02C6';
+        private static final int ACCENT_COMMA_ABOVE = '\u1FBD';
+        private static final int ACCENT_COMMA_ABOVE_RIGHT = '\u02BC';
+        private static final int ACCENT_DOT_ABOVE = '\u02D9';
+        private static final int ACCENT_DOT_BELOW = Constants.CODE_PERIOD; // approximate
+        private static final int ACCENT_DOUBLE_ACUTE = '\u02DD';
+        private static final int ACCENT_GRAVE = '\u02CB';
+        private static final int ACCENT_HOOK_ABOVE = '\u02C0';
+        private static final int ACCENT_HORN = Constants.CODE_SINGLE_QUOTE; // approximate
+        private static final int ACCENT_MACRON = '\u00AF';
+        private static final int ACCENT_MACRON_BELOW = '\u02CD';
+        private static final int ACCENT_OGONEK = '\u02DB';
+        private static final int ACCENT_REVERSED_COMMA_ABOVE = '\u02BD';
+        private static final int ACCENT_RING_ABOVE = '\u02DA';
+        private static final int ACCENT_STROKE = Constants.CODE_DASH; // approximate
+        private static final int ACCENT_TILDE = '\u02DC';
+        private static final int ACCENT_TURNED_COMMA_ABOVE = '\u02BB';
+        private static final int ACCENT_UMLAUT = '\u00A8';
+        private static final int ACCENT_VERTICAL_LINE_ABOVE = '\u02C8';
+        private static final int ACCENT_VERTICAL_LINE_BELOW = '\u02CC';
+
+        /* Legacy dead key display characters used in previous versions of the API (before L)
+         * We still support these characters by mapping them to their non-legacy version. */
+        private static final int ACCENT_GRAVE_LEGACY = Constants.CODE_GRAVE_ACCENT;
+        private static final int ACCENT_CIRCUMFLEX_LEGACY = Constants.CODE_CIRCUMFLEX_ACCENT;
+        private static final int ACCENT_TILDE_LEGACY = Constants.CODE_TILDE;
+
+        /**
+         * Maps Unicode combining diacritical to display-form dead key.
+         */
+        private static final SparseIntArray sCombiningToAccent = new SparseIntArray();
+        private static final SparseIntArray sAccentToCombining = new SparseIntArray();
+        static {
+            // U+0300: COMBINING GRAVE ACCENT
+            addCombining('\u0300', ACCENT_GRAVE);
+            // U+0301: COMBINING ACUTE ACCENT
+            addCombining('\u0301', ACCENT_ACUTE);
+            // U+0302: COMBINING CIRCUMFLEX ACCENT
+            addCombining('\u0302', ACCENT_CIRCUMFLEX);
+            // U+0303: COMBINING TILDE
+            addCombining('\u0303', ACCENT_TILDE);
+            // U+0304: COMBINING MACRON
+            addCombining('\u0304', ACCENT_MACRON);
+            // U+0306: COMBINING BREVE
+            addCombining('\u0306', ACCENT_BREVE);
+            // U+0307: COMBINING DOT ABOVE
+            addCombining('\u0307', ACCENT_DOT_ABOVE);
+            // U+0308: COMBINING DIAERESIS
+            addCombining('\u0308', ACCENT_UMLAUT);
+            // U+0309: COMBINING HOOK ABOVE
+            addCombining('\u0309', ACCENT_HOOK_ABOVE);
+            // U+030A: COMBINING RING ABOVE
+            addCombining('\u030A', ACCENT_RING_ABOVE);
+            // U+030B: COMBINING DOUBLE ACUTE ACCENT
+            addCombining('\u030B', ACCENT_DOUBLE_ACUTE);
+            // U+030C: COMBINING CARON
+            addCombining('\u030C', ACCENT_CARON);
+            // U+030D: COMBINING VERTICAL LINE ABOVE
+            addCombining('\u030D', ACCENT_VERTICAL_LINE_ABOVE);
+            // U+030E: COMBINING DOUBLE VERTICAL LINE ABOVE
+            //addCombining('\u030E', ACCENT_DOUBLE_VERTICAL_LINE_ABOVE);
+            // U+030F: COMBINING DOUBLE GRAVE ACCENT
+            //addCombining('\u030F', ACCENT_DOUBLE_GRAVE);
+            // U+0310: COMBINING CANDRABINDU
+            //addCombining('\u0310', ACCENT_CANDRABINDU);
+            // U+0311: COMBINING INVERTED BREVE
+            //addCombining('\u0311', ACCENT_INVERTED_BREVE);
+            // U+0312: COMBINING TURNED COMMA ABOVE
+            addCombining('\u0312', ACCENT_TURNED_COMMA_ABOVE);
+            // U+0313: COMBINING COMMA ABOVE
+            addCombining('\u0313', ACCENT_COMMA_ABOVE);
+            // U+0314: COMBINING REVERSED COMMA ABOVE
+            addCombining('\u0314', ACCENT_REVERSED_COMMA_ABOVE);
+            // U+0315: COMBINING COMMA ABOVE RIGHT
+            addCombining('\u0315', ACCENT_COMMA_ABOVE_RIGHT);
+            // U+031B: COMBINING HORN
+            addCombining('\u031B', ACCENT_HORN);
+            // U+0323: COMBINING DOT BELOW
+            addCombining('\u0323', ACCENT_DOT_BELOW);
+            // U+0326: COMBINING COMMA BELOW
+            //addCombining('\u0326', ACCENT_COMMA_BELOW);
+            // U+0327: COMBINING CEDILLA
+            addCombining('\u0327', ACCENT_CEDILLA);
+            // U+0328: COMBINING OGONEK
+            addCombining('\u0328', ACCENT_OGONEK);
+            // U+0329: COMBINING VERTICAL LINE BELOW
+            addCombining('\u0329', ACCENT_VERTICAL_LINE_BELOW);
+            // U+0331: COMBINING MACRON BELOW
+            addCombining('\u0331', ACCENT_MACRON_BELOW);
+            // U+0335: COMBINING SHORT STROKE OVERLAY
+            addCombining('\u0335', ACCENT_STROKE);
+            // U+0342: COMBINING GREEK PERISPOMENI
+            //addCombining('\u0342', ACCENT_PERISPOMENI);
+            // U+0344: COMBINING GREEK DIALYTIKA TONOS
+            //addCombining('\u0344', ACCENT_DIALYTIKA_TONOS);
+            // U+0345: COMBINING GREEK YPOGEGRAMMENI
+            //addCombining('\u0345', ACCENT_YPOGEGRAMMENI);
+
+            // One-way mappings to equivalent preferred accents.
+            // U+0340: COMBINING GRAVE TONE MARK
+            sCombiningToAccent.append('\u0340', ACCENT_GRAVE);
+            // U+0341: COMBINING ACUTE TONE MARK
+            sCombiningToAccent.append('\u0341', ACCENT_ACUTE);
+            // U+0343: COMBINING GREEK KORONIS
+            sCombiningToAccent.append('\u0343', ACCENT_COMMA_ABOVE);
+
+            // One-way legacy mappings to preserve compatibility with older applications.
+            // U+0300: COMBINING GRAVE ACCENT
+            sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300');
+            // U+0302: COMBINING CIRCUMFLEX ACCENT
+            sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302');
+            // U+0303: COMBINING TILDE
+            sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303');
+        }
+
+        private static void addCombining(int combining, int accent) {
+            sCombiningToAccent.append(combining, accent);
+            sAccentToCombining.append(accent, combining);
+        }
+
+        // Caution! This may only contain chars, not supplementary code points. It's unlikely
+        // it will ever need to, but if it does we'll have to change this
+        private static final SparseIntArray sNonstandardDeadCombinations = new SparseIntArray();
+        static {
+            // Non-standard decompositions.
+            // Stroke modifier for Finnish multilingual keyboard and others.
+            // U+0110: LATIN CAPITAL LETTER D WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 'D', '\u0110');
+            // U+01E4: LATIN CAPITAL LETTER G WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 'G', '\u01e4');
+            // U+0126: LATIN CAPITAL LETTER H WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 'H', '\u0126');
+            // U+0197: LATIN CAPITAL LETTER I WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 'I', '\u0197');
+            // U+0141: LATIN CAPITAL LETTER L WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 'L', '\u0141');
+            // U+00D8: LATIN CAPITAL LETTER O WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 'O', '\u00d8');
+            // U+0166: LATIN CAPITAL LETTER T WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 'T', '\u0166');
+            // U+0111: LATIN SMALL LETTER D WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 'd', '\u0111');
+            // U+01E5: LATIN SMALL LETTER G WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 'g', '\u01e5');
+            // U+0127: LATIN SMALL LETTER H WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 'h', '\u0127');
+            // U+0268: LATIN SMALL LETTER I WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 'i', '\u0268');
+            // U+0142: LATIN SMALL LETTER L WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 'l', '\u0142');
+            // U+00F8: LATIN SMALL LETTER O WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 'o', '\u00f8');
+            // U+0167: LATIN SMALL LETTER T WITH STROKE
+            addNonStandardDeadCombination(ACCENT_STROKE, 't', '\u0167');
+        }
+
+        private static void addNonStandardDeadCombination(final int deadCodePoint,
+                final int spacingCodePoint, final int result) {
+            final int combination = (deadCodePoint << 16) | spacingCodePoint;
+            sNonstandardDeadCombinations.put(combination, result);
+        }
+
+        public static final int NOT_A_CHAR = 0;
+        public static final int BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION = 16;
+        // Get a non-standard combination
+        public static char getNonstandardCombination(final int deadCodePoint,
+                final int spacingCodePoint) {
+            final int combination = spacingCodePoint |
+                    (deadCodePoint << BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION);
+            return (char)sNonstandardDeadCombinations.get(combination, NOT_A_CHAR);
+        }
+    }
+
     // TODO: make this a list of events instead
     final StringBuilder mDeadSequence = new StringBuilder();
 
+    @Nonnull
+    private Event createEventChainFromSequence(final @Nonnull CharSequence text,
+            final Event originalEvent) {
+        if (text.length() <= 0) {
+            return originalEvent;
+        } else {
+            Event lastEvent = null;
+            int codePoint = 0;
+            for (int i = text.length(); i > 0; i -= Character.charCount(codePoint)) {
+                codePoint = Character.codePointBefore(text, i);
+                final Event thisEvent = Event.createHardwareKeypressEvent(codePoint,
+                        originalEvent.mKeyCode, lastEvent, false /* isKeyRepeat */);
+                lastEvent = thisEvent;
+            }
+            return lastEvent;
+        }
+    }
+
     @Override
     @Nonnull
     public Event processEvent(final ArrayList<Event> previousEvents, final Event event) {
@@ -47,29 +249,48 @@ public class DeadKeyCombiner implements Combiner {
             // simply returns the event as is. The majority of events will go through this path.
             return event;
         } else {
-            // TODO: Allow combining for several dead chars rather than only the first one.
-            // The framework doesn't know how to do this now.
-            final int deadCodePoint = mDeadSequence.codePointAt(0);
-            mDeadSequence.setLength(0);
-            final int resultingCodePoint =
-                    KeyCharacterMap.getDeadChar(deadCodePoint, event.mCodePoint);
-            if (0 == resultingCodePoint) {
-                // We can't combine both characters. We need to commit the dead key as a separate
-                // character, and the next char too unless it's a space (because as a special case,
-                // dead key + space should result in only the dead key being committed - that's
-                // how dead keys work).
-                // If the event is a space, we should commit the dead char alone, but if it's
-                // not, we need to commit both.
-                // TODO: this is not necessarily triggered by hardware key events, so it's not
-                // a good idea to masquerade as one. This should be typed as a software
-                // composite event or something.
-                return Event.createHardwareKeypressEvent(deadCodePoint, event.mKeyCode,
-                        Constants.CODE_SPACE == event.mCodePoint ? null : event /* next */,
-                        false /* isKeyRepeat */);
+            if (Character.isWhitespace(event.mCodePoint)
+                    || event.mCodePoint == mDeadSequence.codePointBefore(mDeadSequence.length())) {
+                // When whitespace or twice the same dead key, we should output the dead sequence
+                // as is.
+                final Event resultEvent = createEventChainFromSequence(mDeadSequence.toString(),
+                        event);
+                mDeadSequence.setLength(0);
+                return resultEvent;
+            } else if (event.isFunctionalKeyEvent()) {
+                if (Constants.CODE_DELETE == event.mKeyCode) {
+                    // Remove the last code point
+                    final int trimIndex = mDeadSequence.length() - Character.charCount(
+                            mDeadSequence.codePointBefore(mDeadSequence.length()));
+                    mDeadSequence.setLength(trimIndex);
+                    return Event.createConsumedEvent(event);
+                } else {
+                    return event;
+                }
+            } else if (event.isDead()) {
+                mDeadSequence.appendCodePoint(event.mCodePoint);
+                return Event.createConsumedEvent(event);
             } else {
-                // We could combine the characters.
-                return Event.createHardwareKeypressEvent(resultingCodePoint, event.mKeyCode,
-                        null /* next */, false /* isKeyRepeat */);
+                // Combine normally.
+                final StringBuilder sb = new StringBuilder();
+                sb.appendCodePoint(event.mCodePoint);
+                int codePointIndex = 0;
+                while (codePointIndex < mDeadSequence.length()) {
+                    final int deadCodePoint = mDeadSequence.codePointAt(codePointIndex);
+                    final char replacementSpacingChar =
+                            Data.getNonstandardCombination(deadCodePoint, event.mCodePoint);
+                    if (Data.NOT_A_CHAR != replacementSpacingChar) {
+                        sb.setCharAt(0, replacementSpacingChar);
+                    } else {
+                        final int combining = Data.sAccentToCombining.get(deadCodePoint);
+                        sb.appendCodePoint(0 == combining ? deadCodePoint : combining);
+                    }
+                    codePointIndex += Character.isSupplementaryCodePoint(deadCodePoint) ? 2 : 1;
+                }
+                final String normalizedString = Normalizer.normalize(sb, Normalizer.Form.NFC);
+                final Event resultEvent = createEventChainFromSequence(normalizedString, event);
+                mDeadSequence.setLength(0);
+                return resultEvent;
             }
         }
     }
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index ef5b04747980205b207c0265b6f5cfaf36e07b18..ff6f880662d1adc7d61a9f4d23f3830d31319ae4 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.event;
 
+import com.android.inputmethod.annotations.ExternallyReferenced;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.utils.StringUtils;
@@ -147,6 +148,7 @@ public class Event {
     }
 
     // This creates an input event for a dead character. @see {@link #FLAG_DEAD}
+    @ExternallyReferenced
     public static Event createDeadEvent(final int codePoint, final int keyCode, final Event next) {
         // TODO: add an argument or something if we ever create a software layout with dead keys.
         return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 43af66eb70464144e69cd45e4daa12f5e8fcabad..729481326dc4935e3347d3261a3b3ab6c789c28b 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -217,6 +217,9 @@ public final class Constants {
     public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
     public static final int CODE_INVERTED_QUESTION_MARK = 0xBF; // ¿
     public static final int CODE_INVERTED_EXCLAMATION_MARK = 0xA1; // ¡
+    public static final int CODE_GRAVE_ACCENT = '`';
+    public static final int CODE_CIRCUMFLEX_ACCENT = '^';
+    public static final int CODE_TILDE = '~';
 
     public static final String REGEXP_PERIOD = "\\.";
     public static final String STRING_SPACE = " ";
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index 38f0b3fee5ab39086d5ed7134c2729ef59bc7cc9..55557de9d308b0bf5235cd3c0e87a60e7c901aec 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -41,9 +41,9 @@ public final class StringUtils {
         // This utility class is not publicly instantiable.
     }
 
-    public static int codePointCount(final String text) {
+    public static int codePointCount(final CharSequence text) {
         if (TextUtils.isEmpty(text)) return 0;
-        return text.codePointCount(0, text.length());
+        return Character.codePointCount(text, 0, text.length());
     }
 
     public static String newSingleCodePointString(int codePoint) {
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTestsDeadKeys.java b/tests/src/com/android/inputmethod/latin/InputLogicTestsDeadKeys.java
new file mode 100644
index 0000000000000000000000000000000000000000..afe7dbe70d1a194cd3f8206be9edad21cbb0b474
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTestsDeadKeys.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2014 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.latin;
+
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.inputmethod.event.Event;
+
+import java.util.ArrayList;
+
+@LargeTest
+public class InputLogicTestsDeadKeys extends InputTestsBase {
+    // A helper class for readability
+    private static class EventList extends ArrayList<Event> {
+        public EventList addCodePoint(final int codePoint, final boolean isDead) {
+            final Event event;
+            if (isDead) {
+                event = Event.createDeadEvent(codePoint, Event.NOT_A_KEY_CODE, null /* next */);
+            } else {
+                event = Event.createSoftwareKeypressEvent(codePoint, Event.NOT_A_KEY_CODE,
+                        Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+                        false /* isKeyRepeat */);
+            }
+            add(event);
+            return this;
+        }
+
+        public EventList addKey(final int keyCode) {
+            add(Event.createSoftwareKeypressEvent(Event.NOT_A_CODE_POINT, keyCode,
+                    Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+                    false /* isKeyRepeat */));
+            return this;
+        }
+    }
+
+    public void testDeadCircumflexSimple() {
+        final int MODIFIER_LETTER_CIRCUMFLEX_ACCENT = 0x02C6;
+        final String EXPECTED_RESULT = "aê";
+        final EventList events = new EventList()
+                .addCodePoint('a', false)
+                .addCodePoint(MODIFIER_LETTER_CIRCUMFLEX_ACCENT, true)
+                .addCodePoint('e', false);
+        for (final Event event : events) {
+            mLatinIME.onEvent(event);
+        }
+        assertEquals("simple dead circumflex", EXPECTED_RESULT, mEditText.getText().toString());
+    }
+
+    public void testDeadCircumflexBackspace() {
+        final int MODIFIER_LETTER_CIRCUMFLEX_ACCENT = 0x02C6;
+        final String EXPECTED_RESULT = "ae";
+        final EventList events = new EventList()
+                .addCodePoint('a', false)
+                .addCodePoint(MODIFIER_LETTER_CIRCUMFLEX_ACCENT, true)
+                .addKey(Constants.CODE_DELETE)
+                .addCodePoint('e', false);
+        for (final Event event : events) {
+            mLatinIME.onEvent(event);
+        }
+        assertEquals("dead circumflex backspace", EXPECTED_RESULT, mEditText.getText().toString());
+    }
+
+    public void testDeadCircumflexFeedback() {
+        final int MODIFIER_LETTER_CIRCUMFLEX_ACCENT = 0x02C6;
+        final String EXPECTED_RESULT = "a\u02C6";
+        final EventList events = new EventList()
+                .addCodePoint('a', false)
+                .addCodePoint(MODIFIER_LETTER_CIRCUMFLEX_ACCENT, true);
+        for (final Event event : events) {
+            mLatinIME.onEvent(event);
+        }
+        assertEquals("dead circumflex gives feedback", EXPECTED_RESULT,
+                mEditText.getText().toString());
+    }
+
+    public void testDeadDiaeresisSpace() {
+        final int MODIFIER_LETTER_DIAERESIS = 0xA8;
+        final String EXPECTED_RESULT = "a\u00A8e\u00A8i";
+        final EventList events = new EventList()
+                .addCodePoint('a', false)
+                .addCodePoint(MODIFIER_LETTER_DIAERESIS, true)
+                .addCodePoint(Constants.CODE_SPACE, false)
+                .addCodePoint('e', false)
+                .addCodePoint(MODIFIER_LETTER_DIAERESIS, true)
+                .addCodePoint(Constants.CODE_ENTER, false)
+                .addCodePoint('i', false);
+        for (final Event event : events) {
+            mLatinIME.onEvent(event);
+        }
+        assertEquals("dead diaeresis space commits the dead char", EXPECTED_RESULT,
+                mEditText.getText().toString());
+    }
+
+    public void testDeadAcuteLetterBackspace() {
+        final int MODIFIER_LETTER_ACUTE = 0xB4;
+        final String EXPECTED_RESULT1 = "aá";
+        final String EXPECTED_RESULT2 = "a";
+        final EventList events = new EventList()
+                .addCodePoint('a', false)
+                .addCodePoint(MODIFIER_LETTER_ACUTE, true)
+                .addCodePoint('a', false);
+        for (final Event event : events) {
+            mLatinIME.onEvent(event);
+        }
+        assertEquals("dead acute on a typed", EXPECTED_RESULT1, mEditText.getText().toString());
+        mLatinIME.onEvent(Event.createSoftwareKeypressEvent(Event.NOT_A_CODE_POINT,
+                Constants.CODE_DELETE, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+                false /* isKeyRepeat */));
+        assertEquals("a with acute deleted", EXPECTED_RESULT2, mEditText.getText().toString());
+    }
+
+    public void testFinnishStroke() {
+        final int MODIFIER_LETTER_STROKE = '-';
+        final String EXPECTED_RESULT = "x\u0110\u0127";
+        final EventList events = new EventList()
+                .addCodePoint('x', false)
+                .addCodePoint(MODIFIER_LETTER_STROKE, true)
+                .addCodePoint('D', false)
+                .addCodePoint(MODIFIER_LETTER_STROKE, true)
+                .addCodePoint('h', false);
+        for (final Event event : events) {
+            mLatinIME.onEvent(event);
+        }
+        assertEquals("Finnish dead stroke", EXPECTED_RESULT,
+                mEditText.getText().toString());
+    }
+
+    public void testDoubleDeadOgonek() {
+        final int MODIFIER_LETTER_OGONEK = 0x02DB;
+        final String EXPECTED_RESULT = "txǫs\u02DBfk";
+        final EventList events = new EventList()
+                .addCodePoint('t', false)
+                .addCodePoint('x', false)
+                .addCodePoint(MODIFIER_LETTER_OGONEK, true)
+                .addCodePoint('o', false)
+                .addCodePoint('s', false)
+                .addCodePoint(MODIFIER_LETTER_OGONEK, true)
+                .addCodePoint(MODIFIER_LETTER_OGONEK, true)
+                .addCodePoint('f', false)
+                .addCodePoint(MODIFIER_LETTER_OGONEK, true)
+                .addCodePoint(MODIFIER_LETTER_OGONEK, true)
+                .addKey(Constants.CODE_DELETE)
+                .addCodePoint('k', false);
+        for (final Event event : events) {
+            mLatinIME.onEvent(event);
+        }
+        assertEquals("double dead ogonek, and backspace", EXPECTED_RESULT,
+                mEditText.getText().toString());
+    }
+
+    public void testDeadCircumflexDeadDiaeresis() {
+        final int MODIFIER_LETTER_CIRCUMFLEX_ACCENT = 0x02C6;
+        final int MODIFIER_LETTER_DIAERESIS = 0xA8;
+        final String EXPECTED_RESULT = "r̂̈";
+
+        final EventList events = new EventList()
+                .addCodePoint(MODIFIER_LETTER_CIRCUMFLEX_ACCENT, true)
+                .addCodePoint(MODIFIER_LETTER_DIAERESIS, true)
+                .addCodePoint('r', false);
+        for (final Event event : events) {
+            mLatinIME.onEvent(event);
+        }
+        assertEquals("both circumflex and diaeresis on r", EXPECTED_RESULT,
+                mEditText.getText().toString());
+    }
+
+    public void testDeadCircumflexDeadDiaeresisBackspace() {
+        final int MODIFIER_LETTER_CIRCUMFLEX_ACCENT = 0x02C6;
+        final int MODIFIER_LETTER_DIAERESIS = 0xA8;
+        final String EXPECTED_RESULT = "û";
+
+        final EventList events = new EventList()
+                .addCodePoint(MODIFIER_LETTER_CIRCUMFLEX_ACCENT, true)
+                .addCodePoint(MODIFIER_LETTER_DIAERESIS, true)
+                .addKey(Constants.CODE_DELETE)
+                .addCodePoint('u', false);
+        for (final Event event : events) {
+            mLatinIME.onEvent(event);
+        }
+        assertEquals("dead circumflex, dead diaeresis, backspace, u", EXPECTED_RESULT,
+                mEditText.getText().toString());
+    }
+
+    public void testDeadCircumflexDoubleDeadDiaeresisBackspace() {
+        final int MODIFIER_LETTER_CIRCUMFLEX_ACCENT = 0x02C6;
+        final int MODIFIER_LETTER_DIAERESIS = 0xA8;
+        final String EXPECTED_RESULT = "\u02C6u";
+
+        final EventList events = new EventList()
+                .addCodePoint(MODIFIER_LETTER_CIRCUMFLEX_ACCENT, true)
+                .addCodePoint(MODIFIER_LETTER_DIAERESIS, true)
+                .addCodePoint(MODIFIER_LETTER_DIAERESIS, true)
+                .addKey(Constants.CODE_DELETE)
+                .addCodePoint('u', false);
+        for (final Event event : events) {
+            mLatinIME.onEvent(event);
+        }
+        assertEquals("dead circumflex, double dead diaeresis, backspace, u", EXPECTED_RESULT,
+                mEditText.getText().toString());
+    }
+}