Skip to content
This repository has been archived by the owner on Nov 8, 2023. It is now read-only.

Commit

Permalink
Add InputConnection#insertContent().
Browse files Browse the repository at this point in the history
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
yukawa committed Jun 11, 2016
1 parent 779adf8 commit 152944f
Show file tree
Hide file tree
Showing 15 changed files with 431 additions and 3 deletions.
15 changes: 15 additions & 0 deletions api/current.txt
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
15 changes: 15 additions & 0 deletions api/system-current.txt
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
15 changes: 15 additions & 0 deletions api/test-current.txt
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions core/java/android/view/inputmethod/BaseInputConnection.java
Expand Up @@ -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; }
}
17 changes: 17 additions & 0 deletions core/java/android/view/inputmethod/EditorInfo.java
Expand Up @@ -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
Expand Down Expand Up @@ -363,6 +365,18 @@ public class EditorInfo implements InputType, Parcelable {
@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
Expand Down Expand Up @@ -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));
}

/**
Expand Down Expand Up @@ -446,6 +461,7 @@ public void writeToParcel(Parcel dest, int flags) {
} else {
LocaleList.getEmptyLocaleList().writeToParcel(dest, flags);
}
dest.writeStringArray(contentMimeTypes);
}

/**
Expand All @@ -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;
}

Expand Down
31 changes: 31 additions & 0 deletions core/java/android/view/inputmethod/InputConnection.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
27 changes: 27 additions & 0 deletions core/java/android/view/inputmethod/InputConnectionInspector.java
Expand Up @@ -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;
Expand All @@ -41,6 +42,8 @@ public final class InputConnectionInspector {
MissingMethodFlags.REQUEST_CURSOR_UPDATES,
MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS,
MissingMethodFlags.GET_HANDLER,
MissingMethodFlags.CLOSE_CONNECTION,
MissingMethodFlags.INSERT_CONTENT,
})
public @interface MissingMethodFlags {
/**
Expand Down Expand Up @@ -78,6 +81,11 @@ public final class InputConnectionInspector {
* {@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(
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
}
Expand Up @@ -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);
}
}
19 changes: 19 additions & 0 deletions 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;

0 comments on commit 152944f

Please sign in to comment.