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

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

Merge "Update RichContentReceiver API (now OnReceiveContentCallback)"

parents 511d1fb4 dd9869aa
Loading
Loading
Loading
Loading
+36 −14
Original line number Diff line number Diff line
@@ -53644,6 +53644,33 @@ package android.view {
    field public int toolType;
  }
  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);
  }
  public static final class OnReceiveContentCallback.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();
    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_CLIPBOARD = 0; // 0x0
    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_PROCESS_TEXT = 4; // 0x4
  }
  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 abstract class OrientationEventListener {
    ctor public OrientationEventListener(android.content.Context);
    ctor public OrientationEventListener(android.content.Context, int);
@@ -54195,6 +54222,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 @ColorInt public int getOutlineAmbientShadowColor();
    method public android.view.ViewOutlineProvider getOutlineProvider();
    method @ColorInt public int getOutlineSpotShadowColor();
@@ -54546,6 +54574,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 setOnScrollChangeListener(android.view.View.OnScrollChangeListener);
    method @Deprecated public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener);
    method public void setOnTouchListener(android.view.View.OnTouchListener);
@@ -60808,17 +60837,6 @@ package android.widget {
    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_CLIPBOARD = 0; // 0x0
    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_PROCESS_TEXT = 4; // 0x4
  }
  public class ScrollView extends android.widget.FrameLayout {
    ctor public ScrollView(android.content.Context);
    ctor public ScrollView(android.content.Context, android.util.AttributeSet);
@@ -61373,10 +61391,10 @@ 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();
    method @NonNull public android.widget.RichContentReceiver<android.widget.TextView> getRichContentReceiver();
    method public int getSelectionEnd();
    method public int getSelectionStart();
    method @ColorInt public int getShadowColor();
@@ -61504,7 +61522,6 @@ package android.widget {
    method public void setPaintFlags(int);
    method public void setPrivateImeOptions(String);
    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 setSelectAllOnFocus(boolean);
    method public void setShadowLayer(float, float, float, int);
@@ -61545,7 +61562,6 @@ package android.widget {
    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_UNIFORM = 1; // 0x1
    field @NonNull public static final android.widget.RichContentReceiver<android.widget.TextView> DEFAULT_RICH_CONTENT_RECEIVER;
  }
  public enum TextView.BufferType {
@@ -61562,6 +61578,12 @@ 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 @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);
  }
  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);
