diff options
| author | Nikita Dubrovsky <dubrovsky@google.com> | 2020-02-07 14:38:23 -0800 |
|---|---|---|
| committer | Nikita Dubrovsky <dubrovsky@google.com> | 2020-03-31 15:15:36 -0700 |
| commit | 832edc3cc92584f7a41f84d471b6eeaadbeeccab (patch) | |
| tree | 9821a8a02998c85b1796e53020cc5c895cbffba8 /core/java/android/widget/RichContentReceiver.java | |
| parent | 53bedad0f1044b80289fbf54ad49f9ee796b6f51 (diff) | |
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
Diffstat (limited to 'core/java/android/widget/RichContentReceiver.java')
| -rw-r--r-- | core/java/android/widget/RichContentReceiver.java | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/core/java/android/widget/RichContentReceiver.java b/core/java/android/widget/RichContentReceiver.java new file mode 100644 index 000000000000..9fbe7ed5de9a --- /dev/null +++ b/core/java/android/widget/RichContentReceiver.java @@ -0,0 +1,200 @@ +/* + * 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<TextView> { + * + * private static final Set<String> SUPPORTED_MIME_TYPES = Collections.unmodifiableSet( + * Set.of("text/*", "image/gif", "image/png", "image/jpg")); + * + * @NonNull + * @Override + * public Set<String> getSupportedMimeTypes() { + * return SUPPORTED_MIME_TYPES; + * } + * + * @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; + } +} |
