java.lang.Object
↳androidx.core.view.inputmethod.InputConnectionCompat
Gradle dependencies
compile group: 'androidx.core', name: 'core', version: '1.9.0-alpha04'
- groupId: androidx.core
- artifactId: core
- version: 1.9.0-alpha04
Artifact androidx.core:core:1.9.0-alpha04 it located at Google repository (https://maven.google.com/)
Androidx artifact mapping:
androidx.core:core com.android.support:support-compat
Androidx class mapping:
androidx.core.view.inputmethod.InputConnectionCompat android.support.v13.view.inputmethod.InputConnectionCompat
Overview
Helper for accessing features in introduced after API level 13 in a
backwards compatible fashion.
Summary
Methods |
---|
public static boolean | commitContent(InputConnection inputConnection, EditorInfo editorInfo, InputContentInfoCompat inputContentInfo, int flags, Bundle opts)
Calls commitContent API, in a backwards compatible fashion. |
public static InputConnection | createWrapper(InputConnection inputConnection, EditorInfo editorInfo, InputConnectionCompat.OnCommitContentListener onCommitContentListener)
Creates a wrapper object from an existing
and InputConnectionCompat.OnCommitContentListener that can be returned to the system. |
public static InputConnection | createWrapper(View view, InputConnection inputConnection, EditorInfo editorInfo)
Creates a wrapper that implements InputConnection's
features on past versions of Android. |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Fields
public static final int
INPUT_CONTENT_GRANT_READ_URI_PERMISSIONWhen this flag is used, the editor will be able to request temporary access permissions to
the content URI contained in the InputContentInfoCompat object, in a similar manner
that has been recommended in
Sharing Files.
Make sure that the content provider owning the Uri sets the
attribute in its manifest or
included the tag.
Supported only on API >= 25.
On API <= 24 devices, IME developers need to ensure that the content URI is accessible
only from the target application, for example, by generating a URL with a unique name that
others cannot guess. IME developers can also rely on the following information of the target
application to do additional access checks in their ContentProvider
.
- On API >= 23 is guaranteed to not be spoofed, which
can later be compared with
ContentProvider
in
the ContentProvider
.
- is guaranteed to not be
spoofed, which can later be compared with in
the
ContentProvider
.
Constructors
public
InputConnectionCompat()
Deprecated: This type should not be instantiated as it contains only static methods.
Methods
public static boolean
commitContent(InputConnection inputConnection, EditorInfo editorInfo,
InputContentInfoCompat inputContentInfo, int flags, Bundle opts)
Calls commitContent API, in a backwards compatible fashion.
Parameters:
inputConnection: with which commitContent API will be called
editorInfo: associated with the given inputConnection
inputContentInfo: content information to be passed to the editor
flags: 0 or InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION
opts: optional bundle data. This can be null
Returns:
true if this request is accepted by the application, no matter if the request
is already handled or still being handled in background
Deprecated: Use #createWrapper(View,InputConnection,EditorInfo) and
{@link ViewCompat#setOnReceiveContentListener} instead.
Creates a wrapper object from an existing
and InputConnectionCompat.OnCommitContentListener that can be returned to the system.
By returning the wrapper object to the IME, the editor can be notified by
InputConnectionCompat.OnCommitContentListener.onCommitContent(InputContentInfoCompat, int, Bundle)
when the IME calls
InputConnectionCompat.commitContent(InputConnection, EditorInfo, InputContentInfoCompat, int, Bundle) and the corresponding Framework API that is available
on API >= 25.
Parameters:
inputConnection: to be wrapped
editorInfo: associated with the given inputConnection
onCommitContentListener: the listener that the wrapper object will call
Returns:
a wrapper object that can be returned to the IME
public static InputConnection
createWrapper(View view, InputConnection inputConnection, EditorInfo editorInfo)
Creates a wrapper that implements InputConnection's
features on past versions of Android.
Currently, handles by dispatching to
ViewCompat.performReceiveContent(View, ContentInfoCompat), enabling apps to use
ViewCompat.setOnReceiveContentListener(View, String[], OnReceiveContentListener) to specify handling for content insertion
from the IME.
Usage:
public class MyWidget extends View {
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
InputConnection ic = super.onCreateInputConnection(outAttrs);
if (ic == null) {
return ic;
}
String[] mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this);
if (mimeTypes != null) {
EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes);
ic = InputConnectionCompat.createWrapper(this, ic, outAttrs);
}
return ic;
}
}
Parameters:
view: The view that the given input connection is associated with.
inputConnection: The input connection to be wrapped.
editorInfo: The editor metadata associated with the given input connection.
Returns:
A wrapper object that can be returned to the IME.
Source
/*
* 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 androidx.core.view.inputmethod;
import static androidx.core.view.ContentInfoCompat.SOURCE_INPUT_METHOD;
import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ContentProvider;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import android.view.inputmethod.InputContentInfo;
import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.util.ObjectsCompat;
import androidx.core.util.Preconditions;
import androidx.core.view.ContentInfoCompat;
import androidx.core.view.OnReceiveContentListener;
import androidx.core.view.ViewCompat;
/**
* Helper for accessing features in {@link InputConnection} introduced after API level 13 in a
* backwards compatible fashion.
*/
@SuppressLint("PrivateConstructorForUtilityClass") // Already launched with public constructor
public final class InputConnectionCompat {
private static final String LOG_TAG = "InputConnectionCompat";
private static final String COMMIT_CONTENT_ACTION =
"androidx.core.view.inputmethod.InputConnectionCompat.COMMIT_CONTENT";
private static final String COMMIT_CONTENT_INTEROP_ACTION =
"android.support.v13.view.inputmethod.InputConnectionCompat.COMMIT_CONTENT";
private static final String COMMIT_CONTENT_CONTENT_URI_KEY =
"androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_URI";
private static final String COMMIT_CONTENT_CONTENT_URI_INTEROP_KEY =
"android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_URI";
private static final String COMMIT_CONTENT_DESCRIPTION_KEY =
"androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_DESCRIPTION";
private static final String COMMIT_CONTENT_DESCRIPTION_INTEROP_KEY =
"android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_DESCRIPTION";
private static final String COMMIT_CONTENT_LINK_URI_KEY =
"androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_LINK_URI";
private static final String COMMIT_CONTENT_LINK_URI_INTEROP_KEY =
"android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_LINK_URI";
private static final String COMMIT_CONTENT_OPTS_KEY =
"androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_OPTS";
private static final String COMMIT_CONTENT_OPTS_INTEROP_KEY =
"android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_OPTS";
private static final String COMMIT_CONTENT_FLAGS_KEY =
"androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_FLAGS";
private static final String COMMIT_CONTENT_FLAGS_INTEROP_KEY =
"android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_FLAGS";
private static final String COMMIT_CONTENT_RESULT_RECEIVER_KEY =
"androidx.core.view.inputmethod.InputConnectionCompat.CONTENT_RESULT_RECEIVER";
private static final String COMMIT_CONTENT_RESULT_INTEROP_RECEIVER_KEY =
"android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_RESULT_RECEIVER";
@SuppressWarnings("deprecation")
static boolean handlePerformPrivateCommand(
@Nullable String action,
@Nullable Bundle data,
@NonNull OnCommitContentListener onCommitContentListener) {
if (data == null) {
return false;
}
final boolean interop;
if (TextUtils.equals(COMMIT_CONTENT_ACTION, action)) {
interop = false;
} else if (TextUtils.equals(COMMIT_CONTENT_INTEROP_ACTION, action)) {
interop = true;
} else {
return false;
}
ResultReceiver resultReceiver = null;
boolean result = false;
try {
resultReceiver = data.getParcelable(interop
? COMMIT_CONTENT_RESULT_INTEROP_RECEIVER_KEY
: COMMIT_CONTENT_RESULT_RECEIVER_KEY);
final Uri contentUri = data.getParcelable(interop
? COMMIT_CONTENT_CONTENT_URI_INTEROP_KEY
: COMMIT_CONTENT_CONTENT_URI_KEY);
final ClipDescription description = data.getParcelable(interop
? COMMIT_CONTENT_DESCRIPTION_INTEROP_KEY
: COMMIT_CONTENT_DESCRIPTION_KEY);
final Uri linkUri = data.getParcelable(interop
? COMMIT_CONTENT_LINK_URI_INTEROP_KEY
: COMMIT_CONTENT_LINK_URI_KEY);
final int flags = data.getInt(interop
? COMMIT_CONTENT_FLAGS_INTEROP_KEY
: COMMIT_CONTENT_FLAGS_KEY);
final Bundle opts = data.getParcelable(interop
? COMMIT_CONTENT_OPTS_INTEROP_KEY
: COMMIT_CONTENT_OPTS_KEY);
if (contentUri != null && description != null) {
final InputContentInfoCompat inputContentInfo =
new InputContentInfoCompat(contentUri, description, linkUri);
result = onCommitContentListener.onCommitContent(inputContentInfo, flags, opts);
}
} finally {
if (resultReceiver != null) {
resultReceiver.send(result ? 1 : 0, null);
}
}
return result;
}
/**
* Calls commitContent API, in a backwards compatible fashion.
*
* @param inputConnection {@link InputConnection} with which commitContent API will be called
* @param editorInfo {@link EditorInfo} associated with the given {@code inputConnection}
* @param inputContentInfo content information to be passed to the editor
* @param flags {@code 0} or {@link #INPUT_CONTENT_GRANT_READ_URI_PERMISSION}
* @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 static boolean commitContent(@NonNull InputConnection inputConnection,
@NonNull EditorInfo editorInfo, @NonNull InputContentInfoCompat inputContentInfo,
int flags, @Nullable Bundle opts) {
if (Build.VERSION.SDK_INT >= 25) {
return Api25Impl.commitContent(inputConnection,
(InputContentInfo) inputContentInfo.unwrap(), flags, opts);
} else {
final int protocol = EditorInfoCompat.getProtocol(editorInfo);
final boolean interop;
switch (protocol) {
case EditorInfoCompat.Protocol.AndroidX_1_0_0:
case EditorInfoCompat.Protocol.AndroidX_1_1_0:
interop = false;
break;
case EditorInfoCompat.Protocol.SupportLib:
interop = true;
break;
default:
// Must not reach here.
return false;
}
final Bundle params = new Bundle();
params.putParcelable(interop
? COMMIT_CONTENT_CONTENT_URI_INTEROP_KEY
: COMMIT_CONTENT_CONTENT_URI_KEY,
inputContentInfo.getContentUri());
params.putParcelable(interop
? COMMIT_CONTENT_DESCRIPTION_INTEROP_KEY
: COMMIT_CONTENT_DESCRIPTION_KEY,
inputContentInfo.getDescription());
params.putParcelable(interop
? COMMIT_CONTENT_LINK_URI_INTEROP_KEY
: COMMIT_CONTENT_LINK_URI_KEY,
inputContentInfo.getLinkUri());
params.putInt(interop
? COMMIT_CONTENT_FLAGS_INTEROP_KEY
: COMMIT_CONTENT_FLAGS_KEY,
flags);
params.putParcelable(interop
? COMMIT_CONTENT_OPTS_INTEROP_KEY
: COMMIT_CONTENT_OPTS_KEY,
opts);
// TODO: Support COMMIT_CONTENT_RESULT_RECEIVER_KEY.
return inputConnection.performPrivateCommand(interop
? COMMIT_CONTENT_INTEROP_ACTION
: COMMIT_CONTENT_ACTION,
params);
}
}
/**
* When this flag is used, the editor will be able to request temporary access permissions to
* the content URI contained in the {@link InputContentInfoCompat} object, in a similar manner
* that has been recommended in
* <a href="{@docRoot}training/secure-file-sharing/index.html">Sharing Files</a>.
*
* <p>Make sure that the content provider owning the Uri sets the
* {@link android.R.attr#grantUriPermissions grantUriPermissions} attribute in its manifest or
* included the {@code <grant-uri-permissions>} tag.</p>
*
* <p>Supported only on API >= 25.</p>
*
* <p>On API <= 24 devices, IME developers need to ensure that the content URI is accessible
* only from the target application, for example, by generating a URL with a unique name that
* others cannot guess. IME developers can also rely on the following information of the target
* application to do additional access checks in their {@link ContentProvider}.
* </p>
* <ul>
* <li>On API >= 23 {@link EditorInfo#packageName} is guaranteed to not be spoofed, which
* can later be compared with {@link ContentProvider#getCallingPackage()} in
* the {@link ContentProvider}.
* </li>
* <li>{@link InputBinding#getUid()} is guaranteed to not be
* spoofed, which can later be compared with {@link Binder#getCallingUid()} in
* the {@link ContentProvider}.</li>
* </ul>
*/
public static final int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = 0x00000001;
/**
* Listener for commitContent method call, in a backwards compatible fashion.
*/
public interface OnCommitContentListener {
/**
* Intercepts InputConnection#commitContent API calls.
*
* @param inputContentInfo content to be committed
* @param flags {@code 0} or {@link #INPUT_CONTENT_GRANT_READ_URI_PERMISSION}
* @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. {@code false} to use the
* default implementation
*/
boolean onCommitContent(@NonNull InputContentInfoCompat inputContentInfo, int flags,
@Nullable Bundle opts);
}
/**
* Creates a wrapper {@link InputConnection} object from an existing {@link InputConnection}
* and {@link OnCommitContentListener} that can be returned to the system.
*
* <p>By returning the wrapper object to the IME, the editor can be notified by
* {@link OnCommitContentListener#onCommitContent(InputContentInfoCompat, int, Bundle)}
* when the IME calls
* {@link InputConnectionCompat#commitContent(InputConnection, EditorInfo,
* InputContentInfoCompat, int, Bundle)} and the corresponding Framework API that is available
* on API >= 25.</p>
*
* @param inputConnection {@link InputConnection} to be wrapped
* @param editorInfo {@link EditorInfo} associated with the given {@code inputConnection}
* @param onCommitContentListener the listener that the wrapper object will call
* @return a wrapper {@link InputConnection} object that can be returned to the IME
* @throws IllegalArgumentException when {@code inputConnection}, {@code editorInfo}, or
* {@code onCommitContentListener} is {@code null}
*
* @deprecated Use {@link #createWrapper(View,InputConnection,EditorInfo) and
* {@link ViewCompat#setOnReceiveContentListener} instead.
*/
@Deprecated
@NonNull
public static InputConnection createWrapper(@NonNull InputConnection inputConnection,
@NonNull EditorInfo editorInfo,
@NonNull OnCommitContentListener onCommitContentListener) {
ObjectsCompat.requireNonNull(inputConnection, "inputConnection must be non-null");
ObjectsCompat.requireNonNull(editorInfo, "editorInfo must be non-null");
ObjectsCompat.requireNonNull(onCommitContentListener,
"onCommitContentListener must be non-null");
if (Build.VERSION.SDK_INT >= 25) {
final OnCommitContentListener listener = onCommitContentListener;
return new InputConnectionWrapper(inputConnection, false /* mutable */) {
@SuppressWarnings("ConstantConditions") // Incorrect warning
@Override
public boolean commitContent(InputContentInfo inputContentInfo, int flags,
Bundle opts) {
if (listener.onCommitContent(InputContentInfoCompat.wrap(inputContentInfo),
flags, opts)) {
return true;
}
return super.commitContent(inputContentInfo, flags, opts);
}
};
} else {
String[] contentMimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo);
if (contentMimeTypes.length == 0) {
return inputConnection;
}
final OnCommitContentListener listener = onCommitContentListener;
return new InputConnectionWrapper(inputConnection, false /* mutable */) {
@Override
public boolean performPrivateCommand(String action, Bundle data) {
if (InputConnectionCompat.handlePerformPrivateCommand(action, data, listener)) {
return true;
}
return super.performPrivateCommand(action, data);
}
};
}
}
/**
* Creates a wrapper {@link InputConnection} that implements {@code InputConnection}'s
* features on past versions of Android.
*
* <p>Currently, handles {@link InputConnection#commitContent} by dispatching to
* {@link ViewCompat#performReceiveContent}, enabling apps to use
* {@link ViewCompat#setOnReceiveContentListener} to specify handling for content insertion
* from the IME.
*
* <p>Usage:<br>
* <pre class="prettyprint">
* public class MyWidget extends View {
* @Override
* public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
* InputConnection ic = super.onCreateInputConnection(outAttrs);
* if (ic == null) {
* return ic;
* }
* String[] mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this);
* if (mimeTypes != null) {
* EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes);
* ic = InputConnectionCompat.createWrapper(this, ic, outAttrs);
* }
* return ic;
* }
* }
* </pre>
*
* @param view The view that the given input connection is associated with.
* @param inputConnection The input connection to be wrapped.
* @param editorInfo The editor metadata associated with the given input connection.
*
* @return A wrapper {@link InputConnection} object that can be returned to the IME.
*/
@SuppressWarnings("deprecation")
@NonNull
public static InputConnection createWrapper(@NonNull View view,
@NonNull InputConnection inputConnection, @NonNull EditorInfo editorInfo) {
OnCommitContentListener onCommitContentListener =
createOnCommitContentListenerUsingPerformReceiveContent(view);
return createWrapper(inputConnection, editorInfo, onCommitContentListener);
}
/**
* Creates an {@link OnCommitContentListener} that uses
* {@link ViewCompat#performReceiveContent} to insert content. This is useful for widgets
* that support content insertion using an {@link OnReceiveContentListener}.
*/
@NonNull
private static OnCommitContentListener createOnCommitContentListenerUsingPerformReceiveContent(
@NonNull View view) {
Preconditions.checkNotNull(view);
return (inputContentInfo, flags, opts) -> {
Bundle extras = opts;
if (Build.VERSION.SDK_INT >= 25
&& (flags & INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
try {
inputContentInfo.requestPermission();
} catch (Exception e) {
Log.w(LOG_TAG,
"Can't insert content from IME; requestPermission() failed", e);
return false;
}
// Permissions granted above are revoked automatically by the platform when the
// corresponding InputContentInfo object is garbage collected. To prevent
// this from happening prematurely (before the receiving app has had a chance
// to process the content), we set the InputContentInfo object into the
// extras of the payload passed to OnReceiveContentListener.
InputContentInfo inputContentInfoFmk =
(InputContentInfo) inputContentInfo.unwrap();
extras = (opts == null) ? new Bundle() : new Bundle(opts);
extras.putParcelable(EXTRA_INPUT_CONTENT_INFO, inputContentInfoFmk);
}
ClipData clip = new ClipData(inputContentInfo.getDescription(),
new ClipData.Item(inputContentInfo.getContentUri()));
ContentInfoCompat payload = new ContentInfoCompat.Builder(clip, SOURCE_INPUT_METHOD)
.setLinkUri(inputContentInfo.getLinkUri())
.setExtras(extras)
.build();
return ViewCompat.performReceiveContent(view, payload) == null;
};
}
/**
* Key for extras in {@link ContentInfoCompat}, to hold the {@link InputContentInfo} object
* passed by the IME. Apps should not access/read this object; it is only set in the extras
* in order to prevent premature garbage collection of {@link InputContentInfo} which in
* turn causes premature revocation of URI permissions.
*/
private static final String EXTRA_INPUT_CONTENT_INFO =
"androidx.core.view.extra.INPUT_CONTENT_INFO";
/** @deprecated This type should not be instantiated as it contains only static methods. */
@Deprecated
public InputConnectionCompat() {
}
@RequiresApi(25)
static class Api25Impl {
private Api25Impl() {
// This class is not instantiable.
}
@DoNotInline
static boolean commitContent(InputConnection inputConnection,
InputContentInfo inputContentInfo, int i, Bundle bundle) {
return inputConnection.commitContent(inputContentInfo, i, bundle);
}
}
}