From f69761ffbe3098067ae720263ef05262f4b5d41e Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Thu, 23 Feb 2017 17:52:01 -0800 Subject: [PATCH] Refactored savableIds() into a SaveInfo class. For now it's a "1-to-1" refactoring that keeps the same functionalities, but soon SaveInfo will be expanded to allow the AutoFillService to customize it. Bug: 35727295 Test: CtsAutoFillServiceTestCases pass Test: m update-api Change-Id: I5aaa705be2b32590048f70ed0142437e05df94b7 --- api/current.txt | 18 +- api/system-current.txt | 18 +- api/test-current.txt | 18 +- .../service/autofill/FillResponse.java | 130 ++++++---- .../android/service/autofill/SaveInfo.aidl | 19 ++ .../android/service/autofill/SaveInfo.java | 236 ++++++++++++++++++ .../autofill/AutoFillManagerServiceImpl.java | 12 +- 7 files changed, 394 insertions(+), 57 deletions(-) create mode 100644 core/java/android/service/autofill/SaveInfo.aidl create mode 100644 core/java/android/service/autofill/SaveInfo.java diff --git a/api/current.txt b/api/current.txt index 62365cce20a93..8843acb1e697c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -36321,10 +36321,10 @@ package android.service.autofill { public static final class FillResponse.Builder { ctor public FillResponse.Builder(); method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset); - method public android.service.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...); method public android.service.autofill.FillResponse build(); method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender, android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle); + method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo); } public final class SaveCallback { @@ -36332,6 +36332,22 @@ package android.service.autofill { method public void onSuccess(); } + public final class SaveInfo implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int SAVE_UI_TYPE_ADDRESS = 2; // 0x2 + field public static final int SAVE_UI_TYPE_CREDENTIALS = 1; // 0x1 + field public static final int SAVE_UI_TYPE_GENERIC = 0; // 0x0 + field public static final int SAVE_UI_TYPE_PAYMENT = 3; // 0x3 + } + + public static final class SaveInfo.Builder { + ctor public SaveInfo.Builder(int); + method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutoFillId...); + method public android.service.autofill.SaveInfo build(); + } + } package android.service.carrier { diff --git a/api/system-current.txt b/api/system-current.txt index 8626f1b33fa4b..a328a0d40ab4c 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -39234,10 +39234,10 @@ package android.service.autofill { public static final class FillResponse.Builder { ctor public FillResponse.Builder(); method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset); - method public android.service.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...); method public android.service.autofill.FillResponse build(); method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender, android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle); + method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo); } public final class SaveCallback { @@ -39245,6 +39245,22 @@ package android.service.autofill { method public void onSuccess(); } + public final class SaveInfo implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int SAVE_UI_TYPE_ADDRESS = 2; // 0x2 + field public static final int SAVE_UI_TYPE_CREDENTIALS = 1; // 0x1 + field public static final int SAVE_UI_TYPE_GENERIC = 0; // 0x0 + field public static final int SAVE_UI_TYPE_PAYMENT = 3; // 0x3 + } + + public static final class SaveInfo.Builder { + ctor public SaveInfo.Builder(int); + method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutoFillId...); + method public android.service.autofill.SaveInfo build(); + } + } package android.service.carrier { diff --git a/api/test-current.txt b/api/test-current.txt index 44386bec120ad..e2f341d92c719 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -36460,10 +36460,10 @@ package android.service.autofill { public static final class FillResponse.Builder { ctor public FillResponse.Builder(); method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset); - method public android.service.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...); method public android.service.autofill.FillResponse build(); method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender, android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle); + method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo); } public final class SaveCallback { @@ -36471,6 +36471,22 @@ package android.service.autofill { method public void onSuccess(); } + public final class SaveInfo implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int SAVE_UI_TYPE_ADDRESS = 2; // 0x2 + field public static final int SAVE_UI_TYPE_CREDENTIALS = 1; // 0x1 + field public static final int SAVE_UI_TYPE_GENERIC = 0; // 0x0 + field public static final int SAVE_UI_TYPE_PAYMENT = 3; // 0x3 + } + + public static final class SaveInfo.Builder { + ctor public SaveInfo.Builder(int); + method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutoFillId...); + method public android.service.autofill.SaveInfo build(); + } + } package android.service.carrier { diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index 91c668ef32e2a..a241d1fa707cc 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -23,7 +23,6 @@ import android.content.IntentSender; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.util.ArraySet; import android.view.autofill.AutoFillId; import android.view.autofill.AutoFillManager; import android.widget.RemoteViews; @@ -69,18 +68,19 @@ import java.util.ArrayList; * *

