merge in jb-mr2-release history after reset to master
diff --git a/java/res/layout/input_view.xml b/java/res/layout/input_view.xml
index 40eff38..136e18c 100644
--- a/java/res/layout/input_view.xml
+++ b/java/res/layout/input_view.xml
@@ -43,8 +43,11 @@
             android:layout_width="@dimen/suggestions_strip_padding"
             android:layout_height="@dimen/suggestions_strip_height"
             style="?attr/suggestionsStripBackgroundStyle" />
+        <!-- To ensure that key preview popup is correctly placed when the current system locale is
+             one of RTL locales, layoutDirection="ltr" is needed in the SDK version 17+. -->
         <com.android.inputmethod.latin.suggestions.SuggestionStripView
             android:id="@+id/suggestion_strip_view"
+            android:layoutDirection="ltr"
             android:layout_weight="1.0"
             android:layout_width="0dp"
             android:layout_height="@dimen/suggestions_strip_height"
@@ -56,8 +59,11 @@
             style="?attr/suggestionsStripBackgroundStyle" />
     </LinearLayout>
 
+    <!-- To ensure that key preview popup is correctly placed when the current system locale is
+         one of RTL locales, layoutDirection="ltr" is needed in the SDK version 17+. -->
     <com.android.inputmethod.keyboard.MainKeyboardView
         android:id="@+id/keyboard_view"
+        android:layoutDirection="ltr"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 165116a..0d0ce57 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -74,6 +74,8 @@
 
     // The path fragment to append after the client ID for dictionary info requests.
     private static final String QUERY_PATH_DICT_INFO = "dict";
+    // The path fragment to append after the client ID for dictionary datafile requests.
+    private static final String QUERY_PATH_DATAFILE = "datafile";
     // The path fragment to append after the client ID for updating the metadata URI.
     private static final String QUERY_PATH_METADATA = "metadata";
     private static final String INSERT_METADATA_CLIENT_ID_COLUMN = "clientid";
