public final class

InputConnectionCompat

extends java.lang.Object

 java.lang.Object

↳androidx.core.view.inputmethod.InputConnectionCompat

Gradle dependencies

compile group: 'androidx.core', name: 'core', version: '1.15.0-alpha02'

  • groupId: androidx.core
  • artifactId: core
  • version: 1.15.0-alpha02

Artifact androidx.core:core:1.15.0-alpha02 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

Fields
public static final intINPUT_CONTENT_GRANT_READ_URI_PERMISSION

When 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.

Constructors
publicInputConnectionCompat()

Methods
public static booleancommitContent(InputConnection inputConnection, EditorInfo editorInfo, InputContentInfoCompat inputContentInfo, int flags, Bundle opts)

Calls commitContent API, in a backwards compatible fashion.

public static InputConnectioncreateWrapper(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 InputConnectioncreateWrapper(View view, InputConnection inputConnection, EditorInfo editorInfo)

Creates a wrapper that implements InputConnection's features on past versions of Android.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final int INPUT_CONTENT_GRANT_READ_URI_PERMISSION

When 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

public static InputConnection createWrapper(InputConnection inputConnection, EditorInfo editorInfo, InputConnectionCompat.OnCommitContentListener onCommitContentListener)

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.Parcelable;
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.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 &gt;= 25.</p>
     *
     * <p>On API &lt;= 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 &gt;= 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 &gt;= 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 {
     *     &#64;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.
                Parcelable inputContentInfoFmk =
                        (Parcelable) 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.
        }

        static boolean commitContent(InputConnection inputConnection,
                InputContentInfo inputContentInfo, int i, Bundle bundle) {
            return inputConnection.commitContent(inputContentInfo, i, bundle);
        }
    }
}