Merge "Add preliminary Myanmar keyboard"
diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml
index 6d4c5bf..13fe7f8 100644
--- a/java/res/values-am/strings.xml
+++ b/java/res/values-am/strings.xml
@@ -210,10 +210,10 @@
     <string name="message_updating" msgid="4457761393932375219">"ዝማኔዎችን በመፈለግ ላይ"</string>
     <string name="message_loading" msgid="5638680861387748936">"በመጫን ላይ…"</string>
     <string name="main_dict_description" msgid="3072821352793492143">"ዋና መዝገበ-ቃላት"</string>
-    <string name="cancel" msgid="6830980399865683324">"ሰርዝ"</string>
+    <string name="cancel" msgid="6830980399865683324">"ይቅር"</string>
     <string name="go_to_settings" msgid="3876892339342569259">"ቅንብሮች"</string>
     <string name="install_dict" msgid="180852772562189365">"ጫን"</string>
-    <string name="cancel_download_dict" msgid="7843340278507019303">"ሰርዝ"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"ይቅር"</string>
     <string name="delete_dict" msgid="756853268088330054">"ሰርዝ"</string>
     <string name="should_download_over_metered_prompt" msgid="1583881200688185508">"በተንቀሳቃሽ መሣሪያዎ ላይ ለተመረጠው ቋንቋ የሚሆን መዝገበ-ቃላት ይገኛል።&lt;br/&gt; የትየባ ተሞክሮዎን ለማሻሻል የ<xliff:g id="LANGUAGE_NAME">%1$s</xliff:g> መዝገበ-ቃላቱን &lt;b&gt;እንዲያወርዱ&lt;/b&gt; እንመክራለን።&lt;br/&gt; &lt;br/&gt; ማውረድ በ3ጂ ላይ አንድ ወይም ሁለት ደቂቃ ሊወስድ ይችላል። &lt;b&gt;ያልተገደበ የውሂብ ዕቅድ&lt;/b&gt; ከሌለዎት ክፍያዎች መከፈል ሊኖርባቸው ይችላል።&lt;br/&gt; የትኛው የውሂብ ዕቅድ እንዳለዎት እርግጠኛ ካልሆኑ ውርዱን በራስ-ሰር ለመጀመር የWi-Fi ግንኙነት እንዲፈልጉ እንመክራለን።&lt;br/&gt; &lt;br/&gt; ጠቃሚ ምክር፦ የተንቀሳቃሽ መሣሪያዎ &lt;b&gt;ቅንብሮች&lt;/b&gt; ምናሌ ውስጥ ወዳለው &lt;b&gt;ቋንቋ እና ግብዓት&lt;/b&gt; በመሄድ መዝገበ-ቃላትን ማውረድና ማስወገድ ይችላሉ።"</string>
     <string name="download_over_metered" msgid="1643065851159409546">"አሁን አውርድ (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g> ሜባ)"</string>
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index ed487e1..41bf36b 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -168,4 +168,8 @@
     public boolean isCommittable() {
         return EVENT_INPUT_KEYPRESS == mType || EVENT_MODE_KEY == mType || EVENT_TOGGLE == mType;
     }
+
+    public boolean isHandled() {
+        return EVENT_NOT_HANDLED != mType;
+    }
 }
diff --git a/java/src/com/android/inputmethod/event/EventInterpreter.java b/java/src/com/android/inputmethod/event/EventInterpreter.java
index 726b920..bcf10fc 100644
--- a/java/src/com/android/inputmethod/event/EventInterpreter.java
+++ b/java/src/com/android/inputmethod/event/EventInterpreter.java
@@ -16,11 +16,6 @@
 
 package com.android.inputmethod.event;
 
-import android.util.SparseArray;
-import android.view.KeyEvent;
-
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.ArrayList;
@@ -37,25 +32,9 @@
     // TODO: Implement an object pool for events, as we'll create a lot of them
     // TODO: Create a combiner
     // TODO: Create an object type to represent input material + visual feedback + decoding state
