Make image keyboard API backport compatible to all previous impls

This is a follow up CL to our recent attempt [1] to work around an
unintentional protocol breakage in image keyboard API on API<25 that
was accidentally introduced in AndroidX 1.0.0 [2].

Basically there are three versions of image keyboard API backports.
 - legacy support lib
 - AndroidX 1.0.0
 - upcoming Android X 1.1.0

As a result, there are 9 cases to consider depending on which version
is used in the app and which version is used by the IME.

With this CL, Android X 1.1.0 version becomes compatible to all the
versions above.  This should make our life much easier, because
regardless of whether it's an app or IME, apps/IMEs becomes compatible
with other IMEs/apps that were built with whatever version of
libraries above just by upgrading to AndroidX 1.1.0.

Note that there is no behavior change for API 25+ devices, where the
platform API is always used.

 [1]: I42a4a1a215a00d42fca62bec31e52b0947d1de74
      08a73da29759682576450ea7c31ad49722265f2c
 [2]: I11a047324832801555673dac45ec1d6590a6338b
      b31c3281d870e9abb673db239234d580dcc4feff

Bug: 129349688
Test: unittest
Change-Id: I6cc9498151a09c4f62435e4dd07def63c223e140
diff --git a/core/src/androidTest/java/androidx/core/view/inputmethod/EditorInfoCompatTest.java b/core/src/androidTest/java/androidx/core/view/inputmethod/EditorInfoCompatTest.java
index a5280ad..9eb6007 100644
--- a/core/src/androidTest/java/androidx/core/view/inputmethod/EditorInfoCompatTest.java
+++ b/core/src/androidTest/java/androidx/core/view/inputmethod/EditorInfoCompatTest.java
@@ -16,9 +16,10 @@
 
 package androidx.core.view.inputmethod;
 
+import static androidx.core.view.inputmethod.EditorInfoTestUtils.createEditorInfoForTest;
+
 import static org.junit.Assert.assertArrayEquals;
 
-import android.os.Bundle;
 import android.support.v4.BaseInstrumentationTestCase;
 import android.view.inputmethod.EditorInfo;
 
