Merge "Support select input method dialog on pre-HC platform"
diff --git a/java/res/values-ar/donottranslate-altchars.xml b/java/res/values-ar/donottranslate-altchars.xml
index b4c103d..e49a840 100644
--- a/java/res/values-ar/donottranslate-altchars.xml
+++ b/java/res/values-ar/donottranslate-altchars.xml
@@ -58,9 +58,12 @@
     <string name="keylabel_for_symbols_comma">\u060c</string>
     <string name="keylabel_for_symbols_question">\u061f</string>
     <string name="keylabel_for_symbols_semicolon">\u061b</string>
+    <!-- \u066a: ARABIC PERCENT SIGN -->
+    <string name="keylabel_for_symbols_percent">\u066a</string>
     <string name="alternates_for_symbols_comma">,</string>
     <string name="alternates_for_symbols_question">\?,¿</string>
     <string name="alternates_for_symbols_semicolon">;</string>
+    <string name="alternates_for_symbols_percent">%,‰</string>
     <string name="keylabel_for_apostrophe">"،"</string>
     <string name="keylabel_for_dash">"."</string>
     <string name="keyhintlabel_for_apostrophe">"؟"</string>
diff --git a/java/res/values/donottranslate-altchars.xml b/java/res/values/donottranslate-altchars.xml
index 9d06d06..f3084d4 100644
--- a/java/res/values/donottranslate-altchars.xml
+++ b/java/res/values/donottranslate-altchars.xml
@@ -80,9 +80,11 @@
     <string name="keylabel_for_symbols_comma">,</string>
     <string name="keylabel_for_symbols_question">\?</string>
     <string name="keylabel_for_symbols_semicolon">;</string>
+    <string name="keylabel_for_symbols_percent">%</string>
     <string name="alternates_for_symbols_comma"></string>
     <string name="alternates_for_symbols_question">¿</string>
     <string name="alternates_for_symbols_semicolon"></string>
+    <string name="alternates_for_symbols_percent">‰</string>
     <string name="keylabel_for_apostrophe">\'</string>
     <string name="keylabel_for_dash">-</string>
     <string name="keyhintlabel_for_apostrophe">\"</string>
diff --git a/java/res/xml-sw600dp/kbd_symbols.xml b/java/res/xml-sw600dp/kbd_symbols.xml
index dea08dd..03d37b0 100644
--- a/java/res/xml-sw600dp/kbd_symbols.xml
+++ b/java/res/xml-sw600dp/kbd_symbols.xml
@@ -75,8 +75,8 @@
         <Key
             latin:keyStyle="currencyKeyStyle" />
         <Key
-            latin:keyLabel="%"
-            latin:popupCharacters="‰" />
+            latin:keyLabel="@string/keylabel_for_symbols_percent"
+            latin:popupCharacters="@string/alternates_for_symbols_percent" />
         <Key
             latin:keyLabel="&amp;" />
         <Key
diff --git a/java/res/xml-sw768dp/kbd_symbols.xml b/java/res/xml-sw768dp/kbd_symbols.xml
index eb77d4d..44ee2fd 100644
--- a/java/res/xml-sw768dp/kbd_symbols.xml
+++ b/java/res/xml-sw768dp/kbd_symbols.xml
@@ -83,8 +83,8 @@
         <Key
             latin:keyStyle="currencyKeyStyle" />
         <Key
-            latin:keyLabel="%"
-            latin:popupCharacters="‰" />
+            latin:keyLabel="@string/keylabel_for_symbols_percent"
+            latin:popupCharacters="@string/alternates_for_symbols_percent" />
         <Key
             latin:keyLabel="&amp;" />
         <Key
diff --git a/java/res/xml/kbd_symbols.xml b/java/res/xml/kbd_symbols.xml
index 8f682d9..7c0cee6 100644
--- a/java/res/xml/kbd_symbols.xml
+++ b/java/res/xml/kbd_symbols.xml
@@ -72,8 +72,8 @@
         <Key
             latin:keyStyle="currencyKeyStyle" />
         <Key
-            latin:keyLabel="%"
-            latin:popupCharacters="‰" />
+            latin:keyLabel="@string/keylabel_for_symbols_percent"
+            latin:popupCharacters="@string/alternates_for_symbols_percent" />
         <Key
             latin:keyLabel="&amp;" />
         <Key
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 0b4fce4..3e45793 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -70,6 +70,14 @@
     public static final int CODE_DASH = '-';
     public static final int CODE_SINGLE_QUOTE = '\'';
     public static final int CODE_DOUBLE_QUOTE = '"';
