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
This commit is contained in:
Felipe Leme
2017-02-23 17:52:01 -08:00
parent 38a65f6c56
commit f69761ffbe
7 changed files with 394 additions and 57 deletions

View File

@@ -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;
*
* <p>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:
*
* <pre class="prettyprint">
* new FillResponse.Builder()
* .addSavableFields(id1, id2)
* .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_CREDENTIALS)
* .addSavableFields(id1, id2))
* .build();
* </pre>
*
* <p>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}:
*
* <pre class="prettyprint">
* 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();
*
* </pre>
@@ -140,9 +141,11 @@ import java.util.ArrayList;
* </pre>
*
* <p>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)}.
*
* <p>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<Dataset> mDatasets;
private final ArraySet<AutoFillId> 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<AutoFillId> 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<Dataset> mDatasets;
private ArraySet<AutoFillId> 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.
*
* <p>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<AutoFillId> 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));

View File

@@ -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;

View File

@@ -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.
*
* <p>A {@link SaveInfo} is always associated with a {@link FillResponse}.
*
* <p>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...)}.
*
* <p>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<AutoFillId> 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<AutoFillId> getSavableIds() {
return mSavableIds;
}
/** @hide */
public void addSavableIds(@Nullable ArrayList<Dataset> datasets) {
if (datasets != null) {
for (Dataset dataset : datasets) {
final ArrayList<AutoFillId> 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<AutoFillId> 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<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() {
@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<AutoFillId> 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];
}
};
}