@@ -96,37 +98,67 @@
     }
 
     /**
+     * Gets the content URI builder for a specified type.
+     *
+     * Supported types include QUERY_PATH_DICT_INFO, which takes the locale as
+     * the extraPath argument, and QUERY_PATH_DATAFILE, which needs a wordlist ID
+     * as the extraPath argument.
+     *
+     * @param clientId the clientId to use
+     * @param contentProviderClient the instance of content provider client
+     * @param queryPathType the path element encoding the type
+     * @param extraPath optional extra argument for this type (typically word list id)
+     * @return a builder that can build the URI for the best supported protocol version
+     * @throws RemoteException if the client can't be contacted
+     */
+    private static Uri.Builder getContentUriBuilderForType(final String clientId,
+            final ContentProviderClient contentProviderClient, final String queryPathType,
+            final String extraPath) throws RemoteException {
+        // Check whether protocol v2 is supported by building a v2 URI and calling getType()
+        // on it. If this returns null, v2 is not supported.
+        final Uri.Builder uriV2Builder = getProviderUriBuilder(clientId);
+        uriV2Builder.appendPath(queryPathType);
+        uriV2Builder.appendPath(extraPath);
+        uriV2Builder.appendQueryParameter(QUERY_PARAMETER_PROTOCOL,
+                QUERY_PARAMETER_PROTOCOL_VALUE);
+        if (null != contentProviderClient.getType(uriV2Builder.build())) return uriV2Builder;
+        // Protocol v2 is not supported, so create and return the protocol v1 uri.
+        return getProviderUriBuilder(extraPath);
+    }
+
+    /**
      * Queries a content provider for the list of word lists for a specific locale
      * available to copy into Latin IME.
      */
     private static List<WordListInfo> getWordListWordListInfos(final Locale locale,
             final Context context, final boolean hasDefaultWordList) {
         final String clientId = context.getString(R.string.dictionary_pack_client_id);
-        final Uri.Builder builder = getProviderUriBuilder(clientId);
-        builder.appendPath(QUERY_PATH_DICT_INFO);
-        builder.appendPath(locale.toString());
-        builder.appendQueryParameter(QUERY_PARAMETER_PROTOCOL, QUERY_PARAMETER_PROTOCOL_VALUE);
-        if (!hasDefaultWordList) {
-            builder.appendQueryParameter(QUERY_PARAMETER_MAY_PROMPT_USER,
-                    QUERY_PARAMETER_TRUE);
-        }
-        final Uri dictionaryPackUri = builder.build();
-
         final ContentProviderClient client = context.getContentResolver().
                 acquireContentProviderClient(getProviderUriBuilder("").build());
         if (null == client) return Collections.<WordListInfo>emptyList();
+
         try {
-            final Cursor c = client.query(dictionaryPackUri, DICTIONARY_PROJECTION, null, null,
-                    null);
-            if (null == c) {
-                reinitializeClientRecordInDictionaryContentProvider(context, client, clientId);
-                return Collections.<WordListInfo>emptyList();
+            final Uri.Builder builder = getContentUriBuilderForType(clientId, client,
+                    QUERY_PATH_DICT_INFO, locale.toString());
+            if (!hasDefaultWordList) {
+                builder.appendQueryParameter(QUERY_PARAMETER_MAY_PROMPT_USER,
+                        QUERY_PARAMETER_TRUE);
             }
+            final Uri queryUri = builder.build();
+            final boolean isProtocolV2 = (QUERY_PARAMETER_PROTOCOL_VALUE.equals(
+                    queryUri.getQueryParameter(QUERY_PARAMETER_PROTOCOL)));
+
+            Cursor c = client.query(queryUri, DICTIONARY_PROJECTION, null, null, null);
+            if (isProtocolV2 && null == c) {
+                reinitializeClientRecordInDictionaryContentProvider(context, client, clientId);
+                c = client.query(queryUri, DICTIONARY_PROJECTION, null, null, null);
+            }
+            if (null == c) return Collections.<WordListInfo>emptyList();
             if (c.getCount() <= 0 || !c.moveToFirst()) {
                 c.close();
                 return Collections.<WordListInfo>emptyList();
             }
-            final List<WordListInfo> list = CollectionUtils.newArrayList();
+            final ArrayList<WordListInfo> list = CollectionUtils.newArrayList();
             do {
                 final String wordListId = c.getString(0);
                 final String wordListLocale = c.getString(1);
@@ -156,13 +188,18 @@
     /**
      * Helper method to encapsulate exception handling.
      */
-    private static AssetFileDescriptor openAssetFileDescriptor(final ContentResolver resolver,
-            final Uri uri) {
+    private static AssetFileDescriptor openAssetFileDescriptor(
+            final ContentProviderClient providerClient, final Uri uri) {
         try {
-            return resolver.openAssetFileDescriptor(uri, "r");
+            return providerClient.openAssetFile(uri, "r");
         } catch (FileNotFoundException e) {
-            // I don't want to log the word list URI here for security concerns
-            Log.e(TAG, "Could not find a word list from the dictionary provider.");
+            // I don't want to log the word list URI here for security concerns. The exception
+            // contains the name of the file, so let's not pass it to Log.e here.
+            Log.e(TAG, "Could not find a word list from the dictionary provider."
+                    /* intentionally don't pass the exception (see comment above) */);
+            return null;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Can't communicate with the dictionary pack", e);
             return null;
         }
     }
@@ -172,9 +209,8 @@
      * to the cache file name designated by its id and locale, overwriting it if already present
      * and creating it (and its containing directory) if necessary.
      */
-    private static AssetFileAddress cacheWordList(final String id, final String locale,
-            final ContentResolver resolver, final Context context) {
-
+    private static AssetFileAddress cacheWordList(final String wordlistId, final String locale,
+            final ContentProviderClient providerClient, final Context context) {
         final int COMPRESSED_CRYPTED_COMPRESSED = 0;
         final int CRYPTED_COMPRESSED = 1;
         final int COMPRESSED_CRYPTED = 2;
@@ -184,11 +220,20 @@
         final int MODE_MIN = COMPRESSED_CRYPTED_COMPRESSED;
         final int MODE_MAX = NONE;
 
-        final Uri.Builder wordListUriBuilder = getProviderUriBuilder(id);
-        final String finalFileName = DictionaryInfoUtils.getCacheFileName(id, locale, context);
+        final String clientId = context.getString(R.string.dictionary_pack_client_id);
+        final Uri.Builder wordListUriBuilder;
+        try {
+            wordListUriBuilder = getContentUriBuilderForType(clientId,
+                    providerClient, QUERY_PATH_DATAFILE, wordlistId /* extraPath */);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Can't communicate with the dictionary pack", e);
+            return null;
+        }
+        final String finalFileName =
+                DictionaryInfoUtils.getCacheFileName(wordlistId, locale, context);
         String tempFileName;
         try {
-            tempFileName = BinaryDictionaryGetter.getTempFileName(id, context);
+            tempFileName = BinaryDictionaryGetter.getTempFileName(wordlistId, context);
         } catch (IOException e) {
             Log.e(TAG, "Can't open the temporary file", e);
             return null;
@@ -206,7 +251,7 @@
             final Uri wordListUri = wordListUriBuilder.build();
             try {
                 // Open input.
-                afd = openAssetFileDescriptor(resolver, wordListUri);
+                afd = openAssetFileDescriptor(providerClient, wordListUri);
                 // If we can't open it at all, don't even try a number of times.
                 if (null == afd) return null;
                 originalSourceStream = afd.createInputStream();
@@ -254,10 +299,10 @@
                 }
                 wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
                         QUERY_PARAMETER_SUCCESS);
-                if (0 >= resolver.delete(wordListUriBuilder.build(), null, null)) {
+                if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) {
                     Log.e(TAG, "Could not have the dictionary pack delete a word list");
                 }
-                BinaryDictionaryGetter.removeFilesWithIdExcept(context, id, finalFile);
+                BinaryDictionaryGetter.removeFilesWithIdExcept(context, wordlistId, finalFile);
                 // Success! Close files (through the finally{} clause) and return.
                 return AssetFileAddress.makeFromFileName(finalFileName);
             } catch (Exception e) {
@@ -297,8 +342,12 @@
         // as invalid.
         wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
                 QUERY_PARAMETER_FAILURE);
-        if (0 >= resolver.delete(wordListUriBuilder.build(), null, null)) {
-            Log.e(TAG, "In addition, we were unable to delete it.");
+        try {
+            if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) {
+                Log.e(TAG, "In addition, we were unable to delete it.");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "In addition, communication with the dictionary provider was cut", e);
         }
         return null;
     }
@@ -315,17 +364,27 @@
      */
     public static List<AssetFileAddress> cacheWordListsFromContentProvider(final Locale locale,
             final Context context, final boolean hasDefaultWordList) {
-        final ContentResolver resolver = context.getContentResolver();
-        final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
-                hasDefaultWordList);
-        final List<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
-        for (WordListInfo id : idList) {
-            final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context);
-            if (null != afd) {
-                fileAddressList.add(afd);
-            }
+        final ContentProviderClient providerClient = context.getContentResolver().
+                acquireContentProviderClient(getProviderUriBuilder("").build());
+        if (null == providerClient) {
+            Log.e(TAG, "Can't establish communication with the dictionary provider");
+            return CollectionUtils.newArrayList();
         }
-        return fileAddressList;
+        try {
+            final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
+                    hasDefaultWordList);
+            final ArrayList<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
+            for (WordListInfo id : idList) {
+                final AssetFileAddress afd =
+                        cacheWordList(id.mId, id.mLocale, providerClient, context);
+                if (null != afd) {
+                    fileAddressList.add(afd);
+                }
+            }
+            return fileAddressList;
+        } finally {
+            providerClient.release();
+        }
     }
 
     /**