Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 832edc3c authored by Nikita Dubrovsky's avatar Nikita Dubrovsky
Browse files

Add unified API for inserting rich content (e.g. pasting an image)

The new callback provides a single API that apps can implement to
support the different ways in which rich content may be inserted.

The API is added to TextView and unifies the following code paths:
* paste from the clipboard (TextView.paste)
* content insertion from the IME (InputConnection.commitContent)
* drag and drop (Editor.onDrop)
* autofill (TextView.autofill)

Corresponding API in support lib: aosp/1200800

Bug: 152068298
Test: Manual and unit tests
  atest FrameworksCoreTests:TextViewRichContentReceiverTest
  atest FrameworksCoreTests:AutofillValueTest
  atest FrameworksCoreTests:TextViewActivityTest
Change-Id: I6e03a398ccb6fa5526d0a282fc114f4e80285099
parent 53bedad0
Loading
Loading
Loading
Loading
+17 −0
Original line number Original line Diff line number Diff line
@@ -54407,6 +54407,7 @@ package android.view {
    field public static final int AUTOFILL_TYPE_DATE = 4; // 0x4
    field public static final int AUTOFILL_TYPE_DATE = 4; // 0x4
    field public static final int AUTOFILL_TYPE_LIST = 3; // 0x3
    field public static final int AUTOFILL_TYPE_LIST = 3; // 0x3
    field public static final int AUTOFILL_TYPE_NONE = 0; // 0x0
    field public static final int AUTOFILL_TYPE_NONE = 0; // 0x0
    field public static final int AUTOFILL_TYPE_RICH_CONTENT = 5; // 0x5
    field public static final int AUTOFILL_TYPE_TEXT = 1; // 0x1
    field public static final int AUTOFILL_TYPE_TEXT = 1; // 0x1
    field public static final int AUTOFILL_TYPE_TOGGLE = 2; // 0x2
    field public static final int AUTOFILL_TYPE_TOGGLE = 2; // 0x2
    field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
    field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
@@ -56675,14 +56676,17 @@ package android.view.autofill {
    method public int describeContents();
    method public int describeContents();
    method public static android.view.autofill.AutofillValue forDate(long);
    method public static android.view.autofill.AutofillValue forDate(long);
    method public static android.view.autofill.AutofillValue forList(int);
    method public static android.view.autofill.AutofillValue forList(int);
    method @NonNull public static android.view.autofill.AutofillValue forRichContent(@NonNull android.content.ClipData);
    method public static android.view.autofill.AutofillValue forText(@Nullable CharSequence);
    method public static android.view.autofill.AutofillValue forText(@Nullable CharSequence);
    method public static android.view.autofill.AutofillValue forToggle(boolean);
    method public static android.view.autofill.AutofillValue forToggle(boolean);
    method public long getDateValue();
    method public long getDateValue();
    method public int getListValue();
    method public int getListValue();
    method @NonNull public android.content.ClipData getRichContentValue();
    method @NonNull public CharSequence getTextValue();
    method @NonNull public CharSequence getTextValue();
    method public boolean getToggleValue();
    method public boolean getToggleValue();
    method public boolean isDate();
    method public boolean isDate();
    method public boolean isList();
    method public boolean isList();
    method public boolean isRichContent();
    method public boolean isText();
    method public boolean isText();
    method public boolean isToggle();
    method public boolean isToggle();
    method public void writeToParcel(android.os.Parcel, int);
    method public void writeToParcel(android.os.Parcel, int);
@@ -60563,6 +60567,16 @@ package android.widget {
    method public android.view.View newGroupView(android.content.Context, android.database.Cursor, boolean, android.view.ViewGroup);
    method public android.view.View newGroupView(android.content.Context, android.database.Cursor, boolean, android.view.ViewGroup);
  }
  }
  public interface RichContentReceiver<T extends android.view.View> {
    method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes();
    method public boolean onReceive(@NonNull T, @NonNull android.content.ClipData, int, int);
    field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
    field public static final int SOURCE_AUTOFILL = 3; // 0x3
    field public static final int SOURCE_DRAG_AND_DROP = 2; // 0x2
    field public static final int SOURCE_INPUT_METHOD = 1; // 0x1
    field public static final int SOURCE_MENU = 0; // 0x0
  }
  public class ScrollView extends android.widget.FrameLayout {
  public class ScrollView extends android.widget.FrameLayout {
    ctor public ScrollView(android.content.Context);
    ctor public ScrollView(android.content.Context);
    ctor public ScrollView(android.content.Context, android.util.AttributeSet);
    ctor public ScrollView(android.content.Context, android.util.AttributeSet);
@@ -61120,6 +61134,7 @@ package android.widget {
    method public android.text.TextPaint getPaint();
    method public android.text.TextPaint getPaint();
    method public int getPaintFlags();
    method public int getPaintFlags();
    method public String getPrivateImeOptions();
    method public String getPrivateImeOptions();
    method @NonNull public android.widget.RichContentReceiver<android.widget.TextView> getRichContentReceiver();
    method @android.view.ViewDebug.ExportedProperty(category="text") public int getSelectionEnd();
    method @android.view.ViewDebug.ExportedProperty(category="text") public int getSelectionEnd();
    method @android.view.ViewDebug.ExportedProperty(category="text") public int getSelectionStart();
    method @android.view.ViewDebug.ExportedProperty(category="text") public int getSelectionStart();
    method @ColorInt public int getShadowColor();
    method @ColorInt public int getShadowColor();
@@ -61247,6 +61262,7 @@ package android.widget {
    method public void setPaintFlags(int);
    method public void setPaintFlags(int);
    method public void setPrivateImeOptions(String);
    method public void setPrivateImeOptions(String);
    method public void setRawInputType(int);
    method public void setRawInputType(int);
    method public void setRichContentReceiver(@NonNull android.widget.RichContentReceiver<android.widget.TextView>);
    method public void setScroller(android.widget.Scroller);
    method public void setScroller(android.widget.Scroller);
    method public void setSelectAllOnFocus(boolean);
    method public void setSelectAllOnFocus(boolean);
    method public void setShadowLayer(float, float, float, int);
    method public void setShadowLayer(float, float, float, int);
@@ -61287,6 +61303,7 @@ package android.widget {
    method public void setWidth(int);
    method public void setWidth(int);
    field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
    field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
    field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
    field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
    field @NonNull public static final android.widget.RichContentReceiver<android.widget.TextView> DEFAULT_RICH_CONTENT_RECEIVER;
  }
  }
  public enum TextView.BufferType {
  public enum TextView.BufferType {
+14 −2
Original line number Original line Diff line number Diff line
@@ -1260,7 +1260,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            AUTOFILL_TYPE_TEXT,
            AUTOFILL_TYPE_TEXT,
            AUTOFILL_TYPE_TOGGLE,
            AUTOFILL_TYPE_TOGGLE,
            AUTOFILL_TYPE_LIST,
            AUTOFILL_TYPE_LIST,
            AUTOFILL_TYPE_DATE
            AUTOFILL_TYPE_DATE,
            AUTOFILL_TYPE_RICH_CONTENT
    })
    })
    @Retention(RetentionPolicy.SOURCE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface AutofillType {}
    public @interface AutofillType {}
@@ -1311,7 +1312,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
     */
    public static final int AUTOFILL_TYPE_LIST = 3;
    public static final int AUTOFILL_TYPE_LIST = 3;
    /**
    /**
     * Autofill type for a field that contains a date, which is represented by a long representing
     * Autofill type for a field that contains a date, which is represented by a long representing
     * the number of milliseconds since the standard base time known as "the epoch", namely
     * the number of milliseconds since the standard base time known as "the epoch", namely
@@ -1325,6 +1325,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
     */
    public static final int AUTOFILL_TYPE_DATE = 4;
    public static final int AUTOFILL_TYPE_DATE = 4;
    /**
     * Autofill type for a field that can accept rich content (text, images, etc).
     *
     * <p>{@link AutofillValue} instances for autofilling a {@link View} can be obtained through
     * {@link AutofillValue#forRichContent(ClipData)}, and the values passed to
     * autofill a {@link View} can be fetched through {@link AutofillValue#getRichContentValue()}.
     *
     * @see #getAutofillType()
     */
    public static final int AUTOFILL_TYPE_RICH_CONTENT = 5;
    /** @hide */
    /** @hide */
    @IntDef(prefix = { "IMPORTANT_FOR_AUTOFILL_" }, value = {
    @IntDef(prefix = { "IMPORTANT_FOR_AUTOFILL_" }, value = {
            IMPORTANT_FOR_AUTOFILL_AUTO,
            IMPORTANT_FOR_AUTOFILL_AUTO,
+45 −0
Original line number Original line Diff line number Diff line
@@ -18,6 +18,7 @@ package android.view.autofill;


import static android.view.View.AUTOFILL_TYPE_DATE;
import static android.view.View.AUTOFILL_TYPE_DATE;
import static android.view.View.AUTOFILL_TYPE_LIST;
import static android.view.View.AUTOFILL_TYPE_LIST;
import static android.view.View.AUTOFILL_TYPE_RICH_CONTENT;
import static android.view.View.AUTOFILL_TYPE_TEXT;
import static android.view.View.AUTOFILL_TYPE_TEXT;
import static android.view.View.AUTOFILL_TYPE_TOGGLE;
import static android.view.View.AUTOFILL_TYPE_TOGGLE;
import static android.view.autofill.Helper.sDebug;
import static android.view.autofill.Helper.sDebug;
@@ -25,6 +26,7 @@ import static android.view.autofill.Helper.sVerbose;


import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.content.ClipData;
import android.os.Looper;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Parcelable;
@@ -139,6 +141,28 @@ public final class AutofillValue implements Parcelable {
        return mType == AUTOFILL_TYPE_DATE;
        return mType == AUTOFILL_TYPE_DATE;
    }
    }


    /**
     * Gets the value to autofill a field that accepts rich content (text, images, etc).
     *
     * <p>See {@link View#AUTOFILL_TYPE_RICH_CONTENT} for more info.</p>
     *
     * @throws IllegalStateException if the value is not a content value
     */
    public @NonNull ClipData getRichContentValue() {
        Preconditions.checkState(isRichContent(),
                "value must be a rich content value, not type=" + mType);
        return (ClipData) mValue;
    }

    /**
     * Checks if this is a rich content value (represented by {@link ClipData}).
     *
     * <p>See {@link View#AUTOFILL_TYPE_RICH_CONTENT} for more info.</p>
     */
    public boolean isRichContent() {
        return mType == AUTOFILL_TYPE_RICH_CONTENT;
    }

    /**
    /**
     * Used to define whether a field is empty so it's not sent to service on save.
     * Used to define whether a field is empty so it's not sent to service on save.
     *
     *
@@ -184,6 +208,10 @@ public final class AutofillValue implements Parcelable {
                .append(", value=");
                .append(", value=");
        if (isText()) {
        if (isText()) {
            Helper.appendRedacted(string, (CharSequence) mValue);
            Helper.appendRedacted(string, (CharSequence) mValue);
        } else if (isRichContent()) {
            string.append("{");
            getRichContentValue().getDescription().toShortStringTypesOnly(string);
            string.append("}");
        } else {
        } else {
            string.append(mValue);
            string.append(mValue);
        }
        }
@@ -216,6 +244,9 @@ public final class AutofillValue implements Parcelable {
            case AUTOFILL_TYPE_DATE:
            case AUTOFILL_TYPE_DATE:
                parcel.writeLong((Long) mValue);
                parcel.writeLong((Long) mValue);
                break;
                break;
            case AUTOFILL_TYPE_RICH_CONTENT:
                ((ClipData) mValue).writeToParcel(parcel, flags);
                break;
        }
        }
    }
    }


@@ -236,6 +267,9 @@ public final class AutofillValue implements Parcelable {
            case AUTOFILL_TYPE_DATE:
            case AUTOFILL_TYPE_DATE:
                mValue = parcel.readLong();
                mValue = parcel.readLong();
                break;
                break;
            case AUTOFILL_TYPE_RICH_CONTENT:
                mValue = ClipData.CREATOR.createFromParcel(parcel);
                break;
            default:
            default:
                throw new IllegalArgumentException("type=" + mType + " not valid");
                throw new IllegalArgumentException("type=" + mType + " not valid");
        }
        }
@@ -303,4 +337,15 @@ public final class AutofillValue implements Parcelable {
    public static AutofillValue forDate(long value) {
    public static AutofillValue forDate(long value) {
        return new AutofillValue(AUTOFILL_TYPE_DATE, value);
        return new AutofillValue(AUTOFILL_TYPE_DATE, value);
    }
    }

    /**
     * Creates a new {@link AutofillValue} to autofill a {@link View} that accepts rich content
     * (text, images, etc).
     *
     * <p>See {@link View#AUTOFILL_TYPE_RICH_CONTENT} for more info.
     */
    public static @NonNull AutofillValue forRichContent(@NonNull ClipData value) {
        Objects.requireNonNull(value.getDescription(), "clip description must not be null");
        return new AutofillValue(AUTOFILL_TYPE_RICH_CONTENT, value);
    }
}
}
+45 −57
Original line number Original line Diff line number Diff line
@@ -27,7 +27,6 @@ import android.app.PendingIntent.CanceledException;
import android.app.RemoteAction;
import android.app.RemoteAction;
import android.compat.annotation.UnsupportedAppUsage;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipData;
import android.content.ClipData.Item;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.content.UndoManager;
import android.content.UndoManager;
@@ -2802,29 +2801,6 @@ public class Editor {
    }
    }


    void onDrop(DragEvent event) {
    void onDrop(DragEvent event) {
        SpannableStringBuilder content = new SpannableStringBuilder();

        final DragAndDropPermissions permissions = DragAndDropPermissions.obtain(event);
        if (permissions != null) {
            permissions.takeTransient();
        }

        try {
            ClipData clipData = event.getClipData();
            final int itemCount = clipData.getItemCount();
            for (int i = 0; i < itemCount; i++) {
                Item item = clipData.getItemAt(i);
                content.append(item.coerceToStyledText(mTextView.getContext()));
            }
        } finally {
            if (permissions != null) {
                permissions.release();
            }
        }

        mTextView.beginBatchEdit();
        mUndoInputFilter.freezeLastEdit();
        try {
        final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY());
        final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY());
        Object localState = event.getLocalState();
        Object localState = event.getLocalState();
        DragLocalState dragLocalState = null;
        DragLocalState dragLocalState = null;
@@ -2833,7 +2809,6 @@ public class Editor {
        }
        }
        boolean dragDropIntoItself = dragLocalState != null
        boolean dragDropIntoItself = dragLocalState != null
                && dragLocalState.sourceTextView == mTextView;
                && dragLocalState.sourceTextView == mTextView;

        if (dragDropIntoItself) {
        if (dragDropIntoItself) {
            if (offset >= dragLocalState.start && offset < dragLocalState.end) {
            if (offset >= dragLocalState.start && offset < dragLocalState.end) {
                // A drop inside the original selection discards the drop.
                // A drop inside the original selection discards the drop.
@@ -2841,19 +2816,37 @@ public class Editor {
            }
            }
        }
        }


        final DragAndDropPermissions permissions = DragAndDropPermissions.obtain(event);
        if (permissions != null) {
            permissions.takeTransient();
        }
        mTextView.beginBatchEdit();
        mUndoInputFilter.freezeLastEdit();
        try {
            final int originalLength = mTextView.getText().length();
            final int originalLength = mTextView.getText().length();
            int min = offset;
            Selection.setSelection((Spannable) mTextView.getText(), offset);
            int max = offset;
            ClipData clip = event.getClipData();

            mTextView.getRichContentReceiver().onReceive(mTextView, clip,
            Selection.setSelection((Spannable) mTextView.getText(), max);
                    RichContentReceiver.SOURCE_DRAG_AND_DROP, 0);
            mTextView.replaceText_internal(min, max, content);

            if (dragDropIntoItself) {
            if (dragDropIntoItself) {
                deleteSourceAfterLocalDrop(dragLocalState, offset, originalLength);
            }
        } finally {
            mTextView.endBatchEdit();
            mUndoInputFilter.freezeLastEdit();
            if (permissions != null) {
                permissions.release();
            }
        }
    }

    private void deleteSourceAfterLocalDrop(@NonNull DragLocalState dragLocalState, int dropOffset,
            int lengthBeforeDrop) {
        int dragSourceStart = dragLocalState.start;
        int dragSourceStart = dragLocalState.start;
        int dragSourceEnd = dragLocalState.end;
        int dragSourceEnd = dragLocalState.end;
                if (max <= dragSourceStart) {
        if (dropOffset <= dragSourceStart) {
            // Inserting text before selection has shifted positions
            // Inserting text before selection has shifted positions
                    final int shift = mTextView.getText().length() - originalLength;
            final int shift = mTextView.getText().length() - lengthBeforeDrop;
            dragSourceStart += shift;
            dragSourceStart += shift;
            dragSourceEnd += shift;
            dragSourceEnd += shift;
        }
        }
@@ -2871,11 +2864,6 @@ public class Editor {
            }
            }
        }
        }
    }
    }
        } finally {
            mTextView.endBatchEdit();
            mUndoInputFilter.freezeLastEdit();
        }
    }


    public void addSpanWatchers(Spannable text) {
    public void addSpanWatchers(Spannable text) {
        final int textLength = text.length();
        final int textLength = text.length();
+200 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2020 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.widget;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.ClipDescription;
import android.view.View;
import android.view.inputmethod.InputConnection;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Set;

/**
 * Callback for apps to implement handling for insertion of rich content. "Rich content" here refers
 * to both text and non-text content: plain text, styled text, HTML, images, videos, audio files,
 * etc.
 *
 * <p>This callback can be attached to different types of UI components. For editable
 * {@link TextView} components, implementations should typically wrap
 * {@link TextView#DEFAULT_RICH_CONTENT_RECEIVER}.
 *
 * <p>Example implementation:<br>
 * <pre class="prettyprint">
 *   public class MyRichContentReceiver extends RichContentReceiver&lt;TextView&gt; {
 *
 *       private static final Set&lt;String&gt; SUPPORTED_MIME_TYPES = Collections.unmodifiableSet(
 *           Set.of("text/*", "image/gif", "image/png", "image/jpg"));
 *
 *       &#64;NonNull
 *       &#64;Override
 *       public Set&lt;String&gt; getSupportedMimeTypes() {
 *           return SUPPORTED_MIME_TYPES;
 *       }
 *
 *       &#64;Override
 *       public boolean onReceive(@NonNull TextView textView, @NonNull ClipData clip,
 *               int source, int flags) {
 *         if (clip.getDescription().hasMimeType("image/*")) {
 *             return receiveImage(textView, clip);
 *         }
 *         return TextView.DEFAULT_RICH_CONTENT_RECEIVER.onReceive(textView, clip, source);
 *       }
 *
 *       private boolean receiveImage(@NonNull TextView textView, @NonNull ClipData clip) {
 *           // ... app-specific logic to handle the content URI in the clip ...
 *       }
 *   }
 * </pre>
 *
 * @param <T> The type of {@link View} with which this receiver can be associated.
 */
@SuppressLint("CallbackMethodName")
public interface RichContentReceiver<T extends View> {
    /**
     * Specifies the UI through which content is being inserted.
     *
     * @hide
     */
    @IntDef(prefix = {"SOURCE_"}, value = {SOURCE_MENU, SOURCE_INPUT_METHOD, SOURCE_DRAG_AND_DROP,
            SOURCE_AUTOFILL})
    @Retention(RetentionPolicy.SOURCE)
    @interface Source {}

    /**
     * Specifies that the operation was triggered from the insertion/selection menu ("Paste" or
     * "Paste as plain text" action).
     */
    int SOURCE_MENU = 0;

    /**
     * Specifies that the operation was triggered from the soft keyboard (also known as input method
     * editor or IME). See https://developer.android.com/guide/topics/text/image-keyboard for more
     * info.
     */
    int SOURCE_INPUT_METHOD = 1;

    /**
     * Specifies that the operation was triggered by the drag/drop framework. See
     * https://developer.android.com/guide/topics/ui/drag-drop for more info.
     */
    int SOURCE_DRAG_AND_DROP = 2;

    /**
     * Specifies that the operation was triggered by the autofill framework. See
     * https://developer.android.com/guide/topics/text/autofill for more info.
     */
    int SOURCE_AUTOFILL = 3;

    /**
     * Flags to configure the insertion behavior.
     *
     * @hide
     */
    @IntDef(flag = true, prefix = {"FLAG_"}, value = {FLAG_CONVERT_TO_PLAIN_TEXT})
    @Retention(RetentionPolicy.SOURCE)
    @interface Flags {}

    /**
     * Flag for {@link #onReceive} requesting that the content should be converted to plain text
     * prior to inserting.
     */
    int FLAG_CONVERT_TO_PLAIN_TEXT = 1 << 0;

    /**
     * Insert the given clip.
     *
     * <p>For editable {@link TextView} components, this function will be invoked for the
     * following scenarios:
     * <ol>
     *     <li>Paste from the clipboard ("Paste" and "Paste as plain text" actions in the
     *     insertion/selection menu)
     *     <li>Content insertion from the keyboard ({@link InputConnection#commitContent})
     *     <li>Drag and drop ({@link View#onDragEvent})
     *     <li>Autofill, when the type for the field is
     *     {@link android.view.View.AutofillType#AUTOFILL_TYPE_RICH_CONTENT}
     * </ol>
     *
     * <p>For text, if the view has a selection, the selection should be overwritten by the
     * clip; if there's no selection, this method should insert the content at the current
     * cursor position.
     *
     * <p>For rich content (e.g. an image), this function may insert the content inline, or it may
     * add the content as an attachment (could potentially go into a completely separate view).
     *
     * <p>This function may be invoked with a clip whose MIME type is not in the list of supported
     * types returned by {@link #getSupportedMimeTypes()}. This provides the opportunity to
     * implement custom fallback logic if desired.
     *
     * @param view   The view where the content insertion was requested.
     * @param clip   The clip to insert.
     * @param source The trigger of the operation.
     * @param flags  Optional flags to configure the insertion behavior. Use 0 for default
     *               behavior. See {@code FLAG_} constants on this interface for other options.
     * @return Returns true if the clip was inserted.
     */
    boolean onReceive(@NonNull T view, @NonNull ClipData clip, @Source int source, int flags);

    /**
     * Returns the MIME types that can be handled by this callback.
     *
     * <p>Different platform features (e.g. pasting from the clipboard, inserting stickers from the
     * keyboard, etc) may use this function to conditionally alter their behavior. For example, the
     * keyboard may choose to hide its UI for inserting GIFs if the input field that has focus has
     * a {@link RichContentReceiver} set and the MIME types returned from this function don't
     * include "image/gif".
     *
     * @return An immutable set with the MIME types supported by this callback. The returned
     * MIME types may contain wildcards such as "text/*", "image/*", etc.
     */
    @NonNull
    Set<String> getSupportedMimeTypes();

    /**
     * Returns true if the MIME type of the given clip is {@link #getSupportedMimeTypes supported}
     * by this receiver.
     *
     * @hide
     */
    default boolean supports(@NonNull ClipDescription description) {
        for (String supportedMimeType : getSupportedMimeTypes()) {
            if (description.hasMimeType(supportedMimeType)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if this receiver {@link #getSupportedMimeTypes supports} non-text content, such
     * as images.
     *
     * @hide
     */
    default boolean supportsNonTextContent() {
        for (String supportedMimeType : getSupportedMimeTypes()) {
            if (!supportedMimeType.startsWith("text/")) {
                return true;
            }
        }
        return false;
    }
}
Loading