Add InputConnection#insertContent().

Providing an official protocol for IMEs to insert an image to the
application is something that has been requested from many IME
developers to Android OS.  With this CL, IMEs are able to ask
applications to insert a content including image files as follows.

 1. An application that opts in to this protocol specifies a list of
    supported content MIME types in EditorInfo#contentMimeTypes.
 2. When an IME is actively interacting with such an application, the
    IME can call InputConnection#insertContent() with a InputContentInfo
    that contains content URI, metadata (ClipDescription), and an
    optional link URI.
 3. The application can read the stream data from the given content URI
    to insert the content into somewhere in the application.

Detailed design background can be found in the JavaDoc of
InputConnection#insertContent().

Bug: 22830793
Change-Id: Iaadf934a997ffcd6000a516cc3c1873db56e60ad
diff --git a/api/current.txt b/api/current.txt
index 64eff6b..651b176 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -44715,6 +44715,7 @@
     method public java.lang.CharSequence getSelectedText(int);
     method public java.lang.CharSequence getTextAfterCursor(int, int);
     method public java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -44809,6 +44810,7 @@
     field public static final int IME_NULL = 0; // 0x0
     field public int actionId;
     field public java.lang.CharSequence actionLabel;
+    field public java.lang.String[] contentMimeTypes;
     field public android.os.Bundle extras;
     field public int fieldId;
     field public java.lang.String fieldName;
@@ -44880,6 +44882,7 @@
     method public abstract java.lang.CharSequence getSelectedText(int);
     method public abstract java.lang.CharSequence getTextAfterCursor(int, int);
     method public abstract java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public abstract boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public abstract boolean performContextMenuAction(int);
     method public abstract boolean performEditorAction(int);
     method public abstract boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -44913,6 +44916,7 @@
     method public java.lang.CharSequence getSelectedText(int);
     method public java.lang.CharSequence getTextAfterCursor(int, int);
     method public java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -44925,6 +44929,17 @@
     method public void setTarget(android.view.inputmethod.InputConnection);
   }
 
+  public class InputContentInfo implements android.os.Parcelable {
+    ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription);
+    ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription, android.net.Uri);
+    method public int describeContents();
+    method public android.net.Uri getContentUri();
+    method public android.content.ClipDescription getDescription();
+    method public android.net.Uri getLinkUri();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.inputmethod.InputContentInfo> CREATOR;
+  }
+
   public abstract interface InputMethod {
     method public abstract void attachToken(android.os.IBinder);
     method public abstract void bindInput(android.view.inputmethod.InputBinding);
diff --git a/api/system-current.txt b/api/system-current.txt
index bdf8299..8c3d5b1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -47716,6 +47716,7 @@
     method public java.lang.CharSequence getSelectedText(int);
     method public java.lang.CharSequence getTextAfterCursor(int, int);
     method public java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -47810,6 +47811,7 @@
     field public static final int IME_NULL = 0; // 0x0
     field public int actionId;
     field public java.lang.CharSequence actionLabel;
+    field public java.lang.String[] contentMimeTypes;
     field public android.os.Bundle extras;
     field public int fieldId;
     field public java.lang.String fieldName;
@@ -47881,6 +47883,7 @@
     method public abstract java.lang.CharSequence getSelectedText(int);
     method public abstract java.lang.CharSequence getTextAfterCursor(int, int);
     method public abstract java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public abstract boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public abstract boolean performContextMenuAction(int);
     method public abstract boolean performEditorAction(int);
     method public abstract boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -47914,6 +47917,7 @@
     method public java.lang.CharSequence getSelectedText(int);
     method public java.lang.CharSequence getTextAfterCursor(int, int);
     method public java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -47926,6 +47930,17 @@
     method public void setTarget(android.view.inputmethod.InputConnection);
   }
 