-    // TODO: Create an interface to call back to Latin IME through the above object
 
-    final EventDecoderSpec mDecoderSpec;
-    final SparseArray<HardwareEventDecoder> mHardwareEventDecoders;
-    final SoftwareEventDecoder mSoftwareEventDecoder;
-    final LatinIME mLatinIme;
-    final ArrayList<Combiner> mCombiners;
-
-    /**
-     * Create a default interpreter.
-     *
-     * This creates a default interpreter that does nothing. A default interpreter should normally
-     * only be used for fallback purposes, when we really don't know what we want to do with input.
-     *
-     * @param latinIme a reference to the ime.
-     */
-    public EventInterpreter(final LatinIME latinIme) {
-        this(null, latinIme);
-    }
+    private final EventDecoderSpec mDecoderSpec;
+    private final ArrayList<Combiner> mCombiners;
 
     /**
      * Create an event interpreter according to a specification.
@@ -70,64 +49,10 @@
      * interpreter that does no specific combining, and assumes the most common cases.
      *
      * @param specification the specification for event interpretation. null for default.
-     * @param latinIme a reference to the ime.
      */
-    public EventInterpreter(final EventDecoderSpec specification, final LatinIME latinIme) {
+    public EventInterpreter(final EventDecoderSpec specification) {
         mDecoderSpec = null != specification ? specification : new EventDecoderSpec();
-        // For both, we expect to have only one decoder in almost all cases, hence the default
-        // capacity of 1.
-        mHardwareEventDecoders = new SparseArray<HardwareEventDecoder>(1);
-        mSoftwareEventDecoder = new SoftwareKeyboardEventDecoder();
         mCombiners = CollectionUtils.newArrayList();
         mCombiners.add(new DeadKeyCombiner());
-        mLatinIme = latinIme;
-    }
-
-    // Helper method to decode a hardware key event into a generic event, and execute any
-    // necessary action.
-    public boolean onHardwareKeyEvent(final KeyEvent hardwareKeyEvent) {
-        final Event decodedEvent = getHardwareKeyEventDecoder(hardwareKeyEvent.getDeviceId())
-                .decodeHardwareKey(hardwareKeyEvent);
-        return onEvent(decodedEvent);
-    }
-
-    public boolean onSoftwareEvent() {
-        final Event decodedEvent = getSoftwareEventDecoder().decodeSoftwareEvent();
-        return onEvent(decodedEvent);
-    }
-
-    private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) {
-        final HardwareEventDecoder decoder = mHardwareEventDecoders.get(deviceId);
-        if (null != decoder) return decoder;
-        // TODO: create the decoder according to the specification
-        final HardwareEventDecoder newDecoder = new HardwareKeyboardEventDecoder(deviceId);
-        mHardwareEventDecoders.put(deviceId, newDecoder);
-        return newDecoder;
-    }
-
-    private SoftwareEventDecoder getSoftwareEventDecoder() {
-        // Within the context of Latin IME, since we never present several software interfaces
-        // at the time, we should never need multiple software event decoders at a time.
-        return mSoftwareEventDecoder;
-    }
-
-    private boolean onEvent(final Event event) {
-        Event currentlyProcessingEvent = event;
-        boolean processed = false;
-        for (int i = 0; i < mCombiners.size(); ++i) {
-            currentlyProcessingEvent = mCombiners.get(i).combine(event);
-        }
-        while (null != currentlyProcessingEvent) {
-            if (currentlyProcessingEvent.isCommittable()) {
-                mLatinIme.onCodeInput(currentlyProcessingEvent.mCodePoint,
-                        Constants.EXTERNAL_KEYBOARD_COORDINATE,
-                        Constants.EXTERNAL_KEYBOARD_COORDINATE);
-                processed = true;
-            } else if (event.isDead()) {
-                processed = true;
-            }
-            currentlyProcessingEvent = currentlyProcessingEvent.mNextEvent;
-        }
-        return processed;
     }
 }
