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:
@@ -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));
|
||||
|
||||
19
core/java/android/service/autofill/SaveInfo.aidl
Normal file
19
core/java/android/service/autofill/SaveInfo.aidl
Normal 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;
|
||||
236
core/java/android/service/autofill/SaveInfo.java
Normal file
236
core/java/android/service/autofill/SaveInfo.java
Normal 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user