+  public class InputContentInfo implements android.os.Parcelable {
+    ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription);
+    ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription, android.net.Uri);
+    method public int describeContents();
+    method public android.net.Uri getContentUri();
+    method public android.content.ClipDescription getDescription();
+    method public android.net.Uri getLinkUri();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.inputmethod.InputContentInfo> CREATOR;
+  }
+
   public abstract interface InputMethod {
     method public abstract void attachToken(android.os.IBinder);
     method public abstract void bindInput(android.view.inputmethod.InputBinding);
diff --git a/api/test-current.txt b/api/test-current.txt
index 93b7368..41a6d42 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -44795,6 +44795,7 @@
     method public java.lang.CharSequence getSelectedText(int);
     method public java.lang.CharSequence getTextAfterCursor(int, int);
     method public java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -44889,6 +44890,7 @@
     field public static final int IME_NULL = 0; // 0x0
     field public int actionId;
     field public java.lang.CharSequence actionLabel;
+    field public java.lang.String[] contentMimeTypes;
     field public android.os.Bundle extras;
     field public int fieldId;
     field public java.lang.String fieldName;
@@ -44960,6 +44962,7 @@
     method public abstract java.lang.CharSequence getSelectedText(int);
     method public abstract java.lang.CharSequence getTextAfterCursor(int, int);
     method public abstract java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public abstract boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public abstract boolean performContextMenuAction(int);
     method public abstract boolean performEditorAction(int);
     method public abstract boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -44993,6 +44996,7 @@
     method public java.lang.CharSequence getSelectedText(int);
     method public java.lang.CharSequence getTextAfterCursor(int, int);
     method public java.lang.CharSequence getTextBeforeCursor(int, int);
+    method public boolean insertContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
@@ -45005,6 +45009,17 @@
     method public void setTarget(android.view.inputmethod.InputConnection);
   }
 
+  public class InputContentInfo implements android.os.Parcelable {
+    ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription);
+    ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription, android.net.Uri);
+    method public int describeContents();
+    method public android.net.Uri getContentUri();
+    method public android.content.ClipDescription getDescription();
+    method public android.net.Uri getLinkUri();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.inputmethod.InputContentInfo> CREATOR;
+  }
+
   public abstract interface InputMethod {
     method public abstract void attachToken(android.os.IBinder);
     method public abstract void bindInput(android.view.inputmethod.InputBinding);
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 89dec2d..7c3fc8b 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -851,4 +851,9 @@
         
         endBatchEdit();
     }
+
+    /**
+     * The default implementation does nothing.
+     */
+    public boolean insertContent(InputContentInfo inputContentInfo, Bundle opts) { return false; }
 }
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 7b7ccae..7ed0d27 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -25,6 +25,8 @@
 import android.text.TextUtils;
 import android.util.Printer;
 
+import java.util.Arrays;
+
 /**
  * An EditorInfo describes several attributes of a text editing object
  * that an input method is communicating with (typically an EditText), most
@@ -363,6 +365,18 @@
     @Nullable
     public LocaleList hintLocales = null;
 
+
+    /**
+     * List of acceptable MIME types for
+     * {@link InputConnection#insertContent(InputContentInfo, Bundle)}.
+     *
+     * <p>{@code null} or an empty array means that
+     * {@link InputConnection#insertContent(InputContentInfo, Bundle)} is not supported in this
+     * editor.</p>
+     */
+    @Nullable
+    public String[] contentMimeTypes = null;
+
     /**
      * Ensure that the data in this EditorInfo is compatible with an application
      * that was developed against the given target API version.  This can
@@ -418,6 +432,7 @@
                 + " fieldName=" + fieldName);
         pw.println(prefix + "extras=" + extras);
         pw.println(prefix + "hintLocales=" + hintLocales);
+        pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes));
     }
 
     /**
@@ -446,6 +461,7 @@
         } else {
             LocaleList.getEmptyLocaleList().writeToParcel(dest, flags);
         }
+        dest.writeStringArray(contentMimeTypes);
     }
 
     /**
@@ -471,6 +487,7 @@
                     res.extras = source.readBundle();
                     LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source);
                     res.hintLocales = hintLocales.isEmpty() ? null : hintLocales;
+                    res.contentMimeTypes = source.readStringArray();
                     return res;
                 }
 
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 9f66429..05e0569 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -16,6 +16,8 @@
 
 package android.view.inputmethod;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.view.KeyCharacterMap;
@@ -836,4 +838,33 @@
      * <p>Note: This does nothing when called from input methods.</p>
      */
     public void closeConnection();
