diff --git a/api/current.txt b/api/current.txt index 5dce913bcc183..300cb2d723db2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -44706,6 +44706,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); @@ -44800,6 +44801,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; @@ -44871,6 +44873,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); @@ -44904,6 +44907,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); @@ -44916,6 +44920,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 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 1b826010af8b0..6b72c51b3fbf8 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -47709,6 +47709,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); @@ -47803,6 +47804,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; @@ -47874,6 +47876,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); @@ -47907,6 +47910,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); @@ -47919,6 +47923,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 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 7a64c349809ef..f0df28b1b9929 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -44786,6 +44786,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); @@ -44880,6 +44881,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; @@ -44951,6 +44953,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); @@ -44984,6 +44987,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); @@ -44996,6 +45000,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 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 89dec2d22d7a7..7c3fc8b5808bd 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -851,4 +851,9 @@ public class BaseInputConnection implements InputConnection { 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 7b7ccae62dede..7ed0d27f65ece 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -25,6 +25,8 @@ import android.text.InputType; 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 @@ public class EditorInfo implements InputType, Parcelable { @Nullable public LocaleList hintLocales = null; + + /** + * List of acceptable MIME types for + * {@link InputConnection#insertContent(InputContentInfo, Bundle)}. + * + *

{@code null} or an empty array means that + * {@link InputConnection#insertContent(InputContentInfo, Bundle)} is not supported in this + * editor.

+ */ + @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 class EditorInfo implements InputType, Parcelable { + " fieldName=" + fieldName); pw.println(prefix + "extras=" + extras); pw.println(prefix + "hintLocales=" + hintLocales); + pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes)); } /** @@ -446,6 +461,7 @@ public class EditorInfo implements InputType, Parcelable { } else { LocaleList.getEmptyLocaleList().writeToParcel(dest, flags); } + dest.writeStringArray(contentMimeTypes); } /** @@ -471,6 +487,7 @@ public class EditorInfo implements InputType, Parcelable { 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 9f66429347683..05e05699df944 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 @@ public interface InputConnection { *

Note: This does nothing when called from input methods.

*/ public void closeConnection(); + + /** + * Called by the input method to insert a content such as PNG image to the editor. + * + *

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:

+ *
    + *
  • Editor must keep {@link EditorInfo#contentMimeTypes} to be {@code null} if it does + * not support this method at all.
  • + *
  • Editor can ignore this request when the MIME type specified in + * {@code inputContentInfo} does not match to any of {@link EditorInfo#contentMimeTypes}. + *
  • + *
  • Editor can ignore the cursor position when inserting the provided context.
  • + *
  • Editor can return {@code true} asynchronously, even before it starts loading the + * content.
  • + *
  • Editor should provide a way to delete the content inserted by this method, or revert + * the effect caused by this method.
  • + *
  • IME should not call this method when there is any composing text, in case calling + * this method causes focus change.
  • + *
  • 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.
  • + *
+ * + * @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 118a61f8d571c..fb24fcd8c77a2 100644 --- a/core/java/android/view/inputmethod/InputConnectionInspector.java +++ b/core/java/android/view/inputmethod/InputConnectionInspector.java @@ -19,6 +19,7 @@ package android.view.inputmethod; 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 @@ 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 { /** @@ -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 sMissingMethodsMap = Collections.synchronizedMap( @@ -127,6 +135,9 @@ public final class InputConnectionInspector { if (!hasCloseConnection(clazz)) { flags |= MissingMethodFlags.CLOSE_CONNECTION; } + if (!hasInsertContent(clazz)) { + flags |= MissingMethodFlags.INSERT_CONTENT; + } sMissingMethodsMap.put(clazz, flags); return flags; } @@ -195,6 +206,16 @@ public final class InputConnectionInspector { } } + 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 final class InputConnectionInspector { } 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 e743f62a5a0a6..431836a44f84d 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -269,4 +269,12 @@ public class InputConnectionWrapper implements InputConnection { 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 0000000000000..1afeee3b4b245 --- /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 0000000000000..e2ecfae7f7642 --- /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 CREATOR + = new Parcelable.Creator() { + @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 b331be72b4cf1..fb532e2dd17f2 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -19,6 +19,7 @@ package android.widget; 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.EditorInfo; 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 abstract class AbsListView extends AdapterView implements Te 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 59b4b35fa24ef..fc189a3e909e6 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.ExtractedTextRequest; 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 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { 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 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { 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 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } 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 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { 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 f7ec420ba6769..bd473557a1b61 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.KeyEvent; 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 @@ import com.android.internal.view.IInputContextCallback; 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 54ea3064bd7e6..933cdc0d87453 100644 --- a/core/java/com/android/internal/view/IInputContextCallback.aidl +++ b/core/java/com/android/internal/view/IInputContextCallback.aidl @@ -28,4 +28,5 @@ oneway interface IInputContextCallback { 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 f9884d85d52cb..6d4d45c7041f1 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.ExtractedTextRequest; 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 class InputConnectionWrapper implements InputConnection { 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 class InputConnectionWrapper implements InputConnection { } } + 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 @@ public class InputConnectionWrapper implements InputConnection { // 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; }