If the user does not have any data associated with this {@link android.app.Activity} but * the service wants to offer the user the option to save the data that was entered, then the - * service could populate the response with {@code savableIds} instead of {@link Dataset}s: + * service could populate the response with a {@link SaveInfo} instead of {@link Dataset}s: * *

  *  new FillResponse.Builder()
- *      .addSavableFields(id1, id2)
+ *      .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_CREDENTIALS)
+ *                   .addSavableFields(id1, id2))
  *      .build();
  * 
* *

Similarly, there might be cases where the user data on the service is enough to populate some * fields but not all, and the service would still be interested on saving the other fields. In this - * scenario, the service could populate the response with both {@link Dataset}s and {@code - * savableIds}: + * scenario, the service could populate the response with both {@link Dataset}s and + * {@link SaveInfo}: * *

  *   new FillResponse.Builder()
@@ -90,7 +90,8 @@ import java.util.ArrayList;
  *          .setTextFieldValue(id3, "742 Evergreen Terrace")  // street
  *          .setTextFieldValue(id4, "Springfield")            // city
  *          .build())
- *       .addSavableFields(id5, id6) // state and zipcode
+ *       .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_ADDRESS)
+ *                   .addSavableFields(id5, id6)) // state and zipcode
  *       .build();
  *
  * 
@@ -140,9 +141,11 @@ import java.util.ArrayList; * * *