+
+    /**
+     * Called by the input method to insert a content such as PNG image to the editor.
+     *
+     * <p>In order to avoid variety of compatibility issues, this focuses on a simple use case,
+     * where we expect editors and IMEs work cooperatively as follows:</p>
+     * <ul>
+     *     <li>Editor must keep {@link EditorInfo#contentMimeTypes} to be {@code null} if it does
+     *     not support this method at all.</li>
+     *     <li>Editor can ignore this request when the MIME type specified in
+     *     {@code inputContentInfo} does not match to any of {@link EditorInfo#contentMimeTypes}.
+     *     </li>
+     *     <li>Editor can ignore the cursor position when inserting the provided context.</li>
+     *     <li>Editor can return {@code true} asynchronously, even before it starts loading the
+     *     content.</li>
+     *     <li>Editor should provide a way to delete the content inserted by this method, or revert
+     *     the effect caused by this method.</li>
+     *     <li>IME should not call this method when there is any composing text, in case calling
+     *     this method causes focus change.</li>
+     *     <li>IME should grant a permission for the editor to read the content. See
+     *     {@link EditorInfo#packageName} about how to obtain the package name of the editor.</li>
+     * </ul>
+     *
+     * @param inputContentInfo Content to be inserted.
+     * @param opts optional bundle data. This can be {@code null}.
+     * @return {@code true} if this request is accepted by the application, no matter if the request
+     * is already handled or still being handled in background.
+     */
+    public boolean insertContent(@NonNull InputContentInfo inputContentInfo, @Nullable Bundle opts);
 }
diff --git a/core/java/android/view/inputmethod/InputConnectionInspector.java b/core/java/android/view/inputmethod/InputConnectionInspector.java
index 118a61f..fb24fcd 100644
--- a/core/java/android/view/inputmethod/InputConnectionInspector.java
+++ b/core/java/android/view/inputmethod/InputConnectionInspector.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Bundle;
 
 import java.lang.annotation.Retention;
 import java.lang.reflect.Method;
@@ -41,6 +42,8 @@
             MissingMethodFlags.REQUEST_CURSOR_UPDATES,
             MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS,
             MissingMethodFlags.GET_HANDLER,
+            MissingMethodFlags.CLOSE_CONNECTION,
+            MissingMethodFlags.INSERT_CONTENT,
     })
     public @interface MissingMethodFlags {
         /**
@@ -78,6 +81,11 @@
          * {@link android.os.Build.VERSION_CODES#N} and later.
          */
         int CLOSE_CONNECTION = 1 << 6;
+        /**
+         * {@link InputConnection#insertContent(InputContentInfo, Bundle)} is available in
+         * {@link android.os.Build.VERSION_CODES#N} MR-1 and later.
+         */
+        int INSERT_CONTENT = 1 << 7;
     }
 
     private static final Map<Class, Integer> sMissingMethodsMap = Collections.synchronizedMap(
@@ -127,6 +135,9 @@
         if (!hasCloseConnection(clazz)) {
             flags |= MissingMethodFlags.CLOSE_CONNECTION;
         }
+        if (!hasInsertContent(clazz)) {
+            flags |= MissingMethodFlags.INSERT_CONTENT;
+        }
         sMissingMethodsMap.put(clazz, flags);
         return flags;
     }
@@ -195,6 +206,16 @@
         }
     }
 
+    private static boolean hasInsertContent(@NonNull final Class clazz) {
+        try {
+            final Method method = clazz.getMethod("insertContent", InputContentInfo.class,
+                    Bundle.class);
+            return !Modifier.isAbstract(method.getModifiers());
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
     public static String getMissingMethodFlagsAsString(@MissingMethodFlags final int flags) {
         final StringBuilder sb = new StringBuilder();
         boolean isEmpty = true;
@@ -242,6 +263,12 @@
             }
             sb.append("closeConnection()");
         }
+        if ((flags & MissingMethodFlags.INSERT_CONTENT) != 0) {
+            if (!isEmpty) {
+                sb.append(",");
+            }
+            sb.append("InsertContent(InputContentInfo, Bundle)");
+        }
         return sb.toString();
     }
 }
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index e743f62..431836a 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -269,4 +269,12 @@
     public void closeConnection() {
         mTarget.closeConnection();
     }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    public boolean insertContent(InputContentInfo inputContentInfo, Bundle opts) {
+        return mTarget.insertContent(inputContentInfo, opts);
+    }
 }
