1 /*
2  * Copyright (C) 2014 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 androidx.core.app;
18 
19 import android.content.ClipData;
20 import android.content.ClipDescription;
21 import android.content.Intent;
22 import android.net.Uri;
23 import android.os.Build;
24 import android.os.Bundle;
25 
26 import androidx.annotation.IntDef;
27 import androidx.annotation.RequiresApi;
28 import androidx.annotation.RestrictTo;
29 
30 import org.jspecify.annotations.NonNull;
31 import org.jspecify.annotations.Nullable;
32 
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.Map;
38 import java.util.Set;
39 
40 /**
41  * Helper for using the {@link android.app.RemoteInput}.
42  */
43 public final class RemoteInput {
44 
45     /** Label used to denote the clip data type used for remote input transport */
46     public static final String RESULTS_CLIP_LABEL = "android.remoteinput.results";
47 
48     /** Extra added to a clip data intent object to hold the text results bundle. */
49     public static final String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
50 
51     /** Extra added to a clip data intent object to hold the data results bundle. */
52     private static final String EXTRA_DATA_TYPE_RESULTS_DATA =
53             "android.remoteinput.dataTypeResultsData";
54 
55     /** Extra added to a clip data intent object identifying the {@link Source} of the results. */
56     private static final String EXTRA_RESULTS_SOURCE = "android.remoteinput.resultsSource";
57 
58     @IntDef({SOURCE_FREE_FORM_INPUT, SOURCE_CHOICE})
59     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
60     @Retention(RetentionPolicy.SOURCE)
61     public @interface Source {}
62 
63     /** The user manually entered the data. */
64     public static final int SOURCE_FREE_FORM_INPUT = 0;
65 
66     /** The user selected one of the choices from {@link #getChoices}. */
67     public static final int SOURCE_CHOICE = 1;
68 
69     @IntDef(value = {EDIT_CHOICES_BEFORE_SENDING_AUTO, EDIT_CHOICES_BEFORE_SENDING_DISABLED,
70             EDIT_CHOICES_BEFORE_SENDING_ENABLED})
71     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
72     @Retention(RetentionPolicy.SOURCE)
73     public @interface EditChoicesBeforeSending {}
74 
75     /** The platform will determine whether choices will be edited before being sent to the app. */
76     public static final int EDIT_CHOICES_BEFORE_SENDING_AUTO = 0;
77 
78     /** Tapping on a choice should send the input immediately, without letting the user edit it. */
79     public static final int EDIT_CHOICES_BEFORE_SENDING_DISABLED = 1;
80 
81     /** Tapping on a choice should let the user edit the input before it is sent to the app. */
82     public static final int EDIT_CHOICES_BEFORE_SENDING_ENABLED = 2;
83 
84     private final String mResultKey;
85     private final CharSequence mLabel;
86     private final CharSequence[] mChoices;
87     private final boolean mAllowFreeFormTextInput;
88     @EditChoicesBeforeSending private final int mEditChoicesBeforeSending;
89     private final Bundle mExtras;
90     private final Set<String> mAllowedDataTypes;
91 
RemoteInput(String resultKey, CharSequence label, CharSequence[] choices, boolean allowFreeFormTextInput, @EditChoicesBeforeSending int editChoicesBeforeSending, Bundle extras, Set<String> allowedDataTypes)92     RemoteInput(String resultKey, CharSequence label, CharSequence[] choices,
93             boolean allowFreeFormTextInput, @EditChoicesBeforeSending int editChoicesBeforeSending,
94             Bundle extras, Set<String> allowedDataTypes) {
95         this.mResultKey = resultKey;
96         this.mLabel = label;
97         this.mChoices = choices;
98         this.mAllowFreeFormTextInput = allowFreeFormTextInput;
99         this.mEditChoicesBeforeSending = editChoicesBeforeSending;
100         this.mExtras = extras;
101         this.mAllowedDataTypes = allowedDataTypes;
102         if (getEditChoicesBeforeSending() == EDIT_CHOICES_BEFORE_SENDING_ENABLED
103                 && !getAllowFreeFormInput()) {
104             throw new IllegalArgumentException(
105                 "setEditChoicesBeforeSending requires setAllowFreeFormInput");
106         }
107     }
108 
109     /**
110      * Get the key that the result of this input will be set in from the Bundle returned by
111      * {@link #getResultsFromIntent} when the {@link android.app.PendingIntent} is sent.
112      */
getResultKey()113     public @NonNull String getResultKey() {
114         return mResultKey;
115     }
116 
117     /**
118      * Get the label to display to users when collecting this input.
119      */
getLabel()120     public @Nullable CharSequence getLabel() {
121         return mLabel;
122     }
123 
124     /**
125      * Get possible input choices. This can be {@code null} if there are no choices to present.
126      */
127     @SuppressWarnings("NullableCollection") // Look, it's not the best API.
getChoices()128     public CharSequence @Nullable [] getChoices() {
129         return mChoices;
130     }
131 
132     @SuppressWarnings("NullableCollection") // That's just how it was defined.
getAllowedDataTypes()133     public @Nullable Set<String> getAllowedDataTypes() {
134         return mAllowedDataTypes;
135     }
136 
137     /**
138      * Returns true if the input only accepts data, meaning {@link #getAllowFreeFormInput}
139      * is false, {@link #getChoices} is null or empty, and {@link #getAllowedDataTypes} is
140      * non-null and not empty.
141      */
isDataOnly()142     public boolean isDataOnly() {
143         return !getAllowFreeFormInput()
144                 && (getChoices() == null || getChoices().length == 0)
145                 && getAllowedDataTypes() != null
146                 && !getAllowedDataTypes().isEmpty();
147     }
148 
149     /**
150      * Get whether or not users can provide an arbitrary value for
151      * input. If you set this to {@code false}, users must select one of the
152      * choices in {@link #getChoices}. An {@link IllegalArgumentException} is thrown
153      * if you set this to false and {@link #getChoices} returns {@code null} or empty.
154      */
getAllowFreeFormInput()155     public boolean getAllowFreeFormInput() {
156         return mAllowFreeFormTextInput;
157     }
158 
159     /**
160      * Gets whether tapping on a choice should let the user edit the input before it is sent to the
161      * app.
162      */
getEditChoicesBeforeSending()163     @EditChoicesBeforeSending public int getEditChoicesBeforeSending() {
164         return mEditChoicesBeforeSending;
165     }
166 
167     /**
168      * Get additional metadata carried around with this remote input.
169      */
getExtras()170     public @NonNull Bundle getExtras() {
171         return mExtras;
172     }
173 
174     /**
175      * Builder class for {@link androidx.core.app.RemoteInput} objects.
176      */
177     public static final class Builder {
178         private final String mResultKey;
179         private final Set<String> mAllowedDataTypes = new HashSet<>();
180         private final Bundle mExtras = new Bundle();
181         private CharSequence mLabel;
182         private CharSequence[] mChoices;
183         private boolean mAllowFreeFormTextInput = true;
184         @EditChoicesBeforeSending
185         private int mEditChoicesBeforeSending = EDIT_CHOICES_BEFORE_SENDING_AUTO;
186 
187         /**
188          * Create a builder object for {@link androidx.core.app.RemoteInput} objects.
189          *
190          * @param resultKey the Bundle key that refers to this input when collected from the user
191          */
Builder(@onNull String resultKey)192         public Builder(@NonNull String resultKey) {
193             if (resultKey == null) {
194                 throw new IllegalArgumentException("Result key can't be null");
195             }
196             mResultKey = resultKey;
197         }
198 
199         /**
200          * Set a label to be displayed to the user when collecting this input.
201          *
202          * @param label The label to show to users when they input a response
203          * @return this object for method chaining
204          */
setLabel(@ullable CharSequence label)205         public @NonNull Builder setLabel(@Nullable CharSequence label) {
206             mLabel = label;
207             return this;
208         }
209 
210         /**
211          * Specifies choices available to the user to satisfy this input.
212          *
213          * <p>Note: Starting in Android P, these choices will always be shown on phones if the app's
214          * target SDK is >= P. However, these choices may also be rendered on other types of devices
215          * regardless of target SDK.
216          *
217          * @param choices an array of pre-defined choices for users input.
218          *        You must provide a non-null and non-empty array if
219          *        you disabled free form input using {@link #setAllowFreeFormInput}
220          * @return this object for method chaining
221          */
setChoices(CharSequence @ullable [] choices)222         public @NonNull Builder setChoices(CharSequence @Nullable [] choices) {
223             mChoices = choices;
224             return this;
225         }
226 
227         /**
228          * Specifies whether the user can provide arbitrary values.
229          *
230          * @param mimeType A mime type that results are allowed to come in.
231          *         Be aware that text results (see {@link #setAllowFreeFormInput}
232          *         are allowed by default. If you do not want text results you will have to
233          *         pass false to {@code setAllowFreeFormInput}
234          * @param doAllow Whether the mime type should be allowed or not
235          * @return this object for method chaining
236          */
setAllowDataType(@onNull String mimeType, boolean doAllow)237         public @NonNull Builder setAllowDataType(@NonNull String mimeType, boolean doAllow) {
238             if (doAllow) {
239                 mAllowedDataTypes.add(mimeType);
240             } else {
241                 mAllowedDataTypes.remove(mimeType);
242             }
243             return this;
244         }
245 
246         /**
247          * Specifies whether the user can provide arbitrary text values.
248          *
249          * @param allowFreeFormTextInput The default is {@code true}.
250          *         If you specify {@code false}, you must either provide a non-null
251          *         and non-empty array to {@link #setChoices}, or enable a data result
252          *         in {@code setAllowDataType}. Otherwise an
253          *         {@link IllegalArgumentException} is thrown
254          * @return this object for method chaining
255          */
setAllowFreeFormInput(boolean allowFreeFormTextInput)256         public @NonNull Builder setAllowFreeFormInput(boolean allowFreeFormTextInput) {
257             mAllowFreeFormTextInput = allowFreeFormTextInput;
258             return this;
259         }
260 
261         /**
262          * Specifies whether tapping on a choice should let the user edit the input before it is
263          * sent to the app. The default is {@link #EDIT_CHOICES_BEFORE_SENDING_AUTO}.
264          *
265          * It cannot be used if {@link #setAllowFreeFormInput} has been set to false.
266          */
setEditChoicesBeforeSending( @ditChoicesBeforeSending int editChoicesBeforeSending)267         public @NonNull Builder setEditChoicesBeforeSending(
268                 @EditChoicesBeforeSending int editChoicesBeforeSending) {
269             mEditChoicesBeforeSending = editChoicesBeforeSending;
270             return this;
271         }
272 
273         /**
274          * Merge additional metadata into this builder.
275          *
276          * <p>Values within the Bundle will replace existing extras values in this Builder.
277          *
278          * @see RemoteInput#getExtras
279          */
addExtras(@onNull Bundle extras)280         public @NonNull Builder addExtras(@NonNull Bundle extras) {
281             if (extras != null) {
282                 mExtras.putAll(extras);
283             }
284             return this;
285         }
286 
287         /**
288          * Get the metadata Bundle used by this Builder.
289          *
290          * <p>The returned Bundle is shared with this Builder.
291          */
getExtras()292         public @NonNull Bundle getExtras() {
293             return mExtras;
294         }
295 
296         /**
297          * Combine all of the options that have been set and return a new {@link
298          * androidx.core.app.RemoteInput} object.
299          */
build()300         public @NonNull RemoteInput build() {
301             return new RemoteInput(
302                     mResultKey,
303                     mLabel,
304                     mChoices,
305                     mAllowFreeFormTextInput,
306                     mEditChoicesBeforeSending,
307                     mExtras,
308                     mAllowedDataTypes);
309         }
310     }
311 
312     /**
313      * Similar as {@link #getResultsFromIntent} but retrieves data results for a
314      * specific RemoteInput result. To retrieve a value use:
315      * <pre>
316      * {@code
317      * Map<String, Uri> results =
318      *     RemoteInput.getDataResultsFromIntent(intent, REMOTE_INPUT_KEY);
319      * if (results != null) {
320      *   Uri data = results.get(MIME_TYPE_OF_INTEREST);
321      * }
322      * }
323      * </pre>
324      * @param intent The intent object that fired in response to an action or content intent
325      *               which also had one or more remote input requested.
326      * @param remoteInputResultKey The result key for the RemoteInput you want results for.
327      */
328     @SuppressWarnings("NullableCollection") // This is what the platform API does.
getDataResultsFromIntent( @onNull Intent intent, @NonNull String remoteInputResultKey)329     public static @Nullable Map<String, Uri> getDataResultsFromIntent(
330             @NonNull Intent intent, @NonNull String remoteInputResultKey) {
331         if (Build.VERSION.SDK_INT >= 26) {
332             return Api26Impl.getDataResultsFromIntent(intent, remoteInputResultKey);
333         } else {
334             Intent clipDataIntent = getClipDataIntentFromIntent(intent);
335             if (clipDataIntent == null) {
336                 return null;
337             }
338             Map<String, Uri> results = new HashMap<>();
339             Bundle extras = clipDataIntent.getExtras();
340             for (String key : extras.keySet()) {
341                 if (key.startsWith(EXTRA_DATA_TYPE_RESULTS_DATA)) {
342                     String mimeType = key.substring(EXTRA_DATA_TYPE_RESULTS_DATA.length());
343                     if (mimeType.isEmpty()) {
344                         continue;
345                     }
346                     Bundle bundle = clipDataIntent.getBundleExtra(key);
347                     String uriStr = bundle.getString(remoteInputResultKey);
348                     if (uriStr == null || uriStr.isEmpty()) {
349                         continue;
350                     }
351                     results.put(mimeType, Uri.parse(uriStr));
352                 }
353             }
354             return results.isEmpty() ? null : results;
355         }
356     }
357 
358     /**
359      * Get the remote input text results bundle from an intent. The returned Bundle will
360      * contain a key/value for every result key populated by remote input collector.
361      * Use the {@link Bundle#getCharSequence(String)} method to retrieve a value. For data results
362      * use {@link #getDataResultsFromIntent}.
363      * @param intent The intent object that fired in response to an action or content intent
364      *               which also had one or more remote input requested.
365      */
366     // This is on purpose.
367     @SuppressWarnings({"NullableCollection", "deprecation"})
getResultsFromIntent(@onNull Intent intent)368     public static @Nullable Bundle getResultsFromIntent(@NonNull Intent intent) {
369         if (Build.VERSION.SDK_INT >= 20) {
370             return Api20Impl.getResultsFromIntent(intent);
371         } else {
372             Intent clipDataIntent = getClipDataIntentFromIntent(intent);
373             if (clipDataIntent == null) {
374                 return null;
375             }
376             return clipDataIntent.getExtras().getParcelable(RemoteInput.EXTRA_RESULTS_DATA);
377         }
378     }
379 
380     /**
381      * Populate an intent object with the results gathered from remote input. This method
382      * should only be called by remote input collection services when sending results to a
383      * pending intent.
384      * @param remoteInputs The remote inputs for which results are being provided
385      * @param intent The intent to add remote inputs to. The {@link android.content.ClipData}
386      *               field of the intent will be modified to contain the results.
387      * @param results A bundle holding the remote input results. This bundle should
388      *                be populated with keys matching the result keys specified in
389      *                {@code remoteInputs} with values being the result per key.
390      */
391     @SuppressWarnings("deprecation")
addResultsToIntent(RemoteInput @onNull [] remoteInputs, @NonNull Intent intent, @NonNull Bundle results)392     public static void addResultsToIntent(RemoteInput @NonNull [] remoteInputs,
393             @NonNull Intent intent, @NonNull Bundle results) {
394         if (Build.VERSION.SDK_INT >= 26) {
395             Api20Impl.addResultsToIntent(fromCompat(remoteInputs), intent, results);
396         } else if (Build.VERSION.SDK_INT >= 20) {
397             // Implementations of RemoteInput#addResultsToIntent prior to SDK 26 don't actually add
398             // results, they wipe out old results and insert the new one. Work around that by
399             // preserving old results.
400             Bundle existingTextResults =
401                     RemoteInput.getResultsFromIntent(intent);
402 
403             // We also need to preserve the results source, as it is also cleared.
404             int resultsSource = getResultsSource(intent);
405 
406             if (existingTextResults == null) {
407                 existingTextResults = results;
408             } else {
409                 existingTextResults.putAll(results);
410             }
411             for (RemoteInput input : remoteInputs) {
412                 // Data results are also wiped out. So grab them and add them back in.
413                 Map<String, Uri> existingDataResults =
414                         RemoteInput.getDataResultsFromIntent(
415                                 intent, input.getResultKey());
416                 RemoteInput[] arr = new RemoteInput[1];
417                 arr[0] = input;
418                 Api20Impl.addResultsToIntent(fromCompat(arr), intent, existingTextResults);
419                 if (existingDataResults != null) {
420                     RemoteInput.addDataResultToIntent(input, intent, existingDataResults);
421                 }
422             }
423 
424             // Now restore the results source.
425             setResultsSource(intent, resultsSource);
426         } else {
427             Intent clipDataIntent = getClipDataIntentFromIntent(intent);
428             if (clipDataIntent == null) {
429                 clipDataIntent = new Intent();  // First time we've added a result.
430             }
431             Bundle resultsBundle = clipDataIntent.getBundleExtra(RemoteInput.EXTRA_RESULTS_DATA);
432             if (resultsBundle == null) {
433                 resultsBundle = new Bundle();
434             }
435             for (RemoteInput remoteInput : remoteInputs) {
436                 Object result = results.get(remoteInput.getResultKey());
437                 if (result instanceof CharSequence) {
438                     resultsBundle.putCharSequence(
439                             remoteInput.getResultKey(), (CharSequence) result);
440                 }
441             }
442             clipDataIntent.putExtra(RemoteInput.EXTRA_RESULTS_DATA, resultsBundle);
443             intent.setClipData(ClipData.newIntent(RemoteInput.RESULTS_CLIP_LABEL, clipDataIntent));
444         }
445     }
446 
447     /**
448      * Same as {@link #addResultsToIntent} but for setting data results.
449      * @param remoteInput The remote input for which results are being provided
450      * @param intent The intent to add remote input results to. The
451      *               {@link ClipData} field of the intent will be
452      *               modified to contain the results.
453      * @param results A map of mime type to the Uri result for that mime type.
454      */
addDataResultToIntent(@onNull RemoteInput remoteInput, @NonNull Intent intent, @NonNull Map<String, Uri> results)455     public static void addDataResultToIntent(@NonNull RemoteInput remoteInput,
456             @NonNull Intent intent, @NonNull Map<String, Uri> results) {
457         if (Build.VERSION.SDK_INT >= 26) {
458             Api26Impl.addDataResultToIntent(remoteInput, intent, results);
459         } else {
460             Intent clipDataIntent = getClipDataIntentFromIntent(intent);
461             if (clipDataIntent == null) {
462                 clipDataIntent = new Intent();  // First time we've added a result.
463             }
464             for (Map.Entry<String, Uri> entry : results.entrySet()) {
465                 String mimeType = entry.getKey();
466                 Uri uri = entry.getValue();
467                 if (mimeType == null) {
468                     continue;
469                 }
470                 Bundle resultsBundle =
471                         clipDataIntent.getBundleExtra(getExtraResultsKeyForData(mimeType));
472                 if (resultsBundle == null) {
473                     resultsBundle = new Bundle();
474                 }
475                 resultsBundle.putString(remoteInput.getResultKey(), uri.toString());
476                 clipDataIntent.putExtra(getExtraResultsKeyForData(mimeType), resultsBundle);
477             }
478             intent.setClipData(ClipData.newIntent(RemoteInput.RESULTS_CLIP_LABEL, clipDataIntent));
479         }
480     }
481 
482     /**
483      * Set the source of the RemoteInput results. This method should only be called by remote
484      * input collection services (e.g.
485      * {@link android.service.notification.NotificationListenerService})
486      * when sending results to a pending intent.
487      *
488      * @see #SOURCE_FREE_FORM_INPUT
489      * @see #SOURCE_CHOICE
490      *
491      * @param intent The intent to add remote input source to. The {@link ClipData}
492      *               field of the intent will be modified to contain the source.
493      * @param source The source of the results.
494      */
setResultsSource(@onNull Intent intent, @Source int source)495     public static void setResultsSource(@NonNull Intent intent, @Source int source) {
496         if (Build.VERSION.SDK_INT >= 28) {
497             Api28Impl.setResultsSource(intent, source);
498         } else {
499             Intent clipDataIntent = getClipDataIntentFromIntent(intent);
500             if (clipDataIntent == null) {
501                 clipDataIntent = new Intent();  // First time we've added a result.
502             }
503             clipDataIntent.putExtra(EXTRA_RESULTS_SOURCE, source);
504             intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent));
505         }
506     }
507 
508     /**
509      * Get the source of the RemoteInput results.
510      *
511      * @see #SOURCE_FREE_FORM_INPUT
512      * @see #SOURCE_CHOICE
513      *
514      * @param intent The intent object that fired in response to an action or content intent
515      *               which also had one or more remote input requested.
516      * @return The source of the results. If no source was set, {@link #SOURCE_FREE_FORM_INPUT} will
517      * be returned.
518      */
519     @Source
getResultsSource(@onNull Intent intent)520     public static int getResultsSource(@NonNull Intent intent) {
521         if (Build.VERSION.SDK_INT >= 28) {
522             return Api28Impl.getResultsSource(intent);
523         } else {
524             Intent clipDataIntent = getClipDataIntentFromIntent(intent);
525             if (clipDataIntent == null) {
526                 return SOURCE_FREE_FORM_INPUT;
527             }
528             return clipDataIntent.getExtras().getInt(EXTRA_RESULTS_SOURCE, SOURCE_FREE_FORM_INPUT);
529         }
530     }
531 
getExtraResultsKeyForData(String mimeType)532     private static String getExtraResultsKeyForData(String mimeType) {
533         return EXTRA_DATA_TYPE_RESULTS_DATA + mimeType;
534     }
535 
536     @RequiresApi(20)
fromCompat(RemoteInput[] srcArray)537     static android.app.RemoteInput[] fromCompat(RemoteInput[] srcArray) {
538         if (srcArray == null) {
539             return null;
540         }
541         android.app.RemoteInput[] result = new android.app.RemoteInput[srcArray.length];
542         for (int i = 0; i < srcArray.length; i++) {
543             result[i] = fromCompat(srcArray[i]);
544         }
545         return result;
546     }
547 
548     @RequiresApi(20)
fromCompat(RemoteInput src)549     static android.app.RemoteInput fromCompat(RemoteInput src) {
550         return Api20Impl.fromCompat(src);
551     }
552 
553     @RequiresApi(20)
fromPlatform(android.app.RemoteInput src)554     static RemoteInput fromPlatform(android.app.RemoteInput src) {
555         return Api20Impl.fromPlatform(src);
556     }
557 
getClipDataIntentFromIntent(Intent intent)558     private static Intent getClipDataIntentFromIntent(Intent intent) {
559         ClipData clipData = intent.getClipData();
560         if (clipData == null) {
561             return null;
562         }
563         ClipDescription clipDescription = clipData.getDescription();
564         if (!clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
565             return null;
566         }
567         if (!clipDescription.getLabel().toString().contentEquals(RemoteInput.RESULTS_CLIP_LABEL)) {
568             return null;
569         }
570         return clipData.getItemAt(0).getIntent();
571     }
572 
573     @RequiresApi(26)
574     static class Api26Impl {
Api26Impl()575         private Api26Impl() {
576             // This class is not instantiable.
577         }
578 
getDataResultsFromIntent(Intent intent, String remoteInputResultKey)579         static Map<String, Uri> getDataResultsFromIntent(Intent intent,
580                 String remoteInputResultKey) {
581             return android.app.RemoteInput.getDataResultsFromIntent(intent, remoteInputResultKey);
582         }
583 
getAllowedDataTypes(Object remoteInput)584         static Set<String> getAllowedDataTypes(Object remoteInput) {
585             return ((android.app.RemoteInput) remoteInput).getAllowedDataTypes();
586         }
587 
addDataResultToIntent(RemoteInput remoteInput, Intent intent, Map<String, Uri> results)588         static void addDataResultToIntent(RemoteInput remoteInput, Intent intent,
589                 Map<String, Uri> results) {
590             android.app.RemoteInput.addDataResultToIntent(fromCompat(remoteInput), intent, results);
591         }
592 
setAllowDataType( android.app.RemoteInput.Builder builder, String mimeType, boolean doAllow)593         static android.app.RemoteInput.Builder setAllowDataType(
594                 android.app.RemoteInput.Builder builder, String mimeType, boolean doAllow) {
595             return builder.setAllowDataType(mimeType, doAllow);
596         }
597     }
598 
599     @RequiresApi(20)
600     static class Api20Impl {
Api20Impl()601         private Api20Impl() {
602             // This class is not instantiable.
603         }
604 
getResultsFromIntent(Intent intent)605         static Bundle getResultsFromIntent(Intent intent) {
606             return android.app.RemoteInput.getResultsFromIntent(intent);
607         }
608 
addResultsToIntent(Object remoteInputs, Intent intent, Bundle results)609         static void addResultsToIntent(Object remoteInputs, Intent intent, Bundle results) {
610             android.app.RemoteInput.addResultsToIntent((android.app.RemoteInput[]) remoteInputs,
611                     intent, results);
612         }
613 
fromPlatform(Object srcObj)614         static RemoteInput fromPlatform(Object srcObj) {
615             android.app.RemoteInput src = (android.app.RemoteInput) srcObj;
616             Builder builder =
617                     new Builder(src.getResultKey())
618                             .setLabel(src.getLabel())
619                             .setChoices(src.getChoices())
620                             .setAllowFreeFormInput(src.getAllowFreeFormInput())
621                             .addExtras(src.getExtras());
622             if (Build.VERSION.SDK_INT >= 26) {
623                 Set<String> allowedDataTypes = Api26Impl.getAllowedDataTypes(src);
624                 if (allowedDataTypes != null) {
625                     for (String allowedDataType : allowedDataTypes) {
626                         builder.setAllowDataType(allowedDataType, true);
627                     }
628                 }
629             }
630             if (Build.VERSION.SDK_INT >= 29) {
631                 builder.setEditChoicesBeforeSending(Api29Impl.getEditChoicesBeforeSending(src));
632             }
633             return builder.build();
634         }
635 
fromCompat(RemoteInput src)636         public static android.app.RemoteInput fromCompat(RemoteInput src) {
637             android.app.RemoteInput.Builder builder =
638                     new android.app.RemoteInput.Builder(src.getResultKey())
639                             .setLabel(src.getLabel())
640                             .setChoices(src.getChoices())
641                             .setAllowFreeFormInput(src.getAllowFreeFormInput())
642                             .addExtras(src.getExtras());
643             if (Build.VERSION.SDK_INT >= 26) {
644                 Set<String> allowedDataTypes = src.getAllowedDataTypes();
645                 if (allowedDataTypes != null) {
646                     for (String allowedDataType : allowedDataTypes) {
647                         Api26Impl.setAllowDataType(builder, allowedDataType, true);
648                     }
649                 }
650             }
651             if (Build.VERSION.SDK_INT >= 29) {
652                 Api29Impl.setEditChoicesBeforeSending(builder, src.getEditChoicesBeforeSending());
653             }
654             return builder.build();
655         }
656     }
657 
658     @RequiresApi(29)
659     static class Api29Impl {
Api29Impl()660         private Api29Impl() {
661             // This class is not instantiable.
662         }
663 
getEditChoicesBeforeSending(Object remoteInput)664         static int getEditChoicesBeforeSending(Object remoteInput) {
665             return ((android.app.RemoteInput) remoteInput).getEditChoicesBeforeSending();
666         }
667 
setEditChoicesBeforeSending( android.app.RemoteInput.Builder builder, int editChoicesBeforeSending)668         static android.app.RemoteInput.Builder setEditChoicesBeforeSending(
669                 android.app.RemoteInput.Builder builder, int editChoicesBeforeSending) {
670             return builder.setEditChoicesBeforeSending(editChoicesBeforeSending);
671         }
672     }
673 
674     @RequiresApi(28)
675     static class Api28Impl {
Api28Impl()676         private Api28Impl() {
677             // This class is not instantiable.
678         }
679 
setResultsSource(Intent intent, int source)680         static void setResultsSource(Intent intent, int source) {
681             android.app.RemoteInput.setResultsSource(intent, source);
682         }
683 
getResultsSource(Intent intent)684         static int getResultsSource(Intent intent) {
685             return android.app.RemoteInput.getResultsSource(intent);
686         }
687     }
688 }
689