diff --git a/java/src/com/android/inputmethod/event/SoftwareEventDecoder.java b/java/src/com/android/inputmethod/event/SoftwareEventDecoder.java
deleted file mode 100644
index d81ee0b..0000000
--- a/java/src/com/android/inputmethod/event/SoftwareEventDecoder.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2012 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.event;
-
-/**
- * An event decoder for events out of a software keyboard.
- *
- * This defines the interface for an event decoder that supports events out of a software keyboard.
- * This differs significantly from hardware keyboard event decoders in several respects. First,
- * a software keyboard does not have a scancode/layout system; the keypresses that insert
- * characters output unicode characters directly.
- */
-public interface SoftwareEventDecoder extends EventDecoder {
-    public Event decodeSoftwareEvent();
-}
diff --git a/java/src/com/android/inputmethod/event/SoftwareKeyboardEventDecoder.java b/java/src/com/android/inputmethod/event/SoftwareKeyboardEventDecoder.java
deleted file mode 100644
index de91567..0000000
--- a/java/src/com/android/inputmethod/event/SoftwareKeyboardEventDecoder.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2012 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.event;
-
-/**
- * A decoder for events from software keyboard, like the ones displayed by Latin IME.
- */
-public class SoftwareKeyboardEventDecoder implements SoftwareEventDecoder {
-    @Override
-    public Event decodeSoftwareEvent() {
-        return null;
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 222e735..4a18c2b 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -45,6 +45,7 @@
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
+import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
@@ -60,6 +61,8 @@
 import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
 import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
 import com.android.inputmethod.event.Event;
+import com.android.inputmethod.event.HardwareEventDecoder;
+import com.android.inputmethod.event.HardwareKeyboardEventDecoder;
 import com.android.inputmethod.event.InputTransaction;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
@@ -120,6 +123,10 @@
     private final Settings mSettings;
     private final InputLogic mInputLogic = new InputLogic(this /* LatinIME */,
             this /* SuggestionStripViewAccessor */);
+    // We expect to have only one decoder in almost all cases, hence the default capacity of 1.
+    // If it turns out we need several, it will get grown seamlessly.
+    final SparseArray<HardwareEventDecoder> mHardwareEventDecoders
+            = new SparseArray<HardwareEventDecoder>(1);
 
     private View mExtractArea;
     private View mKeyPreviewBackingView;
@@ -643,9 +650,7 @@
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.getInstance().initDictionary(newSuggest.mDictionaryFacilitator);
         }
-        final Suggest oldSuggest = mInputLogic.mSuggest;
-        mInputLogic.mSuggest = newSuggest;
-        if (oldSuggest != null) oldSuggest.close();
+        mInputLogic.replaceSuggest(newSuggest);
         refreshPersonalizationDictionarySession();
     }
 
@@ -1588,19 +1593,31 @@
         }
     }
 
+    private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) {
+        final HardwareEventDecoder decoder = mHardwareEventDecoders.get(deviceId);
+        if (null != decoder) return decoder;
+        // TODO: create the decoder according to the specification
+        final HardwareEventDecoder newDecoder = new HardwareKeyboardEventDecoder(deviceId);
+        mHardwareEventDecoders.put(deviceId, newDecoder);
+        return newDecoder;
+    }
+
     // Hooks for hardware keyboard
     @Override