+5 −0
Original line number Diff line number Diff line
@@ -5581,6 +5581,11 @@ package android.widget {
    method public void disableClockTick();
  }

  @android.widget.RemoteViews.RemoteView public class TextView extends android.view.View implements android.view.ViewTreeObserver.OnPreDrawListener {
    method public void onActivityResult(int, int, @Nullable android.content.Intent);
    field public static final int PROCESS_TEXT_REQUEST_CODE = 100; // 0x64
  }

  public class TimePicker extends android.widget.FrameLayout {
    method public android.view.View getAmView();
    method public android.view.View getHourView();
+377 −0
Original line number 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.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;

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).
 *
 * <p>This callback can be attached to different types of UI components using
 * {@link View#setOnReceiveContentCallback}.
 *
 * <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>
 * <pre class="prettyprint">
 * public class MyOnReceiveContentCallback implements OnReceiveContentCallback&lt;TextView&gt; {
 *
 *     private static final Set&lt;String&gt; SUPPORTED_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 ...
 *     }
 * }
 * </pre>
 *
 * @param <T> The type of {@link View} with which this receiver 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>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 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).
     *
     * @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
     * 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}.
     */
    final class Payload {

        /**
         * Specifies the UI through which content is being inserted.
         *
         * @hide
         */
        @IntDef(prefix = {"SOURCE_"}, value = {SOURCE_CLIPBOARD, SOURCE_INPUT_METHOD,
                SOURCE_DRAG_AND_DROP, SOURCE_AUTOFILL, SOURCE_PROCESS_TEXT})
        @Retention(RetentionPolicy.SOURCE)
        public @interface Source {}

        /**
         * Specifies that the operation was triggered by a paste from the clipboard (e.g. "Paste" or
         * "Paste as plain text" action in the insertion/selection menu).
         */
        public static final int SOURCE_CLIPBOARD = 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.
         */
        public static final 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.
         */
        public static final 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.
         */
        public static final int SOURCE_AUTOFILL = 3;

        /**
         * Specifies that the operation was triggered by a result from a
         * {@link android.content.Intent#ACTION_PROCESS_TEXT PROCESS_TEXT} action in the selection
         * menu.
         */
        public static final int SOURCE_PROCESS_TEXT = 4;

        /**
         * Returns the symbolic name of the given source.
         *
         * @hide
         */
        static String sourceToString(@Source int source) {
            switch (source) {
                case SOURCE_CLIPBOARD: return "SOURCE_CLIPBOARD";
                case SOURCE_INPUT_METHOD: return "SOURCE_INPUT_METHOD";
                case SOURCE_DRAG_AND_DROP: return "SOURCE_DRAG_AND_DROP";
                case SOURCE_AUTOFILL: return "SOURCE_AUTOFILL";
                case SOURCE_PROCESS_TEXT: return "SOURCE_PROCESS_TEXT";
            }
            return String.valueOf(source);
        }

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

        /**
         * Flag requesting that the content should be converted to plain text prior to inserting.
         */
        public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1 << 0;

        /**
         * Returns the symbolic names of the set flags or {@code "0"} if no flags are set.
         *
         * @hide
         */
        static String flagsToString(@Flags int flags) {
            if ((flags & FLAG_CONVERT_TO_PLAIN_TEXT) != 0) {
                return "FLAG_CONVERT_TO_PLAIN_TEXT";
            }
            return String.valueOf(flags);
        }

        /**
         * The data to be inserted.
         */
        @NonNull private final ClipData mClip;

        /**
         * The source of the operation. See {@code SOURCE_} constants.
         */
        private final @Source int mSource;

        /**
         * Optional flags that control the insertion behavior. See {@code FLAG_} constants.
         */
        private final @Flags int mFlags;

        /**
         * Optional http/https URI for the content that may be provided by the IME. This is only
         * populated if the source is {@link #SOURCE_INPUT_METHOD} and if a non-empty
         * {@link android.view.inputmethod.InputContentInfo#getLinkUri linkUri} was passed by the
         * IME.
         */
        @Nullable
        private final Uri mLinkUri;

        /**
         * Optional additional metadata. If the source is {@link #SOURCE_INPUT_METHOD}, this will
         * include the {@link android.view.inputmethod.InputConnection#commitContent opts} passed by
         * the IME.
         */
        @Nullable
        private final Bundle mExtras;

        private Payload(Builder b) {
            this.mClip = Objects.requireNonNull(b.mClip);
            this.mSource = Preconditions.checkArgumentInRange(b.mSource, 0, SOURCE_PROCESS_TEXT,
                    "source");
            this.mFlags = Preconditions.checkFlagsArgument(b.mFlags, FLAG_CONVERT_TO_PLAIN_TEXT);
            this.mLinkUri = b.mLinkUri;
            this.mExtras = b.mExtras;
        }

        @NonNull
        @Override
        public String toString() {
            return "Payload{"
                    + "clip=" + mClip.getDescription()
                    + ", source=" + sourceToString(mSource)
                    + ", flags=" + flagsToString(mFlags)
                    + ", linkUri=" + mLinkUri
                    + ", extras=" + mExtras
                    + "}";
        }

        /**
         * The data to be inserted.
         */
        public @NonNull ClipData getClip() {
            return mClip;
        }

        /**
         * The source of the operation. See {@code SOURCE_} constants.
         */
        public @Source int getSource() {
            return mSource;
        }

        /**
         * Optional flags that control the insertion behavior. See {@code FLAG_} constants.
         */
        public @Flags int getFlags() {
            return mFlags;
        }

        /**
         * Optional http/https URI for the content that may be provided by the IME. This is only
         * populated if the source is {@link #SOURCE_INPUT_METHOD} and if a non-empty
         * {@link android.view.inputmethod.InputContentInfo#getLinkUri linkUri} was passed by the
         * IME.
         */
        public @Nullable Uri getLinkUri() {
            return mLinkUri;
        }

        /**
         * Optional additional metadata. If the source is {@link #SOURCE_INPUT_METHOD}, this will
         * include the {@link android.view.inputmethod.InputConnection#commitContent opts} passed by
         * the IME.
         */
        public @Nullable Bundle getExtras() {
            return mExtras;
        }

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

            /**
             * Creates a new builder.
             * @param clip   The data to insert.
             * @param source The source of the operation. See {@code SOURCE_} constants.
             */
            public Builder(@NonNull ClipData clip, @Source int source) {
                mClip = clip;
                mSource = source;
            }

            /**
             * Sets flags that control content insertion behavior.
             * @param flags Optional flags to configure the insertion behavior. Use 0 for default
             *              behavior. See {@code FLAG_} constants.
             * @return this builder
             */
            @NonNull
            public Builder setFlags(@Flags int flags) {
                mFlags = flags;
                return this;
            }

            /**
             * Sets the http/https URI for the content. See
             * {@link android.view.inputmethod.InputContentInfo#getLinkUri} for more info.
             * @param linkUri Optional http/https URI for the content.
             * @return this builder
             */
            @NonNull
            public Builder setLinkUri(@Nullable Uri linkUri) {
                mLinkUri = linkUri;
                return this;
            }

            /**
             * Sets additional metadata.
             * @param extras Optional bundle with additional metadata.
             * @return this builder
             */
            @NonNull
            public Builder setExtras(@Nullable Bundle extras) {
                mExtras = extras;
                return this;
            }

            /**
             * @return A new {@link Payload} instance with the data from this builder.
             */
            @NonNull
            public Payload build() {
                return new Payload(this);
            }
        }
    }
}
+33 −0
Original line number Diff line number Diff line
@@ -5243,6 +5243,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    @InputSourceClass
    int mUnbufferedInputSource = InputDevice.SOURCE_CLASS_NONE;
    @Nullable
    private OnReceiveContentCallback<? extends View> mOnReceiveContentCallback;
    /**
     * Simple constructor to use when creating a view from code.
     *
@@ -8997,6 +9000,36 @@ 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.
     *
     * @param callback The callback to use. This can be null to reset to the default behavior.
     */
    public void setOnReceiveContentCallback(
            @Nullable OnReceiveContentCallback<? extends View> callback) {
        mOnReceiveContentCallback = callback;
    }
    /**
     * Automatically fills the content of this view with the {@code value}.
     *
+38 −2
Original line number Diff line number Diff line
@@ -16,7 +16,10 @@

package android.view.inputmethod;

import static android.view.OnReceiveContentCallback.Payload.SOURCE_INPUT_METHOD;

import android.annotation.CallSuper;
import android.content.ClipData;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
@@ -34,6 +37,7 @@ import android.util.Log;
import android.util.LogPrinter;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.OnReceiveContentCallback;
import android.view.View;

class ComposingText implements NoCopySpan {
@@ -870,9 +874,41 @@ public class BaseInputConnection implements InputConnection {
    }

    /**
     * The default implementation does nothing.
     * 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.
     */
    public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
        @SuppressWarnings("unchecked") final OnReceiveContentCallback<View> receiver =
                (OnReceiveContentCallback<View>) mTargetView.getOnReceiveContentCallback();
        if (receiver == null) {
            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());
            }
            return false;
        }
        if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
            try {
                inputContentInfo.requestPermission();
            } catch (Exception e) {
                Log.w(TAG, "Can't insert content from IME; requestPermission() failed", e);
                return false;
            }
        }
        final ClipData clip = new ClipData(inputContentInfo.getDescription(),
                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);
    }
}
Loading