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

Commit e1608458 authored by Nikita Dubrovsky's avatar Nikita Dubrovsky Committed by Android (Google) Code Review
Browse files

Merge "Add View.onReceiveContent() and View.getOnReceiveContentMimeTypes()"

parents 20d93ec7 82a414a1
Loading
Loading
Loading
Loading
+3 −5
Original line number Diff line number Diff line
@@ -53778,7 +53778,6 @@ package android.view {
  }
  public interface OnReceiveContentCallback<T extends android.view.View> {
    method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull T);
    method public boolean onReceiveContent(@NonNull T, @NonNull android.view.OnReceiveContentCallback.Payload);
  }
@@ -54356,7 +54355,7 @@ package android.view {
    method @IdRes public int getNextFocusRightId();
    method @IdRes public int getNextFocusUpId();
    method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
    method @Nullable public android.view.OnReceiveContentCallback<? extends android.view.View> getOnReceiveContentCallback();
    method @Nullable public String[] getOnReceiveContentMimeTypes();
    method @ColorInt public int getOutlineAmbientShadowColor();
    method public android.view.ViewOutlineProvider getOutlineProvider();
    method @ColorInt public int getOutlineSpotShadowColor();
@@ -54551,6 +54550,7 @@ package android.view {
    method public void onProvideContentCaptureStructure(@NonNull android.view.ViewStructure, int);
    method public void onProvideStructure(android.view.ViewStructure);
    method public void onProvideVirtualStructure(android.view.ViewStructure);
    method public boolean onReceiveContent(@NonNull android.view.OnReceiveContentCallback.Payload);
    method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
    method @CallSuper protected void onRestoreInstanceState(android.os.Parcelable);
    method public void onRtlPropertiesChanged(int);
@@ -54708,7 +54708,7 @@ package android.view {
    method public void setOnHoverListener(android.view.View.OnHoverListener);
    method public void setOnKeyListener(android.view.View.OnKeyListener);
    method public void setOnLongClickListener(@Nullable android.view.View.OnLongClickListener);
    method public void setOnReceiveContentCallback(@Nullable android.view.OnReceiveContentCallback<? extends android.view.View>);
    method public void setOnReceiveContentCallback(@Nullable String[], @Nullable android.view.OnReceiveContentCallback);
    method public void setOnScrollChangeListener(android.view.View.OnScrollChangeListener);
    method @Deprecated public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener);
    method public void setOnTouchListener(android.view.View.OnTouchListener);
@@ -61538,7 +61538,6 @@ package android.widget {
    method public int getMinWidth();
    method public final android.text.method.MovementMethod getMovementMethod();
    method public int getOffsetForPosition(float, float);
    method @Nullable public android.view.OnReceiveContentCallback<android.widget.TextView> getOnReceiveContentCallback();
    method public android.text.TextPaint getPaint();
    method public int getPaintFlags();
    method public String getPrivateImeOptions();
@@ -61727,7 +61726,6 @@ package android.widget {
  public class TextViewOnReceiveContentCallback implements android.view.OnReceiveContentCallback<android.widget.TextView> {
    ctor public TextViewOnReceiveContentCallback();
    method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull android.widget.TextView);
    method public boolean onReceiveContent(@NonNull android.widget.TextView, @NonNull android.view.OnReceiveContentCallback.Payload);
  }
+19 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.content;

import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
@@ -216,6 +217,24 @@ public class ClipDescription implements Parcelable {
        return false;
    }

    /**
     * Check whether the clip description contains any of the given MIME types.
     *
     * @param targetMimeTypes The target MIME types. May use patterns.
     * @return Returns true if at least one of the MIME types in the clip description matches at
     * least one of the target MIME types, else false.
     *
     * @hide
     */
    public boolean hasMimeType(@NonNull String[] targetMimeTypes) {
        for (String targetMimeType : targetMimeTypes) {
            if (hasMimeType(targetMimeType)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Filter the clip description MIME types by the given MIME type.  Returns
     * all MIME types in the clip that match the given MIME type.
+22 −59
Original line number Diff line number Diff line
@@ -19,9 +19,7 @@ package android.view;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.ClipDescription;
import android.net.Uri;
import android.os.Bundle;

@@ -30,11 +28,10 @@ import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.Set;

/**
 * Callback for apps to implement handling for insertion of content. "Content" here refers to both
 * text and non-text (plain/styled text, HTML, images, videos, audio files, etc).
 * Callback for apps to implement handling for insertion of content. Content may be both text and
 * non-text (plain/styled text, HTML, images, videos, audio files, etc).
 *
 * <p>This callback can be attached to different types of UI components using
 * {@link View#setOnReceiveContentCallback}.
@@ -45,32 +42,38 @@ import java.util.Set;
 *
 * <p>Example implementation:<br>
 * <pre class="prettyprint">
 * // (1) Define the callback
 * public class MyOnReceiveContentCallback implements OnReceiveContentCallback&lt;TextView&gt; {
 *
 *     private static final Set&lt;String&gt; SUPPORTED_MIME_TYPES = Collections.unmodifiableSet(
 *     public static final Set&lt;String&gt; MIME_TYPES = Collections.unmodifiableSet(
 *         Set.of("image/*", "video/*"));
 *
 *     &#64;NonNull
 *     &#64;Override
 *     public Set&lt;String&gt; getSupportedMimeTypes() {
 *         return SUPPORTED_MIME_TYPES;
 *     }
 *
 *     &#64;Override
 *     public boolean onReceiveContent(@NonNull TextView view, @NonNull Payload payload) {
 *         // ... app-specific logic to handle the content in the payload ...
 *     }
 * }
 *
 * // (2) Register the callback
 * public class MyActivity extends Activity {
 *     &#64;Override
 *     public void onCreate(Bundle savedInstanceState) {
 *         // ...
 *
 *         EditText myInput = findViewById(R.id.my_input);
 *         myInput.setOnReceiveContentCallback(
 *                 MyOnReceiveContentCallback.MIME_TYPES,
 *                 new MyOnReceiveContentCallback());
 *     }
 * </pre>
 *
 * @param <T> The type of {@link View} with which this receiver can be associated.
 * @param <T> The type of {@link View} with which this callback can be associated.
 */
public interface OnReceiveContentCallback<T extends View> {
    /**
     * Receive the given content.
     *
     * <p>This function will only be invoked if the MIME type of the content is in the set of
     * types returned by {@link #getSupportedMimeTypes}.
     * <p>This method is only invoked for content whose MIME type matches a type specified via
     * {@link View#setOnReceiveContentCallback}.
     *
     * <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.
@@ -81,53 +84,13 @@ public interface OnReceiveContentCallback<T extends View> {
     * @param view The view where the content insertion was requested.
     * @param payload The content to insert and related metadata.
     *
     * @return Returns true if some or all of the content is accepted for insertion. If accepted,
     * actual insertion may be handled asynchronously in the background and may or may not result in
     * successful insertion. For example, the app may not end up inserting an accepted item if it
     * @return Returns true if the content was handled in some way, false otherwise. Actual
     * insertion may be processed asynchronously in the background and may or may not succeed even
     * if this method returns true. For example, an app may not end up inserting an item if it
     * exceeds the app's size limit for that type of content.
     */
    boolean onReceiveContent(@NonNull T view, @NonNull Payload payload);

    /**
     * Returns the MIME types that can be handled by this callback.
     *
     * <p>The {@link #onReceiveContent} callback method will only be invoked if the MIME type of the
     * content is in the set of supported types returned here.
     *
     * <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 OnReceiveContentCallback} set and the MIME types returned from this function don't
     * include "image/gif".
     *
     * <p><em>Note: MIME type matching in the Android framework is case-sensitive, unlike formal RFC
     * MIME types. As a result, you should always write your MIME types with lower case letters, or
     * use {@link android.content.Intent#normalizeMimeType} to ensure that it is converted to lower
     * case.</em>
     *
     * @param view The target view.
     * @return An immutable set with the MIME types supported by this callback. The returned MIME
     * types may contain wildcards such as "text/*", "image/*", etc.
     */
    @SuppressLint("CallbackMethodName")
    @NonNull
    Set<String> getSupportedMimeTypes(@NonNull T view);

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

    /**
     * Holds all the relevant data for a request to {@link OnReceiveContentCallback}.
     */
+65 −19
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import android.annotation.UiThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AutofillOptions;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
@@ -143,6 +144,7 @@ import android.widget.ScrollBarDrawable;
import com.android.internal.R;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.view.ScrollCaptureInternal;
import com.android.internal.view.TooltipPopup;
import com.android.internal.view.menu.MenuBuilder;
@@ -5243,7 +5245,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    int mUnbufferedInputSource = InputDevice.SOURCE_CLASS_NONE;
    @Nullable
    private OnReceiveContentCallback<? extends View> mOnReceiveContentCallback;
    private String[] mOnReceiveContentMimeTypes;
    @Nullable
    private OnReceiveContentCallback mOnReceiveContentCallback;
    /**
     * Simple constructor to use when creating a view from code.
@@ -9000,36 +9004,78 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        }
    }
    /**
     * Returns the callback used for handling insertion of content into this view. See
     * {@link #setOnReceiveContentCallback} for more info.
     *
     * @return The callback for handling insertion of content. Returns null if no callback has been
     * {@link #setOnReceiveContentCallback set}.
     */
    @Nullable
    public OnReceiveContentCallback<? extends View> getOnReceiveContentCallback() {
        return mOnReceiveContentCallback;
    }
    /**
     * Sets the callback to handle insertion of content into this view.
     *
     * <p>Depending on the view, this callback may be invoked for scenarios such as content
     * insertion from the IME, Autofill, etc.
     *
     * <p>The callback will only be invoked if the MIME type of the content is
     * {@link OnReceiveContentCallback#getSupportedMimeTypes declared as supported} by the callback.
     * If the content type is not supported by the callback, the default platform handling will be
     * executed instead.
     * <p>This callback is only invoked for content whose MIME type matches a type specified via
     * the {code mimeTypes} parameter. If the MIME type is not supported by the callback, the
     * default platform handling will be executed instead (no-op for the default {@link View}).
     *
     * <p><em>Note: MIME type matching in the Android framework is case-sensitive, unlike formal RFC
     * MIME types. As a result, you should always write your MIME types with lower case letters, or
     * use {@link android.content.Intent#normalizeMimeType} to ensure that it is converted to lower
     * case.</em>
     *
     * @param mimeTypes The type of content for which the callback should be invoked. This may use
     * wildcards such as "text/*", "image/*", etc. This must not be null or empty if a non-null
     * callback is passed in.
     * @param callback The callback to use. This can be null to reset to the default behavior.
     */
    public void setOnReceiveContentCallback(
            @Nullable OnReceiveContentCallback<? extends View> callback) {
    @SuppressWarnings("rawtypes")
    public void setOnReceiveContentCallback(@Nullable String[] mimeTypes,
            @Nullable OnReceiveContentCallback callback) {
        if (callback != null) {
            Preconditions.checkArgument(mimeTypes != null && mimeTypes.length > 0,
                    "When the callback is set, MIME types must also be set");
        }
        mOnReceiveContentMimeTypes = mimeTypes;
        mOnReceiveContentCallback = callback;
    }
    /**
     * Receives the given content. The default implementation invokes the callback set via
     * {@link #setOnReceiveContentCallback}. If no callback is set or if the callback does not
     * support the given content (based on the MIME type), returns false.
     *
     * @param payload The content to insert and related metadata.
     *
     * @return Returns true if the content was handled in some way, false otherwise. Actual
     * insertion may be processed asynchronously in the background and may or may not succeed even
     * if this method returns true. For example, an app may not end up inserting an item if it
     * exceeds the app's size limit for that type of content.
     */
    public boolean onReceiveContent(@NonNull OnReceiveContentCallback.Payload payload) {
        ClipDescription description = payload.getClip().getDescription();
        if (mOnReceiveContentCallback != null && mOnReceiveContentMimeTypes != null
                && description.hasMimeType(mOnReceiveContentMimeTypes)) {
            return mOnReceiveContentCallback.onReceiveContent(this, payload);
        }
        return false;
    }
    /**
     * Returns the MIME types that can be handled by {@link #onReceiveContent} for this view, as
     * configured via {@link #setOnReceiveContentCallback}. By default returns null.
     *
     * <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
     * soft keyboard may choose to hide its UI for inserting GIFs for a particular input field if
     * the MIME types returned here for that field don't include "image/gif".
     *
     * <p>Note: Comparisons of MIME types should be performed using utilities such as
     * {@link ClipDescription#compareMimeTypes} rather than simple string equality, in order to
     * correctly handle patterns (e.g. "text/*").
     *
     * @return The MIME types supported by {@link #onReceiveContent} for this view. The returned
     * MIME types may contain wildcards such as "text/*", "image/*", etc.
     */
    public @Nullable String[] getOnReceiveContentMimeTypes() {
        return mOnReceiveContentMimeTypes;
    }
    /**
     * Automatically fills the content of this view with the {@code value}.
     *
+12 −16
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.annotation.CallSuper;
import android.annotation.IntRange;
import android.annotation.Nullable;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
@@ -918,23 +919,18 @@ public class BaseInputConnection implements InputConnection {
    }

    /**
     * Default implementation which invokes the target view's {@link OnReceiveContentCallback} if
     * it is {@link View#setOnReceiveContentCallback set} and supports the MIME type of the given
     * content; otherwise, simply returns false.
     * Default implementation which invokes {@link View#onReceiveContent} on the target view if the
     * MIME type of the content matches one of the MIME types returned by
     * {@link View#getOnReceiveContentMimeTypes()}. If the MIME type of the content is not matched,
     * returns false without any side effects.
     */
    public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
        @SuppressWarnings("unchecked") final OnReceiveContentCallback<View> receiver =
                (OnReceiveContentCallback<View>) mTargetView.getOnReceiveContentCallback();
        if (receiver == null) {
        ClipDescription description = inputContentInfo.getDescription();
        final String[] viewMimeTypes = mTargetView.getOnReceiveContentMimeTypes();
        if (viewMimeTypes == null || !description.hasMimeType(viewMimeTypes)) {
            if (DEBUG) {
                Log.d(TAG, "Can't insert content from IME; no callback");
            }
            return false;
        }
        if (!receiver.supports(mTargetView, inputContentInfo.getDescription())) {
            if (DEBUG) {
                Log.d(TAG, "Can't insert content from IME; callback doesn't support MIME type: "
                        + inputContentInfo.getDescription());
                Log.d(TAG, "Can't insert content from IME; unsupported MIME type: content="
                        + description + ", viewMimeTypes=" + viewMimeTypes);
            }
            return false;
        }
@@ -946,13 +942,13 @@ public class BaseInputConnection implements InputConnection {
                return false;
            }
        }
        final ClipData clip = new ClipData(inputContentInfo.getDescription(),
        final ClipData clip = new ClipData(description,
                new ClipData.Item(inputContentInfo.getContentUri()));
        final OnReceiveContentCallback.Payload payload =
                new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_INPUT_METHOD)
                .setLinkUri(inputContentInfo.getLinkUri())
                .setExtras(opts)
                .build();
        return receiver.onReceiveContent(mTargetView, payload);
        return mTargetView.onReceiveContent(payload);
    }
}
Loading