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