Add a way to read the last AutofillSelection.
- Check UID to make sure one service cannot read FillSelections of other
services
- Add id to Dataset to allow to tag the datasets. This id is then found
in the FillSelection.Event
- Add clientState to FillSelection to allow service to store more data
Fixes: 36871500
Test: CtsAutoFillServiceTestCases
Change-Id: Ice894245508227265294a1c59ea97842175e5aec
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 5e49b8f..8fcb87c 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -285,4 +285,22 @@
// TODO(b/33197203): Remove when GCore has migrated off this API
getSystemService(AutofillManager.class).disableOwnedAutofillServices();
}
+
+ /**
+ * Returns the {@link FillEventHistory.Event events} since the last {@link FillResponse} was
+ * returned.
+ *
+ * <p>The history is not persisted over reboots.
+ *
+ * @return The history or {@code null} if there are not events.
+ */
+ @Nullable public final FillEventHistory getFillEventHistory() {
+ AutofillManager afm = getSystemService(AutofillManager.class);
+
+ if (afm == null) {
+ return null;
+ } else {
+ return afm.getFillEventHistory();
+ }
+ }
}
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index e77bd0d..e04fae7 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -24,6 +24,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.widget.RemoteViews;
import com.android.internal.util.Preconditions;
@@ -50,6 +51,7 @@
private final ArrayList<RemoteViews> mFieldPresentations;
private final RemoteViews mPresentation;
private final IntentSender mAuthentication;
+ @Nullable String mId;
private Dataset(Builder builder) {
mFieldIds = builder.mFieldIds;
@@ -57,6 +59,7 @@
mFieldPresentations = builder.mFieldPresentations;
mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
+ mId = builder.mId;
}
/** @hide */
@@ -89,7 +92,7 @@
public String toString() {
if (!DEBUG) return super.toString();
- return new StringBuilder("Dataset [")
+ return new StringBuilder("Dataset " + mId + " [")
.append("fieldIds=").append(mFieldIds)
.append(", fieldValues=").append(mFieldValues)
.append(", fieldPresentations=")
@@ -100,6 +103,17 @@
}
/**
+ * Gets the id of this dataset.
+ *
+ * @return The id of this dataset or {@code null} if not set
+ *
+ * @hide
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
* A builder for {@link Dataset} objects. You must to provide at least
* one value for a field or set an authentication intent.
*/
@@ -110,6 +124,7 @@
private RemoteViews mPresentation;
private IntentSender mAuthentication;
private boolean mDestroyed;
+ @Nullable private String mId;
/**
* Creates a new builder.
@@ -173,6 +188,25 @@
}
/**
+ * Sets the id for the dataset.
+ *
+ * <p>The id of the last selected dataset can be read from
+ * {@link AutofillService#getFillEventHistory()}. If the id is not set it will not be clear
+ * if a dataset was selected as {@link AutofillService#getFillEventHistory()} uses
+ * {@code null} to indicate that no dataset was selected.
+ *
+ * @param id id for this dataset or {@code null} to unset.
+
+ * @return This builder.
+ */
+ public @NonNull Builder setId(@Nullable String id) {
+ throwIfDestroyed();
+
+ mId = id;
+ return this;
+ }
+
+ /**
* Sets the value of a field.
*
* @param id id returned by {@link
@@ -269,6 +303,7 @@
parcel.writeTypedArrayList(mFieldValues, flags);
parcel.writeParcelableList(mFieldPresentations, flags);
parcel.writeParcelable(mAuthentication, flags);
+ parcel.writeString(mId);
}
public static final Creator<Dataset> CREATOR = new Creator<Dataset>() {
@@ -295,6 +330,7 @@
builder.setValueAndPresentation(id, value, fieldPresentation);
}
builder.setAuthentication(parcel.readParcelable(null));
+ builder.setId(parcel.readString());
return builder.build();
}
diff --git a/core/java/android/service/autofill/FillEventHistory.aidl b/core/java/android/service/autofill/FillEventHistory.aidl
new file mode 100644
index 0000000..3c48524
--- /dev/null
+++ b/core/java/android/service/autofill/FillEventHistory.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 FillEventHistory;
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
new file mode 100644
index 0000000..3d72fcc
--- /dev/null
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -0,0 +1,208 @@
+/*
+ * 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 android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Describes what happened after the latest call to {@link FillCallback#onSuccess(FillResponse)}.
+ */
+public final class FillEventHistory implements Parcelable {
+ /**
+ * Not in parcel. The UID of the {@link AutofillService} that created the {@link FillResponse}.
+ */
+ private final int mServiceUid;
+
+ @Nullable private final Bundle mClientState;
+ @Nullable List<Event> mEvents;
+
+ /**
+ * Gets the UID of the {@link AutofillService} that created the {@link FillResponse}.
+ *
+ * @return The UID of the {@link AutofillService}
+ *
+ * @hide
+ */
+ public int getServiceUid() {
+ return mServiceUid;
+ }
+
+ /**
+ * Returns the client state of the {@link FillResponse}.
+ *
+ * @return The client state set by the last {@link FillResponse}
+ */
+ @Nullable public Bundle getClientState() {
+ return mClientState;
+ }
+
+ /**
+ * Returns the events occurred after the latest call to
+ * {@link FillCallback#onSuccess(FillResponse)}.
+ *
+ * @return The list of events or {@code null} if non occurred.
+ */
+ @Nullable public List<Event> getEvents() {
+ return mEvents;
+ }
+
+ /**
+ * @hide
+ */
+ public void addEvent(Event event) {
+ if (mEvents == null) {
+ mEvents = new ArrayList<>(1);
+ }
+ mEvents.add(event);
+ }
+
+ /**
+ * @hide
+ */
+ public FillEventHistory(int serviceUid, @Nullable Bundle clientState) {
+ mClientState = clientState;
+ mServiceUid = serviceUid;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBundle(mClientState);
+
+ if (mEvents == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(mEvents.size());
+
+ int numEvents = mEvents.size();
+ for (int i = 0; i < numEvents; i++) {
+ Event event = mEvents.get(i);
+ dest.writeInt(event.getType());
+ dest.writeString(event.getDatasetId());
+ }
+ }
+ }
+
+ /**
+ * Description of an event that occured after the latest call to
+ * {@link FillCallback#onSuccess(FillResponse)}.
+ */
+ public static final class Event {
+ /**
+ * A dataset was selected. The dataset selected can be read from {@link #getDatasetId()}.
+ */
+ public static final int TYPE_DATASET_SELECTED = 0;
+
+ /**
+ * A {@link Dataset.Builder#setAuthentication(IntentSender) dataset authentication} was
+ * selected. The dataset authenticated can be read from {@link #getDatasetId()}.
+ */
+ public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1;
+
+ /**
+ * A {@link FillResponse.Builder#setAuthentication(AutofillId[], IntentSender, RemoteViews)
+ * fill response authentication} was selected.
+ */
+ public static final int TYPE_AUTHENTICATION_SELECTED = 2;
+
+ /** A save UI was shown. */
+ public static final int TYPE_SAVE_SHOWN = 3;
+
+ /** @hide */
+ @IntDef(
+ value = {TYPE_DATASET_SELECTED,
+ TYPE_DATASET_AUTHENTICATION_SELECTED,
+ TYPE_AUTHENTICATION_SELECTED,
+ TYPE_SAVE_SHOWN})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface EventIds{}
+
+ @EventIds private final int mEventType;
+ @Nullable private final String mDatasetId;
+
+ /**
+ * Returns the type of the event.
+ *
+ * @return The type of the event
+ */
+ public int getType() {
+ return mEventType;
+ }
+
+ /**
+ * Returns the id of dataset the id was on.
+ *
+ * @return The id of dataset, or {@code null} the event is not associated with a dataset.
+ */
+ @Nullable public String getDatasetId() {
+ return mDatasetId;
+ }
+
+ /**
+ * Creates a new event.
+ *
+ * @param eventType The type of the event
+ * @param datasetId The dataset the event was on, or {@code null} if the event was on the
+ * whole response.
+ *
+ * @hide
+ */
+ public Event(int eventType, String datasetId) {
+ mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_SAVE_SHOWN,
+ "eventType");
+ mDatasetId = datasetId;
+ }
+ }
+
+ public static final Parcelable.Creator<FillEventHistory> CREATOR =
+ new Parcelable.Creator<FillEventHistory>() {
+ @Override
+ public FillEventHistory createFromParcel(Parcel parcel) {
+ FillEventHistory selection = new FillEventHistory(0, parcel.readBundle());
+
+ int numEvents = parcel.readInt();
+ for (int i = 0; i < numEvents; i++) {
+ selection.addEvent(new Event(parcel.readInt(), parcel.readString()));
+ }
+
+ return selection;
+ }
+
+ @Override
+ public FillEventHistory[] newArray(int size) {
+ return new FillEventHistory[size];
+ }
+ };
+}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index f9f400d..34e3056 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -31,6 +31,8 @@
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillEventHistory;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -334,6 +336,20 @@
}
/**
+ * Should always be called from {@link AutofillService#getFillEventHistory()}.
+ *
+ * @hide
+ */
+ @Nullable public FillEventHistory getFillEventHistory() {
+ try {
+ return mService.getFillEventHistory();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
* Explicitly requests a new autofill context.
*
* <p>Normally, the autofill context is automatically started when autofillable views are
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 68b3ccabc..df777c4 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -19,6 +19,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
+import android.service.autofill.FillEventHistory;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
@@ -33,6 +34,7 @@
int startSession(IBinder activityToken, IBinder windowToken, in IBinder appCallback,
in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId,
boolean hasCallback, int flags, String packageName);
+ FillEventHistory getFillEventHistory();
boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback);
void setWindow(int sessionId, in IBinder windowToken);
void updateSession(int sessionId, in AutofillId id, in Rect bounds,