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

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

Merge "Update onReceiveContent() logic for app vs platform processing"

parents 40901fa0 a25346bf
Loading
Loading
Loading
Loading
+15 −16
Original line number Diff line number Diff line
@@ -53860,16 +53860,17 @@ package android.view {
    field public int toolType;
  }
  public interface OnReceiveContentCallback<T extends android.view.View> {
    method public boolean onReceiveContent(@NonNull T, @NonNull android.view.OnReceiveContentCallback.Payload);
  public interface OnReceiveContentListener {
    method @Nullable public android.view.OnReceiveContentListener.Payload onReceiveContent(@NonNull android.view.View, @NonNull android.view.OnReceiveContentListener.Payload);
  }
  public static final class OnReceiveContentCallback.Payload {
  public static final class OnReceiveContentListener.Payload {
    method @NonNull public android.content.ClipData getClip();
    method @Nullable public android.os.Bundle getExtras();
    method public int getFlags();
    method @Nullable public android.net.Uri getLinkUri();
    method public int getSource();
    method @NonNull public java.util.Map<java.lang.Boolean,android.view.OnReceiveContentListener.Payload> partition(@NonNull java.util.function.Predicate<android.content.ClipData.Item>);
    field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
    field public static final int SOURCE_APP = 0; // 0x0
    field public static final int SOURCE_AUTOFILL = 4; // 0x4
@@ -53879,12 +53880,15 @@ package android.view {
    field public static final int SOURCE_PROCESS_TEXT = 5; // 0x5
  }
  public static final class OnReceiveContentCallback.Payload.Builder {
    ctor public OnReceiveContentCallback.Payload.Builder(@NonNull android.content.ClipData, int);
    method @NonNull public android.view.OnReceiveContentCallback.Payload build();
    method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setExtras(@Nullable android.os.Bundle);
    method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setFlags(int);
    method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setLinkUri(@Nullable android.net.Uri);
  public static final class OnReceiveContentListener.Payload.Builder {
    ctor public OnReceiveContentListener.Payload.Builder(@NonNull android.view.OnReceiveContentListener.Payload);
    ctor public OnReceiveContentListener.Payload.Builder(@NonNull android.content.ClipData, int);
    method @NonNull public android.view.OnReceiveContentListener.Payload build();
    method @NonNull public android.view.OnReceiveContentListener.Payload.Builder setClip(@NonNull android.content.ClipData);
    method @NonNull public android.view.OnReceiveContentListener.Payload.Builder setExtras(@Nullable android.os.Bundle);
    method @NonNull public android.view.OnReceiveContentListener.Payload.Builder setFlags(int);
    method @NonNull public android.view.OnReceiveContentListener.Payload.Builder setLinkUri(@Nullable android.net.Uri);
    method @NonNull public android.view.OnReceiveContentListener.Payload.Builder setSource(int);
  }
  public abstract class OrientationEventListener {
@@ -54633,7 +54637,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 @Nullable public android.view.OnReceiveContentListener.Payload onReceiveContent(@NonNull android.view.OnReceiveContentListener.Payload);
    method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
    method @CallSuper protected void onRestoreInstanceState(android.os.Parcelable);
    method public void onRtlPropertiesChanged(int);
@@ -54791,7 +54795,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 String[], @Nullable android.view.OnReceiveContentCallback);
    method public void setOnReceiveContentListener(@Nullable String[], @Nullable android.view.OnReceiveContentListener);
    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);
@@ -61808,11 +61812,6 @@ package android.widget {
    field @NonNull public static final android.os.Parcelable.Creator<android.widget.TextView.SavedState> CREATOR;
  }
  public class TextViewOnReceiveContentCallback implements android.view.OnReceiveContentCallback<android.widget.TextView> {
    ctor public TextViewOnReceiveContentCallback();
    method public boolean onReceiveContent(@NonNull android.widget.TextView, @NonNull android.view.OnReceiveContentCallback.Payload);
  }
  public interface ThemedSpinnerAdapter extends android.widget.SpinnerAdapter {
    method @Nullable public android.content.res.Resources.Theme getDropDownViewTheme();
    method public void setDropDownViewTheme(@Nullable android.content.res.Resources.Theme);
+15 −16
Original line number Diff line number Diff line
@@ -51965,16 +51965,17 @@ package android.view {
    field public int toolType;
  }
  public interface OnReceiveContentCallback<T extends android.view.View> {
    method public boolean onReceiveContent(@NonNull T, @NonNull android.view.OnReceiveContentCallback.Payload);
  public interface OnReceiveContentListener {
    method @Nullable public android.view.OnReceiveContentListener.Payload onReceiveContent(@NonNull android.view.View, @NonNull android.view.OnReceiveContentListener.Payload);
  }
  public static final class OnReceiveContentCallback.Payload {
  public static final class OnReceiveContentListener.Payload {
    method @NonNull public android.content.ClipData getClip();
    method @Nullable public android.os.Bundle getExtras();
    method public int getFlags();
    method @Nullable public android.net.Uri getLinkUri();
    method public int getSource();
    method @NonNull public java.util.Map<java.lang.Boolean,android.view.OnReceiveContentListener.Payload> partition(@NonNull java.util.function.Predicate<android.content.ClipData.Item>);
    field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
    field public static final int SOURCE_APP = 0; // 0x0
    field public static final int SOURCE_AUTOFILL = 4; // 0x4
@@ -51984,12 +51985,15 @@ package android.view {
    field public static final int SOURCE_PROCESS_TEXT = 5; // 0x5
  }
  public static final class OnReceiveContentCallback.Payload.Builder {
    ctor public OnReceiveContentCallback.Payload.Builder(@NonNull android.content.ClipData, int);
    method @NonNull public android.view.OnReceiveContentCallback.Payload build();
    method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setExtras(@Nullable android.os.Bundle);
    method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setFlags(int);
    method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setLinkUri(@Nullable android.net.Uri);
  public static final class OnReceiveContentListener.Payload.Builder {
    ctor public OnReceiveContentListener.Payload.Builder(@NonNull android.view.OnReceiveContentListener.Payload);
    ctor public OnReceiveContentListener.Payload.Builder(@NonNull android.content.ClipData, int);
    method @NonNull public android.view.OnReceiveContentListener.Payload build();
    method @NonNull public android.view.OnReceiveContentListener.Payload.Builder setClip(@NonNull android.content.ClipData);
    method @NonNull public android.view.OnReceiveContentListener.Payload.Builder setExtras(@Nullable android.os.Bundle);
    method @NonNull public android.view.OnReceiveContentListener.Payload.Builder setFlags(int);
    method @NonNull public android.view.OnReceiveContentListener.Payload.Builder setLinkUri(@Nullable android.net.Uri);
    method @NonNull public android.view.OnReceiveContentListener.Payload.Builder setSource(int);
  }
  public abstract class OrientationEventListener {
@@ -52738,7 +52742,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 @Nullable public android.view.OnReceiveContentListener.Payload onReceiveContent(@NonNull android.view.OnReceiveContentListener.Payload);
    method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int);
    method @CallSuper protected void onRestoreInstanceState(android.os.Parcelable);
    method public void onRtlPropertiesChanged(int);
@@ -52896,7 +52900,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 String[], @Nullable android.view.OnReceiveContentCallback);
    method public void setOnReceiveContentListener(@Nullable String[], @Nullable android.view.OnReceiveContentListener);
    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);
@@ -59913,11 +59917,6 @@ package android.widget {
    field @NonNull public static final android.os.Parcelable.Creator<android.widget.TextView.SavedState> CREATOR;
  }
  public class TextViewOnReceiveContentCallback implements android.view.OnReceiveContentCallback<android.widget.TextView> {
    ctor public TextViewOnReceiveContentCallback();
    method public boolean onReceiveContent(@NonNull android.widget.TextView, @NonNull android.view.OnReceiveContentCallback.Payload);
  }
  public interface ThemedSpinnerAdapter extends android.widget.SpinnerAdapter {
    method @Nullable public android.content.res.Resources.Theme getDropDownViewTheme();
    method public void setDropDownViewTheme(@Nullable android.content.res.Resources.Theme);
+130 −36
Original line number Diff line number Diff line
@@ -22,77 +22,91 @@ import android.annotation.Nullable;
import android.content.ClipData;
import android.net.Uri;
import android.os.Bundle;
import android.util.ArrayMap;

import com.android.internal.util.Preconditions;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;

/**
 * Callback for apps to implement handling for insertion of content. Content may be both text and
 * Listener 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}.
 * <p>This listener can be attached to different types of UI components using
 * {@link View#setOnReceiveContentListener}.
 *
 * <p>For editable {@link android.widget.TextView} components, implementations can extend from
 * {@link android.widget.TextViewOnReceiveContentCallback} to reuse default platform behavior for
 * handling text.
 *
 * <p>Example implementation:<br>
 * <p>Here is a sample implementation that handles content URIs and delegates the processing for
 * text and everything else to the platform:<br>
 * <pre class="prettyprint">
 * // (1) Define the callback
 * public class MyOnReceiveContentCallback implements OnReceiveContentCallback&lt;TextView&gt; {
 *     public static final Set&lt;String&gt; MIME_TYPES = Collections.unmodifiableSet(
 *         Set.of("image/*", "video/*"));
 * // (1) Define the listener
 * public class MyReceiver implements OnReceiveContentListener {
 *     public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"};
 *
 *     &#64;Override
 *     public boolean onReceiveContent(@NonNull TextView view, @NonNull Payload payload) {
 *         // ... app-specific logic to handle the content in the payload ...
 *     public Payload onReceiveContent(TextView view, Payload payload) {
 *         Map&lt;Boolean, Payload&gt; split = payload.partition(item -&gt; item.getUri() != null);
 *         if (split.get(true) != null) {
 *             ClipData clip = payload.getClip();
 *             for (int i = 0; i < clip.getItemCount(); i++) {
 *                 Uri uri = clip.getItemAt(i).getUri();
 *                 // ... app-specific logic to handle the URI ...
 *             }
 *         }
 *         // Return anything that we didn't handle ourselves. This preserves the default platform
 *         // behavior for text and anything else for which we are not implementing custom handling.
 *         return split.get(false);
 *     }
 * }
 *
 * // (2) Register the callback
 * // (2) Register the listener
 * 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());
 *         myInput.setOnReceiveContentListener(MyReceiver.MIME_TYPES, new MyReceiver());
 *     }
 * </pre>
 *
 * @param <T> The type of {@link View} with which this callback can be associated.
 */
public interface OnReceiveContentCallback<T extends View> {
public interface OnReceiveContentListener {
    /**
     * Receive the given content.
     *
     * <p>This method is only invoked for content whose MIME type matches a type specified via
     * {@link View#setOnReceiveContentCallback}.
     * <p>Implementations should handle any content items of interest and return all unhandled
     * items to preserve the default platform behavior for content that does not have app-specific
     * handling. For example, an implementation may provide handling for content URIs (to provide
     * support for inserting images, etc) and delegate the processing of text to the platform to
     * preserve the common behavior for inserting text. See the class javadoc for a sample
     * implementation and see {@link Payload#partition} for a convenient way to split the passed-in
     * content.
     *
     * <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>If implementing handling for text: if the view has a selection, the selection should
     * be overwritten by the passed-in content; if there's no selection, the passed-in content
     * should be inserted at the current cursor position.
     *
     * <p>For non-text content (e.g. an image), the content may be inserted inline, or it may be
     * added as an attachment (could potentially be shown in a completely separate view).
     * <p>If implementing handling for non-text content (e.g. images): the content may be
     * inserted inline, or it may be added as an attachment (could potentially be shown in a
     * completely separate view).
     *
     * @param view The view where the content insertion was requested.
     * @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.
     * @return The portion of the passed-in content whose processing should be delegated to
     * the platform. Return null if all content was handled in some way. Actual insertion of
     * the content may be processed asynchronously in the background and may or may not
     * succeed even if this method returns null. For example, an app may end up not inserting
     * an item if it exceeds the app's size limit for that type of content.
     */
    boolean onReceiveContent(@NonNull T view, @NonNull Payload payload);
    @Nullable Payload onReceiveContent(@NonNull View view, @NonNull Payload payload);

    /**
     * Holds all the relevant data for a request to {@link OnReceiveContentCallback}.
     * Holds all the relevant data for a request to {@link OnReceiveContentListener}.
     */
    final class Payload {

@@ -206,7 +220,7 @@ public interface OnReceiveContentCallback<T extends View> {
        @Override
        public String toString() {
            return "Payload{"
                    + "clip=" + mClip.getDescription()
                    + "clip=" + mClip
                    + ", source=" + sourceToString(mSource)
                    + ", flags=" + flagsToString(mFlags)
                    + ", linkUri=" + mLinkUri
@@ -255,16 +269,74 @@ public interface OnReceiveContentCallback<T extends View> {
            return mExtras;
        }

        /**
         * Partitions this payload based on the given predicate.
         *
         * <p>Similar to a
         * {@link java.util.stream.Collectors#partitioningBy(Predicate) partitioning collector},
         * this function classifies the content in this payload and organizes it into a map,
         * grouping the content that matched vs didn't match the predicate.
         *
         * <p>Except for the {@link ClipData} items, the returned payloads will contain all the same
         * metadata as the original payload.
         *
         * @param itemPredicate The predicate to test each {@link ClipData.Item} to determine which
         * partition to place it into.
         * @return A map containing the partitioned content. The map will contain a single entry if
         * all items were classified into the same partition (all matched or all didn't match the
         * predicate) or two entries (if there's at least one item that matched the predicate and at
         * least one item that didn't match the predicate).
         */
        public @NonNull Map<Boolean, Payload> partition(
                @NonNull Predicate<ClipData.Item> itemPredicate) {
            if (mClip.getItemCount() == 1) {
                Map<Boolean, Payload> result = new ArrayMap<>(1);
                result.put(itemPredicate.test(mClip.getItemAt(0)), this);
                return result;
            }
            ArrayList<ClipData.Item> accepted = new ArrayList<>();
            ArrayList<ClipData.Item> remaining = new ArrayList<>();
            for (int i = 0; i < mClip.getItemCount(); i++) {
                ClipData.Item item = mClip.getItemAt(i);
                if (itemPredicate.test(item)) {
                    accepted.add(item);
                } else {
                    remaining.add(item);
                }
            }
            Map<Boolean, Payload> result = new ArrayMap<>(2);
            if (!accepted.isEmpty()) {
                ClipData acceptedClip = new ClipData(mClip.getDescription(), accepted);
                result.put(true, new Builder(this).setClip(acceptedClip).build());
            }
            if (!remaining.isEmpty()) {
                ClipData remainingClip = new ClipData(mClip.getDescription(), remaining);
                result.put(false, new Builder(this).setClip(remainingClip).build());
            }
            return result;
        }

        /**
         * Builder for {@link Payload}.
         */
        public static final class Builder {
            @NonNull private final ClipData mClip;
            private final @Source int mSource;
            @NonNull private ClipData mClip;
            private @Source int mSource;
            private @Flags int mFlags;
            @Nullable private Uri mLinkUri;
            @Nullable private Bundle mExtras;

            /**
             * Creates a new builder initialized with the data from the given builder.
             */
            public Builder(@NonNull Payload payload) {
                mClip = payload.mClip;
                mSource = payload.mSource;
                mFlags = payload.mFlags;
                mLinkUri = payload.mLinkUri;
                mExtras = payload.mExtras;
            }

            /**
             * Creates a new builder.
             * @param clip   The data to insert.
@@ -275,6 +347,28 @@ public interface OnReceiveContentCallback<T extends View> {
                mSource = source;
            }

            /**
             * Sets the data to be inserted.
             * @param clip The data to insert.
             * @return this builder
             */
            @NonNull
            public Builder setClip(@NonNull ClipData clip) {
                mClip = clip;
                return this;
            }

            /**
             * Sets the source of the operation.
             * @param source The source of the operation. See {@code SOURCE_} constants.
             * @return this builder
             */
            @NonNull
            public Builder setSource(@Source int source) {
                mSource = source;
                return this;
            }

            /**
             * Sets flags that control content insertion behavior.
             * @param flags Optional flags to configure the insertion behavior. Use 0 for default
+68 −45

File changed.

Preview size limit exceeded, changes collapsed.

+6 −8
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ package android.view.autofill;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.view.OnReceiveContentCallback.Payload.SOURCE_AUTOFILL;
import static android.view.OnReceiveContentListener.Payload.SOURCE_AUTOFILL;
import static android.view.autofill.Helper.sDebug;
import static android.view.autofill.Helper.sVerbose;
import static android.view.autofill.Helper.toList;
@@ -62,7 +62,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.KeyEvent;
import android.view.OnReceiveContentCallback;
import android.view.OnReceiveContentListener.Payload;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -2371,12 +2371,10 @@ public final class AutofillManager {
                reportAutofillContentFailure(id);
                return;
            }
            OnReceiveContentCallback.Payload payload =
                    new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_AUTOFILL)
                            .build();
            boolean handled = view.onReceiveContent(payload);
            if (!handled) {
                Log.w(TAG, "autofillContent(): receiver returned false: id=" + id
            Payload payload = new Payload.Builder(clip, SOURCE_AUTOFILL).build();
            Payload result = view.onReceiveContent(payload);
            if (result != null) {
                Log.w(TAG, "autofillContent(): receiver could not insert content: id=" + id
                        + ", view=" + view + ", clip=" + clip);
                reportAutofillContentFailure(id);
                return;
Loading