@@ -34,11 +35,6 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class EditorInfoCompatTest extends BaseInstrumentationTestCase<TestActivity> {
-    private static final String CONTENT_MIME_TYPES_KEY =
-            "androidx.core.view.inputmethod.EditorInfoCompat.CONTENT_MIME_TYPES";
-    private static final String CONTENT_MIME_TYPES_INTEROP_KEY =
-            "android.support.v13.view.inputmethod.EditorInfoCompat.CONTENT_MIME_TYPES";
-
     public EditorInfoCompatTest() {
         super(TestActivity.class);
     }
@@ -63,28 +59,19 @@
 
     @Test
     @SdkSuppress(maxSdkVersion = 24)
-    public void testRoundTripInteropOnlyNew() {
-        EditorInfo editorInfo = new EditorInfo();
+    public void testRoundTripSupportLibAndroidX100() {
         String[] mimeTypes = new String[]{"image/gif", "image/jpeg", "image/png"};
-
-        if (editorInfo.extras == null) {
-            editorInfo.extras = new Bundle();
-        }
-        editorInfo.extras.putStringArray(CONTENT_MIME_TYPES_KEY, mimeTypes);
-
-        assertArrayEquals(EditorInfoCompat.getContentMimeTypes(editorInfo), mimeTypes);
+        assertArrayEquals(EditorInfoCompat.getContentMimeTypes(
+                createEditorInfoForTest(mimeTypes, EditorInfoCompat.Protocol.AndroidX_1_0_0)),
+                mimeTypes);
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = 24)
-    public void testRoundTripInteropOnlyOld() {
-        EditorInfo editorInfo = new EditorInfo();
+    public void testRoundTripSupportLibAndroidX110() {
         String[] mimeTypes = new String[]{"image/gif", "image/jpeg", "image/png"};
-
-        if (editorInfo.extras == null) {
-            editorInfo.extras = new Bundle();
-        }
-        editorInfo.extras.putStringArray(CONTENT_MIME_TYPES_INTEROP_KEY, mimeTypes);
-        assertArrayEquals(EditorInfoCompat.getContentMimeTypes(editorInfo), mimeTypes);
+        assertArrayEquals(EditorInfoCompat.getContentMimeTypes(
+                createEditorInfoForTest(mimeTypes, EditorInfoCompat.Protocol.AndroidX_1_1_0)),
+                mimeTypes);
     }
 }
diff --git a/core/src/androidTest/java/androidx/core/view/inputmethod/EditorInfoTestUtils.java b/core/src/androidTest/java/androidx/core/view/inputmethod/EditorInfoTestUtils.java
new file mode 100644
index 0000000..3196637
--- /dev/null
+++ b/core/src/androidTest/java/androidx/core/view/inputmethod/EditorInfoTestUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2019 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 androidx.core.view.inputmethod;
+
+import android.os.Bundle;
+import android.view.inputmethod.EditorInfo;
+
+final class EditorInfoTestUtils {
+    private static final String CONTENT_MIME_TYPES_KEY =
+            "androidx.core.view.inputmethod.EditorInfoCompat.CONTENT_MIME_TYPES";
+    private static final String CONTENT_MIME_TYPES_INTEROP_KEY =
+            "android.support.v13.view.inputmethod.EditorInfoCompat.CONTENT_MIME_TYPES";
+
+    static EditorInfo createEditorInfoForTest(String[] mimeTypes, int protocol) {
+        EditorInfo editorInfo = new EditorInfo();
+        if (editorInfo.extras == null) {
+            editorInfo.extras = new Bundle();
+        }
+        switch (protocol) {
+            case EditorInfoCompat.Protocol.SupportLib:
+                editorInfo.extras.putStringArray(CONTENT_MIME_TYPES_INTEROP_KEY, mimeTypes);
+                break;
+            case EditorInfoCompat.Protocol.AndroidX_1_0_0:
+                editorInfo.extras.putStringArray(CONTENT_MIME_TYPES_KEY, mimeTypes);
+                break;
+            case EditorInfoCompat.Protocol.AndroidX_1_1_0:
+                editorInfo.extras.putStringArray(CONTENT_MIME_TYPES_INTEROP_KEY, mimeTypes);
+                editorInfo.extras.putStringArray(CONTENT_MIME_TYPES_KEY, mimeTypes);
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported protocol=" + protocol);
+        }
+        return editorInfo;
+    }
+}
diff --git a/core/src/androidTest/java/androidx/core/view/inputmethod/InputConnectionCompatTest.java b/core/src/androidTest/java/androidx/core/view/inputmethod/InputConnectionCompatTest.java
new file mode 100644
index 0000000..8e0eb23
--- /dev/null
+++ b/core/src/androidTest/java/androidx/core/view/inputmethod/InputConnectionCompatTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2019 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 androidx.core.view.inputmethod;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.ClipDescription;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputContentInfo;
+
+import androidx.core.app.TestActivity;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+
+import java.util.Objects;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InputConnectionCompatTest extends BaseInstrumentationTestCase<TestActivity> {
+    public InputConnectionCompatTest() {
+        super(TestActivity.class);
+    }
+
+    private static final String COMMIT_CONTENT_ACTION =
+            "androidx.core.view.inputmethod.InputConnectionCompat.COMMIT_CONTENT";
+    private static final String COMMIT_CONTENT_INTEROP_ACTION =
+            "android.support.v13.view.inputmethod.InputConnectionCompat.COMMIT_CONTENT";
+    private static final String COMMIT_CONTENT_CONTENT_URI_KEY =
+            "androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_URI";
+    private static final String COMMIT_CONTENT_CONTENT_URI_INTEROP_KEY =
+            "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_URI";
+    private static final String COMMIT_CONTENT_DESCRIPTION_KEY =
+            "androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_DESCRIPTION";
+    private static final String COMMIT_CONTENT_DESCRIPTION_INTEROP_KEY =
+            "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_DESCRIPTION";
+    private static final String COMMIT_CONTENT_LINK_URI_KEY =
+            "androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_LINK_URI";
+    private static final String COMMIT_CONTENT_LINK_URI_INTEROP_KEY =
+            "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_LINK_URI";
+    private static final String COMMIT_CONTENT_OPTS_KEY =
+            "androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_OPTS";
+    private static final String COMMIT_CONTENT_OPTS_INTEROP_KEY =
+            "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_OPTS";
+    private static final String COMMIT_CONTENT_FLAGS_KEY =
+            "androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_FLAGS";
+    private static final String COMMIT_CONTENT_FLAGS_INTEROP_KEY =
+            "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_FLAGS";
+
+    private static final String[] TEST_MIME_TYPES = new String[]{"image/gif"};
+    private static final ClipDescription TEST_CLIP_DESCRIPTION =
+            new ClipDescription("test", TEST_MIME_TYPES);
+
+    private static final Uri TEST_CONTENT_URI = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority("androidx.core.view.inputmethod.test")
+            .appendPath("foobar")
+            .build();
+
+    private static final Uri TEST_LINK_URI = Uri.parse("https://example.com");
+
+    private static final InputContentInfoCompat TEST_INPUT_CONTENT_INFO =
+            new InputContentInfoCompat(
+                    TEST_CONTENT_URI, TEST_CLIP_DESCRIPTION, TEST_LINK_URI);
+
+    private static final Bundle TEST_BUNDLE = new Bundle();
+    private static final int TEST_FLAGS = 0;
+
+    @Test
+    @SdkSuppress(minSdkVersion = 25)
+    public void commitContentPlatformApi() {
+        EditorInfo editorInfo = new EditorInfo();
+        EditorInfoCompat.setContentMimeTypes(editorInfo, TEST_MIME_TYPES);
+
+        InputConnection ic = mock(InputConnection.class);
+        doReturn(true).when(ic).commitContent(
+                any(InputContentInfo.class), anyInt(), any(Bundle.class));
+
+        InputConnectionCompat.commitContent(
+                ic, editorInfo, TEST_INPUT_CONTENT_INFO, TEST_FLAGS, TEST_BUNDLE);
+
+        verify(ic).commitContent(
+                argThat(new ArgumentMatcher<InputContentInfo>() {
+                    @Override
+                    public boolean matches(InputContentInfo info) {
+                        return Objects.equals(
+                                TEST_INPUT_CONTENT_INFO.getContentUri(), info.getContentUri())
+                                && Objects.equals(TEST_INPUT_CONTENT_INFO.getDescription(),
+                                info.getDescription())
+                                && Objects.equals(TEST_INPUT_CONTENT_INFO.getLinkUri(),
+                                info.getLinkUri());
+                    }
+                }),
+                eq(TEST_FLAGS),
+                eq(TEST_BUNDLE)
+        );
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 24)
+    public void commitContentSupportLib() {
+        verifyCommitContentCompat(EditorInfoCompat.Protocol.SupportLib);
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 24)
+    public void commitContentAndroidX100() {
+        verifyCommitContentCompat(EditorInfoCompat.Protocol.AndroidX_1_0_0);
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 24)
+    public void commitContentAndroidX110() {
+        verifyCommitContentCompat(EditorInfoCompat.Protocol.AndroidX_1_1_0);
+    }
+
+    private void verifyCommitContentCompat(int protocol) {
+        EditorInfo editorInfo = EditorInfoTestUtils.createEditorInfoForTest(
+                TEST_MIME_TYPES, protocol);
+
+        InputConnection ic = mock(InputConnection.class);
+        doReturn(true).when(ic).performPrivateCommand(anyString(), any(Bundle.class));
+
+        InputContentInfoCompat inputContentInfoCompat = new InputContentInfoCompat(
+                TEST_CONTENT_URI, TEST_CLIP_DESCRIPTION, TEST_LINK_URI);
+        InputConnectionCompat.commitContent(
+                ic, editorInfo, inputContentInfoCompat, TEST_FLAGS, TEST_BUNDLE);
+
+        verify(ic).performPrivateCommand(
+                eq(getActionName(protocol)), argThat(getBundleMatcher(protocol)));
+    }
+
+    private static String getActionName(int protocol) {
+        switch (protocol) {
+            case EditorInfoCompat.Protocol.SupportLib:
+                // If the target app is based on support-lib version, use legacy action name.
+                return COMMIT_CONTENT_INTEROP_ACTION;
+            case EditorInfoCompat.Protocol.AndroidX_1_0_0:
+            case EditorInfoCompat.Protocol.AndroidX_1_1_0:
+                // Otherwise, use new action name.
+                return COMMIT_CONTENT_ACTION;
+            default:
+                throw new UnsupportedOperationException("Unsupported protocol=" + protocol);
+        }
+    }
+
+    private static ArgumentMatcher<Bundle> getBundleMatcher(int protocol) {
+        final String contentUriKey;
+        final String descriptionKey;
+        final String linkUriKey;
+        final String optsKey;
+        final String flagsKey;
+        switch (protocol) {
+            case EditorInfoCompat.Protocol.SupportLib:
+                // If the target app is based on support-lib version, use legacy keys.
+                contentUriKey = COMMIT_CONTENT_CONTENT_URI_INTEROP_KEY;
+                descriptionKey = COMMIT_CONTENT_DESCRIPTION_INTEROP_KEY;
+                linkUriKey = COMMIT_CONTENT_LINK_URI_INTEROP_KEY;
+                flagsKey = COMMIT_CONTENT_FLAGS_INTEROP_KEY;
+                optsKey = COMMIT_CONTENT_OPTS_INTEROP_KEY;
+                break;
+            case EditorInfoCompat.Protocol.AndroidX_1_0_0:
+            case EditorInfoCompat.Protocol.AndroidX_1_1_0:
+                // Otherwise, use new keys.
+                contentUriKey = COMMIT_CONTENT_CONTENT_URI_KEY;
+                descriptionKey = COMMIT_CONTENT_DESCRIPTION_KEY;
+                linkUriKey = COMMIT_CONTENT_LINK_URI_KEY;
+                flagsKey = COMMIT_CONTENT_FLAGS_KEY;
+                optsKey = COMMIT_CONTENT_OPTS_KEY;
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported protocol=" + protocol);
+        }
+        return new ArgumentMatcher<Bundle>() {
+            @Override
+            public boolean matches(Bundle data) {
+                final Uri contentUri = data.getParcelable(contentUriKey);
+                final ClipDescription description = data.getParcelable(descriptionKey);
+                final Uri linkUri = data.getParcelable(linkUriKey);
+                final int flags = data.getInt(flagsKey);
+                final Bundle opts = data.getParcelable(optsKey);
+                return TEST_CONTENT_URI.equals(contentUri)
+                        && TEST_CLIP_DESCRIPTION.equals(description)
+                        && TEST_LINK_URI.equals(linkUri)
+                        && flags == TEST_FLAGS
+                        && opts.equals(TEST_BUNDLE);
+            }
+        };
+    }
+}
diff --git a/core/src/main/java/androidx/core/view/inputmethod/EditorInfoCompat.java b/core/src/main/java/androidx/core/view/inputmethod/EditorInfoCompat.java
index 756b925..7345206 100644
--- a/core/src/main/java/androidx/core/view/inputmethod/EditorInfoCompat.java
+++ b/core/src/main/java/androidx/core/view/inputmethod/EditorInfoCompat.java
@@ -16,13 +16,18 @@
 
 package androidx.core.view.inputmethod;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
 import android.os.Build;
 import android.os.Bundle;
 import android.view.inputmethod.EditorInfo;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import java.lang.annotation.Retention;
+
 /**
  * Helper for accessing features in {@link EditorInfo} in a backwards compatible fashion.
  */
@@ -75,6 +80,22 @@
     private static final String CONTENT_MIME_TYPES_INTEROP_KEY =
             "android.support.v13.view.inputmethod.EditorInfoCompat.CONTENT_MIME_TYPES";
 
+    @Retention(SOURCE)
+    @IntDef({Protocol.Unknown, Protocol.PlatformApi, Protocol.SupportLib, Protocol.AndroidX_1_0_0,
+            Protocol.AndroidX_1_1_0})
+    @interface Protocol {
+        /** Platform API is not available. Backport protocol is also not detected. */
+        int Unknown = 0;
+        /** Uses platform API. */
+        int PlatformApi = 1;
+        /** Uses legacy backport protocol that was used by support lib. */
+        int SupportLib = 2;
+        /** Uses new backport protocol that was accidentally introduced in AndroidX 1.0.0. */
+        int AndroidX_1_0_0 = 3;
+        /** Uses new backport protocol that was introduced in AndroidX 1.1.0. */
+        int AndroidX_1_1_0 = 4;
+    }
+
     /**
      * Sets MIME types that can be accepted by the target editor if the IME calls
      * {@link InputConnectionCompat#commitContent(InputConnection, EditorInfo,
@@ -126,6 +147,35 @@
         }
     }
 
+    /**
+     * Returns protocol version to work around an accidental internal key migration happened between
+     * legacy support lib and AndroidX 1.0.0.
+     *
+     * @param editorInfo the editor from which we get the MIME types.
+     * @return protocol number based on {@code editorInfo}.
+     */
+    @Protocol
+    static int getProtocol(EditorInfo editorInfo) {
+        if (Build.VERSION.SDK_INT >= 25) {
+            return Protocol.PlatformApi;
+        }
+        if (editorInfo.extras == null) {
+            return Protocol.Unknown;
+        }
+        final boolean hasNewKey = editorInfo.extras.containsKey(CONTENT_MIME_TYPES_KEY);
+        final boolean hasOldKey = editorInfo.extras.containsKey(CONTENT_MIME_TYPES_INTEROP_KEY);
+        if (hasNewKey && hasOldKey) {
+            return Protocol.AndroidX_1_1_0;
+        }
+        if (hasNewKey) {
+            return Protocol.AndroidX_1_0_0;
+        }
+        if (hasOldKey) {
+            return Protocol.SupportLib;
+        }
+        return Protocol.Unknown;
+    }
+
     /** @deprecated This type should not be instantiated as it contains only static methods. */
     @Deprecated
     @SuppressWarnings("PrivateConstructorForUtilityClass")
diff --git a/core/src/main/java/androidx/core/view/inputmethod/InputConnectionCompat.java b/core/src/main/java/androidx/core/view/inputmethod/InputConnectionCompat.java
index c38f5c0..804c9d4 100644
--- a/core/src/main/java/androidx/core/view/inputmethod/InputConnectionCompat.java
+++ b/core/src/main/java/androidx/core/view/inputmethod/InputConnectionCompat.java
@@ -37,43 +37,76 @@
 public final class InputConnectionCompat {
 
     private static final String COMMIT_CONTENT_ACTION =
+            "androidx.core.view.inputmethod.InputConnectionCompat.COMMIT_CONTENT";
+    private static final String COMMIT_CONTENT_INTEROP_ACTION =
             "android.support.v13.view.inputmethod.InputConnectionCompat.COMMIT_CONTENT";
     private static final String COMMIT_CONTENT_CONTENT_URI_KEY =
+            "androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_URI";
+    private static final String COMMIT_CONTENT_CONTENT_URI_INTEROP_KEY =
             "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_URI";
     private static final String COMMIT_CONTENT_DESCRIPTION_KEY =
+            "androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_DESCRIPTION";
+    private static final String COMMIT_CONTENT_DESCRIPTION_INTEROP_KEY =
             "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_DESCRIPTION";
     private static final String COMMIT_CONTENT_LINK_URI_KEY =
+            "androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_LINK_URI";
+    private static final String COMMIT_CONTENT_LINK_URI_INTEROP_KEY =
             "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_LINK_URI";
     private static final String COMMIT_CONTENT_OPTS_KEY =
+            "androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_OPTS";
+    private static final String COMMIT_CONTENT_OPTS_INTEROP_KEY =
             "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_OPTS";
     private static final String COMMIT_CONTENT_FLAGS_KEY =
+            "androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_FLAGS";
+    private static final String COMMIT_CONTENT_FLAGS_INTEROP_KEY =
             "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_FLAGS";
-    private static final String COMMIT_CONTENT_RESULT_RECEIVER =
+    private static final String COMMIT_CONTENT_RESULT_RECEIVER_KEY =
+            "androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_RESULT_RECEIVER";
+    private static final String COMMIT_CONTENT_RESULT_INTEROP_RECEIVER_KEY =
             "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_RESULT_RECEIVER";
 
     static boolean handlePerformPrivateCommand(
             @Nullable String action,
             @NonNull Bundle data,
             @NonNull OnCommitContentListener onCommitContentListener) {
-        if (!TextUtils.equals(COMMIT_CONTENT_ACTION, action)) {
+        if (data == null) {
             return false;
         }
-        if (data == null) {
+
+        final boolean interop;
+        if (TextUtils.equals(COMMIT_CONTENT_ACTION, action)) {
+            interop = false;
+        } else if (TextUtils.equals(COMMIT_CONTENT_INTEROP_ACTION, action)) {
+            interop = true;
+        } else {
             return false;
         }
         ResultReceiver resultReceiver = null;
         boolean result = false;
         try {
-            resultReceiver = data.getParcelable(COMMIT_CONTENT_RESULT_RECEIVER);
-            final Uri contentUri = data.getParcelable(COMMIT_CONTENT_CONTENT_URI_KEY);
-            final ClipDescription description = data.getParcelable(
-                    COMMIT_CONTENT_DESCRIPTION_KEY);
-            final Uri linkUri = data.getParcelable(COMMIT_CONTENT_LINK_URI_KEY);
-            final int flags = data.getInt(COMMIT_CONTENT_FLAGS_KEY);
-            final Bundle opts = data.getParcelable(COMMIT_CONTENT_OPTS_KEY);
-            final InputContentInfoCompat inputContentInfo =
-                    new InputContentInfoCompat(contentUri, description, linkUri);
-            result = onCommitContentListener.onCommitContent(inputContentInfo, flags, opts);
+            resultReceiver = data.getParcelable(interop
+                    ? COMMIT_CONTENT_RESULT_INTEROP_RECEIVER_KEY
+                    : COMMIT_CONTENT_RESULT_RECEIVER_KEY);
+            final Uri contentUri = data.getParcelable(interop
+                    ? COMMIT_CONTENT_CONTENT_URI_INTEROP_KEY
+                    : COMMIT_CONTENT_CONTENT_URI_KEY);
+            final ClipDescription description = data.getParcelable(interop
+                    ? COMMIT_CONTENT_DESCRIPTION_INTEROP_KEY
+                    : COMMIT_CONTENT_DESCRIPTION_KEY);
+            final Uri linkUri = data.getParcelable(interop
+                    ? COMMIT_CONTENT_LINK_URI_INTEROP_KEY
+                    : COMMIT_CONTENT_LINK_URI_KEY);
+            final int flags = data.getInt(interop
+                    ? COMMIT_CONTENT_FLAGS_INTEROP_KEY
+                    : COMMIT_CONTENT_FLAGS_KEY);
+            final Bundle opts = data.getParcelable(interop
+                    ? COMMIT_CONTENT_OPTS_INTEROP_KEY
+                    : COMMIT_CONTENT_OPTS_KEY);
+            if (contentUri != null && description != null) {
+                final InputContentInfoCompat inputContentInfo =
+                        new InputContentInfoCompat(contentUri, description, linkUri);
+                result = onCommitContentListener.onCommitContent(inputContentInfo, flags, opts);
+            }
         } finally {
             if (resultReceiver != null) {
                 resultReceiver.send(result ? 1 : 0, null);
@@ -112,14 +145,47 @@
             return inputConnection.commitContent(
                     (InputContentInfo) inputContentInfo.unwrap(), flags, opts);
         } else {
+            final int protocol = EditorInfoCompat.getProtocol(editorInfo);
+            final boolean interop;
+            switch (protocol) {
+                case EditorInfoCompat.Protocol.AndroidX_1_0_0:
+                case EditorInfoCompat.Protocol.AndroidX_1_1_0:
+                    interop = false;
+                    break;
+                case EditorInfoCompat.Protocol.SupportLib:
+                    interop = true;
+                    break;
+                default:
+                    // Must not reach here.
+                    return false;
+            }
+
             final Bundle params = new Bundle();
-            params.putParcelable(COMMIT_CONTENT_CONTENT_URI_KEY, inputContentInfo.getContentUri());
-            params.putParcelable(COMMIT_CONTENT_DESCRIPTION_KEY, inputContentInfo.getDescription());
-            params.putParcelable(COMMIT_CONTENT_LINK_URI_KEY, inputContentInfo.getLinkUri());
-            params.putInt(COMMIT_CONTENT_FLAGS_KEY, flags);
-            params.putParcelable(COMMIT_CONTENT_OPTS_KEY, opts);
-            // TODO: Support COMMIT_CONTENT_RESULT_RECEIVER.
-            return inputConnection.performPrivateCommand(COMMIT_CONTENT_ACTION, params);
+            params.putParcelable(interop
+                            ? COMMIT_CONTENT_CONTENT_URI_INTEROP_KEY
+                            : COMMIT_CONTENT_CONTENT_URI_KEY,
+                    inputContentInfo.getContentUri());
+            params.putParcelable(interop
+                            ? COMMIT_CONTENT_DESCRIPTION_INTEROP_KEY
+                            : COMMIT_CONTENT_DESCRIPTION_KEY,
+                    inputContentInfo.getDescription());
+            params.putParcelable(interop
+                            ? COMMIT_CONTENT_LINK_URI_INTEROP_KEY
+                            : COMMIT_CONTENT_LINK_URI_KEY,
+                    inputContentInfo.getLinkUri());
+            params.putInt(interop
+                            ? COMMIT_CONTENT_FLAGS_INTEROP_KEY
+                            : COMMIT_CONTENT_FLAGS_KEY,
+                    flags);
+            params.putParcelable(interop
+                            ? COMMIT_CONTENT_OPTS_INTEROP_KEY
+                            : COMMIT_CONTENT_OPTS_KEY,
+                    opts);
+            // TODO: Support COMMIT_CONTENT_RESULT_RECEIVER_KEY.
+            return inputConnection.performPrivateCommand(interop
+                            ? COMMIT_CONTENT_INTEROP_ACTION
+                            : COMMIT_CONTENT_ACTION,
+                    params);
         }
     }