• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SuppressLint;
23 import android.annotation.TestApi;
24 import android.content.ClipData;
25 import android.content.ClipDescription;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.util.Pair;
31 import android.view.inputmethod.InputContentInfo;
32 
33 import com.android.internal.util.Preconditions;
34 
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.util.ArrayList;
38 import java.util.Objects;
39 import java.util.function.Predicate;
40 
41 /**
42  * Holds all the relevant data for a request to {@link View#performReceiveContent}.
43  */
44 public final class ContentInfo implements Parcelable {
45 
46     /**
47      * Specifies the UI through which content is being inserted. Future versions of Android may
48      * support additional values.
49      *
50      * @hide
51      */
52     @IntDef(prefix = {"SOURCE_"}, value = {SOURCE_APP, SOURCE_CLIPBOARD, SOURCE_INPUT_METHOD,
53             SOURCE_DRAG_AND_DROP, SOURCE_AUTOFILL, SOURCE_PROCESS_TEXT})
54     @Retention(RetentionPolicy.SOURCE)
55     public @interface Source {}
56 
57     /**
58      * Specifies that the operation was triggered by the app that contains the target view.
59      */
60     public static final int SOURCE_APP = 0;
61 
62     /**
63      * Specifies that the operation was triggered by a paste from the clipboard (e.g. "Paste" or
64      * "Paste as plain text" action in the insertion/selection menu).
65      */
66     public static final int SOURCE_CLIPBOARD = 1;
67 
68     /**
69      * Specifies that the operation was triggered from the soft keyboard (also known as input
70      * method editor or IME). See https://developer.android.com/guide/topics/text/image-keyboard
71      * for more info.
72      */
73     public static final int SOURCE_INPUT_METHOD = 2;
74 
75     /**
76      * Specifies that the operation was triggered by the drag/drop framework. See
77      * https://developer.android.com/guide/topics/ui/drag-drop for more info.
78      */
79     public static final int SOURCE_DRAG_AND_DROP = 3;
80 
81     /**
82      * Specifies that the operation was triggered by the autofill framework. See
83      * https://developer.android.com/guide/topics/text/autofill for more info.
84      */
85     public static final int SOURCE_AUTOFILL = 4;
86 
87     /**
88      * Specifies that the operation was triggered by a result from a
89      * {@link android.content.Intent#ACTION_PROCESS_TEXT PROCESS_TEXT} action in the selection
90      * menu.
91      */
92     public static final int SOURCE_PROCESS_TEXT = 5;
93 
94     /**
95      * Returns the symbolic name of the given source.
96      *
97      * @hide
98      */
sourceToString(@ource int source)99     static String sourceToString(@Source int source) {
100         switch (source) {
101             case SOURCE_APP: return "SOURCE_APP";
102             case SOURCE_CLIPBOARD: return "SOURCE_CLIPBOARD";
103             case SOURCE_INPUT_METHOD: return "SOURCE_INPUT_METHOD";
104             case SOURCE_DRAG_AND_DROP: return "SOURCE_DRAG_AND_DROP";
105             case SOURCE_AUTOFILL: return "SOURCE_AUTOFILL";
106             case SOURCE_PROCESS_TEXT: return "SOURCE_PROCESS_TEXT";
107         }
108         return String.valueOf(source);
109     }
110 
111     /**
112      * Flags to configure the insertion behavior.
113      *
114      * @hide
115      */
116     @IntDef(flag = true, prefix = {"FLAG_"}, value = {FLAG_CONVERT_TO_PLAIN_TEXT})
117     @Retention(RetentionPolicy.SOURCE)
118     public @interface Flags {}
119 
120     /**
121      * Flag requesting that the content should be converted to plain text prior to inserting.
122      */
123     public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1 << 0;
124 
125     /**
126      * Returns the symbolic names of the set flags or {@code "0"} if no flags are set.
127      *
128      * @hide
129      */
flagsToString(@lags int flags)130     static String flagsToString(@Flags int flags) {
131         if ((flags & FLAG_CONVERT_TO_PLAIN_TEXT) != 0) {
132             return "FLAG_CONVERT_TO_PLAIN_TEXT";
133         }
134         return String.valueOf(flags);
135     }
136 
137     @NonNull
138     private final ClipData mClip;
139     @Source
140     private final int mSource;
141     @Flags
142     private final int mFlags;
143     @Nullable
144     private final Uri mLinkUri;
145     @Nullable
146     private final Bundle mExtras;
147     @Nullable
148     private final InputContentInfo mInputContentInfo;
149     @Nullable
150     private final DragAndDropPermissions mDragAndDropPermissions;
151 
ContentInfo(Builder b)152     private ContentInfo(Builder b) {
153         this.mClip = Objects.requireNonNull(b.mClip);
154         this.mSource = Preconditions.checkArgumentInRange(b.mSource, 0, SOURCE_PROCESS_TEXT,
155                 "source");
156         this.mFlags = Preconditions.checkFlagsArgument(b.mFlags, FLAG_CONVERT_TO_PLAIN_TEXT);
157         this.mLinkUri = b.mLinkUri;
158         this.mExtras = b.mExtras;
159         this.mInputContentInfo = b.mInputContentInfo;
160         this.mDragAndDropPermissions = b.mDragAndDropPermissions;
161     }
162 
163     /**
164      * If the content came from a source that supports proactive release of URI permissions
165      * (e.g. IME), releases permissions; otherwise a no-op.
166      *
167      * @hide
168      */
169     @TestApi
releasePermissions()170     public void releasePermissions() {
171         if (mInputContentInfo != null) {
172             mInputContentInfo.releasePermission();
173         }
174         if (mDragAndDropPermissions != null) {
175             mDragAndDropPermissions.release();
176         }
177     }
178 
179     @NonNull
180     @Override
toString()181     public String toString() {
182         return "ContentInfo{"
183                 + "clip=" + mClip
184                 + ", source=" + sourceToString(mSource)
185                 + ", flags=" + flagsToString(mFlags)
186                 + ", linkUri=" + mLinkUri
187                 + ", extras=" + mExtras
188                 + "}";
189     }
190 
191     /**
192      * The data to be inserted.
193      */
194     @NonNull
getClip()195     public ClipData getClip() {
196         return mClip;
197     }
198 
199     /**
200      * The source of the operation. See {@code SOURCE_} constants. Future versions of Android
201      * may pass additional values.
202      */
203     @Source
getSource()204     public int getSource() {
205         return mSource;
206     }
207 
208     /**
209      * Optional flags that control the insertion behavior. See {@code FLAG_} constants.
210      */
211     @Flags
getFlags()212     public int getFlags() {
213         return mFlags;
214     }
215 
216     /**
217      * Optional http/https URI for the content that may be provided by the IME. This is only
218      * populated if the source is {@link #SOURCE_INPUT_METHOD} and if a non-empty
219      * {@link android.view.inputmethod.InputContentInfo#getLinkUri linkUri} was passed by the
220      * IME.
221      */
222     @Nullable
getLinkUri()223     public Uri getLinkUri() {
224         return mLinkUri;
225     }
226 
227     /**
228      * Optional additional metadata. If the source is {@link #SOURCE_INPUT_METHOD}, this will
229      * include the {@link android.view.inputmethod.InputConnection#commitContent opts} passed by
230      * the IME.
231      */
232     @Nullable
233     @SuppressLint("NullableCollection")
getExtras()234     public Bundle getExtras() {
235         return mExtras;
236     }
237 
238     /**
239      * Partitions this content based on the given predicate.
240      *
241      * <p>This function classifies the content and organizes it into a pair, grouping the items
242      * that matched vs didn't match the predicate.
243      *
244      * <p>Except for the {@link ClipData} items, the returned objects will contain all the same
245      * metadata as this {@link ContentInfo}.
246      *
247      * @param itemPredicate The predicate to test each {@link ClipData.Item} to determine which
248      *                      partition to place it into.
249      * @return A pair containing the partitioned content. The pair's first object will have the
250      * content that matched the predicate, or null if none of the items matched. The pair's
251      * second object will have the content that didn't match the predicate, or null if all of
252      * the items matched.
253      *
254      * @hide
255      */
256     @TestApi
257     @NonNull
partition( @onNull Predicate<ClipData.Item> itemPredicate)258     public Pair<ContentInfo, ContentInfo> partition(
259             @NonNull Predicate<ClipData.Item> itemPredicate) {
260         if (mClip.getItemCount() == 1) {
261             boolean matched = itemPredicate.test(mClip.getItemAt(0));
262             return Pair.create(matched ? this : null, matched ? null : this);
263         }
264         ArrayList<ClipData.Item> acceptedItems = new ArrayList<>();
265         ArrayList<ClipData.Item> remainingItems = new ArrayList<>();
266         for (int i = 0; i < mClip.getItemCount(); i++) {
267             ClipData.Item item = mClip.getItemAt(i);
268             if (itemPredicate.test(item)) {
269                 acceptedItems.add(item);
270             } else {
271                 remainingItems.add(item);
272             }
273         }
274         if (acceptedItems.isEmpty()) {
275             return Pair.create(null, this);
276         }
277         if (remainingItems.isEmpty()) {
278             return Pair.create(this, null);
279         }
280         ContentInfo accepted = new Builder(this)
281                 .setClip(new ClipData(new ClipDescription(mClip.getDescription()), acceptedItems))
282                 .build();
283         ContentInfo remaining = new Builder(this)
284                 .setClip(new ClipData(new ClipDescription(mClip.getDescription()), remainingItems))
285                 .build();
286         return Pair.create(accepted, remaining);
287     }
288 
289     /**
290      * Builder for {@link ContentInfo}.
291      */
292     public static final class Builder {
293         @NonNull
294         private ClipData mClip;
295         @Source
296         private int mSource;
297         @Flags
298         private  int mFlags;
299         @Nullable
300         private Uri mLinkUri;
301         @Nullable
302         private Bundle mExtras;
303         @Nullable
304         private InputContentInfo mInputContentInfo;
305         @Nullable
306         private DragAndDropPermissions mDragAndDropPermissions;
307 
308         /**
309          * Creates a new builder initialized with the data from the given builder.
310          */
Builder(@onNull ContentInfo other)311         public Builder(@NonNull ContentInfo other) {
312             mClip = other.mClip;
313             mSource = other.mSource;
314             mFlags = other.mFlags;
315             mLinkUri = other.mLinkUri;
316             mExtras = other.mExtras;
317             mInputContentInfo = other.mInputContentInfo;
318             mDragAndDropPermissions = other.mDragAndDropPermissions;
319         }
320 
321         /**
322          * Creates a new builder.
323          * @param clip   The data to insert.
324          * @param source The source of the operation. See {@code SOURCE_} constants.
325          */
Builder(@onNull ClipData clip, @Source int source)326         public Builder(@NonNull ClipData clip, @Source int source) {
327             mClip = clip;
328             mSource = source;
329         }
330 
331         /**
332          * Sets the data to be inserted.
333          * @param clip The data to insert.
334          * @return this builder
335          */
336         @NonNull
setClip(@onNull ClipData clip)337         public Builder setClip(@NonNull ClipData clip) {
338             mClip = clip;
339             return this;
340         }
341 
342         /**
343          * Sets the source of the operation.
344          * @param source The source of the operation. See {@code SOURCE_} constants.
345          * @return this builder
346          */
347         @NonNull
setSource(@ource int source)348         public Builder setSource(@Source int source) {
349             mSource = source;
350             return this;
351         }
352 
353         /**
354          * Sets flags that control content insertion behavior.
355          * @param flags Optional flags to configure the insertion behavior. Use 0 for default
356          *              behavior. See {@code FLAG_} constants.
357          * @return this builder
358          */
359         @NonNull
setFlags(@lags int flags)360         public Builder setFlags(@Flags int flags) {
361             mFlags = flags;
362             return this;
363         }
364 
365         /**
366          * Sets the http/https URI for the content. See
367          * {@link android.view.inputmethod.InputContentInfo#getLinkUri} for more info.
368          * @param linkUri Optional http/https URI for the content.
369          * @return this builder
370          */
371         @NonNull
setLinkUri(@ullable Uri linkUri)372         public Builder setLinkUri(@Nullable Uri linkUri) {
373             mLinkUri = linkUri;
374             return this;
375         }
376 
377         /**
378          * Sets additional metadata.
379          * @param extras Optional bundle with additional metadata.
380          * @return this builder
381          */
382         @NonNull
setExtras(@uppressLint"NullableCollection") @ullable Bundle extras)383         public Builder setExtras(@SuppressLint("NullableCollection") @Nullable Bundle extras) {
384             mExtras = extras;
385             return this;
386         }
387 
388         /**
389          * Set the {@link InputContentInfo} object if the content is coming from the IME. This can
390          * be used for proactive cleanup of permissions.
391          *
392          * @hide
393          */
394         @TestApi
395         @SuppressLint("MissingGetterMatchingBuilder")
396         @NonNull
setInputContentInfo(@ullable InputContentInfo inputContentInfo)397         public Builder setInputContentInfo(@Nullable InputContentInfo inputContentInfo) {
398             mInputContentInfo = inputContentInfo;
399             return this;
400         }
401 
402         /**
403          * Set the {@link DragAndDropPermissions} object if the content is coming via drag-and-drop.
404          * This can be used for proactive cleanup of permissions.
405          *
406          * @hide
407          */
408         @TestApi
409         @SuppressLint("MissingGetterMatchingBuilder")
410         @NonNull
setDragAndDropPermissions(@ullable DragAndDropPermissions permissions)411         public Builder setDragAndDropPermissions(@Nullable DragAndDropPermissions permissions) {
412             mDragAndDropPermissions = permissions;
413             return this;
414         }
415 
416 
417         /**
418          * @return A new {@link ContentInfo} instance with the data from this builder.
419          */
420         @NonNull
build()421         public ContentInfo build() {
422             return new ContentInfo(this);
423         }
424     }
425 
426     /**
427      * {@inheritDoc}
428      */
429     @Override
describeContents()430     public int describeContents() {
431         return 0;
432     }
433 
434     /**
435      * Writes this object into the given parcel.
436      *
437      * @param dest  The parcel to write into.
438      * @param flags The flags to use for parceling.
439      */
440     @Override
writeToParcel(@onNull Parcel dest, int flags)441     public void writeToParcel(@NonNull Parcel dest, int flags) {
442         mClip.writeToParcel(dest, flags);
443         dest.writeInt(mSource);
444         dest.writeInt(mFlags);
445         Uri.writeToParcel(dest, mLinkUri);
446         dest.writeBundle(mExtras);
447         if (mInputContentInfo == null) {
448             dest.writeInt(0);
449         } else {
450             dest.writeInt(1);
451             mInputContentInfo.writeToParcel(dest, flags);
452         }
453         if (mDragAndDropPermissions == null) {
454             dest.writeInt(0);
455         } else {
456             dest.writeInt(1);
457             mDragAndDropPermissions.writeToParcel(dest, flags);
458         }
459     }
460 
461     /**
462      * Creates {@link ContentInfo} instances from parcels.
463      */
464     @NonNull
465     public static final Parcelable.Creator<ContentInfo> CREATOR =
466             new Parcelable.Creator<ContentInfo>() {
467         @Override
468         public ContentInfo createFromParcel(Parcel parcel) {
469             ClipData clip = ClipData.CREATOR.createFromParcel(parcel);
470             int source = parcel.readInt();
471             int flags = parcel.readInt();
472             Uri linkUri = Uri.CREATOR.createFromParcel(parcel);
473             Bundle extras = parcel.readBundle();
474             InputContentInfo inputContentInfo = null;
475             if (parcel.readInt() != 0) {
476                 inputContentInfo = InputContentInfo.CREATOR.createFromParcel(parcel);
477             }
478             DragAndDropPermissions dragAndDropPermissions = null;
479             if (parcel.readInt() != 0) {
480                 dragAndDropPermissions = DragAndDropPermissions.CREATOR.createFromParcel(parcel);
481             }
482             return new ContentInfo.Builder(clip, source)
483                     .setFlags(flags)
484                     .setLinkUri(linkUri)
485                     .setExtras(extras)
486                     .setInputContentInfo(inputContentInfo)
487                     .setDragAndDropPermissions(dragAndDropPermissions)
488                     .build();
489         }
490 
491         @Override
492         public ContentInfo[] newArray(int size) {
493             return new ContentInfo[size];
494         }
495     };
496 }
497