Merge "Prep for GCC 4.6 that will be used in unbundled branches"
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index 2284576..6e65015 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -72,10 +72,10 @@
     <string name="gesture_floating_preview_text" msgid="4443240334739381053">"Pré-visual. flutuante dinâmica"</string>
     <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"Ver palavra sugerida enquanto toca"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: guardada"</string>
-    <string name="label_go_key" msgid="1635148082137219148">"OK"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"Ir"</string>
     <string name="label_next_key" msgid="362972844525672568">"Avançar"</string>
     <string name="label_previous_key" msgid="1211868118071386787">"Ant."</string>
-    <string name="label_done_key" msgid="2441578748772529288">"Feito"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"OK"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Enviar"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Esp."</string>
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 879f1db..165116a 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -23,6 +23,7 @@
 import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -95,23 +96,6 @@
     }
 
     /**
-     * Finds out whether the dictionary pack is available on this device.
-     * @param context A context to get the content resolver.
-     * @return whether the dictionary pack is present or not.
-     */
-    private static boolean isDictionaryPackPresent(final Context context) {
-        final ContentResolver cr = context.getContentResolver();
-        final ContentProviderClient client =
-                cr.acquireContentProviderClient(getProviderUriBuilder("").build());
-        if (client != null) {
-            client.release();
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
      * Queries a content provider for the list of word lists for a specific locale
      * available to copy into Latin IME.
      */
@@ -128,15 +112,14 @@
         }
         final Uri dictionaryPackUri = builder.build();
 
-        final ContentResolver resolver = context.getContentResolver();
+        final ContentProviderClient client = context.getContentResolver().
+                acquireContentProviderClient(getProviderUriBuilder("").build());
+        if (null == client) return Collections.<WordListInfo>emptyList();
         try {
-            final Cursor c = resolver.query(dictionaryPackUri, DICTIONARY_PROJECTION, null, null,
+            final Cursor c = client.query(dictionaryPackUri, DICTIONARY_PROJECTION, null, null,
                     null);
             if (null == c) {
-                if (isDictionaryPackPresent(context)) {
-                    reinitializeClientRecordInDictionaryContentProvider(context, resolver,
-                            clientId);
-                }
+                reinitializeClientRecordInDictionaryContentProvider(context, client, clientId);
                 return Collections.<WordListInfo>emptyList();
             }
             if (c.getCount() <= 0 || !c.moveToFirst()) {
@@ -152,21 +135,20 @@
             } while (c.moveToNext());
             c.close();
             return list;
-        } catch (IllegalArgumentException e) {
-            // Any method call on the content resolver may unexpectedly crash without notice
-            // if the content provider is not present (for example, while crypting a device).
-            // Testing seems to indicate that ContentResolver#query() merely returns null
-            // while ContentResolver#delete throws IllegalArgumentException but this is
-            // undocumented, so all ContentResolver methods should be protected. A crash here is
-            // dangerous because crashing here would brick any encrypted device - we need the
-            // keyboard to be up and working to enter the password. So let's be as safe as possible.
-            Log.e(TAG, "IllegalArgumentException: the dictionary pack can't be contacted?", e);
+        } catch (RemoteException e) {
+            // The documentation is unclear as to in which cases this may happen, but it probably
+            // happens when the content provider got suddenly killed because it crashed or because
+            // the user disabled it through Settings.
+            Log.e(TAG, "RemoteException: communication with the dictionary pack cut", e);
             return Collections.<WordListInfo>emptyList();
         } catch (Exception e) {
-            // Just in case we hit a problem in communication with the dictionary pack.
-            // We don't want to die.
-            Log.e(TAG, "Exception communicating with the dictionary pack", e);
+            // A crash here is dangerous because crashing here would brick any encrypted device -
+            // we need the keyboard to be up and working to enter the password, so we don't want
+            // to die no matter what. So let's be as safe as possible.
+            Log.e(TAG, "Unexpected exception communicating with the dictionary pack", e);
             return Collections.<WordListInfo>emptyList();
+        } finally {
+            client.release();
         }
     }
 
@@ -380,7 +362,7 @@
     }
 
     private static void reinitializeClientRecordInDictionaryContentProvider(final Context context,
-            final ContentResolver resolver, final String clientId) {
+            final ContentProviderClient client, final String clientId) throws RemoteException {
         final String metadataFileUri = context.getString(R.string.dictionary_pack_metadata_uri);
         if (TextUtils.isEmpty(metadataFileUri)) return;
         // Tell the content provider to reset all information about this client id
@@ -388,12 +370,12 @@
                 .appendPath(QUERY_PATH_METADATA)
                 .appendQueryParameter(QUERY_PARAMETER_PROTOCOL, QUERY_PARAMETER_PROTOCOL_VALUE)
                 .build();
-        resolver.delete(metadataContentUri, null, null);
+        client.delete(metadataContentUri, null, null);
         // Update the metadata URI
         final ContentValues metadataValues = new ContentValues();
         metadataValues.put(INSERT_METADATA_CLIENT_ID_COLUMN, clientId);
         metadataValues.put(INSERT_METADATA_METADATA_URI_COLUMN, metadataFileUri);
-        resolver.insert(metadataContentUri, metadataValues);
+        client.insert(metadataContentUri, metadataValues);
 
         // Update the dictionary list.
         final Uri dictionaryContentUriBase = getProviderUriBuilder(clientId)
@@ -405,7 +387,7 @@
         final int length = dictionaryList.size();
         for (int i = 0; i < length; ++i) {
             final DictionaryInfo info = dictionaryList.get(i);
-            resolver.insert(Uri.withAppendedPath(dictionaryContentUriBase, info.mId),
+            client.insert(Uri.withAppendedPath(dictionaryContentUriBase, info.mId),
                     info.toContentValues());
         }
     }
diff --git a/java/src/com/android/inputmethod/research/LogStatement.java b/java/src/com/android/inputmethod/research/LogStatement.java
index 059146a..09b12fc 100644
--- a/java/src/com/android/inputmethod/research/LogStatement.java
+++ b/java/src/com/android/inputmethod/research/LogStatement.java
@@ -35,7 +35,7 @@
  * associated with the {@code String[] keys} are likely to reveal information about the user.  The
  * actual values are stored separately.
  */
-class LogStatement {
+public class LogStatement {
     private static final String TAG = LogStatement.class.getSimpleName();
     private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
 
@@ -166,6 +166,8 @@
     /**
      * Write the contents out through jsonWriter.
      *
+     * The JsonWriter class must have already had {@code JsonWriter.beginArray} called on it.
+     *
      * Note that this method is not thread safe for the same jsonWriter.  Callers must ensure
      * thread safety.
      */
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index a584a3a..1a9a720 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -110,7 +110,13 @@
     }
 
     /**
-     * Publish the contents of this LogUnit to researchLog.
+     * Publish the contents of this LogUnit to {@code researchLog}.
+     *
+     * For each publishable {@code LogStatement}, invoke {@link LogStatement#outputToLocked}.
+     *
+     * @param researchLog where to publish the contents of this {@code LogUnit}
+     * @param canIncludePrivateData whether the private data in this {@code LogUnit} should be
+     * included
      */
     public synchronized void publishTo(final ResearchLog researchLog,
             final boolean canIncludePrivateData) {
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 24bf7d1..5114977 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -81,10 +81,7 @@
         }
     }
 
-    public ResearchLog(final File outputFile, Context context) {
-        if (outputFile == null) {
-            throw new IllegalArgumentException();
-        }
+    public ResearchLog(final File outputFile, final Context context) {
         mExecutor = Executors.newSingleThreadScheduledExecutor();
         mFile = outputFile;
         mContext = context;
@@ -112,7 +109,7 @@
                     Log.d(TAG, "error when closing ResearchLog:");
                     e.printStackTrace();
                 } finally {
-                    if (mFile.exists()) {
+                    if (mFile != null && mFile.exists()) {
                         mFile.setWritable(false, false);
                     }
                     if (onClosed != null) {
@@ -139,7 +136,9 @@
                         mHasWrittenData = false;
                     }
                 } finally {
-                    mIsAbortSuccessful = mFile.delete();
+                    if (mFile != null) {
+                        mIsAbortSuccessful = mFile.delete();
+                    }
                 }
                 return null;
             }
@@ -209,7 +208,7 @@
      */
     public JsonWriter getValidJsonWriterLocked() {
         try {
-            if (mJsonWriter == NULL_JSON_WRITER) {
+            if (mJsonWriter == NULL_JSON_WRITER && mFile != null) {
                 final FileOutputStream fos =
                         mContext.openFileOutput(mFile.getName(), Context.MODE_PRIVATE);
                 mJsonWriter = new JsonWriter(new BufferedWriter(new OutputStreamWriter(fos)));