Permalink
Browse files

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
  • Loading branch information...
1 parent 779adf8 commit 152944f4909c47917473293b258d266435c6ab35 @yukawa yukawa committed Jun 11, 2016
View
@@ -44715,6 +44715,7 @@ package android.view.inputmethod {
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 @@ package android.view.inputmethod {
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 @@ package android.view.inputmethod {
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 @@ package android.view.inputmethod {
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 @@ package android.view.inputmethod {
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);
@@ -47716,6 +47716,7 @@ package android.view.inputmethod {
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 @@ package android.view.inputmethod {
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 @@ package android.view.inputmethod {
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 @@ package android.view.inputmethod {
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 @@ package android.view.inputmethod {
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);
View
@@ -44795,6 +44795,7 @@ package android.view.inputmethod {
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 @@ package android.view.inputmethod {
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 @@ package android.view.inputmethod {
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 @@ package android.view.inputmethod {
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 @@ package android.view.inputmethod {
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);
@@ -851,4 +851,9 @@ private void replaceText(CharSequence text, int newCursorPosition,
endBatchEdit();
}
+
+ /**
+ * The default implementation does nothing.
+ */
+ public boolean insertContent(InputContentInfo inputContentInfo, Bundle opts) { return false; }
}
@@ -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 @@ public void dump(Printer pw, String prefix) {
+ " fieldName=" + fieldName);
pw.println(prefix + "extras=" + extras);
pw.println(prefix + "hintLocales=" + hintLocales);
+ pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes));
}
/**
@@ -446,6 +461,7 @@ public void writeToParcel(Parcel dest, int flags) {
} else {
LocaleList.getEmptyLocaleList().writeToParcel(dest, flags);
}
+ dest.writeStringArray(contentMimeTypes);
}
/**
@@ -471,6 +487,7 @@ public EditorInfo createFromParcel(Parcel source) {
res.extras = source.readBundle();
LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source);
res.hintLocales = hintLocales.isEmpty() ? null : hintLocales;
+ res.contentMimeTypes = source.readStringArray();
return res;
}
@@ -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 @@ public ExtractedText getExtractedText(ExtractedTextRequest request,
* <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);
}
@@ -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 @@ public static int getMissingMethodFlagsInternal(@NonNull final Class clazz) {
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 hasCloseConnection(@NonNull final Class clazz) {
}
}
+ 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 @@ public static String getMissingMethodFlagsAsString(@MissingMethodFlags final int
}
sb.append("closeConnection()");
}
+ if ((flags & MissingMethodFlags.INSERT_CONTENT) != 0) {
+ if (!isEmpty) {
+ sb.append(",");
+ }
+ sb.append("InsertContent(InputContentInfo, Bundle)");
+ }
return sb.toString();
}
}
@@ -269,4 +269,12 @@ public Handler getHandler() {
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);
+ }
}
@@ -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;
Oops, something went wrong.

0 comments on commit 152944f

Please sign in to comment.