Merge "Add null analysis annotations to keyboard package"
diff --git a/common/src/com/android/inputmethod/latin/common/LocaleUtils.java b/common/src/com/android/inputmethod/latin/common/LocaleUtils.java
index 7f2333b..d5878c0 100644
--- a/common/src/com/android/inputmethod/latin/common/LocaleUtils.java
+++ b/common/src/com/android/inputmethod/latin/common/LocaleUtils.java
@@ -104,7 +104,8 @@
      * @param testedLocale the locale to test.
      * @return a constant that measures how well the tested locale matches the reference locale.
      */
-    public static int getMatchLevel(final String referenceLocale, final String testedLocale) {
+    public static int getMatchLevel(@Nullable final String referenceLocale,
+            @Nullable final String testedLocale) {
         if (StringUtils.isEmpty(referenceLocale)) {
             return StringUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH;
         }
@@ -167,11 +168,8 @@
      * @param localeString a string specification of a locale, in a format of "ll_cc_variant" where
      * "ll" is a language code, "cc" is a country code.
      */
-    @Nullable
-    public static Locale constructLocaleFromString(@Nullable final String localeString) {
-        if (localeString == null) {
-            return null;
-        }
+    @Nonnull
+    public static Locale constructLocaleFromString(@Nonnull final String localeString) {
         synchronized (sLocaleCache) {
             if (sLocaleCache.containsKey(localeString)) {
                 return sLocaleCache.get(localeString);
diff --git a/java/res/layout/additional_subtype_dialog.xml b/java/res/layout/additional_subtype_dialog.xml
index b7804f5..2de7d07 100644
--- a/java/res/layout/additional_subtype_dialog.xml
+++ b/java/res/layout/additional_subtype_dialog.xml
@@ -38,7 +38,6 @@
             android:text="@string/subtype_locale" />
         <Spinner
             android:id="@+id/subtype_locale_spinner"
-            android:spinnerMode="dialog"
             android:layout_width="0dp"
             android:layout_weight="70"
             android:layout_height="wrap_content"
@@ -47,7 +46,8 @@
             android:layout_marginTop="8dip"
             android:layout_gravity="fill_horizontal|center_vertical"
             android:gravity="start|left"
-            android:prompt="@string/subtype_locale" />
+            android:prompt="@string/subtype_locale"
+            style="@style/additionalSubtypeSpinnerStyle" />
         </LinearLayout>
     <LinearLayout
         android:orientation="horizontal"
@@ -63,7 +63,6 @@
             android:text="@string/keyboard_layout_set" />
         <Spinner
             android:id="@+id/keyboard_layout_set_spinner"
-            android:spinnerMode="dialog"
             android:layout_width="0dp"
             android:layout_weight="70"
             android:layout_height="wrap_content"
@@ -72,6 +71,7 @@
             android:layout_marginTop="8dip"
             android:layout_gravity="fill_horizontal|center_vertical"
             android:gravity="start|left"
-            android:prompt="@string/keyboard_layout_set" />
+            android:prompt="@string/keyboard_layout_set"
+            style="@style/additionalSubtypeSpinnerStyle" />
     </LinearLayout>
 </LinearLayout>
diff --git a/java/res/values-v19/spinner-style.xml b/java/res/values-v19/spinner-style.xml
new file mode 100644
index 0000000..7de59ed
--- /dev/null
+++ b/java/res/values-v19/spinner-style.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Until KitKat (API 19), {@link android.widget.Spinner} of dialog mode in a Dialog can't
+         handle orientation change correctly. Using dropdown mode avoids the issue.
+         This file overrides values/spinner-style.xml on KitKat and newer device. -->
+    <style name="additionalSubtypeSpinnerStyle">
+        <item name="android:spinnerMode">dialog</item>
+    </style>
+</resources>
diff --git a/java/res/values/spinner-style.xml b/java/res/values/spinner-style.xml
new file mode 100644
index 0000000..4043ad4
--- /dev/null
+++ b/java/res/values/spinner-style.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Until KitKat (API 19), {@link android.widget.Spinner} of dialog mode in a Dialog can't
+         handle orientation change correctly. Using dropdown mode avoids the issue.
+         This file is overridden by values-v19/spinner-style.xml on KitKat and newer device. -->
+    <style name="additionalSubtypeSpinnerStyle">
+        <item name="android:spinnerMode">dropdown</item>
+    </style>
+</resources>
diff --git a/java/res/xml-sw600dp/key_styles_common.xml b/java/res/xml-sw600dp/key_styles_common.xml
index 006cda3..ea8f292 100644
--- a/java/res/xml-sw600dp/key_styles_common.xml
+++ b/java/res/xml-sw600dp/key_styles_common.xml
@@ -35,10 +35,6 @@
                 latin:keyLabelFlags="hasShiftedLetterHint" />
         </default>
     </switch>
-    <!-- Base key style for the key which may have settings key as more keys. -->
-    <key-style
-        latin:styleName="baseSettingsMoreKeysStyle"
-        latin:parentStyle="hasShiftedLetterHintStyle" />
     <include
         latin:keyboardLayout="@xml/key_styles_settings" />
     <!-- Functional key styles -->
diff --git a/java/res/xml/key_styles_common.xml b/java/res/xml/key_styles_common.xml
index b36ddf2..d85438d 100644
--- a/java/res/xml/key_styles_common.xml
+++ b/java/res/xml/key_styles_common.xml
@@ -35,9 +35,6 @@
                 latin:keyLabelFlags="hasShiftedLetterHint" />
         </default>
     </switch>
-    <!-- Base key style for the key which may have settings key as more keys. -->
-    <key-style
-        latin:styleName="baseSettingsMoreKeysStyle" />
     <include
         latin:keyboardLayout="@xml/key_styles_settings" />
     <!-- Functional key styles -->
diff --git a/java/res/xml/key_styles_settings.xml b/java/res/xml/key_styles_settings.xml
index a504bed..43ee601 100644
--- a/java/res/xml/key_styles_settings.xml
+++ b/java/res/xml/key_styles_settings.xml
@@ -21,16 +21,14 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <!-- Base key style for the key which may have settings key as more keys. -->
-    <!-- Kept as a separate file for cleaner overriding by an overlay.  -->
+    <!-- Key style for the key which may have settings key as more keys. -->
     <switch>
         <case
             latin:clobberSettingsKey="true"
         >
             <key-style
                 latin:styleName="settingsMoreKeysStyle"
-                latin:backgroundType="functional"
-                latin:parentStyle="baseSettingsMoreKeysStyle" />
+                latin:backgroundType="functional" />
         </case>
         <!-- clobberSettingsKey="false" -->
         <default>
@@ -38,8 +36,7 @@
                 latin:styleName="settingsMoreKeysStyle"
                 latin:keyLabelFlags="hasPopupHint"
                 latin:additionalMoreKeys="!text/keyspec_settings"
-                latin:backgroundType="functional"
-                latin:parentStyle="baseSettingsMoreKeysStyle" />
+                latin:backgroundType="functional" />
         </default>
     </switch>
 </merge>
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index be07443..c38ea00 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -118,11 +118,7 @@
             if (TextUtils.isEmpty(localeString)) {
                 continue;
             }
-            final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
-            if (locale == null) {
-                continue;
-            }
-            return locale;
+            return LocaleUtils.constructLocaleFromString(localeString);
         }
         return null;
     }
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
index e6acb8f..c678f08 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.IBinder;
+import android.util.Log;
 import android.widget.Toast;
 
 import com.android.inputmethod.latin.R;
@@ -33,6 +34,8 @@
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
+import javax.annotation.Nonnull;
+
 /**
  * Service that handles background tasks for the dictionary provider.
  *
@@ -51,6 +54,8 @@
  *     to access, and mark the current state as such.
  */
 public final class DictionaryService extends Service {
+    private static final String TAG = DictionaryService.class.getSimpleName();
+
     /**
      * The package name, to use in the intent actions.
      */
@@ -156,9 +161,14 @@
             final int startId) {
         final DictionaryService self = this;
         if (SHOW_DOWNLOAD_TOAST_INTENT_ACTION.equals(intent.getAction())) {
-            // This is a UI action, it can't be run in another thread
-            showStartDownloadingToast(this, LocaleUtils.constructLocaleFromString(
-                    intent.getStringExtra(LOCALE_INTENT_ARGUMENT)));
+            final String localeString = intent.getStringExtra(LOCALE_INTENT_ARGUMENT);
+            if (localeString == null) {
+                Log.e(TAG, "Received " + intent.getAction() + " without locale; skipped");
+            } else {
+                // This is a UI action, it can't be run in another thread
+                showStartDownloadingToast(
+                        this, LocaleUtils.constructLocaleFromString(localeString));
+            }
         } else {
             // If it's a command that does not require UI, arrange for the work to be done on a
             // separate thread, so that we can return right away. The executor will spawn a thread
@@ -245,7 +255,8 @@
     /**
      * Shows a toast informing the user that an automatic dictionary download is starting.
      */
-    private static void showStartDownloadingToast(final Context context, final Locale locale) {
+    private static void showStartDownloadingToast(final Context context,
+            @Nonnull final Locale locale) {
         final String toastText = String.format(
                 context.getString(R.string.toast_downloading_suggestions),
                 locale.getDisplayName());
diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java b/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
index 50b3c72..91ed673 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
@@ -28,7 +28,7 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.common.LocaleUtils;
 
-import java.util.Locale;
+import javax.annotation.Nullable;
 
 /**
  * This implements the dialog for asking the user whether it's okay to download dictionaries over
@@ -54,11 +54,11 @@
         setTexts(localeString, size);
     }
 
-    private void setTexts(final String localeString, final long size) {
+    private void setTexts(@Nullable final String localeString, final long size) {
         final String promptFormat = getString(R.string.should_download_over_metered_prompt);
         final String allowButtonFormat = getString(R.string.download_over_metered);
-        final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
-        final String language = (null == locale ? "" : locale.getDisplayLanguage());
+        final String language = (null == localeString) ? ""
+                : LocaleUtils.constructLocaleFromString(localeString).getDisplayLanguage();
         final TextView prompt = (TextView)findViewById(R.id.download_over_metered_prompt);
         prompt.setText(Html.fromHtml(String.format(promptFormat, language)));
         final Button allowButton = (Button)findViewById(R.id.allow_button);
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index aeb6667..f05db9d 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -57,7 +57,6 @@
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Locale;
 import java.util.Set;
 import java.util.TreeSet;
 
@@ -880,8 +879,8 @@
         // None of those are expected to happen, but just in case...
         if (null == notificationIntent || null == notificationManager) return;
 
-        final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
-        final String language = (null == locale ? "" : locale.getDisplayLanguage());
+        final String language = (null == localeString) ? ""
+                : LocaleUtils.constructLocaleFromString(localeString).getDisplayLanguage();
         final String titleFormat = context.getString(R.string.dict_available_notification_title);
         final String notificationTitle = String.format(titleFormat, language);
         final Notification.Builder builder = new Notification.Builder(context)
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index f2d7a8c..acf9cf1 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -264,7 +264,7 @@
         return mMostProbableDictionaryGroup;
     }
 
-    public void switchMostProbableLanguage(final Locale locale) {
+    public void switchMostProbableLanguage(@Nullable final Locale locale) {
         if (null == locale) {
             // In many cases, there is no locale to a committed word. For example, a typed word
             // that is in none of the currently active dictionaries but still does not
@@ -301,6 +301,9 @@
         if (!mostProbableDictionaryGroup.mLocale.equals(mLocale)) {
             return false;
         }
+        if (mDictionaryGroups.length <= 1) {
+            return true;
+        }
         return mostProbableDictionaryGroup.mConfidence >= CONFIDENCE_THRESHOLD;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 4de1f99..781ab06 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -98,25 +98,23 @@
             }
             final String wordlistId =
                     DictionaryInfoUtils.getWordListIdFromFileName(new File(f.mFilename).getName());
-            if (null != wordlistId) {
-                // TODO: this is a reasonable last resort, but it is suboptimal.
-                // The following will remove the entry for this dictionary with the dictionary
-                // provider. When the metadata is downloaded again, we will try downloading it
-                // again.
-                // However, in the practice that will mean the user will find themselves without
-                // the new dictionary. That's fine for languages where it's included in the APK,
-                // but for other languages it will leave the user without a dictionary at all until
-                // the next update, which may be a few days away.
-                // Ideally, we would trigger a new download right away, and use increasing retry
-                // delays for this particular id/version combination.
-                // Then again, this is expected to only ever happen in case of human mistake. If
-                // the wrong file is on the server, the following is still doing the right thing.
-                // If it's a file left over from the last version however, it's not great.
-                BinaryDictionaryFileDumper.reportBrokenFileToDictionaryProvider(
-                        providerClient,
-                        context.getString(R.string.dictionary_pack_client_id),
-                        wordlistId);
-            }
+            // TODO: this is a reasonable last resort, but it is suboptimal.
+            // The following will remove the entry for this dictionary with the dictionary
+            // provider. When the metadata is downloaded again, we will try downloading it
+            // again.
+            // However, in the practice that will mean the user will find themselves without
+            // the new dictionary. That's fine for languages where it's included in the APK,
+            // but for other languages it will leave the user without a dictionary at all until
+            // the next update, which may be a few days away.
+            // Ideally, we would trigger a new download right away, and use increasing retry
+            // delays for this particular id/version combination.
+            // Then again, this is expected to only ever happen in case of human mistake. If
+            // the wrong file is on the server, the following is still doing the right thing.
+            // If it's a file left over from the last version however, it's not great.
+            BinaryDictionaryFileDumper.reportBrokenFileToDictionaryProvider(
+                    providerClient,
+                    context.getString(R.string.dictionary_pack_client_id),
+                    wordlistId);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 390b311..30dd51a 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -27,7 +27,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
-import java.util.Locale;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
diff --git a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
index 78bfd2b..8cc3552 100644
--- a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
+++ b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
@@ -101,12 +101,12 @@
         final File file = new File(dirPath, fileName.toString());
         final DictionaryHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file);
         final StringBuilder message = new StringBuilder();
-        final String locale = header.getLocaleString();
-        for (String key : header.mDictionaryOptions.mAttributes.keySet()) {
+        final String localeString = header.mLocaleString;
+        for (final String key : header.mDictionaryOptions.mAttributes.keySet()) {
             message.append(key + " = " + header.mDictionaryOptions.mAttributes.get(key));
             message.append("\n");
         }
-        final String languageName = LocaleUtils.constructLocaleFromString(locale)
+        final String languageName = LocaleUtils.constructLocaleFromString(localeString)
                 .getDisplayName(Locale.getDefault());
         final String title = String.format(
                 context.getString(R.string.read_external_dictionary_confirm_install_message),
@@ -146,11 +146,12 @@
         BufferedOutputStream outputStream = null;
         File tempFile = null;
         try {
-            final String locale = header.getLocaleString();
+            final String localeString = header.mLocaleString;
             // Create the id for a main dictionary for this locale
             final String id = BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY
-                    + BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale;
-            final String finalFileName = DictionaryInfoUtils.getCacheFileName(id, locale, context);
+                    + BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + localeString;
+            final String finalFileName = DictionaryInfoUtils.getCacheFileName(
+                    id, localeString, context);
             final String tempFileName = BinaryDictionaryGetter.getTempFileName(id, context);
             tempFile = new File(tempFileName);
             tempFile.delete();
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java
index 644818b..4d253b0 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java
@@ -19,13 +19,24 @@
 import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 /**
  * Class representing dictionary header.
  */
 public final class DictionaryHeader {
     public final int mBodyOffset;
+    @Nonnull
     public final DictionaryOptions mDictionaryOptions;
+    @Nonnull
     public final FormatOptions mFormatOptions;
+    @Nonnull
+    public final String mLocaleString;
+    @Nonnull
+    public final String mVersionString;
+    @Nonnull
+    public final String mIdString;
 
     // Note that these are corresponding definitions in native code in latinime::HeaderPolicy
     // and latinime::HeaderReadWriteUtils.
@@ -46,39 +57,32 @@
     public static final String ATTRIBUTE_VALUE_TRUE = "1";
     public static final String CODE_POINT_TABLE_KEY = "codePointTable";
 
-    public DictionaryHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
-            final FormatOptions formatOptions) throws UnsupportedFormatException {
+    public DictionaryHeader(final int headerSize,
+            @Nonnull final DictionaryOptions dictionaryOptions,
+            @Nonnull final FormatOptions formatOptions) throws UnsupportedFormatException {
         mDictionaryOptions = dictionaryOptions;
         mFormatOptions = formatOptions;
         mBodyOffset = formatOptions.mVersion < FormatSpec.VERSION4 ? headerSize : 0;
-        if (null == getLocaleString()) {
+        final String localeString = dictionaryOptions.mAttributes.get(DICTIONARY_LOCALE_KEY);
+        if (null == localeString) {
             throw new UnsupportedFormatException("Cannot create a FileHeader without a locale");
         }
-        if (null == getVersion()) {
+        final String versionString = dictionaryOptions.mAttributes.get(DICTIONARY_VERSION_KEY);
+        if (null == versionString) {
             throw new UnsupportedFormatException(
                     "Cannot create a FileHeader without a version");
         }
-        if (null == getId()) {
+        final String idString = dictionaryOptions.mAttributes.get(DICTIONARY_ID_KEY);
+        if (null == idString) {
             throw new UnsupportedFormatException("Cannot create a FileHeader without an ID");
         }
-    }
-
-    // Helper method to get the locale as a String
-    public String getLocaleString() {
-        return mDictionaryOptions.mAttributes.get(DICTIONARY_LOCALE_KEY);
-    }
-
-    // Helper method to get the version String
-    public String getVersion() {
-        return mDictionaryOptions.mAttributes.get(DICTIONARY_VERSION_KEY);
-    }
-
-    // Helper method to get the dictionary ID as a String
-    public String getId() {
-        return mDictionaryOptions.mAttributes.get(DICTIONARY_ID_KEY);
+        mLocaleString = localeString;
+        mVersionString = versionString;
+        mIdString = idString;
     }
 
     // Helper method to get the description
+    @Nullable
     public String getDescription() {
         // TODO: Right now each dictionary file comes with a description in its own language.
         // It will display as is no matter the device's locale. It should be internationalized.
diff --git a/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java b/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java
index b749aa5..21ea8f8 100644
--- a/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java
+++ b/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java
@@ -196,16 +196,6 @@
         }
     }
 
-    private static int getSpinnerPosition(final Spinner spinner) {
-        if (spinner == null) return -1;
-        return spinner.getSelectedItemPosition();
-    }
-
-    private static void setSpinnerPosition(final Spinner spinner, final int position) {
-        if (spinner == null || position < 0) return;
-        spinner.setSelection(position);
-    }
-
     @Override
     protected Parcelable onSaveInstanceState() {
         final Parcelable superState = super.onSaveInstanceState();
@@ -216,8 +206,6 @@
 
         final SavedState myState = new SavedState(superState);
         myState.mSubtype = mSubtype;
-        myState.mSubtypeLocaleSelectedPos = getSpinnerPosition(mSubtypeLocaleSpinner);
-        myState.mKeyboardLayoutSetSelectedPos = getSpinnerPosition(mKeyboardLayoutSetSpinner);
         return myState;
     }
 
@@ -230,15 +218,11 @@
 
         final SavedState myState = (SavedState) state;
         super.onRestoreInstanceState(myState.getSuperState());
-        setSpinnerPosition(mSubtypeLocaleSpinner, myState.mSubtypeLocaleSelectedPos);
-        setSpinnerPosition(mKeyboardLayoutSetSpinner, myState.mKeyboardLayoutSetSelectedPos);
         setSubtype(myState.mSubtype);
     }
 
     static final class SavedState extends Preference.BaseSavedState {
         InputMethodSubtype mSubtype;
-        int mSubtypeLocaleSelectedPos;
-        int mKeyboardLayoutSetSelectedPos;
 
         public SavedState(final Parcelable superState) {
             super(superState);
@@ -247,15 +231,11 @@
         @Override
         public void writeToParcel(final Parcel dest, final int flags) {
             super.writeToParcel(dest, flags);
-            dest.writeInt(mSubtypeLocaleSelectedPos);
-            dest.writeInt(mKeyboardLayoutSetSelectedPos);
             dest.writeParcelable(mSubtype, 0);
         }
 
         public SavedState(final Parcel source) {
             super(source);
-            mSubtypeLocaleSelectedPos = source.readInt();
-            mKeyboardLayoutSetSelectedPos = source.readInt();
             mSubtype = (InputMethodSubtype)source.readParcelable(null);
         }
 
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index c90e8a3..4b8d2a3 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -115,7 +115,8 @@
     @Override
     public void onCreate() {
         final String localeString = getLocale();
-        mLocale = LocaleUtils.constructLocaleFromString(localeString);
+        mLocale = (null == localeString) ? null
+                : LocaleUtils.constructLocaleFromString(localeString);
         mScript = ScriptUtils.getScriptFromSpellCheckerLocale(mLocale);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
index 22fc35a..69029c5 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
@@ -34,6 +34,8 @@
 import java.util.Locale;
 import java.util.TreeSet;
 
+import javax.annotation.Nullable;
+
 // Caveat: This class is basically taken from
 // packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java
 // in order to deal with some devices that have issues with the user dictionary handling
@@ -218,8 +220,8 @@
     public static class LocaleRenderer {
         private final String mLocaleString;
         private final String mDescription;
-        // LocaleString may NOT be null.
-        public LocaleRenderer(final Context context, final String localeString) {
+
+        public LocaleRenderer(final Context context, @Nullable final String localeString) {
             mLocaleString = localeString;
             if (null == localeString) {
                 mDescription = context.getString(R.string.user_dict_settings_more_languages);
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
index b9ed353..6254b54 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
@@ -37,6 +37,8 @@
 import java.util.Locale;
 import java.util.TreeSet;
 
+import javax.annotation.Nullable;
+
 // Caveat: This class is basically taken from
 // packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryList.java
 // in order to deal with some devices that have issues with the user dictionary handling
@@ -131,21 +133,23 @@
 
     /**
      * Create a single User Dictionary Preference object, with its parameters set.
-     * @param locale The locale for which this user dictionary is for.
+     * @param localeString The locale for which this user dictionary is for.
      * @return The corresponding preference.
      */
-    protected Preference createUserDictionaryPreference(final String locale) {
+    protected Preference createUserDictionaryPreference(@Nullable final String localeString) {
         final Preference newPref = new Preference(getActivity());
         final Intent intent = new Intent(USER_DICTIONARY_SETTINGS_INTENT_ACTION);
-        if (null == locale) {
+        if (null == localeString) {
             newPref.setTitle(Locale.getDefault().getDisplayName());
         } else {
-            if ("".equals(locale))
+            if (localeString.isEmpty()) {
                 newPref.setTitle(getString(R.string.user_dict_settings_all_languages));
-            else
-                newPref.setTitle(LocaleUtils.constructLocaleFromString(locale).getDisplayName());
-            intent.putExtra("locale", locale);
-            newPref.getExtras().putString("locale", locale);
+            } else {
+                newPref.setTitle(
+                        LocaleUtils.constructLocaleFromString(localeString).getDisplayName());
+            }
+            intent.putExtra("locale", localeString);
+            newPref.getExtras().putString("locale", localeString);
         }
         newPref.setIntent(intent);
         newPref.setFragment(UserDictionarySettings.class.getName());
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index 71d724d..fcce1ec 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -40,6 +40,9 @@
 import java.util.Locale;
 import java.util.concurrent.TimeUnit;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 /**
  * This class encapsulates the logic for the Latin-IME side of dictionary information management.
  */
@@ -59,19 +62,26 @@
         private static final String DATE_COLUMN = "date";
         private static final String FILESIZE_COLUMN = "filesize";
         private static final String VERSION_COLUMN = "version";
+        @Nonnull
         public final String mId;
+        @Nonnull
         public final Locale mLocale;
+        @Nullable
         public final String mDescription;
+        @Nonnull
         public final AssetFileAddress mFileAddress;
         public final int mVersion;
-        public DictionaryInfo(final String id, final Locale locale, final String description,
-                final AssetFileAddress fileAddress, final int version) {
+
+        public DictionaryInfo(@Nonnull final String id, @Nonnull final Locale locale,
+                @Nullable final String description, @Nonnull final AssetFileAddress fileAddress,
+                final int version) {
             mId = id;
             mLocale = locale;
             mDescription = description;
             mFileAddress = fileAddress;
             mVersion = version;
         }
+
         public ContentValues toContentValues() {
             final ContentValues values = new ContentValues();
             values.put(WORDLISTID_COLUMN, mId);
@@ -144,7 +154,8 @@
     /**
      * Reverse escaping done by replaceFileNameDangerousCharacters.
      */
-    public static String getWordListIdFromFileName(final String fname) {
+    @Nonnull
+    public static String getWordListIdFromFileName(@Nonnull final String fname) {
         final StringBuilder sb = new StringBuilder();
         final int fnameLength = fname.length();
         for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) {
@@ -176,12 +187,15 @@
      * {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}.
      * @return The category as a string or null if it can't be found in the file name.
      */
-    public static String getCategoryFromFileName(final String fileName) {
+    @Nullable
+    public static String getCategoryFromFileName(@Nonnull final String fileName) {
         final String id = getWordListIdFromFileName(fileName);
         final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
         // An id is supposed to be in format category:locale, so splitting on the separator
         // should yield a 2-elements array
-        if (2 != idArray.length) return null;
+        if (2 != idArray.length) {
+            return null;
+        }
         return idArray[0];
     }
 
@@ -225,7 +239,9 @@
         final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
         // An id is supposed to be in format category:locale, so splitting on the separator
         // should yield a 2-elements array
-        if (2 != idArray.length) return false;
+        if (2 != idArray.length) {
+            return false;
+        }
         return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
     }
 
@@ -277,7 +293,9 @@
      */
     public static int getMainDictionaryResourceId(final Resources res, final Locale locale) {
         int resourceId = getMainDictionaryResourceIdIfAvailableForLocale(res, locale);
-        if (0 != resourceId) return resourceId;
+        if (0 != resourceId) {
+            return resourceId;
+        }
         return res.getIdentifier(DEFAULT_MAIN_DICT, "raw", RESOURCE_PACKAGE_NAME);
     }
 
@@ -346,10 +364,10 @@
         if (header == null) {
             return null;
         }
-        final String id = header.getId();
-        final Locale locale = LocaleUtils.constructLocaleFromString(header.getLocaleString());
+        final String id = header.mIdString;
+        final Locale locale = LocaleUtils.constructLocaleFromString(header.mLocaleString);
         final String description = header.getDescription();
-        final String version = header.getVersion();
+        final String version = header.mVersionString;
         return new DictionaryInfo(id, locale, description, fileAddress, Integer.parseInt(version));
     }
 
@@ -377,10 +395,13 @@
         if (null != directoryList) {
             for (final File directory : directoryList) {
                 final String localeString = getWordListIdFromFileName(directory.getName());
-                File[] dicts = BinaryDictionaryGetter.getCachedWordLists(localeString, context);
+                final File[] dicts = BinaryDictionaryGetter.getCachedWordLists(
+                        localeString, context);
                 for (final File dict : dicts) {
                     final String wordListId = getWordListIdFromFileName(dict.getName());
-                    if (!DictionaryInfoUtils.isMainWordListId(wordListId)) continue;
+                    if (!DictionaryInfoUtils.isMainWordListId(wordListId)) {
+                        continue;
+                    }
                     final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
                     final AssetFileAddress fileAddress = AssetFileAddress.makeFromFile(dict);
                     final DictionaryInfo dictionaryInfo =
@@ -388,7 +409,9 @@
                     // Protect against cases of a less-specific dictionary being found, like an
                     // en dictionary being used for an en_US locale. In this case, the en dictionary
                     // should be used for en_US but discounted for listing purposes.
-                    if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) continue;
+                    if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) {
+                        continue;
+                    }
                     addOrUpdateDictInfo(dictList, dictionaryInfo);
                 }
             }
@@ -402,14 +425,18 @@
             final int resourceId =
                     DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale(
                             context.getResources(), locale);
-            if (0 == resourceId) continue;
+            if (0 == resourceId) {
+                continue;
+            }
             final AssetFileAddress fileAddress =
                     BinaryDictionaryGetter.loadFallbackResource(context, resourceId);
             final DictionaryInfo dictionaryInfo = createDictionaryInfoFromFileAddress(fileAddress);
             // Protect against cases of a less-specific dictionary being found, like an
             // en dictionary being used for an en_US locale. In this case, the en dictionary
             // should be used for en_US but discounted for listing purposes.
-            if (!dictionaryInfo.mLocale.equals(locale)) continue;
+            if (!dictionaryInfo.mLocale.equals(locale)) {
+                continue;
+            }
             addOrUpdateDictInfo(dictList, dictionaryInfo);
         }
 
@@ -419,7 +446,9 @@
     @UsedForTesting
     public static boolean looksValidForDictionaryInsertion(final CharSequence text,
             final SpacingAndPunctuations spacingAndPunctuations) {
-        if (TextUtils.isEmpty(text)) return false;
+        if (TextUtils.isEmpty(text)) {
+            return false;
+        }
         final int length = text.length();
         if (length > Constants.DICTIONARY_MAX_WORD_LENGTH) {
             return false;
@@ -435,7 +464,9 @@
                 digitCount += charCount;
                 continue;
             }
-            if (!spacingAndPunctuations.isWordCodePoint(codePoint)) return false;
+            if (!spacingAndPunctuations.isWordCodePoint(codePoint)) {
+                return false;
+            }
         }
         // We reject strings entirely comprised of digits to avoid using PIN codes or credit
         // card numbers. It would come in handy for word prediction though; a good example is
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 0e7f471..54a3fc3 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -199,8 +199,7 @@
         if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
             languageString = localeString;
         } else {
-            final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
-            languageString = locale.getLanguage();
+            languageString = LocaleUtils.constructLocaleFromString(localeString).getLanguage();
         }
         return getSubtypeLocaleDisplayNameInternal(languageString, displayLocale);
     }
@@ -232,8 +231,8 @@
             };
             displayName = getExceptionalName.runInLocale(sResources, displayLocale);
         } else {
-            final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
-            displayName = locale.getDisplayName(displayLocale);
+            displayName = LocaleUtils.constructLocaleFromString(localeString)
+                    .getDisplayName(displayLocale);
         }
         return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale);
     }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
index 84c3956..b5ed94c 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
@@ -37,8 +37,6 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
-import java.util.Arrays;
-import java.util.ArrayList;
 import java.util.HashMap;
 
 import javax.annotation.Nonnull;
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Header.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Header.java
index ba96c0a..aa1762f 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Header.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Header.java
@@ -16,14 +16,12 @@
 
 package com.android.inputmethod.latin.dicttool;
 
-import com.android.inputmethod.latin.BinaryDictionary;
 import com.android.inputmethod.latin.dicttool.BinaryDictOffdeviceUtils.DecoderChainSpec;
 import com.android.inputmethod.latin.makedict.DictionaryHeader;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 
 import java.io.File;
 import java.util.Arrays;
-import java.util.Locale;
 
 public class Header extends Dicttool.Command {
     public static final String COMMAND = "header";
diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
index ea9d4cc..e68aeb0 100644
--- a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
+++ b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
@@ -154,7 +154,6 @@
     public void runTestHeaderReaderProcessorWithOneSpec(final boolean compress, final boolean crypt)
             throws IOException, UnsupportedFormatException {
         final String dictName = "testHeaderReaderProcessor";
-        final String dictVersion = Long.toString(System.currentTimeMillis());
         final FormatOptions formatOptions = BinaryDictUtils.STATIC_OPTIONS;
         final int MAX_NUMBER_OF_OPTIONS_TO_ADD = 5;
         final HashMap<String, String> options = new HashMap<>();