diff options
| -rw-r--r-- | api/current.txt | 8 | ||||
| -rw-r--r-- | core/java/android/content/ClipDescription.java | 19 | ||||
| -rw-r--r-- | core/java/android/view/OnReceiveContentCallback.java | 81 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 84 | ||||
| -rw-r--r-- | core/java/android/view/inputmethod/BaseInputConnection.java | 28 | ||||
| -rw-r--r-- | core/java/android/widget/TextView.java | 68 | ||||
| -rw-r--r-- | core/java/android/widget/TextViewOnReceiveContentCallback.java | 37 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/widget/TextViewOnReceiveContentCallbackTest.java | 6 | ||||
| -rw-r--r-- | non-updatable-api/current.txt | 8 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java | 61 |
10 files changed, 211 insertions, 189 deletions
diff --git a/api/current.txt b/api/current.txt index b35940449f9e..1807283f1ed2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); } diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java index 38ab8aa2dcbc..f9e6308515cf 100644 --- a/core/java/android/content/ClipDescription.java +++ b/core/java/android/content/ClipDescription.java @@ -16,6 +16,7 @@ package android.content; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; @@ -217,6 +218,24 @@ public class ClipDescription implements Parcelable { } /** + * 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. * diff --git a/core/java/android/view/OnReceiveContentCallback.java b/core/java/android/view/OnReceiveContentCallback.java index a217ff642ab7..d74938c1d1fd 100644 --- a/core/java/android/view/OnReceiveContentCallback.java +++ b/core/java/android/view/OnReceiveContentCallback.java @@ -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<TextView> { - * - * private static final Set<String> SUPPORTED_MIME_TYPES = Collections.unmodifiableSet( + * public static final Set<String> MIME_TYPES = Collections.unmodifiableSet( * Set.of("image/*", "video/*")); * - * @NonNull - * @Override - * public Set<String> getSupportedMimeTypes() { - * return SUPPORTED_MIME_TYPES; - * } - * * @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 { + * @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,54 +84,14 @@ 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}. */ final class Payload { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index bda368ecb3a2..ac628e145eee 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -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. @@ -9001,36 +9005,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}. * * <p>Views support the Autofill Framework mainly by: diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index e0711132f459..093dfb4e196e 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -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); } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3fc0f4efd608..5280a48596b4 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8747,12 +8747,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.initialSelEnd = getSelectionEnd(); outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); outAttrs.setInitialSurroundingText(mText); - // If a custom `OnReceiveContentCallback` is set, pass its supported MIME types. - OnReceiveContentCallback<TextView> receiver = getOnReceiveContentCallback(); - if (receiver != null) { - outAttrs.contentMimeTypes = receiver.getSupportedMimeTypes(this) - .toArray(new String[0]); - } + outAttrs.contentMimeTypes = getOnReceiveContentMimeTypes(); return ic; } } @@ -13735,20 +13730,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * 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}. - */ - @SuppressWarnings("unchecked") - @Nullable - @Override - public OnReceiveContentCallback<TextView> getOnReceiveContentCallback() { - return (OnReceiveContentCallback<TextView>) super.getOnReceiveContentCallback(); - } - - /** * Sets the callback to handle insertion of content into this view. * * <p>This callback will be invoked for the following scenarios: @@ -13761,32 +13742,51 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * <li>{@link Intent#ACTION_PROCESS_TEXT} replacement * </ol> * - * <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. */ + @SuppressWarnings("rawtypes") @Override public void setOnReceiveContentCallback( - @Nullable OnReceiveContentCallback<? extends View> callback) { - super.setOnReceiveContentCallback(callback); + @Nullable String[] mimeTypes, + @Nullable OnReceiveContentCallback callback) { + super.setOnReceiveContentCallback(mimeTypes, callback); } /** - * Handles the request to insert content using the configured callback or the default 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), executes the default platform handling + * (e.g. coerces content to text if the source is + * {@link OnReceiveContentCallback.Payload#SOURCE_CLIPBOARD} and this is an editable + * {@link TextView}). * - * @hide + * @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. */ - void onReceiveContent(@NonNull OnReceiveContentCallback.Payload payload) { - OnReceiveContentCallback<TextView> receiver = getOnReceiveContentCallback(); - ClipDescription description = payload.getClip().getDescription(); - if (receiver != null && receiver.supports(this, description)) { - receiver.onReceiveContent(this, payload); + @Override + public boolean onReceiveContent(@NonNull OnReceiveContentCallback.Payload payload) { + if (super.onReceiveContent(payload)) { + return true; } else if (mEditor != null) { - mEditor.getDefaultOnReceiveContentCallback().onReceiveContent(this, payload); + return mEditor.getDefaultOnReceiveContentCallback().onReceiveContent(this, payload); } + return false; } private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { diff --git a/core/java/android/widget/TextViewOnReceiveContentCallback.java b/core/java/android/widget/TextViewOnReceiveContentCallback.java index d7c95b7eae86..7ed70ec18a7b 100644 --- a/core/java/android/widget/TextViewOnReceiveContentCallback.java +++ b/core/java/android/widget/TextViewOnReceiveContentCallback.java @@ -20,12 +20,12 @@ import static android.content.ContentResolver.SCHEME_CONTENT; import static android.view.OnReceiveContentCallback.Payload.FLAG_CONVERT_TO_PLAIN_TEXT; import static android.view.OnReceiveContentCallback.Payload.SOURCE_AUTOFILL; import static android.view.OnReceiveContentCallback.Payload.SOURCE_DRAG_AND_DROP; +import static android.view.OnReceiveContentCallback.Payload.SOURCE_INPUT_METHOD; import static java.util.Collections.singleton; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; @@ -72,16 +72,6 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac @Nullable private InputConnectionInfo mInputConnectionInfo; @Nullable private ArraySet<String> mCachedSupportedMimeTypes; - @SuppressLint("CallbackMethodName") - @NonNull - @Override - public Set<String> getSupportedMimeTypes(@NonNull TextView view) { - if (!isUsageOfImeCommitContentEnabled(view)) { - return MIME_TYPES_ALL_TEXT; - } - return getSupportedMimeTypesAugmentedWithImeCommitContentMimeTypes(); - } - @Override public boolean onReceiveContent(@NonNull TextView view, @NonNull Payload payload) { if (Log.isLoggable(LOG_TAG, Log.DEBUG)) { @@ -90,6 +80,11 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac ClipData clip = payload.getClip(); @Source int source = payload.getSource(); @Flags int flags = payload.getFlags(); + if (source == SOURCE_INPUT_METHOD) { + // InputConnection.commitContent() should only be used for non-text input which is not + // supported by the default implementation. + return false; + } if (source == SOURCE_AUTOFILL) { return onReceiveForAutofill(view, clip, flags); } @@ -123,7 +118,7 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac } } } - return true; + return didFirst; } private static void replaceSelection(@NonNull Editable editable, @@ -160,7 +155,7 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac @NonNull ClipData clip, @Flags int flags) { final CharSequence text = coerceToText(clip, textView.getContext(), flags); if (text.length() == 0) { - return true; + return false; } replaceSelection((Editable) textView.getText(), text); return true; @@ -205,7 +200,7 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac * non-text content. */ private static boolean isUsageOfImeCommitContentEnabled(@NonNull View view) { - if (view.getOnReceiveContentCallback() != null) { + if (view.getOnReceiveContentMimeTypes() != null) { if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) { Log.v(LOG_TAG, "Fallback to commitContent disabled (custom callback is set)"); } @@ -267,6 +262,17 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac mInputConnectionInfo = null; } + // TODO(b/168253885): Use this to populate the assist structure for Autofill + + /** @hide */ + @VisibleForTesting + public Set<String> getMimeTypes(TextView view) { + if (!isUsageOfImeCommitContentEnabled(view)) { + return MIME_TYPES_ALL_TEXT; + } + return getSupportedMimeTypesAugmentedWithImeCommitContentMimeTypes(); + } + private Set<String> getSupportedMimeTypesAugmentedWithImeCommitContentMimeTypes() { InputConnectionInfo icInfo = mInputConnectionInfo; if (icInfo == null) { @@ -291,7 +297,8 @@ public class TextViewOnReceiveContentCallback implements OnReceiveContentCallbac } /** - * We want to avoid creating a new set on every invocation of {@link #getSupportedMimeTypes}. + * We want to avoid creating a new set on every invocation of + * {@link #getSupportedMimeTypesAugmentedWithImeCommitContentMimeTypes()}. * This method will check if the cached set of MIME types matches the data in the given array * from {@link EditorInfo} or if a new set should be created. The custom logic is needed for * comparing the data because the set contains the additional "text/*" MIME type. diff --git a/core/tests/coretests/src/android/widget/TextViewOnReceiveContentCallbackTest.java b/core/tests/coretests/src/android/widget/TextViewOnReceiveContentCallbackTest.java index 5112326ea0b6..ef659af6c570 100644 --- a/core/tests/coretests/src/android/widget/TextViewOnReceiveContentCallbackTest.java +++ b/core/tests/coretests/src/android/widget/TextViewOnReceiveContentCallbackTest.java @@ -66,8 +66,8 @@ import org.mockito.Mockito; * {@link android.widget.cts.TextViewOnReceiveContentCallbackTest}. This class tests some internal * implementation details, e.g. fallback to the keyboard image API. */ -@RunWith(AndroidJUnit4.class) @MediumTest +@RunWith(AndroidJUnit4.class) public class TextViewOnReceiveContentCallbackTest { private static final Uri SAMPLE_CONTENT_URI = Uri.parse("content://com.example/path"); @@ -101,7 +101,7 @@ public class TextViewOnReceiveContentCallbackTest { // Assert that the callback returns the MIME types declared in the EditorInfo in addition to // the default. - assertThat(mDefaultCallback.getSupportedMimeTypes(mEditText)).containsExactly( + assertThat(mDefaultCallback.getMimeTypes(mEditText)).containsExactly( "text/*", "image/gif", "image/png"); } @@ -118,7 +118,7 @@ public class TextViewOnReceiveContentCallbackTest { onView(withId(mEditText.getId())).perform(clickOnTextAtIndex(0)); // Assert that the callback returns the default MIME types. - assertThat(mDefaultCallback.getSupportedMimeTypes(mEditText)).containsExactly("text/*"); + assertThat(mDefaultCallback.getMimeTypes(mEditText)).containsExactly("text/*"); } @Test diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt index 8adedf5779d1..262e836d20e0 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -51897,7 +51897,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); } @@ -52475,7 +52474,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(); @@ -52670,6 +52669,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); @@ -52827,7 +52827,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); @@ -59657,7 +59657,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(); @@ -59846,7 +59845,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); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index c84589a9e142..4552026ced4b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -73,9 +73,7 @@ import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewW import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.LightBarController; -import java.util.Collections; import java.util.HashMap; -import java.util.Set; import java.util.function.Consumer; /** @@ -315,7 +313,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mRemoteInputs = remoteInputs; mRemoteInput = remoteInput; mEditText.setHint(mRemoteInput.getLabel()); - mEditText.mSupportedMimeTypes = remoteInput.getAllowedDataTypes(); + mEditText.mSupportedMimeTypes = (remoteInput.getAllowedDataTypes() == null) ? null + : remoteInput.getAllowedDataTypes().toArray(new String[0]); mEntry.editedSuggestionInfo = editedSuggestionInfo; if (editedSuggestionInfo != null) { @@ -574,7 +573,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene boolean mShowImeOnInputConnection; private LightBarController mLightBarController; UserHandle mUser; - private Set<String> mSupportedMimeTypes; + private String[] mSupportedMimeTypes; public RemoteEditText(Context context, AttributeSet attrs) { super(context, attrs); @@ -585,35 +584,31 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene @Override protected void onFinishInflate() { super.onFinishInflate(); - setOnReceiveContentCallback(new OnReceiveContentCallback<View>() { - @Override - public boolean onReceiveContent(@NonNull View view, @NonNull Payload payload) { - ClipData clip = payload.getClip(); - if (clip.getItemCount() == 0) { - return false; - } - Uri contentUri = clip.getItemAt(0).getUri(); - ClipDescription description = clip.getDescription(); - String mimeType = null; - if (description.getMimeTypeCount() > 0) { - mimeType = description.getMimeType(0); - } - if (mimeType != null) { - Intent dataIntent = mRemoteInputView - .prepareRemoteInputFromData(mimeType, contentUri); - mRemoteInputView.sendRemoteInput(dataIntent); - } - return true; - } - - @NonNull - @Override - public Set<String> getSupportedMimeTypes(@NonNull View view) { - return mSupportedMimeTypes != null - ? mSupportedMimeTypes - : Collections.emptySet(); - } - }); + if (mSupportedMimeTypes != null && mSupportedMimeTypes.length > 0) { + setOnReceiveContentCallback(mSupportedMimeTypes, + new OnReceiveContentCallback<View>() { + @Override + public boolean onReceiveContent(@NonNull View view, + @NonNull Payload payload) { + ClipData clip = payload.getClip(); + if (clip.getItemCount() == 0) { + return false; + } + Uri contentUri = clip.getItemAt(0).getUri(); + ClipDescription description = clip.getDescription(); + String mimeType = null; + if (description.getMimeTypeCount() > 0) { + mimeType = description.getMimeType(0); + } + if (mimeType != null) { + Intent dataIntent = mRemoteInputView + .prepareRemoteInputFromData(mimeType, contentUri); + mRemoteInputView.sendRemoteInput(dataIntent); + } + return true; + } + }); + } } private void defocusIfNeeded(boolean animate) { |