-    public boolean onKeyDown(final int keyCode, final KeyEvent event) {
-        if (!ProductionFlag.IS_HARDWARE_KEYBOARD_SUPPORTED) return super.onKeyDown(keyCode, event);
-        // onHardwareKeyEvent, like onKeyDown returns true if it handled the event, false if
-        // it doesn't know what to do with it and leave it to the application. For example,
-        // hardware key events for adjusting the screen's brightness are passed as is.
-        if (mInputLogic.mEventInterpreter.onHardwareKeyEvent(event)) {
-            final long keyIdentifier = event.getDeviceId() << 32 + event.getKeyCode();
-            mInputLogic.mCurrentlyPressedHardwareKeys.add(keyIdentifier);
+    public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
+        if (!ProductionFlag.IS_HARDWARE_KEYBOARD_SUPPORTED) {
+            return super.onKeyDown(keyCode, keyEvent);
+        }
+        final Event event = getHardwareKeyEventDecoder(
+                keyEvent.getDeviceId()).decodeHardwareKey(keyEvent);
+        // If the event is not handled by LatinIME, we just pass it to the parent implementation.
+        // If it's handled, we return true because we did handle it.
+        if (event.isHandled()) {
+            mInputLogic.onCodeInput(mSettings.getCurrent(), event,
+                    mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
             return true;
         }
-        return super.onKeyDown(keyCode, event);
+        return super.onKeyDown(keyCode, keyEvent);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index cb55aa0..fa7c4b4 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -80,8 +80,6 @@
     public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
     // TODO: mSuggest should be touched by a single thread.
     public volatile Suggest mSuggest;
-    // The event interpreter should never be null.
-    public final EventInterpreter mEventInterpreter;
 
     public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
     public final WordComposer mWordComposer;
@@ -104,11 +102,19 @@
         mLatinIME = latinIME;
         mSuggestionStripViewAccessor = suggestionStripViewAccessor;
         mWordComposer = new WordComposer();
-        mEventInterpreter = new EventInterpreter(latinIME);
         mConnection = new RichInputConnection(latinIME);
         mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
     }
 
+    // Replace the old Suggest with the passed Suggest and close it.
+    public void replaceSuggest(final Suggest newSuggest) {
+        final Suggest oldSuggest = mSuggest;
+        mSuggest = newSuggest;
+        if (oldSuggest != null) {
+            oldSuggest.close();
+        }
+    }
+
     /**
      * Initializes the input logic for input in an editor.
      *
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 4c57af0..4e6ff95 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -344,10 +344,6 @@
 #define MAX_POINTER_COUNT 1
 #define MAX_POINTER_COUNT_G 2
 
-// DEBUG
-#define INPUTLENGTH_FOR_DEBUG (-1)
-#define MIN_OUTPUT_INDEX_FOR_DEBUG (-1)
-
 #define DISALLOW_DEFAULT_CONSTRUCTOR(TypeName) \
   TypeName() = delete
 
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_scoring.h b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
index 7ef905d..8982800 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
@@ -32,25 +32,24 @@
  public:
     static const TypingScoring *getInstance() { return &sInstance; }
 
-    AK_FORCE_INLINE bool getMostProbableString(
-            const DicTraverseSession *const traverseSession, const int terminalSize,
-            const float languageWeight, int *const outputCodePoints, int *const type,
-            int *const freq) const {
+    AK_FORCE_INLINE bool getMostProbableString(const DicTraverseSession *const traverseSession,
+            const int terminalSize, const float languageWeight, int *const outputCodePoints,
+            int *const type, int *const freq) const {
         return false;
     }
 
-    AK_FORCE_INLINE void safetyNetForMostProbableString(const int scoreCount,
-            const int maxScore, int *const outputCodePoints, int *const scores) const {
+    AK_FORCE_INLINE void safetyNetForMostProbableString(const int scoreCount, const int maxScore,
+            int *const outputCodePoints, int *const scores) const {
     }
 
     AK_FORCE_INLINE float getAdjustedLanguageWeight(DicTraverseSession *const traverseSession,
-             DicNode *const terminals, const int size) const {
+            DicNode *const terminals, const int size) const {
         return 1.0f;
     }
 
-    AK_FORCE_INLINE int calculateFinalScore(const float compoundDistance,
-            const int inputSize, const ErrorTypeUtils::ErrorType containedErrorTypes,
-            const bool forceCommit, const bool boostExactMatches) const {
+    AK_FORCE_INLINE int calculateFinalScore(const float compoundDistance, const int inputSize,
+            const ErrorTypeUtils::ErrorType containedErrorTypes, const bool forceCommit,
+            const bool boostExactMatches) const {
         const float maxDistance = ScoringParams::DISTANCE_WEIGHT_LANGUAGE
                 + static_cast<float>(inputSize) * ScoringParams::TYPING_MAX_OUTPUT_SCORE_PER_INPUT;
         float score = ScoringParams::TYPING_BASE_OUTPUT_SCORE - compoundDistance / maxDistance;
@@ -85,8 +84,8 @@
         return true;
     }
 
-    AK_FORCE_INLINE bool sameAsTyped(
-            const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const {
+    AK_FORCE_INLINE bool sameAsTyped(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const {
         return traverseSession->getProximityInfoState(0)->sameAsTyped(
                 dicNode->getOutputWordBuf(), dicNode->getNodeCodePointCount());
     }
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
index 41314ef..b36605a 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
@@ -72,8 +72,6 @@
     float getMatchedCost(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode, DicNode_InputStateG *inputStateG) const {
         const int pointIndex = dicNode->getInputIndex(0);
-        // Note: min() required since length can be MAX_POINT_TO_KEY_LENGTH for characters not on
-        // the keyboard (like accented letters)
         const float normalizedSquaredLength = traverseSession->getProximityInfoState(0)
                 ->getPointToKeyLength(pointIndex,
                         CharUtils::toBaseLowerCase(dicNode->getNodeCodePoint()));
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Hebrew.java b/tests/src/com/android/inputmethod/keyboard/layout/Hebrew.java
new file mode 100644
index 0000000..a5befab
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Hebrew.java
@@ -0,0 +1,195 @@
+/*
+ * 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.keyboard.layout;
+
+import com.android.inputmethod.keyboard.layout.Symbols.RtlSymbols;
+import com.android.inputmethod.keyboard.layout.SymbolsShifted.RtlSymbolsShifted;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
+import com.android.inputmethod.latin.Constants;
+
+import java.util.Locale;
+
+public final class Hebrew extends LayoutBase {
+    private static final String LAYOUT_NAME = "hebrew";
+
+    public Hebrew(final LayoutCustomizer customizer) {
+        super(customizer, HebrewSymbols.class, RtlSymbolsShifted.class);
+    }
+
+    @Override
+    public String getName() { return LAYOUT_NAME; }
+
+    public static class HebrewCustomizer extends LayoutCustomizer {
+        public HebrewCustomizer(final Locale locale) {
+            super(locale);
+        }
+
+        @Override
+        public ExpectedKey getAlphabetKey() { return HEBREW_ALPHABET_KEY; }
+
+        @Override
+        public ExpectedKey getCurrencyKey() { return CURRENCY_NEW_SHEQEL; }
+
+        @Override
+        public ExpectedKey[] getOtherCurrencyKeys() {
+            return SymbolsShifted.CURRENCIES_OTHER_GENERIC;
+        }
+
+        @Override
+        public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_LR9; }
+
+        @Override
+        public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_LR9; }
+
+        @Override
+        public ExpectedKey[] getDoubleAngleQuoteKeys() {
+            return RtlSymbols.DOUBLE_ANGLE_QUOTES_LR_RTL;
+        }
+
+        @Override
+        public ExpectedKey[] getSingleAngleQuoteKeys() {
+            return RtlSymbols.SINGLE_ANGLE_QUOTES_LR_RTL;
+        }
+
+        @Override
+        public ExpectedKey[] getLeftShiftKeys(final boolean isPhone) {
+            return EMPTY_KEYS;
+        }
+
+        @Override
+        public ExpectedKey[] getRightShiftKeys(final boolean isPhone) {
+            return isPhone ? EMPTY_KEYS : EXCLAMATION_AND_QUESTION_MARKS;
+        }
+
+        @Override
+        public ExpectedKey[] getPunctuationMoreKeys(final boolean isPhone) {
+            return isPhone ? RTL_PHONE_PUNCTUATION_MORE_KEYS
+                    : RTL_TABLET_PUNCTUATION_MORE_KEYS;
+        }
+
+        // U+05D0: "א" HEBREW LETTER ALEF
+        // U+05D1: "ב" HEBREW LETTER BET
+        // U+05D2: "ג" HEBREW LETTER GIMEL
+        private static final ExpectedKey HEBREW_ALPHABET_KEY = key(
+                "\u05D0\u05D1\u05D2", Constants.CODE_SWITCH_ALPHA_SYMBOL);
+        // U+20AA: "₪" NEW SHEQEL SIGN
+        private static final ExpectedKey CURRENCY_NEW_SHEQEL = key("\u20AA",
+                Symbols.CURRENCY_GENERIC_MORE_KEYS);
+        private static final ExpectedKey[] RTL_PHONE_PUNCTUATION_MORE_KEYS = joinKeys(
+                ";", "/", key("(", ")"), key(")", "("), "#", "!", ",", "?",
+                "&", "%", "+", "\"", "-", ":", "'", "@");
+        // Punctuation more keys for tablet form factor.
+        private static final ExpectedKey[] RTL_TABLET_PUNCTUATION_MORE_KEYS = joinKeys(
+                ";", "/", key("(", ")"), key(")", "("), "#", "'", ",",
+                "&", "%", "+", "\"", "-", ":", "@");
+    }
+
+    @Override
+    ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; }
+
+    @Override
+    ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, final int elementId) {
+        return null;
+    }
+
+    private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder()
+            .setKeysOfRow(1,
+                    key("'", joinMoreKeys("1", "\"")),
+                    key("-", joinMoreKeys("2", "_")),
+                    // U+05E7: "ק" HEBREW LETTER QOF
+                    key("\u05E7", moreKey("3")),
+                    // U+05E8: "ר" HEBREW LETTER RESH
+                    key("\u05E8", moreKey("4")),
+                    // U+05D0: "א" HEBREW LETTER ALEF
+                    key("\u05D0", moreKey("5")),
+                    // U+05D8: "ט" HEBREW LETTER TET
+                    key("\u05D8", moreKey("6")),
+                    // U+05D5: "ו" HEBREW LETTER VAV
+                    key("\u05D5", moreKey("7")),
+                    // U+05DF: "ן" HEBREW LETTER FINAL NUN
+                    key("\u05DF", moreKey("8")),
+                    // U+05DD: "ם" HEBREW LETTER FINAL MEM
+                    key("\u05DD", moreKey("9")),
+                    // U+05E4: "פ" HEBREW LETTER PE
+                    key("\u05E4", moreKey("0")))
+            .setKeysOfRow(2,
+                    // U+05E9: "ש" HEBREW LETTER SHIN
+                    key("\u05E9"),
+                    // U+05D3: "ד" HEBREW LETTER DALET
+                    key("\u05D3"),
+                    // U+05D2: "ג" HEBREW LETTER GIMEL
+                    // U+05D2 U+05F3: "ג׳" HEBREW LETTER GIMEL + HEBREW PUNCTUATION GERESH
+                    key("\u05D2", moreKey("\u05D2\u05F3")),
+                    // U+05DB: "כ" HEBREW LETTER KAF
+                    key("\u05DB"),
+                    // U+05E2: "ע" HEBREW LETTER AYIN
+                    key("\u05E2"),
+                    // U+05D9: "י" HEBREW LETTER YOD
+                    // U+05F2 U+05B7: "ײַ" HEBREW LIGATURE YIDDISH DOUBLE YOD + HEBREW POINT PATAH
+                    key("\u05D9", moreKey("\u05F2\u05B7")),
+                    // U+05D7: "ח" HEBREW LETTER HET
+                    // U+05D7 U+05F3: "ח׳" HEBREW LETTER HET + HEBREW PUNCTUATION GERESH
+                    key("\u05D7", moreKey("\u05D7\u05F3")),
+                    // U+05DC: "ל" HEBREW LETTER LAMED
+                    key("\u05DC"),
+                    // U+05DA: "ך" HEBREW LETTER FINAL KAF
+                    key("\u05DA"),
+                    // U+05E3: "ף" HEBREW LETTER FINAL PE
+                    key("\u05E3"))
+            .setKeysOfRow(3,
+                    // U+05D6: "ז" HEBREW LETTER ZAYIN
+                    // U+05D6 U+05F3: "ז׳" HEBREW LETTER ZAYIN + HEBREW PUNCTUATION GERESH
+                    key("\u05D6", moreKey("\u05D6\u05F3")),
+                    // U+05E1: "ס" HEBREW LETTER SAMEKH
+                    key("\u05E1"),
+                    // U+05D1: "ב" HEBREW LETTER BET
+                    key("\u05D1"),
+                    // U+05D4: "ה" HEBREW LETTER HE
+                    key("\u05D4"),
+                    // U+05E0: "נ" HEBREW LETTER NUN
+                    key("\u05E0"),
+                    // U+05DE: "מ" HEBREW LETTER MEM
+                    key("\u05DE"),
+                    // U+05E6: "צ" HEBREW LETTER TSADI
+                    // U+05E6 U+05F3: "צ׳" HEBREW LETTER TSADI + HEBREW PUNCTUATION GERESH
+                    key("\u05E6", moreKey("\u05E6\u05F3")),
+                    // U+05EA: "ת" HEBREW LETTER TAV
+                    // U+05EA U+05F3: "ת׳" HEBREW LETTER TAV + HEBREW PUNCTUATION GERESH
+                    key("\u05EA", moreKey("\u05EA\u05F3")),
+                    // U+05E5: "ץ" HEBREW LETTER FINAL TSADI
+                    // U+05E5 U+05F3: "ץ׳" HEBREW LETTER FINAL TSADI + HEBREW PUNCTUATION GERESH
+                    key("\u05E5", moreKey("\u05E5\u05F3")))
+            .build();
+
+    private static class HebrewSymbols extends RtlSymbols {
+        public HebrewSymbols(final LayoutCustomizer customizer) {
+            super(customizer);
+        }
+
+        @Override
+        public ExpectedKey[][] getLayout(final boolean isPhone) {
+            return new ExpectedKeyboardBuilder(super.getLayout(isPhone))
+                    // U+00B1: "±" PLUS-MINUS SIGN
+                    // U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN
+                    .setMoreKeysOf("+", "\u00B1", "\uFB29")
+                    // U+2605: "★" BLACK STAR
+                    .setMoreKeysOf("*", "\u2605")
+                    .build();
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHebrew.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHebrew.java
new file mode 100644
index 0000000..c0243a8
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHebrew.java
@@ -0,0 +1,37 @@
+/*
+ * 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.keyboard.layout.tests;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.inputmethod.keyboard.layout.Hebrew;
+import com.android.inputmethod.keyboard.layout.Hebrew.HebrewCustomizer;
+import com.android.inputmethod.keyboard.layout.LayoutBase;
+
+import java.util.Locale;
+
+/**
+ * iw: Hebrew/hebrew
+ */
+@SmallTest
+public class TestsHebrew extends LayoutTestsBase {
+    private static final Locale LOCALE = new Locale("iw");
+    private static final LayoutBase LAYOUT = new Hebrew(new HebrewCustomizer(LOCALE));
+
+    @Override
+    LayoutBase getLayout() { return LAYOUT; }
+}