+    // TODO: Check how this should work for right-to-left languages. It seems to stand
+    // that for rtl languages, a closing parenthesis is a left parenthesis. Is this
+    // managed by the font? Or is it a different char?
+    public static final int CODE_CLOSING_PARENTHESIS = ')';
+    public static final int CODE_CLOSING_SQUARE_BRACKET = ']';
+    public static final int CODE_CLOSING_CURLY_BRACKET = '}';
+    public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
+
 
     /** Special keys code.  These should be aligned with values/keycodes.xml */
     public static final int CODE_DUMMY = 0;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 448274e..f959321 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -152,8 +152,7 @@
             boolean voiceButtonOnPrimary) {
         mSwitchState = SWITCH_STATE_ALPHA;
         try {
-            final boolean isSymbols = (mCurrentId != null) ? mCurrentId.isSymbolsKeyboard() : false;
-            loadKeyboardInternal(attribute, voiceKeyEnabled, voiceButtonOnPrimary, isSymbols);
+            loadKeyboardInternal(attribute, voiceKeyEnabled, voiceButtonOnPrimary, false);
         } catch (RuntimeException e) {
             // Get KeyboardId to record which keyboard has been failed to load.
             final KeyboardId id = getKeyboardId(attribute, false);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 7ce9292..bce787d 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -91,7 +91,9 @@
                 Log.e(TAG, "Unable to read source data for locale "
                         + locale.toString() + ": falling back to internal dictionary");
             }
-            return Arrays.asList(loadFallbackResource(context, fallbackResId));
+            final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId);
+            if (null == fallbackAsset) return null;
+            return Arrays.asList(fallbackAsset);
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index 96225f2..4baf52e 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -38,7 +38,6 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
@@ -51,7 +50,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-public class CandidateView extends LinearLayout implements OnClickListener, OnLongClickListener {
+public class CandidateView extends LinearLayout implements OnClickListener {
 
     public interface Listener {
         public boolean addWordToDictionary(String word);
@@ -323,8 +322,6 @@
             final TextView word = (TextView)inflater.inflate(R.layout.candidate_word, null);
             word.setTag(i);
             word.setOnClickListener(this);
-            if (i == 0)
-                word.setOnLongClickListener(this);
             mWords.add(word);
             mInfos.add((TextView)inflater.inflate(R.layout.candidate_info, null));
             mDividers.add(inflater.inflate(R.layout.candidate_divider, null));
@@ -750,22 +747,6 @@
     }
 
     @Override
-    public boolean onLongClick(View view) {
-        final Object tag = view.getTag();
-        if (!(tag instanceof Integer))
-            return true;
-        final int index = (Integer) tag;
-        if (index >= mSuggestions.size())
-            return true;
-
-        final CharSequence word = mSuggestions.getWord(index);
-        if (word.length() < 2)
-            return false;
-        addToDictionary(word);
-        return true;
-    }
-
-    @Override
     public void onClick(View view) {
         if (view == mWordToSave) {
             addToDictionary(((TextView)view).getText());
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 5e7de3e..e987d9f 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -32,7 +32,11 @@
     }
 
     public DictionaryCollection(Dictionary... dictionaries) {
-        mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+        if (null == dictionaries) {
+            mDictionaries = new CopyOnWriteArrayList<Dictionary>();
+        } else {
+            mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+        }
     }
 
     public DictionaryCollection(Collection<Dictionary> dictionaries) {
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index bba3318..a35b0f5 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -52,13 +52,23 @@
         }
 
         final List<Dictionary> dictList = new LinkedList<Dictionary>();
-        for (final AssetFileAddress f : BinaryDictionaryGetter.getDictionaryFiles(locale,
-                context, fallbackResId)) {
-            dictList.add(new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, null));
+        final List<AssetFileAddress> assetFileList =
+                BinaryDictionaryGetter.getDictionaryFiles(locale, context, fallbackResId);
+        if (null != assetFileList) {
+            for (final AssetFileAddress f : assetFileList) {
+                dictList.add(
+                        new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, null));
+            }
         }
 
-        if (null == dictList) return null;
-        return new DictionaryCollection(dictList);
+        // null == dictList is not supposed to be possible, but better safe than sorry and it's
+        // safer for future extension. In this case, rather than returning null, it should be safer
+        // to return an empty DictionaryCollection.
+        if (null == dictList) {
+            return new DictionaryCollection();
+        } else {
+            return new DictionaryCollection(dictList);
+        }
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/EditingUtils.java b/java/src/com/android/inputmethod/latin/EditingUtils.java
index e56aa69..634dbbd 100644
--- a/java/src/com/android/inputmethod/latin/EditingUtils.java
+++ b/java/src/com/android/inputmethod/latin/EditingUtils.java
@@ -33,6 +33,7 @@
      * Number of characters we want to look back in order to identify the previous word
      */
     private static final int LOOKBACK_CHARACTER_NUM = 15;
+    private static final int INVALID_CURSOR_POSITION = -1;
 
     private EditingUtils() {
         // Unintentional empty constructor for singleton.
@@ -63,10 +64,11 @@
     }
 
     private static int getCursorPosition(InputConnection connection) {
+        if (null == connection) return INVALID_CURSOR_POSITION;
         ExtractedText extracted = connection.getExtractedText(
             new ExtractedTextRequest(), 0);
         if (extracted == null) {
-          return -1;
+            return INVALID_CURSOR_POSITION;
         }
         return extracted.startOffset + extracted.selectionStart;
     }
@@ -79,6 +81,7 @@
      *   represents the cursor, then "hello " will be returned.
      */
     public static String getWordAtCursor(InputConnection connection, String separators) {
+        // getWordRangeAtCursor returns null if the connection is null
         Range r = getWordRangeAtCursor(connection, separators);
         return (r == null) ? null : r.mWord;
     }
@@ -88,6 +91,7 @@
      * getWordAtCursor.
      */
     public static void deleteWordAtCursor(InputConnection connection, String separators) {
+        // getWordRangeAtCursor returns null if the connection is null
         Range range = getWordRangeAtCursor(connection, separators);
         if (range == null) return;
 
@@ -165,6 +169,7 @@
     public static CharSequence getPreviousWord(InputConnection connection,
             String sentenceSeperators) {
         //TODO: Should fix this. This could be slow!
+        if (null == connection) return null;
         CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
         return getPreviousWord(prev, sentenceSeperators);
     }
@@ -194,6 +199,7 @@
     }
 
     public static CharSequence getThisWord(InputConnection connection, String sentenceSeperators) {
+        if (null == connection) return null;
         final CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
         return getThisWord(prev, sentenceSeperators);
     }
@@ -256,12 +262,14 @@
             int selStart, int selEnd, String wordSeparators) {
         if (selStart == selEnd) {
             // There is just a cursor, so get the word at the cursor
+            // getWordRangeAtCursor returns null if the connection is null
             EditingUtils.Range range = getWordRangeAtCursor(ic, wordSeparators);
             if (range != null && !TextUtils.isEmpty(range.mWord)) {
                 return new SelectedWord(selStart - range.mCharsBefore, selEnd + range.mCharsAfter,
                         range.mWord);
             }
         } else {
+            if (null == ic) return null;
             // Is the previous character empty or a word separator? If not, return null.
             CharSequence charsBefore = ic.getTextBeforeCursor(1, 0);
             if (!isWordBoundary(charsBefore, wordSeparators)) {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index a820a40..29cf63d 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -988,13 +988,25 @@
         }
     }
 
+    private static boolean canBeFollowedByPeriod(final int codePoint) {
+        // TODO: Check again whether there really ain't a better way to check this.
+        // TODO: This should probably be language-dependant...
+        return Character.isLetterOrDigit(codePoint)
+                || codePoint == Keyboard.CODE_SINGLE_QUOTE
+                || codePoint == Keyboard.CODE_DOUBLE_QUOTE
+                || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
+                || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
+                || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
+                || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
+    }
+
     private void maybeDoubleSpace() {
         if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
         final InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
-        CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
+        final CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
         if (lastThree != null && lastThree.length() == 3
-                && Character.isLetterOrDigit(lastThree.charAt(0))
+                && canBeFollowedByPeriod(lastThree.charAt(0))
                 && lastThree.charAt(1) == Keyboard.CODE_SPACE
                 && lastThree.charAt(2) == Keyboard.CODE_SPACE
                 && mHandler.isAcceptingDoubleSpaces()) {
@@ -1498,9 +1510,15 @@
 
         final WordComposer wordComposer = mWordComposer;
         // TODO: May need a better way of retrieving previous word
-        CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(),
-                mSettingsValues.mWordSeparators);
-        SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
+        final InputConnection ic = getCurrentInputConnection();
+        final CharSequence prevWord;
+        if (null == ic) {
+            prevWord = null;
+        } else {
+            prevWord = EditingUtils.getPreviousWord(ic, mSettingsValues.mWordSeparators);
+        }
+        // getSuggestedWordBuilder handles gracefully a null value of prevWord
+        final SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
                 mKeyboardSwitcher.getKeyboardView(), wordComposer, prevWord);
 
         boolean autoCorrectionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection();
@@ -1772,10 +1790,13 @@
             // We don't want to register as bigrams words separated by a separator.
             // For example "I will, and you too" : we don't want the pair ("will" "and") to be
             // a bigram.
-            CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(),
-                    mSettingsValues.mWordSeparators);
-            if (!TextUtils.isEmpty(prevWord)) {
-                mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString());
+            final InputConnection ic = getCurrentInputConnection();
+            if (null != ic) {
+                final CharSequence prevWord =
+                        EditingUtils.getPreviousWord(ic, mSettingsValues.mWordSeparators);
+                if (!TextUtils.isEmpty(prevWord)) {
+                    mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString());
+                }
             }
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java b/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
index 382d640..e41230b 100644
--- a/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
@@ -36,6 +36,8 @@
  * for suggesting and re-ordering of candidates.
  */
 public class UserUnigramDictionary extends ExpandableDictionary {
+    static final boolean ENABLE_USER_UNIGRAM_DICTIONARY = false;
+
     // Weight added to a user picking a new word from the suggestion strip
     static final int FREQUENCY_FOR_PICKED = 3;
     // Weight added to a user typing a new word that doesn't get corrected (or is reverted)
@@ -71,17 +73,22 @@
     private static HashMap<String, String> sDictProjectionMap;
 
     static {
-        sDictProjectionMap = new HashMap<String, String>();
-        sDictProjectionMap.put(COLUMN_ID, COLUMN_ID);
-        sDictProjectionMap.put(COLUMN_WORD, COLUMN_WORD);
-        sDictProjectionMap.put(COLUMN_FREQUENCY, COLUMN_FREQUENCY);
-        sDictProjectionMap.put(COLUMN_LOCALE, COLUMN_LOCALE);
+        if (ENABLE_USER_UNIGRAM_DICTIONARY) {
+            sDictProjectionMap = new HashMap<String, String>();
+            sDictProjectionMap.put(COLUMN_ID, COLUMN_ID);
+            sDictProjectionMap.put(COLUMN_WORD, COLUMN_WORD);
+            sDictProjectionMap.put(COLUMN_FREQUENCY, COLUMN_FREQUENCY);
+            sDictProjectionMap.put(COLUMN_LOCALE, COLUMN_LOCALE);
+        }
     }
 
     private static DatabaseHelper sOpenHelper = null;
 
     public UserUnigramDictionary(Context context, LatinIME ime, String locale, int dicTypeId) {
         super(context, dicTypeId);
+        // Super must be first statement of the constructor... I'd like not to do it if the
+        // user unigram dictionary is not enabled, but Java won't let me.
+        if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
         mIme = ime;
         mLocale = locale;
         if (sOpenHelper == null) {
@@ -94,22 +101,25 @@
 
     @Override
     public synchronized boolean isValidWord(CharSequence word) {
+        if (!ENABLE_USER_UNIGRAM_DICTIONARY) return false;
         final int frequency = getWordFrequency(word);
         return frequency >= VALIDITY_THRESHOLD;
     }
 
     @Override
     public void close() {
+        super.close();
+        if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
         flushPendingWrites();
         // Don't close the database as locale changes will require it to be reopened anyway
         // Also, the database is written to somewhat frequently, so it needs to be kept alive
         // throughout the life of the process.
         // mOpenHelper.close();
-        super.close();
     }
 
     @Override
     public void loadDictionaryAsync() {
+        if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
         // Load the words that correspond to the current input locale
         Cursor cursor = query(COLUMN_LOCALE + "=?", new String[] { mLocale });
         try {
@@ -134,6 +144,7 @@
 
     @Override
     public void addWord(String newWord, int addFrequency) {
+        if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
         String word = newWord;
         final int length = word.length();
         // Don't add very short or very long words.
@@ -156,6 +167,7 @@
      * Schedules a background thread to write any pending words to the database.
      */
     public void flushPendingWrites() {
+        if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
         synchronized (mPendingWritesLock) {
             // Nothing pending? Return
             if (mPendingWrites.isEmpty()) return;