SaveInfo.java revision 6a2ec5b5187f46b4509da75ebc527aca78b7acb4
1/* 2 * Copyright (C) 2017 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 17package android.service.autofill; 18 19import static android.view.autofill.Helper.sDebug; 20 21import android.annotation.IntDef; 22import android.annotation.NonNull; 23import android.annotation.Nullable; 24import android.app.Activity; 25import android.content.IntentSender; 26import android.os.Parcel; 27import android.os.Parcelable; 28import android.util.DebugUtils; 29import android.view.autofill.AutofillId; 30import android.view.autofill.AutofillManager; 31import android.view.autofill.AutofillValue; 32 33import com.android.internal.util.ArrayUtils; 34import com.android.internal.util.Preconditions; 35 36import java.lang.annotation.Retention; 37import java.lang.annotation.RetentionPolicy; 38import java.util.Arrays; 39 40/** 41 * Information used to indicate that an {@link AutofillService} is interested on saving the 42 * user-inputed data for future use, through a 43 * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} 44 * call. 45 * 46 * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}, and it contains at least 47 * two pieces of information: 48 * 49 * <ol> 50 * <li>The type(s) of user data (like password or credit card info) that would be saved. 51 * <li>The minimum set of views (represented by their {@link AutofillId}) that need to be changed 52 * to trigger a save request. 53 * </ol> 54 * 55 * <p>Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}: 56 * 57 * <pre class="prettyprint"> 58 * new FillResponse.Builder() 59 * .addDataset(new Dataset.Builder() 60 * .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer")) // username 61 * .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer")) // password 62 * .build()) 63 * .setSaveInfo(new SaveInfo.Builder( 64 * SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD, 65 * new AutofillId[] { id1, id2 }).build()) 66 * .build(); 67 * </pre> 68 * 69 * <p>The save type flags are used to display the appropriate strings in the save UI affordance. 70 * You can pass multiple values, but try to keep it short if possible. In the above example, just 71 * {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough. 72 * 73 * <p>There might be cases where the {@link AutofillService} knows how to fill the screen, 74 * but the user has no data for it. In that case, the {@link FillResponse} should contain just the 75 * {@link SaveInfo}, but no {@link Dataset Datasets}: 76 * 77 * <pre class="prettyprint"> 78 * new FillResponse.Builder() 79 * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD, 80 * new AutofillId[] { id1, id2 }).build()) 81 * .build(); 82 * </pre> 83 * 84 * <p>There might be cases where the user data in the {@link AutofillService} is enough 85 * to populate some fields but not all, and the service would still be interested on saving the 86 * other fields. In that case, the service could set the 87 * {@link SaveInfo.Builder#setOptionalIds(AutofillId[])} as well: 88 * 89 * <pre class="prettyprint"> 90 * new FillResponse.Builder() 91 * .addDataset(new Dataset.Builder() 92 * .setValue(id1, AutofillValue.forText("742 Evergreen Terrace"), 93 * createPresentation("742 Evergreen Terrace")) // street 94 * .setValue(id2, AutofillValue.forText("Springfield"), 95 * createPresentation("Springfield")) // city 96 * .build()) 97 * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS, 98 * new AutofillId[] { id1, id2 }) // street and city 99 * .setOptionalIds(new AutofillId[] { id3, id4 }) // state and zipcode 100 * .build()) 101 * .build(); 102 * </pre> 103 * 104 * <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after 105 * any of the following events: 106 * <ul> 107 * <li>The {@link Activity} finishes. 108 * <li>The app explicitly called {@link AutofillManager#commit()}. 109 * <li>All required views became invisible (if the {@link SaveInfo} was created with the 110 * {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag). 111 * </ul> 112 * 113 * <p>But it is only triggered when all conditions below are met: 114 * <ul> 115 * <li>The {@link SaveInfo} associated with the {@link FillResponse} is not {@code null}. 116 * <li>The {@link AutofillValue}s of all required views (as set by the {@code requiredIds} passed 117 * to the {@link SaveInfo.Builder} constructor are not empty. 118 * <li>The {@link AutofillValue} of at least one view (be it required or optional) has changed 119 * (i.e., it's neither the same value passed in a {@link Dataset}, nor the initial value 120 * presented in the view). 121 * <li>There is no {@link Dataset} in the last {@link FillResponse} that completely matches the 122 * screen state (i.e., all required and optional fields in the dataset have the same value as 123 * the fields in the screen). 124 * <li>The user explicitly tapped the UI affordance asking to save data for autofill. 125 * </ul> 126 * 127 * <p>The service can also customize some aspects of the save UI affordance: 128 * <ul> 129 * <li>Add a simple subtitle by calling {@link Builder#setDescription(CharSequence)}. 130 * <li>Add a customized subtitle by calling 131 * {@link Builder#setCustomDescription(CustomDescription)}. 132 * <li>Customize the button used to reject the save request by calling 133 * {@link Builder#setNegativeAction(int, IntentSender)}. 134 * <li>Decide whether the UI should be shown based on the user input validation by calling 135 * {@link Builder#setValidator(Validator)}. 136 * </ul> 137 */ 138public final class SaveInfo implements Parcelable { 139 140 /** 141 * Type used when the service can save the contents of a screen, but cannot describe what 142 * the content is for. 143 */ 144 public static final int SAVE_DATA_TYPE_GENERIC = 0x0; 145 146 /** 147 * Type used when the {@link FillResponse} represents user credentials that have a password. 148 */ 149 public static final int SAVE_DATA_TYPE_PASSWORD = 0x01; 150 151 /** 152 * Type used on when the {@link FillResponse} represents a physical address (such as street, 153 * city, state, etc). 154 */ 155 public static final int SAVE_DATA_TYPE_ADDRESS = 0x02; 156 157 /** 158 * Type used when the {@link FillResponse} represents a credit card. 159 */ 160 public static final int SAVE_DATA_TYPE_CREDIT_CARD = 0x04; 161 162 /** 163 * Type used when the {@link FillResponse} represents just an username, without a password. 164 */ 165 public static final int SAVE_DATA_TYPE_USERNAME = 0x08; 166 167 /** 168 * Type used when the {@link FillResponse} represents just an email address, without a password. 169 */ 170 public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 0x10; 171 172 /** 173 * Style for the negative button of the save UI to cancel the 174 * save operation. In this case, the user tapping the negative 175 * button signals that they would prefer to not save the filled 176 * content. 177 */ 178 public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0; 179 180 /** 181 * Style for the negative button of the save UI to reject the 182 * save operation. This could be useful if the user needs to 183 * opt-in your service and the save prompt is an advertisement 184 * of the potential value you can add to the user. In this 185 * case, the user tapping the negative button sends a strong 186 * signal that the feature may not be useful and you may 187 * consider some backoff strategy. 188 */ 189 public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1; 190 191 /** @hide */ 192 @IntDef( 193 value = { 194 NEGATIVE_BUTTON_STYLE_CANCEL, 195 NEGATIVE_BUTTON_STYLE_REJECT}) 196 @Retention(RetentionPolicy.SOURCE) 197 @interface NegativeButtonStyle{} 198 199 /** @hide */ 200 @IntDef( 201 flag = true, 202 value = { 203 SAVE_DATA_TYPE_GENERIC, 204 SAVE_DATA_TYPE_PASSWORD, 205 SAVE_DATA_TYPE_ADDRESS, 206 SAVE_DATA_TYPE_CREDIT_CARD, 207 SAVE_DATA_TYPE_USERNAME, 208 SAVE_DATA_TYPE_EMAIL_ADDRESS}) 209 @Retention(RetentionPolicy.SOURCE) 210 @interface SaveDataType{} 211 212 /** 213 * Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} 214 * is called once the {@link Activity} finishes. If this flag is set it is called once all 215 * saved views become invisible. 216 */ 217 public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1; 218 219 /** @hide */ 220 @IntDef( 221 flag = true, 222 value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE}) 223 @Retention(RetentionPolicy.SOURCE) 224 @interface SaveInfoFlags{} 225 226 private final @SaveDataType int mType; 227 private final @NegativeButtonStyle int mNegativeButtonStyle; 228 private final IntentSender mNegativeActionListener; 229 private final AutofillId[] mRequiredIds; 230 private final AutofillId[] mOptionalIds; 231 private final CharSequence mDescription; 232 private final int mFlags; 233 private final CustomDescription mCustomDescription; 234 private final InternalValidator mValidator; 235 236 private SaveInfo(Builder builder) { 237 mType = builder.mType; 238 mNegativeButtonStyle = builder.mNegativeButtonStyle; 239 mNegativeActionListener = builder.mNegativeActionListener; 240 mRequiredIds = builder.mRequiredIds; 241 mOptionalIds = builder.mOptionalIds; 242 mDescription = builder.mDescription; 243 mFlags = builder.mFlags; 244 mCustomDescription = builder.mCustomDescription; 245 mValidator = builder.mValidator; 246 } 247 248 /** @hide */ 249 public @NegativeButtonStyle int getNegativeActionStyle() { 250 return mNegativeButtonStyle; 251 } 252 253 /** @hide */ 254 public @Nullable IntentSender getNegativeActionListener() { 255 return mNegativeActionListener; 256 } 257 258 /** @hide */ 259 public @Nullable AutofillId[] getRequiredIds() { 260 return mRequiredIds; 261 } 262 263 /** @hide */ 264 public @Nullable AutofillId[] getOptionalIds() { 265 return mOptionalIds; 266 } 267 268 /** @hide */ 269 public @SaveDataType int getType() { 270 return mType; 271 } 272 273 /** @hide */ 274 public @SaveInfoFlags int getFlags() { 275 return mFlags; 276 } 277 278 /** @hide */ 279 public CharSequence getDescription() { 280 return mDescription; 281 } 282 283 /** @hide */ 284 @Nullable 285 public CustomDescription getCustomDescription() { 286 return mCustomDescription; 287 } 288 289 /** @hide */ 290 @Nullable 291 public InternalValidator getValidator() { 292 return mValidator; 293 } 294 295 /** 296 * A builder for {@link SaveInfo} objects. 297 */ 298 public static final class Builder { 299 300 private final @SaveDataType int mType; 301 private @NegativeButtonStyle int mNegativeButtonStyle = NEGATIVE_BUTTON_STYLE_CANCEL; 302 private IntentSender mNegativeActionListener; 303 private final AutofillId[] mRequiredIds; 304 private AutofillId[] mOptionalIds; 305 private CharSequence mDescription; 306 private boolean mDestroyed; 307 private int mFlags; 308 private CustomDescription mCustomDescription; 309 private InternalValidator mValidator; 310 311 /** 312 * Creates a new builder. 313 * 314 * @param type the type of information the associated {@link FillResponse} represents. It 315 * can be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}, 316 * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD}, 317 * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD}, 318 * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or 319 * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}. 320 * @param requiredIds ids of all required views that will trigger a save request. 321 * 322 * <p>See {@link SaveInfo} for more info. 323 * 324 * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty, or if 325 * it contains any {@code null} entry. 326 */ 327 public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) { 328 mType = type; 329 mRequiredIds = assertValid(requiredIds); 330 } 331 332 /** 333 * Creates a new builder when no id is required. 334 * 335 * <p>When using this builder, caller must call {@link #setOptionalIds(AutofillId[])} before 336 * calling {@link #build()}. 337 * 338 * @param type the type of information the associated {@link FillResponse} represents. It 339 * can be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}, 340 * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD}, 341 * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD}, 342 * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or 343 * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}. 344 * 345 * <p>See {@link SaveInfo} for more info. 346 */ 347 public Builder(@SaveDataType int type) { 348 mType = type; 349 mRequiredIds = null; 350 } 351 352 private AutofillId[] assertValid(AutofillId[] ids) { 353 Preconditions.checkArgument(ids != null && ids.length > 0, 354 "must have at least one id: " + Arrays.toString(ids)); 355 for (int i = 0; i < ids.length; i++) { 356 final AutofillId id = ids[i]; 357 Preconditions.checkArgument(id != null, 358 "cannot have null id: " + Arrays.toString(ids)); 359 } 360 return ids; 361 } 362 363 /** 364 * Sets flags changing the save behavior. 365 * 366 * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or {@code 0}. 367 * @return This builder. 368 */ 369 public @NonNull Builder setFlags(@SaveInfoFlags int flags) { 370 throwIfDestroyed(); 371 372 mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE); 373 return this; 374 } 375 376 /** 377 * Sets the ids of additional, optional views the service would be interested to save. 378 * 379 * <p>See {@link SaveInfo} for more info. 380 * 381 * @param ids The ids of the optional views. 382 * @return This builder. 383 * 384 * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if 385 * it contains any {@code null} entry. 386 */ 387 public @NonNull Builder setOptionalIds(@NonNull AutofillId[] ids) { 388 throwIfDestroyed(); 389 mOptionalIds = assertValid(ids); 390 return this; 391 } 392 393 /** 394 * Sets an optional description to be shown in the UI when the user is asked to save. 395 * 396 * <p>Typically, it describes how the data will be stored by the service, so it can help 397 * users to decide whether they can trust the service to save their data. 398 * 399 * @param description a succint description. 400 * @return This Builder. 401 * 402 * @throws IllegalStateException if this call was made after calling 403 * {@link #setCustomDescription(CustomDescription)}. 404 */ 405 public @NonNull Builder setDescription(@Nullable CharSequence description) { 406 throwIfDestroyed(); 407 Preconditions.checkState(mCustomDescription == null, 408 "Can call setDescription() or setCustomDescription(), but not both"); 409 mDescription = description; 410 return this; 411 } 412 413 /** 414 * Sets a custom description to be shown in the UI when the user is asked to save. 415 * 416 * <p>Typically used when the service must show more info about the object being saved, 417 * like a credit card logo, masked number, and expiration date. 418 * 419 * @param customDescription the custom description. 420 * @return This Builder. 421 * 422 * @throws IllegalStateException if this call was made after calling 423 * {@link #setDescription(CharSequence)}. 424 */ 425 public @NonNull Builder setCustomDescription(@NonNull CustomDescription customDescription) { 426 throwIfDestroyed(); 427 Preconditions.checkState(mDescription == null, 428 "Can call setDescription() or setCustomDescription(), but not both"); 429 mCustomDescription = customDescription; 430 return this; 431 } 432 433 /** 434 * Sets the style and listener for the negative save action. 435 * 436 * <p>This allows an autofill service to customize the style and be 437 * notified when the user selects the negative action in the save 438 * UI. Note that selecting the negative action regardless of its style 439 * and listener being customized would dismiss the save UI and if a 440 * custom listener intent is provided then this intent is 441 * started. The default style is {@link #NEGATIVE_BUTTON_STYLE_CANCEL}</p> 442 * 443 * @param style The action style. 444 * @param listener The action listener. 445 * @return This builder. 446 * 447 * @see #NEGATIVE_BUTTON_STYLE_CANCEL 448 * @see #NEGATIVE_BUTTON_STYLE_REJECT 449 * 450 * @throws IllegalArgumentException If the style is invalid 451 */ 452 public @NonNull Builder setNegativeAction(@NegativeButtonStyle int style, 453 @Nullable IntentSender listener) { 454 throwIfDestroyed(); 455 if (style != NEGATIVE_BUTTON_STYLE_CANCEL 456 && style != NEGATIVE_BUTTON_STYLE_REJECT) { 457 throw new IllegalArgumentException("Invalid style: " + style); 458 } 459 mNegativeButtonStyle = style; 460 mNegativeActionListener = listener; 461 return this; 462 } 463 464 /** 465 * Sets an object used to validate the user input - if the input is not valid, the Save UI 466 * affordance is not shown. 467 * 468 * <p>Typically used to validate credit card numbers. Examples: 469 * 470 * <p>Validator for a credit number that must have exactly 16 digits: 471 * 472 * <pre class="prettyprint"> 473 * Validator validator = new RegexValidator(ccNumberId, Pattern.compile(""^\\d{16}$")) 474 * </pre> 475 * 476 * <p>Validator for a credit number that must pass a Luhn checksum and either have 477 * 16 digits, or 15 digits starting with 108: 478 * 479 * <pre class="prettyprint"> 480 * import static android.service.autofill.Validators.and; 481 * import static android.service.autofill.Validators.or; 482 * 483 * Validator validator = 484 * and( 485 * new LuhnChecksumValidator(ccNumberId), 486 * or( 487 * new RegexValidator(ccNumberId, Pattern.compile("^\\d{16}$")), 488 * new RegexValidator(ccNumberId, Pattern.compile("^108\\d{12}$")) 489 * ) 490 * ); 491 * </pre> 492 * 493 * <p><b>NOTE: </b>the example above is just for illustrative purposes; the same validator 494 * could be created using a single regex for the {@code OR} part: 495 * 496 * <pre class="prettyprint"> 497 * Validator validator = 498 * and( 499 * new LuhnChecksumValidator(ccNumberId), 500 * new RegexValidator(ccNumberId, Pattern.compile(""^(\\d{16}|108\\d{12})$")) 501 * ); 502 * </pre> 503 * 504 * <p>Validator for a credit number contained in just 4 fields and that must have exactly 505 * 4 digits on each field: 506 * 507 * <pre class="prettyprint"> 508 * import static android.service.autofill.Validators.and; 509 * 510 * Validator validator = 511 * and( 512 * new RegexValidator(ccNumberId1, Pattern.compile("^\\d{4}$")), 513 * new RegexValidator(ccNumberId2, Pattern.compile("^\\d{4}$")), 514 * new RegexValidator(ccNumberId3, Pattern.compile("^\\d{4}$")), 515 * new RegexValidator(ccNumberId4, Pattern.compile("^\\d{4}$")) 516 * ); 517 * </pre> 518 * 519 * @param validator an implementation provided by the Android System. 520 * @return this builder. 521 * 522 * @throws IllegalArgumentException if {@code validator} is not a class provided 523 * by the Android System. 524 */ 525 public @NonNull Builder setValidator(@NonNull Validator validator) { 526 throwIfDestroyed(); 527 Preconditions.checkArgument((validator instanceof InternalValidator), 528 "not provided by Android System: " + validator); 529 mValidator = (InternalValidator) validator; 530 return this; 531 } 532 533 /** 534 * Builds a new {@link SaveInfo} instance. 535 * 536 * @throws IllegalStateException if no 537 * {@link #SaveInfo.Builder(int, AutofillId[]) required ids} 538 * or {@link #setOptionalIds(AutofillId[]) optional ids} were set 539 */ 540 public SaveInfo build() { 541 throwIfDestroyed(); 542 Preconditions.checkState( 543 !ArrayUtils.isEmpty(mRequiredIds) || !ArrayUtils.isEmpty(mOptionalIds), 544 "must have at least one required or optional id"); 545 mDestroyed = true; 546 return new SaveInfo(this); 547 } 548 549 private void throwIfDestroyed() { 550 if (mDestroyed) { 551 throw new IllegalStateException("Already called #build()"); 552 } 553 } 554 } 555 556 ///////////////////////////////////// 557 // Object "contract" methods. // 558 ///////////////////////////////////// 559 @Override 560 public String toString() { 561 if (!sDebug) return super.toString(); 562 563 return new StringBuilder("SaveInfo: [type=") 564 .append(DebugUtils.flagsToString(SaveInfo.class, "SAVE_DATA_TYPE_", mType)) 565 .append(", requiredIds=").append(Arrays.toString(mRequiredIds)) 566 .append(", optionalIds=").append(Arrays.toString(mOptionalIds)) 567 .append(", description=").append(mDescription) 568 .append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_", 569 mNegativeButtonStyle)) 570 .append(", mFlags=").append(mFlags) 571 .append(", mCustomDescription=").append(mCustomDescription) 572 .append(", validation=").append(mValidator) 573 .append("]").toString(); 574 } 575 576 ///////////////////////////////////// 577 // Parcelable "contract" methods. // 578 ///////////////////////////////////// 579 580 @Override 581 public int describeContents() { 582 return 0; 583 } 584 585 @Override 586 public void writeToParcel(Parcel parcel, int flags) { 587 parcel.writeInt(mType); 588 parcel.writeParcelableArray(mRequiredIds, flags); 589 parcel.writeParcelableArray(mOptionalIds, flags); 590 parcel.writeInt(mNegativeButtonStyle); 591 parcel.writeParcelable(mNegativeActionListener, flags); 592 parcel.writeCharSequence(mDescription); 593 parcel.writeParcelable(mCustomDescription, flags); 594 parcel.writeParcelable(mValidator, flags); 595 parcel.writeInt(mFlags); 596 } 597 598 public static final Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() { 599 @Override 600 public SaveInfo createFromParcel(Parcel parcel) { 601 602 // Always go through the builder to ensure the data ingested by 603 // the system obeys the contract of the builder to avoid attacks 604 // using specially crafted parcels. 605 final int type = parcel.readInt(); 606 final AutofillId[] requiredIds = parcel.readParcelableArray(null, AutofillId.class); 607 final Builder builder = requiredIds != null 608 ? new Builder(type, requiredIds) 609 : new Builder(type); 610 final AutofillId[] optionalIds = parcel.readParcelableArray(null, AutofillId.class); 611 if (optionalIds != null) { 612 builder.setOptionalIds(optionalIds); 613 } 614 615 builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null)); 616 builder.setDescription(parcel.readCharSequence()); 617 final CustomDescription customDescripton = parcel.readParcelable(null); 618 if (customDescripton != null) { 619 builder.setCustomDescription(customDescripton); 620 } 621 final InternalValidator validator = parcel.readParcelable(null); 622 if (validator != null) { 623 builder.setValidator(validator); 624 } 625 builder.setFlags(parcel.readInt()); 626 return builder.build(); 627 } 628 629 @Override 630 public SaveInfo[] newArray(int size) { 631 return new SaveInfo[size]; 632 } 633 }; 634} 635