From 00c771dc7d8242362f1491ae4ce3efd641235b36 Mon Sep 17 00:00:00 2001 From: Svet Ganov Date: Sun, 19 Feb 2017 00:06:22 -0800 Subject: [PATCH] Add support for remote views backed auto-fill UI Test: CTS and manual Change-Id: Ia09f0208d78439491052e4886a0e71c387b73370 --- api/current.txt | 6 +- api/system-current.txt | 6 +- api/test-current.txt | 6 +- core/java/android/app/Activity.java | 3 +- .../android/service/autofill/Dataset.java | 72 +++--- .../service/autofill/FillResponse.java | 162 +++++++----- .../android/view/autofill/AutoFillValue.java | 7 +- .../res/layout/autofill_dataset_picker.xml | 23 ++ core/res/res/values/symbols.xml | 1 + .../autofill/AutoFillManagerServiceImpl.java | 58 ++--- .../android/server/autofill/AutoFillUI.java | 241 +++++++----------- .../server/autofill/DatasetPicker.java | 85 +++--- .../com/android/server/autofill/Helper.java | 24 -- .../android/server/autofill/SavePrompt.java | 3 - .../android/server/autofill/SignInPrompt.java | 38 --- 15 files changed, 350 insertions(+), 385 deletions(-) create mode 100644 core/res/res/layout/autofill_dataset_picker.xml delete mode 100644 services/autofill/java/com/android/server/autofill/SignInPrompt.java diff --git a/api/current.txt b/api/current.txt index 7f7474c539b02..81f5f434d113b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -36246,9 +36246,10 @@ package android.service.autofill { } public static final class Dataset.Builder { - ctor public Dataset.Builder(java.lang.CharSequence); + ctor public Dataset.Builder(); method public android.service.autofill.Dataset build(); method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender); + method public android.service.autofill.Dataset.Builder setPresentation(android.widget.RemoteViews); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue); } @@ -36270,6 +36271,7 @@ package android.service.autofill { method public android.service.autofill.FillResponse build(); method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender); method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle); + method public android.service.autofill.FillResponse.Builder setPresentation(android.widget.RemoteViews); } public final class SaveCallback { @@ -51238,7 +51240,7 @@ package dalvik.system { method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException; } - public final class InMemoryDexClassLoader extends java.lang.ClassLoader { + public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader { ctor public InMemoryDexClassLoader(java.nio.ByteBuffer, java.lang.ClassLoader); } diff --git a/api/system-current.txt b/api/system-current.txt index 4aca489e49ab8..6a5aceb5b7d32 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -39326,9 +39326,10 @@ package android.service.autofill { } public static final class Dataset.Builder { - ctor public Dataset.Builder(java.lang.CharSequence); + ctor public Dataset.Builder(); method public android.service.autofill.Dataset build(); method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender); + method public android.service.autofill.Dataset.Builder setPresentation(android.widget.RemoteViews); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue); } @@ -39350,6 +39351,7 @@ package android.service.autofill { method public android.service.autofill.FillResponse build(); method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender); method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle); + method public android.service.autofill.FillResponse.Builder setPresentation(android.widget.RemoteViews); } public final class SaveCallback { @@ -55107,7 +55109,7 @@ package dalvik.system { method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException; } - public final class InMemoryDexClassLoader extends java.lang.ClassLoader { + public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader { ctor public InMemoryDexClassLoader(java.nio.ByteBuffer, java.lang.ClassLoader); } diff --git a/api/test-current.txt b/api/test-current.txt index 9f7b8e903619f..647d5d240177f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -36385,9 +36385,10 @@ package android.service.autofill { } public static final class Dataset.Builder { - ctor public Dataset.Builder(java.lang.CharSequence); + ctor public Dataset.Builder(); method public android.service.autofill.Dataset build(); method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender); + method public android.service.autofill.Dataset.Builder setPresentation(android.widget.RemoteViews); method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue); } @@ -36409,6 +36410,7 @@ package android.service.autofill { method public android.service.autofill.FillResponse build(); method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender); method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle); + method public android.service.autofill.FillResponse.Builder setPresentation(android.widget.RemoteViews); } public final class SaveCallback { @@ -51617,7 +51619,7 @@ package dalvik.system { method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException; } - public final class InMemoryDexClassLoader extends java.lang.ClassLoader { + public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader { ctor public InMemoryDexClassLoader(java.nio.ByteBuffer, java.lang.ClassLoader); } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index c56772ead97ba..6fc60e9eaaf22 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -7039,7 +7039,8 @@ public class Activity extends ContextThemeWrapper } } } else if (who.startsWith(AUTO_FILL_AUTH_WHO_PREFIX)) { - getSystemService(AutoFillManager.class).onAuthenticationResult(data); + Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null; + getSystemService(AutoFillManager.class).onAuthenticationResult(resultData); } else { Fragment frag = mFragments.findFragmentByWho(who); if (frag != null) { diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index d06f528777cb3..59b494c3f8a60 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -25,7 +25,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.view.autofill.AutoFillId; import android.view.autofill.AutoFillValue; - +import android.widget.RemoteViews; import com.android.internal.util.Preconditions; import java.util.ArrayList; @@ -36,32 +36,27 @@ import java.util.ArrayList; *

It contains: * *

    - *
  1. A name used to identify the dataset in the UI. - *
  2. A list of id/value pairs for the fields that can be auto-filled. - *
  3. A list of savable ids in addition to the ones with a provided value. + *
  4. A list of values for input fields. + *
  5. A presentation view to visualize. + *
  6. An optional intent to authenticate. *
* * @see android.service.autofill.FillResponse for examples. */ public final class Dataset implements Parcelable { - private final CharSequence mName; private final ArrayList mFieldIds; private final ArrayList mFieldValues; + private final RemoteViews mPresentation; private final IntentSender mAuthentication; private Dataset(Builder builder) { - mName = builder.mName; mFieldIds = builder.mFieldIds; mFieldValues = builder.mFieldValues; + mPresentation = builder.mPresentation; mAuthentication = builder.mAuthentication; } - /** @hide */ - public @NonNull CharSequence getName() { - return mName; - } - /** @hide */ public @Nullable ArrayList getFieldIds() { return mFieldIds; @@ -72,6 +67,11 @@ public final class Dataset implements Parcelable { return mFieldValues; } + /** @hide */ + public @Nullable RemoteViews getPresentation() { + return mPresentation; + } + /** @hide */ public @Nullable IntentSender getAuthentication() { return mAuthentication; @@ -86,11 +86,12 @@ public final class Dataset implements Parcelable { public String toString() { if (!DEBUG) return super.toString(); - final StringBuilder builder = new StringBuilder("Dataset [name=").append(mName) + return new StringBuilder("Dataset [") .append(", fieldIds=").append(mFieldIds) .append(", fieldValues=").append(mFieldValues) - .append(", hasAuthentication=").append(mAuthentication != null); - return builder.append(']').toString(); + .append(", hasPresentation=").append(mPresentation != null) + .append(", hasAuthentication=").append(mAuthentication != null) + .append(']').toString(); } /** @@ -98,21 +99,22 @@ public final class Dataset implements Parcelable { * one value for a field or set an authentication intent. */ public static final class Builder { - private CharSequence mName; private ArrayList mFieldIds; private ArrayList mFieldValues; + private RemoteViews mPresentation; private IntentSender mAuthentication; private boolean mDestroyed; /** - * Creates a new builder. + * Sets the presentation used to visualize this dataset. * - * @param name Name used to identify the dataset in the UI. Typically it's the same value as - * the first field in the dataset (like username or email address) or a user-provided name - * (like "My Work Address"). + * @param presentation The presentation view. + * + * @return This builder. */ - public Builder(@NonNull CharSequence name) { - mName = Preconditions.checkStringNotEmpty(name, "name cannot be empty or null"); + public @NonNull Builder setPresentation(@Nullable RemoteViews presentation) { + mPresentation = presentation; + return this; } /** @@ -121,7 +123,7 @@ public final class Dataset implements Parcelable { *

This method is called when you need to provide an authentication * UI for the data set. For example, when a data set contains credit card information * (such as number, expiration date, and verification code), you can display UI - * asking for the verification code to before filing in the data). Even if the + * asking for the verification code before filing in the data. Even if the * data set is completely populated the system will launch the specified authentication * intent and will need your approval to fill it in. Since the data set is "locked" * until the user authenticates it, typically this data set name is masked @@ -138,7 +140,7 @@ public final class Dataset implements Parcelable { * android.app.Activity#RESULT_OK} and provide the fully populated {@link Dataset * dataset} by setting it to the {@link * android.view.autofill.AutoFillManager#EXTRA_AUTHENTICATION_RESULT} extra. For example, - * if you provided an credit card information without the CVV for the data set in the + * if you provided credit card information without the CVV for the data set in the * {@link FillResponse response} then the returned data set should contain the * CVV entry.

* @@ -147,6 +149,7 @@ public final class Dataset implements Parcelable { * platform needs to fill in the authentication arguments.

* * @param authentication Intent to an activity with your authentication flow. + * @return This builder. * * @see android.app.PendingIntent */ @@ -162,6 +165,7 @@ public final class Dataset implements Parcelable { * @param id id returned by {@link * android.app.assist.AssistStructure.ViewNode#getAutoFillId()}. * @param value value to be auto filled. + * @return This builder. */ public @NonNull Builder setValue(@NonNull AutoFillId id, @NonNull AutoFillValue value) { throwIfDestroyed(); @@ -184,14 +188,21 @@ public final class Dataset implements Parcelable { /** * Creates a new {@link Dataset} instance. You should not interact - * with this builder once this method is called. + * with this builder once this method is called. It is required + * that you specified at least one field. Also it is mandatory to + * provide a presentation view to visualize the data set in the UI. + * + * @return The built dataset. */ public @NonNull Dataset build() { throwIfDestroyed(); mDestroyed = true; - if (mFieldIds == null && mAuthentication == null) { + if (mFieldIds == null) { throw new IllegalArgumentException( - "at least one value or an authentication must be set"); + "at least one value must be set"); + } + if (mPresentation == null) { + throw new IllegalArgumentException("presentation must be set"); } return new Dataset(this); } @@ -214,9 +225,9 @@ public final class Dataset implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeCharSequence(mName); - parcel.writeTypedArrayList(mFieldIds, 0); - parcel.writeTypedArrayList(mFieldValues, 0); + parcel.writeTypedArrayList(mFieldIds, flags); + parcel.writeTypedArrayList(mFieldValues, flags); + parcel.writeParcelable(mPresentation, flags); parcel.writeParcelable(mAuthentication, flags); } @@ -226,7 +237,7 @@ public final class Dataset implements Parcelable { // 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.readCharSequence()); + final Builder builder = new Builder(); final ArrayList ids = parcel.readTypedArrayList(null); final ArrayList values = parcel.readTypedArrayList(null); final int idCount = (ids != null) ? ids.size() : 0; @@ -236,6 +247,7 @@ public final class Dataset implements Parcelable { AutoFillValue value = (valueCount > i) ? values.get(i) : null; builder.setValue(id, value); } + builder.setPresentation(parcel.readParcelable(null)); builder.setAuthentication(parcel.readParcelable(null)); return builder.build(); } diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index fe78973dce876..bac43915281a9 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -26,12 +26,12 @@ import android.os.Parcelable; import android.util.ArraySet; import android.view.autofill.AutoFillId; import android.view.autofill.AutoFillManager; +import android.widget.RemoteViews; /** * Response for a {@link * AutoFillService#onFillRequest(android.app.assist.AssistStructure, - * Bundle, android.os.CancellationSignal, FillCallback)} and - * authentication requests. + * Bundle, android.os.CancellationSignal, FillCallback)}. * *

The response typically contains one or more {@link Dataset}s, each representing a set of * fields that can be auto-filled together, and the Android system displays a dataset picker UI @@ -43,7 +43,8 @@ import android.view.autofill.AutoFillManager; * *

  *  new FillResponse.Builder()
- *      .add(new Dataset.Builder("homer")
+ *      .add(new Dataset.Builder()
+ *          .setPresentation(createPresentation())
  *          .setTextFieldValue(id1, "homer")
  *          .setTextFieldValue(id2, "D'OH!")
  *          .build())
@@ -54,11 +55,13 @@ import android.view.autofill.AutoFillManager;
  *
  * 
  *  new FillResponse.Builder()
- *      .add(new Dataset.Builder("Homer's Account")
+ *      .add(new Dataset.Builder()
+ *          .setPresentation(createFirstPresentation())
  *          .setTextFieldValue(id1, "homer")
  *          .setTextFieldValue(id2, "D'OH!")
  *          .build())
- *      .add(new Dataset.Builder("Bart's Account")
+ *      .add(new Dataset.Builder()
+ *          .setPresentation(createSecondPresentation())
  *          .setTextFieldValue(id1, "elbarto")
  *          .setTextFieldValue(id2, "cowabonga")
  *          .build())
@@ -82,7 +85,8 @@ import android.view.autofill.AutoFillManager;
  *
  * 
  *   new FillResponse.Builder()
- *       .add(new Dataset.Builder("Homer")
+ *       .add(new Dataset.Builder(")
+ *          .setPresentation(createPresentation())
  *          .setTextFieldValue(id1, "Homer")                  // first name
  *          .setTextFieldValue(id2, "Simpson")                // last name
  *          .setTextFieldValue(id3, "742 Evergreen Terrace")  // street
@@ -110,27 +114,31 @@ import android.view.autofill.AutoFillManager;
  *
  * 
  *  new FillResponse.Builder()
- *      .add(new Dataset.Builder("Homer")
+ *      .add(new Dataset.Builder()
+ *          .setPresentation(createFirstPresentation())
  *          .setTextFieldValue(id1, "Homer")
  *          .setTextFieldValue(id2, "Simpson")
  *          .build())
- *      .add(new Dataset.Builder("Bart")
+ *      .add(new Dataset.Builder()
+ *          .setPresentation(createSecondPresentation())
  *          .setTextFieldValue(id1, "Bart")
  *          .setTextFieldValue(id2, "Simpson")
  *          .build())
  *      .build();
  * 
* - *

Then after the user picks the {@code Homer} dataset and taps the {@code Street} field to + *

Then after the user picks the second dataset and taps the street field to * trigger another auto-fill request, the second response could be: * *

  *  new FillResponse.Builder()
- *      .add(new Dataset.Builder("Home")
+ *      .add(new Dataset.Builder()
+ *          .setPresentation(createThirdPresentation())
  *          .setTextFieldValue(id3, "742 Evergreen Terrace")
  *          .setTextFieldValue(id4, "Springfield")
  *          .build())
- *      .add(new Dataset.Builder("Work")
+ *      .add(new Dataset.Builder()
+ *          .setPresentation(createFourthPresentation())
  *          .setTextFieldValue(id3, "Springfield Power Plant")
  *          .setTextFieldValue(id4, "Springfield")
  *          .build())
@@ -141,29 +149,31 @@ import android.view.autofill.AutoFillManager;
  * {@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
- * which would allow you to provide the dataset names to the user and if they choose one
- * them challenge the user to onAuthenticate. 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 have any sensitive
- * data while the address data is in an encrypted storage. If the user chooses Home, then the
- * platform will start your authentication flow. If you encrypt all data and require auth
- * at the response level the user will have to interact with the fill UI to trigger a request
- * for the datasets as they don't see Home and Work options which will trigger your auth
- * flow and after successfully authenticating the user will be presented with the Home and
- * Work options where they can pick one. Hence, you have flexibility how to implement your
- * auth while storing labels non-encrypted and data encrypted provides a better user
- * experience.

+ * 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 + * have any sensitive data while the address data is in an encrypted storage. If the user + * chooses Home, then the platform will start your authentication flow. If you encrypt all + * data and require auth at the response level the user will have to interact with the fill + * UI to trigger a request for the datasets (as they don't see the presentation views for the + * possible options) which will start your auth flow and after successfully authenticating + * the user will be presented with the Home and Work options to pick one. Hence, you have + * flexibility how to implement your auth while storing labels non-encrypted and data + * encrypted provides a better user experience.

*/ public final class FillResponse implements Parcelable { private final ArraySet mDatasets; private final ArraySet mSavableIds; private final Bundle mExtras; + private final RemoteViews mPresentation; private final IntentSender mAuthentication; private FillResponse(@NonNull Builder builder) { mDatasets = builder.mDatasets; mSavableIds = builder.mSavableIds; mExtras = builder.mExtras; + mPresentation = builder.mPresentation; mAuthentication = builder.mAuthentication; } @@ -182,6 +192,11 @@ public final class FillResponse implements Parcelable { return mSavableIds; } + /** @hide */ + public @Nullable RemoteViews getPresentation() { + return mPresentation; + } + /** @hide */ public @Nullable IntentSender getAuthentication() { return mAuthentication; @@ -189,20 +204,31 @@ public final class FillResponse implements Parcelable { /** * Builder for {@link FillResponse} objects. You must to provide at least - * one dataset or set an authentication intent. + * one dataset or set an authentication intent with a presentation view. */ public static final class Builder { private ArraySet mDatasets; private ArraySet mSavableIds; private Bundle mExtras; + private RemoteViews mPresentation; private IntentSender mAuthentication; private boolean mDestroyed; /** - * Creates a new {@link FillResponse} builder. + * Sets the presentation used to visualize this response. You should + * set this only if you need an authentication as this is the only + * case the response needs to be presented to the user. + * + * @param presentation The presentation view. + * + * @return This builder. + * + * @see #setAuthentication(IntentSender) */ - public Builder() { - + public @NonNull + FillResponse.Builder setPresentation(@Nullable RemoteViews presentation) { + mPresentation = presentation; + return this; } /** @@ -215,14 +241,15 @@ public final class FillResponse implements Parcelable { * auth on the data set level leading to a better user experience. Note that if you * use sensitive data as a label, for example an email address, then it should also * be encrypted. The provided {@link android.app.PendingIntent intent} must be an - * activity which implements your authentication flow.

+ * activity which implements your authentication flow. Also if you provide an auth + * intent you also need to specify the presentation view to be shown in the fill UI + * for the user to trigger your authentication flow.

* *

When a user triggers auto-fill, the system launches the provided intent - * whose extras will have the {@link - * AutoFillManager#EXTRA_ASSIST_STRUCTURE screen + * whose extras will have the {@link AutoFillManager#EXTRA_ASSIST_STRUCTURE screen * content}. Once you complete your authentication flow you should set the activity - * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated {@link - * FillResponse response} by setting it to the {@link + * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated + * {@link FillResponse response} by setting it to the {@link * AutoFillManager#EXTRA_AUTHENTICATION_RESULT} extra. * For example, if you provided an empty {@link FillResponse resppnse} because the * user's data was locked and marked that the response needs an authentication then @@ -235,8 +262,10 @@ public final class FillResponse implements Parcelable { * platform needs to fill in the authentication arguments.

* * @param authentication Intent to an activity with your authentication flow. + * @return This builder. * * @see android.app.PendingIntent#getIntentSender() + * @see #setPresentation(RemoteViews) */ public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) { throwIfDestroyed(); @@ -245,10 +274,9 @@ public final class FillResponse implements Parcelable { } /** - * Adds a new {@link Dataset} to this response. Adding a dataset with the - * same id updates the existing one. + * Adds a new {@link Dataset} to this response. * - * @throws IllegalArgumentException if a dataset with same {@code name} already exists. + * @return This builder. */ public@NonNull Builder addDataset(@Nullable Dataset dataset) { throwIfDestroyed(); @@ -258,23 +286,18 @@ public final class FillResponse implements Parcelable { if (mDatasets == null) { mDatasets = new ArraySet<>(); } - final int datasetCount = mDatasets.size(); - for (int i = 0; i < datasetCount; i++) { - if (mDatasets.valueAt(i).getName().equals(dataset.getName())) { - throw new IllegalArgumentException("Duplicate dataset name: " - + dataset.getName()); - } - } if (!mDatasets.add(dataset)) { return this; } - 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<>(); + 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); } - mSavableIds.add(id); } return this; } @@ -285,7 +308,10 @@ public final class FillResponse implements Parcelable { * android.app.assist.AssistStructure, Bundle, SaveCallback)}) * but were not indirectly set through {@link #addDataset(Dataset)}. * - *

See {@link FillResponse} for examples. + * @param ids The savable ids. + * @return This builder. + * + * @see FillResponse */ public @NonNull Builder addSavableFields(@Nullable AutoFillId... ids) { throwIfDestroyed(); @@ -306,10 +332,11 @@ public final class FillResponse implements Parcelable { * manipulate this response. For example, they are passed to subsequent * calls to {@link AutoFillService#onFillRequest( * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, - * FillCallback)} and {@link - * AutoFillService#onSaveRequest( - * android.app.assist.AssistStructure, Bundle, - * SaveCallback)}. + * FillCallback)} and {@link AutoFillService#onSaveRequest( + * android.app.assist.AssistStructure, Bundle, SaveCallback)}. + * + * @param extras The response extras. + * @return This builder. */ public Builder setExtras(Bundle extras) { throwIfDestroyed(); @@ -318,10 +345,22 @@ public final class FillResponse implements Parcelable { } /** - * Builds a new {@link FillResponse} instance. + * Builds a new {@link FillResponse} instance. You must provide at least + * one dataset or some savable ids or an authentication with a presentation + * view. + * + * @return A built response. */ public FillResponse build() { throwIfDestroyed(); + if (mAuthentication == null ^ mPresentation == null) { + throw new IllegalArgumentException("authentication and presentation" + + " must be both non-null or null"); + } + 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"); + } mDestroyed = true; return new FillResponse(this); } @@ -339,12 +378,13 @@ public final class FillResponse implements Parcelable { @Override public String toString() { if (!DEBUG) return super.toString(); - final StringBuilder builder = new StringBuilder( + return new StringBuilder( "FillResponse: [datasets=").append(mDatasets) .append(", savableIds=").append(mSavableIds) .append(", hasExtras=").append(mExtras != null) - .append(", hasAuthentication=").append(mAuthentication != null); - return builder.append(']').toString(); + .append(", hasPresentation=").append(mPresentation != null) + .append(", hasAuthentication=").append(mAuthentication != null) + .toString(); } ///////////////////////////////////// @@ -358,10 +398,11 @@ public final class FillResponse implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeTypedArraySet(mDatasets, 0); - parcel.writeTypedArraySet(mSavableIds, 0); - parcel.writeParcelable(mExtras, 0); - parcel.writeParcelable(mAuthentication, 0); + parcel.writeTypedArraySet(mDatasets, flags); + parcel.writeTypedArraySet(mSavableIds, flags); + parcel.writeParcelable(mExtras, flags); + parcel.writeParcelable(mPresentation, flags); + parcel.writeParcelable(mAuthentication, flags); } public static final Parcelable.Creator CREATOR = @@ -383,6 +424,7 @@ public final class FillResponse implements Parcelable { builder.addSavableFields(fillIds.valueAt(i)); } builder.setExtras(parcel.readParcelable(null)); + builder.setPresentation(parcel.readParcelable(null)); builder.setAuthentication(parcel.readParcelable(null)); return builder.build(); } diff --git a/core/java/android/view/autofill/AutoFillValue.java b/core/java/android/view/autofill/AutoFillValue.java index af7036130cd8e..d9afa3ba2dee5 100644 --- a/core/java/android/view/autofill/AutoFillValue.java +++ b/core/java/android/view/autofill/AutoFillValue.java @@ -32,7 +32,6 @@ import android.view.View; * {@code sub-type} define its semantics (like a postal address). */ public final class AutoFillValue implements Parcelable { - private final String mText; private final int mListIndex; private final boolean mToggle; @@ -100,6 +99,12 @@ public final class AutoFillValue implements Parcelable { return true; } + /** @hide */ + public String coerceToString() { + // TODO(b/33197203): How can we filter on toggles or list values? + return mText; + } + @Override public String toString() { if (!DEBUG) return super.toString(); diff --git a/core/res/res/layout/autofill_dataset_picker.xml b/core/res/res/layout/autofill_dataset_picker.xml new file mode 100644 index 0000000000000..40cce7b04d875 --- /dev/null +++ b/core/res/res/layout/autofill_dataset_picker.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e3156a3504c8e..89ded8d5fbe69 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2840,6 +2840,7 @@ + diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java index a697a8ee5c4ac..081acf5a94d9d 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java @@ -451,7 +451,7 @@ final class AutoFillManagerServiceImpl { * Called when the fill UI is ready to be shown for this view. */ void onFillReady(ViewState viewState, FillResponse fillResponse, Rect bounds, - @Nullable AutoFillValue value); + AutoFillId focusedId, @Nullable AutoFillValue value); } final AutoFillId mId; @@ -509,12 +509,14 @@ final class AutoFillManagerServiceImpl { } /** - * Calls {@link Listener#onFillReady(ViewState, FillResponse, Rect, AutoFillValue)} if the + * Calls {@link + * Listener#onFillReady(ViewState, FillResponse, Rect, AutoFillId, AutoFillValue)} if the * fill UI is ready to be displayed (i.e. when response and bounds are set). */ void maybeCallOnFillReady() { - if (mResponse != null && mBounds != null) { - mListener.onFillReady(this, mResponse, mBounds, mAutoFillValue); + if (mResponse != null && (mResponse.getAuthentication() != null + || mResponse.getDatasets() != null) && mBounds != null) { + mListener.onFillReady(this, mResponse, mBounds, mId, mAutoFillValue); } } @@ -641,8 +643,14 @@ final class AutoFillManagerServiceImpl { // FillServiceCallbacks @Override - public void authenticate(IntentSender intent, Intent fillInIntent) { - startAuthentication(intent, fillInIntent); + public void authenticate(IntentSender intent) { + final Intent fillInIntent; + synchronized (mLock) { + fillInIntent = createAuthFillInIntent(mStructure); + } + mHandlerCaller.getHandler().post(() -> { + startAuthentication(intent, fillInIntent); + }); } // FillServiceCallbacks @@ -675,15 +683,10 @@ final class AutoFillManagerServiceImpl { processResponseLocked(mCurrentResponse); } else if (result instanceof Dataset) { Dataset dataset = (Dataset) result; - final int datasetIndex = Helper.indexOfDataset( - dataset.getName(), mCurrentResponse); - if (datasetIndex <= 0) { - Slog.e(TAG, "Response for a dataset auth has" - + " an invalid dataset result: " + dataset.getName()); - } - mCurrentResponse.getDatasets().removeAt(datasetIndex); + mCurrentResponse.getDatasets().remove(mAutoFilledDataset); mCurrentResponse.getDatasets().add(dataset); - autoFill(dataset); + mAutoFilledDataset = dataset; + processResponseLocked(mCurrentResponse); } } } @@ -727,6 +730,7 @@ final class AutoFillManagerServiceImpl { return; } } + // Nothing changed... if (DEBUG) Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities"); } @@ -802,8 +806,11 @@ final class AutoFillManagerServiceImpl { } } - // Just change value, don't update the UI + // Change value viewState.mAutoFillValue = value; + + // Update the chooser UI + mUi.updateFillUi(value.coerceToString()); return; } @@ -838,7 +845,7 @@ final class AutoFillManagerServiceImpl { @Override public void onFillReady(ViewState viewState, FillResponse response, Rect bounds, - @Nullable AutoFillValue value) { + AutoFillId filledId, @Nullable AutoFillValue value) { String filterText = ""; if (value != null) { // TODO(b/33197203): Handle other AutoFillValue types @@ -848,8 +855,7 @@ final class AutoFillManagerServiceImpl { } } - getUiForShowing().showFillUi(mActivityToken, viewState, response.getDatasets(), - bounds, filterText); + getUiForShowing().showFillUi(filledId, response, bounds, filterText); } private void processResponseLocked(FillResponse response) { @@ -869,26 +875,17 @@ final class AutoFillManagerServiceImpl { if (mCurrentResponse.getAuthentication() != null) { // Handle authentication. final Intent fillInIntent = createAuthFillInIntent(mStructure); - mCurrentViewState.setResponse(mCurrentResponse, fillInIntent); return; } - final ArraySet savableIds = mCurrentResponse.getSavableIds(); - if (savableIds == null || savableIds.isEmpty()) { - // NOTE: it's assuming the response has no datasets, since when a dataset is added - // it's view id is automatically added to savable_ids - if (DEBUG) Slog.d(TAG, "processResponseLocked(): nothing to do"); - - removeSelf(); - return; - } - mCurrentViewState.setResponse(mCurrentResponse); } void autoFill(Dataset dataset) { synchronized (mLock) { + mAutoFilledDataset = dataset; + // Autofill it directly... if (dataset.getAuthentication() == null) { autoFillApp(dataset); @@ -948,9 +945,7 @@ final class AutoFillManagerServiceImpl { synchronized (mLock) { try { if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); - mClient.autoFill(dataset.getFieldIds(), dataset.getFieldValues()); - mAutoFilledDataset = dataset; } catch (RemoteException e) { Slog.w(TAG, "Error auto-filling activity: " + e); } @@ -999,7 +994,6 @@ final class AutoFillManagerServiceImpl { private void destroyLocked() { mRemoteFillService.destroy(); - mUi.hideAll(); mUi.setCallbackLocked(null, null); } diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java index e83dc1e90137d..78dd247f2a027 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java @@ -18,32 +18,23 @@ package com.android.server.autofill; import static com.android.server.autofill.Helper.DEBUG; import android.annotation.Nullable; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.StatusBarManager; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.IntentSender; import android.graphics.Rect; -import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.service.autofill.Dataset; -import android.util.ArraySet; -import android.os.Looper; +import android.service.autofill.FillResponse; import android.text.format.DateUtils; import android.util.Slog; import android.view.Gravity; import android.view.View; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import android.view.autofill.AutoFillId; import android.widget.Toast; -import com.android.internal.os.HandlerCaller; import com.android.server.UiThread; -import com.android.server.autofill.AutoFillManagerServiceImpl.ViewState; import java.io.PrintWriter; @@ -53,36 +44,24 @@ import java.io.PrintWriter; // TODO(b/33197203): document exactly what once the auto-fill bar is implemented final class AutoFillUI { private static final String TAG = "AutoFillUI"; - private static final long SNACK_BAR_LIFETIME_MS = 30 * DateUtils.SECOND_IN_MILLIS; + + private static final long SNACK_BAR_LIFETIME_MS = 5 * DateUtils.SECOND_IN_MILLIS; + private static final int MSG_HIDE_SNACK_BAR = 1; + private final Handler mHandler = UiThread.getHandler(); + private final Context mContext; + private final WindowManager mWm; - // TODO(b/33197203) Fix locking - some state requires lock and some not - requires refactoring - private final Object mLock = new Object(); - - // Fill UI variables private AnchoredWindow mFillWindow; - private View mFillView; - private ViewState mViewState; + + private DatasetPicker mDatasetPicker; private AutoFillUiCallback mCallback; - private IBinder mActivityToken; - private final HandlerCaller.Callback mHandlerCallback = (msg) -> { - switch (msg.what) { - case MSG_HIDE_SNACK_BAR: { - hideSnackbarUiThread(); - return; - } - default: { - Slog.w(TAG, "Invalid message: " + msg); - } - } - }; - private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), - mHandlerCallback, true); + private IBinder mActivityToken; /** * Custom snackbar UI used for saving autofill or other informational messages. @@ -95,32 +74,32 @@ final class AutoFillUI { } void setCallbackLocked(AutoFillUiCallback callback, IBinder activityToken) { - hideAll(); - mCallback = callback; - mActivityToken = activityToken; + mHandler.post(() -> { + hideAllUiThread(); + mCallback = callback; + mActivityToken = activityToken; + }); } /** * Displays an error message to the user. */ void showError(CharSequence message) { - if (!hasCallback()) { - return; - } - hideAll(); // TODO(b/33197203): proper implementation - UiThread.getHandler().runWithScissors(() -> { + UiThread.getHandler().post(() -> { + if (!hasCallback()) { + return; + } + hideAllUiThread(); Toast.makeText(mContext, "AutoFill error: " + message, Toast.LENGTH_LONG).show(); - }, 0); + }); } /** * Hides the fill UI. */ void hideFillUi() { - UiThread.getHandler().runWithScissors(() -> { - hideFillUiUiThread(); - }, 0); + mHandler.post(() -> hideFillUiUiThread()); } @android.annotation.UiThread @@ -129,111 +108,80 @@ final class AutoFillUI { if (DEBUG) Slog.d(TAG, "hideFillUiUiThread(): hide" + mFillWindow); mFillWindow.hide(); } - - mViewState = null; - mFillView = null; mFillWindow = null; + mDatasetPicker = null; + } + + void updateFillUi(@Nullable String filterText) { + mHandler.post(() -> { + if (!hasCallback()) { + return; + } + hideSnackbarUiThread(); + if (mDatasetPicker != null) { + mDatasetPicker.update(filterText); + } + }); } /** * Shows the fill UI, removing the previous fill UI if the has changed. * - * @param appToken the token of the app to be autofilled - * @param viewState the view state, compared by reference to know if new UI should be shown - * @param datasets the datasets to show, not used if viewState is the same + * @param focusedId the currently focused field + * @param response the current fill response * @param bounds bounds of the view to be filled, used if changed * @param filterText text of the view to be filled, used if changed */ - void showFillUi(IBinder appToken, ViewState viewState, @Nullable ArraySet datasets, - Rect bounds, String filterText) { - if (!hasCallback()) { - return; - } - - UiThread.getHandler().runWithScissors(() -> { + void showFillUi(AutoFillId focusedId, @Nullable FillResponse response, Rect bounds, + String filterText) { + mHandler.post(() -> { + if (!hasCallback()) { + return; + } hideSnackbarUiThread(); - }, 0); - - if (datasets == null && viewState.mAuthIntent == null) { - // TODO(b/33197203): shouldn't be called, but keeping the WTF for a while just to be - // safe, otherwise it would crash system server... - Slog.wtf(TAG, "showFillUI(): no dataset"); - return; - } - - // TODO(b/33197203): should not display UI after we launched an authentication intent, since - // we have no warranty the provider will call onFailure() if the authentication failed or - // user dismissed the auth window - // because if the service does not handle calling the callback, - - - UiThread.getHandler().runWithScissors(() -> { - // The dataset picker is only shown when authentication is not required... - DatasetPicker datasetPicker = null; - - if (mViewState == null || !mViewState.mId.equals(viewState.mId)) { - hideFillUiUiThread(); - - mViewState = viewState; - if (viewState.mAuthIntent != null) { - final CharSequence serviceName = viewState.getServiceName(); - - mFillView = new SignInPrompt(mContext, serviceName, (e) -> { - final IntentSender intentSender = viewState.mResponse.getAuthentication(); - final AutoFillUiCallback callback; - final Intent authIntent; - synchronized (mLock) { - callback = mCallback; - authIntent = viewState.mAuthIntent; - // Must reset the authentication intent so UI display the datasets after - // the user authenticated. - viewState.mAuthIntent = null; + final View content; + if (response.getPresentation() != null) { + content = response.getPresentation().apply(mContext, null); + content.setOnClickListener((view) -> { + if (mCallback != null) { + mCallback.authenticate(response.getAuthentication()); + } + hideFillUiUiThread(); + }); + } else { + mDatasetPicker = new DatasetPicker(mContext, response.getDatasets(), + focusedId, new DatasetPicker.Listener() { + @Override + public void onDatasetPicked(Dataset dataset) { + if (mCallback != null) { + mCallback.fill(dataset); } - if (callback != null) { - callback.authenticate(intentSender, authIntent); - } else { - // TODO(b/33197203): need to figure out why it's null sometimes - Slog.w(TAG, "no callback on showFillUi().auth for " + viewState.mId); - } - }); + hideFillUiUiThread(); + } - } else { - mFillView = datasetPicker = new DatasetPicker(mContext, datasets, - (dataset) -> { - final AutoFillUiCallback callback; - synchronized (mLock) { - callback = mCallback; - } - if (callback != null) { - callback.fill(dataset); - } else { - // TODO(b/33197203): need to figure out why it's null sometimes - Slog.w(TAG, "no callback on showFillUi() for " + viewState.mId); - } - hideFillUiUiThread(); - }); - } - mFillWindow = new AnchoredWindow(mWm, appToken, mFillView); + @Override + public void onCanceled() { + hideFillUiUiThread(); + } + }); + mDatasetPicker.update(filterText); + content = mDatasetPicker; + } - if (DEBUG) Slog.d(TAG, "showFillUi(): view changed for: " + viewState.mId); - } - if (datasetPicker != null) { - datasetPicker.update(filterText); - } + mFillWindow = new AnchoredWindow(mWm, mActivityToken, content); mFillWindow.show(bounds); - - }, 0); + }); } /** * Shows the UI asking the user to save for auto-fill. */ void showSaveUi() { - if (!hasCallback()) { - return; - } - hideAll(); - UiThread.getHandler().runWithScissors(() -> { + mHandler.post(() -> { + if (!hasCallback()) { + return; + } + hideAllUiThread(); showSnackbarUiThread(new SavePrompt(mContext, new SavePrompt.OnSaveListener() { @Override @@ -249,17 +197,20 @@ final class AutoFillUI { hideSnackbarUiThread(); } })); - }, 0); + }); } /** * Hides all UI affordances. */ void hideAll() { - UiThread.getHandler().runWithScissors(() -> { - hideSnackbarUiThread(); - hideFillUiUiThread(); - }, 0); + mHandler.post(() -> hideAllUiThread()); + } + + @android.annotation.UiThread + private void hideAllUiThread() { + hideSnackbarUiThread(); + hideFillUiUiThread(); } void dump(PrintWriter pw) { @@ -267,14 +218,13 @@ final class AutoFillUI { final String prefix = " "; pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); pw.print(prefix); pw.print("mSnackBar: "); pw.println(mSnackbar); - pw.print(prefix); pw.print("mViewState: "); pw.println(mViewState); } //similar to a snackbar, but can be a bit custom since it is more than just text. This will //allow two buttons for saving or not saving the autofill for instance as well. + @android.annotation.UiThread private void showSnackbarUiThread(View snackBar) { final LayoutParams params = new LayoutParams(); - params.setTitle("AutoFill Save"); params.type = LayoutParams.TYPE_PHONE; // TODO(b/33197203) use app window token params.flags = LayoutParams.FLAG_NOT_FOCUSABLE // don't receive input events, @@ -286,20 +236,21 @@ final class AutoFillUI { params.width = LayoutParams.MATCH_PARENT; params.height = LayoutParams.WRAP_CONTENT; - UiThread.getHandler().runWithScissors(() -> { + mHandler.post(() -> { mSnackbar = snackBar; mWm.addView(mSnackbar, params); - }, 0); + }); if (DEBUG) { Slog.d(TAG, "showSnackbar(): auto dismissing it in " + SNACK_BAR_LIFETIME_MS + " ms"); } - mHandlerCaller.sendMessageDelayed(mHandlerCaller.obtainMessage(MSG_HIDE_SNACK_BAR), - SNACK_BAR_LIFETIME_MS); + mHandler.sendMessageDelayed(mHandler + .obtainMessage(MSG_HIDE_SNACK_BAR), SNACK_BAR_LIFETIME_MS); } + @android.annotation.UiThread private void hideSnackbarUiThread() { - mHandlerCaller.getHandler().removeMessages(MSG_HIDE_SNACK_BAR); + mHandler.removeMessages(MSG_HIDE_SNACK_BAR); if (mSnackbar != null) { mWm.removeView(mSnackbar); mSnackbar = null; @@ -307,13 +258,11 @@ final class AutoFillUI { } private boolean hasCallback() { - synchronized (mLock) { - return mCallback != null; - } + return mCallback != null; } interface AutoFillUiCallback { - void authenticate(IntentSender intent, Intent fillInIntent); + void authenticate(IntentSender intent); void fill(Dataset dataset); void save(); } diff --git a/services/autofill/java/com/android/server/autofill/DatasetPicker.java b/services/autofill/java/com/android/server/autofill/DatasetPicker.java index a54cab928f4b2..3190f600e649d 100644 --- a/services/autofill/java/com/android/server/autofill/DatasetPicker.java +++ b/services/autofill/java/com/android/server/autofill/DatasetPicker.java @@ -16,73 +16,72 @@ package com.android.server.autofill; import android.content.Context; -import android.graphics.Color; import android.service.autofill.Dataset; -import android.text.TextUtils; import android.util.ArraySet; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.autofill.AutoFillId; +import android.view.autofill.AutoFillValue; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; -import android.widget.Filter.FilterListener; +import android.widget.FrameLayout; import android.widget.ListView; -import android.widget.TextView; +import android.widget.RemoteViews; +import com.android.internal.R; +import com.android.internal.R; import java.util.ArrayList; import java.util.List; /** - * View for dataset picker. - * - *

A fill session starts when a View is clicked and FillResponse is supplied. - *

A fill session ends when 1) the user takes action in the UI, 2) another View is clicked, or - * 3) the View is detached. + * This class manages the dataset selection UI. */ -final class DatasetPicker extends ListView implements OnItemClickListener { +final class DatasetPicker extends FrameLayout implements OnItemClickListener { interface Listener { void onDatasetPicked(Dataset dataset); + void onCanceled(); } private final Listener mListener; - DatasetPicker(Context context, ArraySet datasets, Listener listener) { + private final ArrayAdapter mAdapter; + + DatasetPicker(Context context, ArraySet datasets, AutoFillId filteredViewId, + Listener listener) { super(context); mListener = listener; final List items = new ArrayList<>(datasets.size()); for (Dataset dataset : datasets) { - items.add(new ViewItem(dataset)); + final int index = dataset.getFieldIds().indexOf(filteredViewId); + if (index >= 0) { + AutoFillValue value = dataset.getFieldValues().get(index); + items.add(new ViewItem(dataset, value.coerceToString())); + } } - final ArrayAdapter adapter = new ArrayAdapter( - context, - android.R.layout.simple_list_item_1, - android.R.id.text1, - items) { + mAdapter = new ArrayAdapter(context, 0, items) { @Override public View getView(int position, View convertView, ViewGroup parent) { - final TextView textView = (TextView) super.getView(position, convertView, parent); - textView.setSingleLine(); - textView.setEllipsize(TextUtils.TruncateAt.END); - textView.setMinHeight( - getDimen(com.android.internal.R.dimen.autofill_fill_item_height)); - return textView; + RemoteViews presentation = getItem(position).getDataset().getPresentation(); + return presentation.apply(context, parent); } }; - setAdapter(adapter); - setBackgroundColor(Color.WHITE); - setDivider(null); - setElevation(getDimen(com.android.internal.R.dimen.autofill_fill_elevation)); - setOnItemClickListener(this); + + LayoutInflater inflater = LayoutInflater.from(context); + ListView content = (ListView) inflater.inflate( + com.android.internal.R.layout.autofill_dataset_picker, this, true) + .findViewById(com.android.internal.R.id.list); + content.setAdapter(mAdapter); + content.setOnItemClickListener(this); } public void update(String prefix) { - final ArrayAdapter adapter = (ArrayAdapter) getAdapter(); - adapter.getFilter().filter(prefix, new FilterListener() { - @Override - public void onFilterComplete(int count) { - setVisibility(count > 0 ? View.VISIBLE : View.GONE); + mAdapter.getFilter().filter(prefix, (count) -> { + if (count <= 0 && mListener != null) { + mListener.onCanceled(); } }); } @@ -91,29 +90,27 @@ final class DatasetPicker extends ListView implements OnItemClickListener { public void onItemClick(AdapterView adapterView, View view, int pos, long id) { if (mListener != null) { final ViewItem vi = (ViewItem) adapterView.getItemAtPosition(pos); - mListener.onDatasetPicked(vi.getData()); + mListener.onDatasetPicked(vi.getDataset()); } } - private int getDimen(int resId) { - return getContext().getResources().getDimensionPixelSize(resId); - } - private static class ViewItem { - private final Dataset mData; + private final String mValue; + private final Dataset mDataset; - ViewItem(Dataset data) { - mData = data; + ViewItem(Dataset dataset, String value) { + mDataset = dataset; + mValue = value; } - public Dataset getData() { - return mData; + public Dataset getDataset() { + return mDataset; } @Override public String toString() { // used by ArrayAdapter - return mData.getName().toString(); + return mValue; } } } diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index 48ae635c281f2..2f600c21d1fd0 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -19,8 +19,6 @@ package com.android.server.autofill; import android.annotation.Nullable; import android.os.Bundle; import android.service.autofill.Dataset; -import android.service.autofill.FillResponse; -import android.util.ArraySet; import android.view.autofill.AutoFillId; import android.view.autofill.AutoFillValue; @@ -77,28 +75,6 @@ final class Helper { return null; } - /** - * Finds the index of a data set given its name. - * - * @param name The dataset name. - * @param response The response to search. - * @return The index of dataset if found or -1. - */ - static int indexOfDataset(CharSequence name, FillResponse response) { - ArraySet datasets = response.getDatasets(); - if (datasets == null || datasets.isEmpty()) { - return -1; - } - final int datasetCount = datasets.size(); - for (int i = 0; i < datasetCount; i++) { - Dataset dataset = datasets.valueAt(i); - if (dataset.getName().toString().equals(name.toString())) { - return i; - } - } - return -1; - } - private Helper() { throw new UnsupportedOperationException("contains static members only"); } diff --git a/services/autofill/java/com/android/server/autofill/SavePrompt.java b/services/autofill/java/com/android/server/autofill/SavePrompt.java index f0b51e260dcdd..85c6d7d2c2d38 100644 --- a/services/autofill/java/com/android/server/autofill/SavePrompt.java +++ b/services/autofill/java/com/android/server/autofill/SavePrompt.java @@ -17,9 +17,7 @@ package com.android.server.autofill; import android.content.Context; -import android.graphics.Color; import android.widget.RelativeLayout; -import android.widget.RelativeLayout.LayoutParams; import android.widget.TextView; import android.view.LayoutInflater; import android.view.View; @@ -55,6 +53,5 @@ final class SavePrompt extends RelativeLayout { mListener.onSaveClick(); }); - //addView(view); } } diff --git a/services/autofill/java/com/android/server/autofill/SignInPrompt.java b/services/autofill/java/com/android/server/autofill/SignInPrompt.java deleted file mode 100644 index 6d17acda4bdad..0000000000000 --- a/services/autofill/java/com/android/server/autofill/SignInPrompt.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 com.android.server.autofill; - -import android.content.Context; -import android.view.View; -import android.widget.Button; - -/** - * A view displaying the sign-in prompt for an auto-fill service. - */ -final class SignInPrompt extends Button { - - SignInPrompt(Context context, CharSequence serviceName, View.OnClickListener listener) { - super(context); - // TODO(b/33197203): use strings.xml - final String text = serviceName != null - ? "Sign in to " + serviceName + " to autofill" - : "Sign in to autofill"; - - // TODO(b/33197203): polish UI / use better altenative than a button... - setText(text); - setOnClickListener(listener); - } -}