diff --git a/core/java/android/view/inputmethod/InputContentInfo.aidl b/core/java/android/view/inputmethod/InputContentInfo.aidl
new file mode 100644
index 0000000..1afeee3
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputContentInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 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 android.view.inputmethod;
+
+parcelable InputContentInfo;
diff --git a/core/java/android/view/inputmethod/InputContentInfo.java b/core/java/android/view/inputmethod/InputContentInfo.java
new file mode 100644
index 0000000..e2ecfae
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputContentInfo.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2016 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 android.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.InvalidParameterException;
+
+/**
+ * A container object with which input methods can send content files to the target application.
+ */
+public class InputContentInfo implements Parcelable {
+
+    @NonNull
+    private final Uri mContentUri;
+    @NonNull
+    private final ClipDescription mDescription;
+    @Nullable
+    private final Uri mLinkUri;
+
+    /**
+     * Constructs {@link InputContentInfo} object only with mandatory data.
+     *
+     * @param contentUri Content URI to be exported from the input method.
+     * This cannot be {@code null}.
+     * @param description A {@link ClipDescription} object that contains the metadata of
+     * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
+     * {@link ClipDescription#getLabel()} should be describing the content specified by
+     * {@code contentUri} for accessibility reasons.
+     */
+    public InputContentInfo(@NonNull Uri contentUri, @NonNull ClipDescription description) {
+        this(contentUri, description, null /* link Uri */);
+    }
+
+    /**
+     * Constructs {@link InputContentInfo} object with additional link URI.
+     *
+     * @param contentUri Content URI to be exported from the input method.
+     * This cannot be {@code null}.
+     * @param description A {@link ClipDescription} object that contains the metadata of
+     * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
+     * {@link ClipDescription#getLabel()} should be describing the content specified by
+     * {@code contentUri} for accessibility reasons.
+     * @param linkUri An optional {@code http} or {@code https} URI. The editor author may provide
+     * a way to navigate the user to the specified web page if this is not {@code null}.
+     * @throws InvalidParameterException if any invalid parameter is specified.
+     */
+    public InputContentInfo(@NonNull Uri contentUri, @NonNull ClipDescription description,
+            @Nullable Uri linkUri) {
+        validateInternal(contentUri, description, linkUri, true /* throwException */);
+        mContentUri = contentUri;
+        mDescription = description;
+        mLinkUri = linkUri;
+    }
+
+    /**
+     * @return {@code true} if all the fields are valid.
+     * @hide
+     */
+    public boolean validate() {
+        return validateInternal(mContentUri, mDescription, mLinkUri, false /* throwException */);
+    }
+
+    /**
+     * Constructs {@link InputContentInfo} object with additional link URI.
+     *
+     * @param contentUri Content URI to be exported from the input method.
+     * This cannot be {@code null}.
+     * @param description A {@link ClipDescription} object that contains the metadata of
+     * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
+     * {@link ClipDescription#getLabel()} should be describing the content specified by
+     * {@code contentUri} for accessibility reasons.
+     * @param linkUri An optional {@code http} or {@code https} URI. The editor author may provide
+     * a way to navigate the user to the specified web page if this is not {@code null}.
+     * @param throwException {@code true} if this method should throw an
+     * {@link InvalidParameterException}.
+     * @throws InvalidParameterException if any invalid parameter is specified.
+     */
+    private static boolean validateInternal(@NonNull Uri contentUri,
+            @NonNull ClipDescription description, @Nullable Uri linkUri, boolean throwException) {
+        if (contentUri == null) {
+            if (throwException) {
+                throw new NullPointerException("contentUri");
+            }
+            return false;
+        }
+        if (description == null) {
+            if (throwException) {
+                throw new NullPointerException("description");
+            }
+            return false;
+        }
+        final String contentUriScheme = contentUri.getScheme();
+        if (contentUriScheme == null || !contentUriScheme.equalsIgnoreCase("content")) {
+            if (throwException) {
+                throw new InvalidParameterException("contentUri must have content scheme");
+            }
+            return false;
+        }
+        if (linkUri != null) {
+            final String scheme = linkUri.getScheme();
+            if (scheme == null ||
+                    (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https"))) {
+                if (throwException) {
+                    throw new InvalidParameterException(
+                            "linkUri must have either http or https scheme");
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @return Content URI with which the content can be obtained.
+     */
+    @NonNull
+    public Uri getContentUri() { return mContentUri; }
+
+    /**
+     * @return {@link ClipDescription} object that contains the metadata of {@code contentUri} such
+     * as MIME type(s). {@link ClipDescription#getLabel()} can be used for accessibility purpose.
+     */
+    @NonNull
+    public ClipDescription getDescription() { return mDescription; }
+
+    /**
+     * @return An optional {@code http} or {@code https} URI that is related to this content.
+     */
+    @Nullable
+    public Uri getLinkUri() { return mLinkUri; }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        Uri.writeToParcel(dest, mContentUri);
+        mDescription.writeToParcel(dest, flags);
+        Uri.writeToParcel(dest, mLinkUri);
+    }
+
+    private InputContentInfo(@NonNull Parcel source) {
+        mContentUri = Uri.CREATOR.createFromParcel(source);
+        mDescription = ClipDescription.CREATOR.createFromParcel(source);
+        mLinkUri = Uri.CREATOR.createFromParcel(source);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final Parcelable.Creator<InputContentInfo> CREATOR
+            = new Parcelable.Creator<InputContentInfo>() {
+        @Override
+        public InputContentInfo createFromParcel(Parcel source) {
+            return new InputContentInfo(source);
+        }
+
+        @Override
+        public InputContentInfo[] newArray(int size) {
+            return new InputContentInfo[size];
+        }
+    };
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index b331be7..fb532e2 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -19,6 +19,7 @@
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
+import android.content.ClipData;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.TypedArray;
@@ -75,6 +76,7 @@
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.RemoteViews.OnClickHandler;
 
@@ -5982,6 +5984,11 @@
         public void closeConnection() {
             getTarget().closeConnection();
         }
+
+        @Override
+        public boolean insertContent(InputContentInfo inputContentInfo, Bundle opts) {
+            return getTarget().insertContent(inputContentInfo, opts);
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 59b4b35..fc189a3 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -33,6 +33,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionInspector;
 import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
+import android.view.inputmethod.InputContentInfo;
 
 public abstract class IInputConnectionWrapper extends IInputContext.Stub {
     static final String TAG = "IInputConnectionWrapper";
@@ -61,6 +62,7 @@
     private static final int DO_CLEAR_META_KEY_STATES = 130;
     private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140;
     private static final int DO_CLOSE_CONNECTION = 150;
+    private static final int DO_INSERT_CONTENT = 160;
 
     @GuardedBy("mLock")
     @Nullable
@@ -241,6 +243,11 @@
         dispatchMessage(obtainMessage(DO_CLOSE_CONNECTION));
     }
 
+    public void insertContent(InputContentInfo inputContentInfo, Bundle opts,
+            int seq, IInputContextCallback callback) {
+        dispatchMessage(obtainMessageOOSC(DO_INSERT_CONTENT, inputContentInfo, opts, seq, callback));
+    }
+
     void dispatchMessage(Message msg) {
         // If we are calling this from the main thread, then we can call
         // right through.  Otherwise, we need to send the message to the
@@ -552,6 +559,29 @@
                 }
                 return;
             }
+            case DO_INSERT_CONTENT: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                try {
+                    InputConnection ic = getInputConnection();
+                    if (ic == null || !isActive()) {
+                        Log.w(TAG, "insertContent on inactive InputConnection");
+                        args.callback.setInsertContentResult(false, args.seq);
+                        return;
+                    }
+                    final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1;
+                    if (inputContentInfo == null || !inputContentInfo.validate()) {
+                        Log.w(TAG, "insertContent with invalid inputContentInfo="
+                                + inputContentInfo);
+                        args.callback.setInsertContentResult(false, args.seq);
+                        return;
+                    }
+                    args.callback.setInsertContentResult(
+                            ic.insertContent(inputContentInfo, (Bundle) args.arg2), args.seq);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Got RemoteException calling insertContent", e);
+                }
+                return;
+            }
         }
         Log.w(TAG, "Unhandled message code: " + msg.what);
     }
@@ -582,9 +612,11 @@
         return mH.obtainMessage(what, arg1, arg2, args);
     }
 
-    Message obtainMessageOSC(int what, Object arg1, int seq, IInputContextCallback callback) {
+    Message obtainMessageOOSC(int what, Object arg1, Object arg2, int seq,
+            IInputContextCallback callback) {
         SomeArgs args = new SomeArgs();
         args.arg1 = arg1;
+        args.arg2 = arg2;
         args.callback = callback;
         args.seq = seq;
         return mH.obtainMessage(what, 0, 0, args);
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index f7ec420..bd47355 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -21,6 +21,7 @@
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputContentInfo;
 
 import com.android.internal.view.IInputContextCallback;
 
@@ -74,6 +75,9 @@
 
     void getSelectedText(int flags, int seq, IInputContextCallback callback);
 
-    void requestUpdateCursorAnchorInfo(in int cursorUpdateMode, int seq,
+    void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
+            IInputContextCallback callback);
+
+    void insertContent(in InputContentInfo inputContentInfo, in Bundle opts, int sec,
             IInputContextCallback callback);
 }
diff --git a/core/java/com/android/internal/view/IInputContextCallback.aidl b/core/java/com/android/internal/view/IInputContextCallback.aidl
index 54ea306..933cdc0 100644
--- a/core/java/com/android/internal/view/IInputContextCallback.aidl
+++ b/core/java/com/android/internal/view/IInputContextCallback.aidl
@@ -28,4 +28,5 @@
     void setExtractedText(in ExtractedText extractedText, int seq);
     void setSelectedText(CharSequence selectedText, int seq);
     void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq);
+    void setInsertContentResult(boolean result, int seq);
 }
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index f9884d8..6d4d45c 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.view;
 
+import android.content.ClipData;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -29,6 +30,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionInspector;
 import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
+import android.view.inputmethod.InputContentInfo;
 
 public class InputConnectionWrapper implements InputConnection {
     private static final int MAX_WAIT_TIME_MILLIS = 2000;
@@ -46,7 +48,8 @@
         public ExtractedText mExtractedText;
         public int mCursorCapsMode;
         public boolean mRequestUpdateCursorAnchorInfoResult;
-        
+        public boolean mInsertContentResult;
+
         // A 'pool' of one InputContextCallback.  Each ICW request will attempt to gain
         // exclusive access to this object.
         private static InputContextCallback sInstance = new InputContextCallback();
@@ -172,6 +175,19 @@
             }
         }
 
+        public void setInsertContentResult(boolean result, int seq) {
+            synchronized (this) {
+                if (seq == mSeq) {
+                    mInsertContentResult = result;
+                    mHaveValue = true;
+                    notifyAll();
+                } else {
+                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+                            + ") in setInsertContentResult, ignoring.");
+                }
+            }
+        }
+
         /**
          * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
          * 
@@ -491,6 +507,28 @@
         // Nothing should happen when called from input method.
     }
 
+    public boolean insertContent(InputContentInfo inputContentInfo, Bundle opts) {
+        boolean result = false;
+        if (isMethodMissing(MissingMethodFlags.INSERT_CONTENT)) {
+            // This method is not implemented.
+            return false;
+        }
+        try {
+            InputContextCallback callback = InputContextCallback.getInstance();
+            mIInputContext.insertContent(inputContentInfo, opts, callback.mSeq, callback);
+            synchronized (callback) {
+                callback.waitForResultLocked();
+                if (callback.mHaveValue) {
+                    result = callback.mInsertContentResult;
+                }
+            }
+            callback.dispose();
+        } catch (RemoteException e) {
+            return false;
+        }
+        return result;
+    }
+
     private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
         return (mMissingMethods & methodFlag) == methodFlag;
     }