The service could require user authentication at the {@link FillResponse} or the - * {@link Dataset} level, prior to auto-filling an activity - see {@link FillResponse.Builder - * #setAuthentication(IntentSender)} and {@link Dataset.Builder#setAuthentication(IntentSender)}. - * It is recommended that you encrypt only the sensitive data but leave the labels unencrypted + * {@link Dataset} level, prior to auto-filling an activity - see + * {@link FillResponse.Builder#setAuthentication(IntentSender, RemoteViews)} and + * {@link Dataset.Builder#setAuthentication(IntentSender)}. + * + *

It is recommended that you encrypt only the sensitive data but leave the labels unencrypted * which would allow you to provide a dataset presentation views with labels and if the user * chooses one of them challenge the user to authenticate. For example, if the user has a * home and a work address the Home and Work labels could be stored unencrypted as they don't @@ -158,14 +161,45 @@ import java.util.ArrayList; public final class FillResponse implements Parcelable { private final ArrayList mDatasets; - private final ArraySet mSavableIds; + private final SaveInfo mSaveInfo; private final Bundle mExtras; private final RemoteViews mPresentation; private final IntentSender mAuthentication; private FillResponse(@NonNull Builder builder) { mDatasets = builder.mDatasets; - mSavableIds = builder.mSavableIds; + + if (false) { + // TODO(b/33197203, 35727295): this is how mSaveInfo will be set once we don't support + // FillResponse.setSavableIds() + mSaveInfo = builder.mSaveInfo; + if (mSaveInfo != null) { + mSaveInfo.addSavableIds(mDatasets); + if (mSaveInfo.getSavableIds() == null) { + throw new IllegalArgumentException( + "need to provide at least one savable id on SaveInfo"); + } + } + } else { + // Temporary workaround to support FillResponse.setSavableIds() + SaveInfo saveInfo = builder.mSaveInfoBuilder != null ? builder.mSaveInfoBuilder.build() + : builder.mSaveInfo; + + // Handle the the case where service didn't call setSavableIds() because it would + // contain just the ids from the datasets. + if (saveInfo == null && mDatasets != null) { + saveInfo = new SaveInfo.Builder(SaveInfo.SAVE_UI_TYPE_GENERIC).build(); + } + if (saveInfo != null) { + saveInfo.addSavableIds(mDatasets); + if (saveInfo.getSavableIds() == null) { + throw new IllegalArgumentException( + "need to provide at least one savable id on SaveInfo"); + } + } + mSaveInfo = saveInfo; + } + mExtras = builder.mExtras; mPresentation = builder.mPresentation; mAuthentication = builder.mAuthentication; @@ -182,8 +216,8 @@ public final class FillResponse implements Parcelable { } /** @hide */ - public @Nullable ArraySet getSavableIds() { - return mSavableIds; + public @Nullable SaveInfo getSaveInfo() { + return mSaveInfo; } /** @hide */ @@ -202,7 +236,10 @@ public final class FillResponse implements Parcelable { */ public static final class Builder { private ArrayList mDatasets; - private ArraySet mSavableIds; + // TODO(b/33197203, 35727295): temporary builder use by deprecated addSavableIds() method, + // should be removed once that method is gone + private SaveInfo.Builder mSaveInfoBuilder; + private SaveInfo mSaveInfo; private Bundle mExtras; private RemoteViews mPresentation; private IntentSender mAuthentication; @@ -276,41 +313,37 @@ public final class FillResponse implements Parcelable { if (!mDatasets.add(dataset)) { return this; } - if (dataset.getFieldIds() != null) { - final int fieldCount = dataset.getFieldIds().size(); - for (int i = 0; i < fieldCount; i++) { - final AutoFillId id = dataset.getFieldIds().get(i); - if (mSavableIds == null) { - mSavableIds = new ArraySet<>(); - } - mSavableIds.add(id); - } + return this; + } + + /** @hide */ + // TODO(b/33197203, 35727295): remove when not used by clients + public @NonNull Builder addSavableFields(@Nullable AutoFillId... ids) { + throwIfDestroyed(); + if (mSaveInfo != null) { + throw new IllegalStateException("setSaveInfo() already called"); } + if (mSaveInfoBuilder == null) { + mSaveInfoBuilder = new SaveInfo.Builder(SaveInfo.SAVE_UI_TYPE_GENERIC); + } + mSaveInfoBuilder.addSavableIds(ids); + return this; } /** - * Adds ids of additional fields that the service would be interested to save (through - * {@link AutoFillService#onSaveRequest( - * android.app.assist.AssistStructure, Bundle, SaveCallback)}) - * but were not indirectly set through {@link #addDataset(Dataset)}. + * Sets the {@link SaveInfo} associated with this response. + * + *

See {@link FillResponse} for more info. * - * @param ids The savable ids. * @return This builder. - * - * @see FillResponse */ - public @NonNull Builder addSavableFields(@Nullable AutoFillId... ids) { + public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) { throwIfDestroyed(); - if (ids == null) { - return this; - } - for (AutoFillId id : ids) { - if (mSavableIds == null) { - mSavableIds = new ArraySet<>(); - } - mSavableIds.add(id); + if (mSaveInfoBuilder != null) { + throw new IllegalStateException("addSavableFields() already called"); } + mSaveInfo = saveInfo; return this; } @@ -340,9 +373,11 @@ public final class FillResponse implements Parcelable { */ public FillResponse build() { throwIfDestroyed(); - if (mAuthentication == null && mDatasets == null && mSavableIds == null) { - throw new IllegalArgumentException("need to provide at least one" - + " data set or savable ids or an authentication with a presentation"); + + if (mAuthentication == null && mDatasets == null && mSaveInfoBuilder == null + && mSaveInfo == null) { + throw new IllegalArgumentException("need to provide at least one DataSet or a " + + "SaveInfo or an authentication with a presentation"); } mDestroyed = true; return new FillResponse(this); @@ -361,9 +396,10 @@ public final class FillResponse implements Parcelable { @Override public String toString() { if (!DEBUG) return super.toString(); + return new StringBuilder( "FillResponse: [datasets=").append(mDatasets) - .append(", savableIds=").append(mSavableIds) + .append(", saveInfo=").append(mSaveInfo) .append(", hasExtras=").append(mExtras != null) .append(", hasPresentation=").append(mPresentation != null) .append(", hasAuthentication=").append(mAuthentication != null) @@ -382,7 +418,7 @@ public final class FillResponse implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeTypedArrayList(mDatasets, flags); - parcel.writeTypedArraySet(mSavableIds, flags); + parcel.writeParcelable(mSaveInfo, flags); parcel.writeParcelable(mExtras, flags); parcel.writeParcelable(mAuthentication, flags); parcel.writeParcelable(mPresentation, flags); @@ -401,11 +437,7 @@ public final class FillResponse implements Parcelable { for (int i = 0; i < datasetCount; i++) { builder.addDataset(datasets.get(i)); } - final ArraySet fillIds = parcel.readTypedArraySet(null); - final int fillIdCount = (fillIds != null) ? fillIds.size() : 0; - for (int i = 0; i < fillIdCount; i++) { - builder.addSavableFields(fillIds.valueAt(i)); - } + builder.setSaveInfo(parcel.readParcelable(null)); builder.setExtras(parcel.readParcelable(null)); builder.setAuthentication(parcel.readParcelable(null), parcel.readParcelable(null)); diff --git a/core/java/android/service/autofill/SaveInfo.aidl b/core/java/android/service/autofill/SaveInfo.aidl new file mode 100644 index 0000000000000..8cda608e18140 --- /dev/null +++ b/core/java/android/service/autofill/SaveInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +parcelable SaveInfo; diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java new file mode 100644 index 0000000000000..148bd24f552c0 --- /dev/null +++ b/core/java/android/service/autofill/SaveInfo.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import static android.view.autofill.Helper.DEBUG; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArraySet; +import android.view.autofill.AutoFillId; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + +/** + * Information used to indicate that a service is interested on saving the user-inputed data for + * future use. + * + *

A {@link SaveInfo} is always associated with a {@link FillResponse}. + * + *

A {@link SaveInfo} must define the type it represents, and contain at least one + * {@code savableId}. A {@code savableId} is the {@link AutoFillId} of a view the service is + * interested to save in a {@code onSaveRequest()}; the ids of all {@link Dataset} present in the + * {@link FillResponse} associated with this {@link SaveInfo} are already marked as savable, + * but additional ids can be added through {@link Builder#addSavableIds(AutoFillId...)}. + * + *

See {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, + * SaveCallback)} and {@link FillResponse} for more info. + */ +public final class SaveInfo implements Parcelable { + + /** + * Type used on when the service can save the contents of an activity, but cannot describe what + * the content is for. + */ + public static final int SAVE_UI_TYPE_GENERIC = 0; + + /** + * Type used when the {@link FillResponse} represents user credentials (such as username and + * password). + */ + public static final int SAVE_UI_TYPE_CREDENTIALS = 1; + + /** + * Type used on when the {@link FillResponse} represents a physical address (such as street, + * city, state, etc). + */ + public static final int SAVE_UI_TYPE_ADDRESS = 2; + + /** + * Type used when the {@link FillResponse} represents a payment (such as credit card number + * and expiration date). + */ + public static final int SAVE_UI_TYPE_PAYMENT = 3; + + private final @SaveUiType int mType; + private ArraySet mSavableIds; + + /** @hide */ + @IntDef({ + SAVE_UI_TYPE_GENERIC, + SAVE_UI_TYPE_CREDENTIALS, + SAVE_UI_TYPE_ADDRESS, + SAVE_UI_TYPE_PAYMENT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SaveUiType { + } + + private SaveInfo(Builder builder) { + mType = builder.mType; + mSavableIds = builder.mSavableIds; + } + + /** @hide */ + public @Nullable ArraySet getSavableIds() { + return mSavableIds; + } + + /** @hide */ + public void addSavableIds(@Nullable ArrayList datasets) { + if (datasets != null) { + for (Dataset dataset : datasets) { + final ArrayList ids = dataset.getFieldIds(); + if (ids != null) { + final int fieldCount = ids.size(); + for (int i = 0; i < fieldCount; i++) { + final AutoFillId id = ids.get(i); + if (mSavableIds == null) { + mSavableIds = new ArraySet<>(); + } + mSavableIds.add(id); + } + } + } + } + } + + /** + * A builder for {@link SaveInfo} objects. + */ + public static final class Builder { + + private final @SaveUiType int mType; + private ArraySet mSavableIds; + private boolean mDestroyed; + + /** + * Creates a new builder. + * + * @param type the type of information the associated {@link FillResponse} represents. Must + * be {@link SaveInfo#SAVE_UI_TYPE_GENERIC}, {@link SaveInfo#SAVE_UI_TYPE_CREDENTIALS}, + * {@link SaveInfo#SAVE_UI_TYPE_ADDRESS}, or {@link SaveInfo#SAVE_UI_TYPE_PAYMENT}; + * otherwise it will assume {@link SaveInfo#SAVE_UI_TYPE_GENERIC}. + */ + public Builder(@SaveUiType int type) { + switch (type) { + case SAVE_UI_TYPE_CREDENTIALS: + case SAVE_UI_TYPE_ADDRESS: + case SAVE_UI_TYPE_PAYMENT: + mType = type; + break; + default: + mType = SAVE_UI_TYPE_GENERIC; + } + } + + /** + * Adds ids of additional views the service would be interested to save, but were not + * indirectly set through {@link FillResponse.Builder#addDataset(Dataset)}. + * + * @param ids The savable ids. + * @return This builder. + * + * @see FillResponse + */ + public @NonNull Builder addSavableIds(@Nullable AutoFillId... ids) { + throwIfDestroyed(); + + if (ids == null) { + return this; + } + for (AutoFillId id : ids) { + if (mSavableIds == null) { + mSavableIds = new ArraySet<>(); + } + mSavableIds.add(id); + } + return this; + } + + /** + * Builds a new {@link SaveInfo} instance. + */ + public SaveInfo build() { + throwIfDestroyed(); + mDestroyed = true; + return new SaveInfo(this); + } + + private void throwIfDestroyed() { + if (mDestroyed) { + throw new IllegalStateException("Already called #build()"); + } + } + + } + + ///////////////////////////////////// + // Object "contract" methods. // + ///////////////////////////////////// + @Override + public String toString() { + if (!DEBUG) return super.toString(); + + return new StringBuilder("SaveInfo: [type=").append(mType) + .append(", savableIds=").append(mSavableIds) + .append("]").toString(); + } + + ///////////////////////////////////// + // Parcelable "contract" methods. // + ///////////////////////////////////// + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mType); + parcel.writeTypedArraySet(mSavableIds, flags); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public SaveInfo createFromParcel(Parcel parcel) { + // Always go through the builder to ensure the data ingested by + // the system obeys the contract of the builder to avoid attacks + // using specially crafted parcels. + final Builder builder = new Builder(parcel.readInt()); + final ArraySet savableIds = parcel.readTypedArraySet(null); + final int savableIdsCount = (savableIds != null) ? savableIds.size() : 0; + for (int i = 0; i < savableIdsCount; i++) { + builder.addSavableIds(savableIds.valueAt(i)); + } + + return builder.build(); + } + + @Override + public SaveInfo[] newArray(int size) { + return new SaveInfo[size]; + } + }; +} diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java index f7cb0104d3a2b..7e9cbca3df95d 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java @@ -56,6 +56,7 @@ import android.service.autofill.AutoFillServiceInfo; import android.service.autofill.Dataset; import android.service.autofill.FillResponse; import android.service.autofill.IAutoFillService; +import android.service.autofill.SaveInfo; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -719,18 +720,19 @@ final class AutoFillManagerServiceImpl { } return; } - final ArraySet savableIds = mCurrentResponse.getSavableIds(); + final SaveInfo saveInfo = mCurrentResponse.getSaveInfo(); if (DEBUG) { - Slog.d(TAG, "showSaveLocked(): savableIds=" + savableIds); + Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo); } - if (savableIds == null || savableIds.isEmpty()) { + if (saveInfo == null || saveInfo.getSavableIds() == null + || saveInfo.getSavableIds().isEmpty()) { return; } - final int size = savableIds.size(); + final int size = saveInfo.getSavableIds().size(); for (int i = 0; i < size; i++) { - final AutoFillId id = savableIds.valueAt(i); + final AutoFillId id = saveInfo.getSavableIds().valueAt(i); final ViewState state = mViewStates.get(id); if (state != null && state.mValueUpdated) { final AutoFillValue filledValue = findValue(mAutoFilledDataset, id);