diff options
25 files changed, 1824 insertions, 2059 deletions
| diff --git a/Android.mk b/Android.mk index 22323c5e6772..1da4783bcac5 100644 --- a/Android.mk +++ b/Android.mk @@ -265,8 +265,9 @@ LOCAL_SRC_FILES += \  	core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl \  	core/java/android/service/autofill/IAutoFillAppCallback.aidl \  	core/java/android/service/autofill/IAutoFillManagerService.aidl \ -	core/java/android/service/autofill/IAutoFillServerCallback.aidl \  	core/java/android/service/autofill/IAutoFillService.aidl \ +	core/java/android/service/autofill/IFillCallback.aidl \ +	core/java/android/service/autofill/ISaveCallback.aidl \  	core/java/android/service/carrier/ICarrierService.aidl \  	core/java/android/service/carrier/ICarrierMessagingCallback.aidl \  	core/java/android/service/carrier/ICarrierMessagingService.aidl \ diff --git a/api/current.txt b/api/current.txt index eca3c27d56b6..adc32ee95247 100644 --- a/api/current.txt +++ b/api/current.txt @@ -9092,6 +9092,10 @@ package android.content {      field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";      field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE";      field public static final java.lang.String EXTRA_ASSIST_UID = "android.intent.extra.ASSIST_UID"; +    field public static final java.lang.String EXTRA_AUTO_FILL_ASSIST_STRUCTURE = "android.intent.extra.AUTO_FILL_ASSIST_STRUCTURE"; +    field public static final java.lang.String EXTRA_AUTO_FILL_CALLBACK = "android.intent.extra.AUTO_FILL_CALLBACK"; +    field public static final java.lang.String EXTRA_AUTO_FILL_EXTRAS = "android.intent.extra.AUTO_FILL_EXTRAS"; +    field public static final java.lang.String EXTRA_AUTO_FILL_ITEM_ID = "android.intent.extra.AUTO_FILL_ITEM_ID";      field public static final java.lang.String EXTRA_BCC = "android.intent.extra.BCC";      field public static final java.lang.String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";      field public static final java.lang.String EXTRA_CC = "android.intent.extra.CC"; @@ -36096,26 +36100,19 @@ package android.service.autofill {      ctor public AutoFillService();      method public final android.os.IBinder onBind(android.content.Intent);      method public void onConnected(); -    method public void onDatasetAuthenticationRequest(android.os.Bundle, int);      method public void onDisconnected();      method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback); -    method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);      method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback); -    field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS"; -    field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS"; -    field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4 -    field public static final int FLAG_AUTHENTICATION_REQUESTED = 1; // 0x1 -    field public static final int FLAG_AUTHENTICATION_SUCCESS = 2; // 0x2 -    field public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 8; // 0x8      field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";      field public static final java.lang.String SERVICE_META_DATA = "android.autofill";    } -  public final class FillCallback { -    method public void onDatasetAuthentication(android.view.autofill.Dataset, int); +  public final class FillCallback implements android.os.Parcelable { +    method public int describeContents();      method public void onFailure(java.lang.CharSequence); -    method public void onFillResponseAuthentication(int);      method public void onSuccess(android.view.autofill.FillResponse); +    method public void writeToParcel(android.os.Parcel, int); +    field public static final android.os.Parcelable.Creator<android.service.autofill.FillCallback> CREATOR;    }    public final class SaveCallback { @@ -46755,10 +46752,9 @@ package android.view.autofill {    }    public static final class Dataset.Builder { -    ctor public Dataset.Builder(java.lang.CharSequence); +    ctor public Dataset.Builder(java.lang.String, java.lang.CharSequence);      method public android.view.autofill.Dataset build(); -    method public android.view.autofill.Dataset.Builder requiresCustomAuthentication(android.os.Bundle, int); -    method public android.view.autofill.Dataset.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int); +    method public android.view.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);      method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);      method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);    } @@ -46770,12 +46766,11 @@ package android.view.autofill {    }    public static final class FillResponse.Builder { -    ctor public FillResponse.Builder(); +    ctor public FillResponse.Builder(java.lang.String);      method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);      method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);      method public android.view.autofill.FillResponse build(); -    method public android.view.autofill.FillResponse.Builder requiresCustomAuthentication(android.os.Bundle, int); -    method public android.view.autofill.FillResponse.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int); +    method public android.view.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender);      method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);    } @@ -47201,7 +47196,7 @@ package android.view.textclassifier {    public final class TextClassificationManager {      method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence); -    method public android.view.textclassifier.TextClassifier getDefaultTextClassifier(); +    method public synchronized android.view.textclassifier.TextClassifier getDefaultTextClassifier();    }    public final class TextClassificationResult { diff --git a/api/system-current.txt b/api/system-current.txt index 55b32f6c3b93..a8000dcf7173 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -9504,6 +9504,10 @@ package android.content {      field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";      field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE";      field public static final java.lang.String EXTRA_ASSIST_UID = "android.intent.extra.ASSIST_UID"; +    field public static final java.lang.String EXTRA_AUTO_FILL_ASSIST_STRUCTURE = "android.intent.extra.AUTO_FILL_ASSIST_STRUCTURE"; +    field public static final java.lang.String EXTRA_AUTO_FILL_CALLBACK = "android.intent.extra.AUTO_FILL_CALLBACK"; +    field public static final java.lang.String EXTRA_AUTO_FILL_EXTRAS = "android.intent.extra.AUTO_FILL_EXTRAS"; +    field public static final java.lang.String EXTRA_AUTO_FILL_ITEM_ID = "android.intent.extra.AUTO_FILL_ITEM_ID";      field public static final java.lang.String EXTRA_BCC = "android.intent.extra.BCC";      field public static final java.lang.String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";      field public static final java.lang.String EXTRA_CC = "android.intent.extra.CC"; @@ -39128,26 +39132,19 @@ package android.service.autofill {      ctor public AutoFillService();      method public final android.os.IBinder onBind(android.content.Intent);      method public void onConnected(); -    method public void onDatasetAuthenticationRequest(android.os.Bundle, int);      method public void onDisconnected();      method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback); -    method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);      method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback); -    field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS"; -    field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS"; -    field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4 -    field public static final int FLAG_AUTHENTICATION_REQUESTED = 1; // 0x1 -    field public static final int FLAG_AUTHENTICATION_SUCCESS = 2; // 0x2 -    field public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 8; // 0x8      field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";      field public static final java.lang.String SERVICE_META_DATA = "android.autofill";    } -  public final class FillCallback { -    method public void onDatasetAuthentication(android.view.autofill.Dataset, int); +  public final class FillCallback implements android.os.Parcelable { +    method public int describeContents();      method public void onFailure(java.lang.CharSequence); -    method public void onFillResponseAuthentication(int);      method public void onSuccess(android.view.autofill.FillResponse); +    method public void writeToParcel(android.os.Parcel, int); +    field public static final android.os.Parcelable.Creator<android.service.autofill.FillCallback> CREATOR;    }    public final class SaveCallback { @@ -50160,10 +50157,9 @@ package android.view.autofill {    }    public static final class Dataset.Builder { -    ctor public Dataset.Builder(java.lang.CharSequence); +    ctor public Dataset.Builder(java.lang.String, java.lang.CharSequence);      method public android.view.autofill.Dataset build(); -    method public android.view.autofill.Dataset.Builder requiresCustomAuthentication(android.os.Bundle, int); -    method public android.view.autofill.Dataset.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int); +    method public android.view.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);      method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);      method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);    } @@ -50175,12 +50171,11 @@ package android.view.autofill {    }    public static final class FillResponse.Builder { -    ctor public FillResponse.Builder(); +    ctor public FillResponse.Builder(java.lang.String);      method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);      method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);      method public android.view.autofill.FillResponse build(); -    method public android.view.autofill.FillResponse.Builder requiresCustomAuthentication(android.os.Bundle, int); -    method public android.view.autofill.FillResponse.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int); +    method public android.view.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender);      method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);    } @@ -50606,7 +50601,7 @@ package android.view.textclassifier {    public final class TextClassificationManager {      method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence); -    method public android.view.textclassifier.TextClassifier getDefaultTextClassifier(); +    method public synchronized android.view.textclassifier.TextClassifier getDefaultTextClassifier();    }    public final class TextClassificationResult { diff --git a/api/test-current.txt b/api/test-current.txt index bc7a0015485b..d36b8de463dc 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -9118,6 +9118,10 @@ package android.content {      field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";      field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE";      field public static final java.lang.String EXTRA_ASSIST_UID = "android.intent.extra.ASSIST_UID"; +    field public static final java.lang.String EXTRA_AUTO_FILL_ASSIST_STRUCTURE = "android.intent.extra.AUTO_FILL_ASSIST_STRUCTURE"; +    field public static final java.lang.String EXTRA_AUTO_FILL_CALLBACK = "android.intent.extra.AUTO_FILL_CALLBACK"; +    field public static final java.lang.String EXTRA_AUTO_FILL_EXTRAS = "android.intent.extra.AUTO_FILL_EXTRAS"; +    field public static final java.lang.String EXTRA_AUTO_FILL_ITEM_ID = "android.intent.extra.AUTO_FILL_ITEM_ID";      field public static final java.lang.String EXTRA_BCC = "android.intent.extra.BCC";      field public static final java.lang.String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";      field public static final java.lang.String EXTRA_CC = "android.intent.extra.CC"; @@ -36231,26 +36235,19 @@ package android.service.autofill {      ctor public AutoFillService();      method public final android.os.IBinder onBind(android.content.Intent);      method public void onConnected(); -    method public void onDatasetAuthenticationRequest(android.os.Bundle, int);      method public void onDisconnected();      method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback); -    method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);      method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback); -    field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS"; -    field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS"; -    field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4 -    field public static final int FLAG_AUTHENTICATION_REQUESTED = 1; // 0x1 -    field public static final int FLAG_AUTHENTICATION_SUCCESS = 2; // 0x2 -    field public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 8; // 0x8      field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";      field public static final java.lang.String SERVICE_META_DATA = "android.autofill";    } -  public final class FillCallback { -    method public void onDatasetAuthentication(android.view.autofill.Dataset, int); +  public final class FillCallback implements android.os.Parcelable { +    method public int describeContents();      method public void onFailure(java.lang.CharSequence); -    method public void onFillResponseAuthentication(int);      method public void onSuccess(android.view.autofill.FillResponse); +    method public void writeToParcel(android.os.Parcel, int); +    field public static final android.os.Parcelable.Creator<android.service.autofill.FillCallback> CREATOR;    }    public final class SaveCallback { @@ -47068,10 +47065,9 @@ package android.view.autofill {    }    public static final class Dataset.Builder { -    ctor public Dataset.Builder(java.lang.CharSequence); +    ctor public Dataset.Builder(java.lang.String, java.lang.CharSequence);      method public android.view.autofill.Dataset build(); -    method public android.view.autofill.Dataset.Builder requiresCustomAuthentication(android.os.Bundle, int); -    method public android.view.autofill.Dataset.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int); +    method public android.view.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);      method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);      method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);    } @@ -47083,12 +47079,11 @@ package android.view.autofill {    }    public static final class FillResponse.Builder { -    ctor public FillResponse.Builder(); +    ctor public FillResponse.Builder(java.lang.String);      method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);      method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);      method public android.view.autofill.FillResponse build(); -    method public android.view.autofill.FillResponse.Builder requiresCustomAuthentication(android.os.Bundle, int); -    method public android.view.autofill.FillResponse.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int); +    method public android.view.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender);      method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);    } @@ -47514,7 +47509,7 @@ package android.view.textclassifier {    public final class TextClassificationManager {      method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence); -    method public android.view.textclassifier.TextClassifier getDefaultTextClassifier(); +    method public synchronized android.view.textclassifier.TextClassifier getDefaultTextClassifier();    }    public final class TextClassificationResult { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 11303274e9bf..6a8141f865d9 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1806,6 +1806,41 @@ public class Intent implements Parcelable, Cloneable {      @SystemApi      public static final String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME"; +    /** +     * Intent extra: An id if an autofill item ({@link +     * android.view.autofill.Dataset} or {@link android.view.autofill.FillResponse}). +     * <p> +     * Type: String +     * </p> +     */ +    public static final String EXTRA_AUTO_FILL_ITEM_ID = "android.intent.extra.AUTO_FILL_ITEM_ID"; + +    /** +     * Intent extra: The assist structure which captures the filled screen. +     * <p> +     * Type: {@link android.app.assist.AssistStructure} +     * </p> +     */ +    public static final String EXTRA_AUTO_FILL_ASSIST_STRUCTURE = +            "android.intent.extra.AUTO_FILL_ASSIST_STRUCTURE"; + +    /** +     * Intent extra: The metadata associated with the authenticated entity ({@link +     * android.view.autofill.Dataset} or {@link android.view.autofill.FillResponse}). +     * <p> +     * Type: {@link android.os.Bundle} +     * </p> +     */ +    public static final String EXTRA_AUTO_FILL_EXTRAS = "android.intent.extra.AUTO_FILL_EXTRAS"; + +    /** +     * Intent extra: A callback to report an authentication result. +     * <p> +     * Type: {@link android.view.autofill.FillResponse} +     * </p> +     */ +    public static final String EXTRA_AUTO_FILL_CALLBACK = "android.intent.extra.AUTO_FILL_CALLBACK"; +      // ---------------------------------------------------------------------      // ---------------------------------------------------------------------      // Standard intent broadcast actions (see action variable). diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index e99d303642ba..f94e89a2785d 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -17,6 +17,7 @@  package android.os;  import android.annotation.IntegerRes; +import android.annotation.NonNull;  import android.annotation.Nullable;  import android.text.TextUtils;  import android.util.ArrayMap; @@ -1339,7 +1340,7 @@ public final class Parcel {      }      /** -     * Flatten a heterogeneous array containing a particular object type into +     * Flatten a homogeneous array containing a particular object type into       * the parcel, at       * the current dataPosition() and growing dataCapacity() if needed.  The       * type of the objects in the array must be one that implements Parcelable. @@ -1361,7 +1362,7 @@ public final class Parcel {          if (val != null) {              int N = val.length;              writeInt(N); -            for (int i=0; i<N; i++) { +            for (int i = 0; i < N; i++) {                  T item = val[i];                  if (item != null) {                      writeInt(1); @@ -1376,6 +1377,146 @@ public final class Parcel {      }      /** +     * Write a uniform (all items are null or the same class) array list of +     * parcelables. +     * +     * @param list The list to write. +     * +     * @hide +     */ +    public final <T extends Parcelable> void writeTypedArrayList(@Nullable ArrayList<T> list, +            int parcelableFlags) { +        if (list != null) { +            int N = list.size(); +            writeInt(N); +            boolean wroteCreator = false; +            for (int i = 0; i < N; i++) { +                T item = list.get(i); +                if (item != null) { +                    writeInt(1); +                    if (!wroteCreator) { +                        writeParcelableCreator(item); +                        wroteCreator = true; +                    } +                    item.writeToParcel(this, parcelableFlags); +                } else { +                    writeInt(0); +                } +            } +        } else { +            writeInt(-1); +        } +    } + +    /** +     * Reads a uniform (all items are null or the same class) array list of +     * parcelables. +     * +     * @return The list or null. +     * +     * @hide +     */ +    public final @Nullable <T> ArrayList<T> readTypedArrayList(@Nullable ClassLoader loader) { +        int N = readInt(); +        if (N <= 0) { +            return null; +        } +        Parcelable.Creator<?> creator = null; +        ArrayList<T> result = new ArrayList<T>(N); +        for (int i = 0; i < N; i++) { +            if (readInt() != 0) { +                if (creator == null) { +                    creator = readParcelableCreator(loader); +                    if (creator == null) { +                        return null; +                    } +                } +                final T parcelable; +                if (creator instanceof Parcelable.ClassLoaderCreator<?>) { +                    Parcelable.ClassLoaderCreator<?> classLoaderCreator = +                            (Parcelable.ClassLoaderCreator<?>) creator; +                    parcelable = (T) classLoaderCreator.createFromParcel(this, loader); +                } else { +                    parcelable = (T) creator.createFromParcel(this); +                } +                result.add(parcelable); +            } else { +                result.add(null); +            } +        } +        return result; +    } + +    /** +     * Write a uniform (all items are null or the same class) array set of +     * parcelables. +     * +     * @param set The set to write. +     * +     * @hide +     */ +    public final <T extends Parcelable> void writeTypedArraySet(@Nullable ArraySet<T> set, +            int parcelableFlags) { +        if (set != null) { +            int N = set.size(); +            writeInt(N); +            boolean wroteCreator = false; +            for (int i = 0; i < N; i++) { +                T item = set.valueAt(i); +                if (item != null) { +                    writeInt(1); +                    if (!wroteCreator) { +                        writeParcelableCreator(item); +                        wroteCreator = true; +                    } +                    item.writeToParcel(this, parcelableFlags); +                } else { +                    writeInt(0); +                } +            } +        } else { +            writeInt(-1); +        } +    } + +    /** +     * Reads a uniform (all items are null or the same class) array set of +     * parcelables. +     * +     * @return The set or null. +     * +     * @hide +     */ +    public final @Nullable <T> ArraySet<T> readTypedArraySet(@Nullable ClassLoader loader) { +        int N = readInt(); +        if (N <= 0) { +            return null; +        } +        Parcelable.Creator<?> creator = null; +        ArraySet<T> result = new ArraySet<T>(N); +        for (int i = 0; i < N; i++) { +            T parcelable = null; +            if (readInt() != 0) { +                if (creator == null) { +                    creator = readParcelableCreator(loader); +                    if (creator == null) { +                        return null; +                    } +                } +                if (creator instanceof Parcelable.ClassLoaderCreator<?>) { +                    Parcelable.ClassLoaderCreator<?> classLoaderCreator = +                            (Parcelable.ClassLoaderCreator<?>) creator; +                    parcelable = (T) classLoaderCreator.createFromParcel(this, loader); +                } else { +                    parcelable = (T) creator.createFromParcel(this); +                } +            } +            result.append(parcelable); +        } +        return result; +    } + +    /**       * Flatten the Parcelable object into the parcel.       *       * @param val The Parcelable object to be written. diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java index bfaf23cd5fae..aa4d26c18d7c 100644 --- a/core/java/android/service/autofill/AutoFillService.java +++ b/core/java/android/service/autofill/AutoFillService.java @@ -15,7 +15,10 @@   */  package android.service.autofill; -import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.RemoteException; +import com.android.internal.os.HandlerCaller;  import android.annotation.SdkConstant;  import android.app.Activity;  import android.app.Service; @@ -24,21 +27,13 @@ import android.content.Intent;  import android.os.Bundle;  import android.os.CancellationSignal;  import android.os.IBinder; +import android.os.ICancellationSignal;  import android.os.Looper; -import android.os.Message; -import android.os.RemoteException;  import android.util.Log; -import android.view.autofill.Dataset;  import android.view.autofill.FillResponse; -import com.android.internal.os.HandlerCaller;  import com.android.internal.os.SomeArgs; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; -  //TODO(b/33197203): improve javadoc (of both class and methods); in particular, make sure the  //life-cycle (and how state could be maintained on server-side) is well documented. @@ -48,12 +43,7 @@ import java.util.List;   * <p>Apps providing auto-fill capabilities must extend this service.   */  public abstract class AutoFillService extends Service { -      private static final String TAG = "AutoFillService"; -    static final boolean DEBUG = true; // TODO(b/33197203): set to false once stable - -    // TODO(b/33197203): check for device's memory size instead of DEBUG? -    static final boolean DEBUG_PENDING_CALLBACKS = DEBUG;      /**       * The {@link Intent} that must be declared as handled by the service. @@ -79,85 +69,33 @@ public abstract class AutoFillService extends Service {      // Internal bundle keys.      /** @hide */ public static final String KEY_CALLBACK = "callback"; -    /** @hide */ public static final String KEY_SAVABLE_IDS = "savable_ids"; -    /** @hide */ public static final String EXTRA_CRYPTO_OBJECT_ID = "crypto_object_id"; - -    // Prefix for public bundle keys. -    private static final String KEY_PREFIX = "android.service.autofill.extra."; - -    /** -     * Key of the {@link Bundle} passed to methods such as -     * {@link #onSaveRequest(AssistStructure, Bundle, SaveCallback)} containing the extras set by -     * {@link android.view.autofill.FillResponse.Builder#setExtras(Bundle)}. -     */ -    public static final String EXTRA_RESPONSE_EXTRAS = KEY_PREFIX + "RESPONSE_EXTRAS"; - -    /** -     * Key of the {@link Bundle} passed to methods such as -     * {@link #onSaveRequest(AssistStructure, Bundle, SaveCallback)} containing the extras set by -     * {@link android.view.autofill.Dataset.Builder#setExtras(Bundle)}. -     */ -    public static final String EXTRA_DATASET_EXTRAS = KEY_PREFIX + "DATASET_EXTRAS"; - -    /** -     * Used to indicate the user selected an action that requires authentication. -     */ -    public static final int FLAG_AUTHENTICATION_REQUESTED = 1 << 0; - -    /** -     * Used to indicate the user authentication succeeded. -     */ -    public static final int FLAG_AUTHENTICATION_SUCCESS = 1 << 1; - -    /** -     * Used to indicate the user authentication failed. -     */ -    public static final int FLAG_AUTHENTICATION_ERROR = 1 << 2; - -    /** -     * Used when the service requested Fingerprint authentication but such option is not available. -     */ -    public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE  = 1 << 3;      // Handler messages.      private static final int MSG_CONNECT = 1;      private static final int MSG_DISCONNECT = 2; -    private static final int MSG_AUTO_FILL_ACTIVITY = 3; -    private static final int MSG_AUTHENTICATE_FILL_RESPONSE = 4; -    private static final int MSG_AUTHENTICATE_DATASET = 5; -    private static final int MSG_SAVE = 6; +    private static final int MSG_ON_FILL_REQUEST = 3; +    private static final int MSG_ON_SAVE_REQUEST = 4;      private final IAutoFillService mInterface = new IAutoFillService.Stub() { -          @Override -        public void autoFill(AssistStructure structure, IAutoFillServerCallback callback) { -            mHandlerCaller -                    .obtainMessageOO(MSG_AUTO_FILL_ACTIVITY, structure, callback) -                    .sendToTarget(); -        } - -        @Override -        public void save(AssistStructure structure, IAutoFillServerCallback callback, -                Bundle extras) throws RemoteException { -            mHandlerCaller -                    .obtainMessageOOO(MSG_SAVE, structure, callback, extras) +        public void onFillRequest(AssistStructure structure, Bundle extras, +                IFillCallback callback) { +            ICancellationSignal transport = CancellationSignal.createTransport(); +            try { +                callback.onCancellable(transport); +            } catch (RemoteException e) { +                e.rethrowFromSystemServer(); +            } +            mHandlerCaller.obtainMessageOOOO(MSG_ON_FILL_REQUEST, structure, +                    CancellationSignal.fromTransport(transport), extras, callback)                      .sendToTarget();          }          @Override -        public void authenticateFillResponse(Bundle extras, int flags) { -            final Message msg = mHandlerCaller.obtainMessage(MSG_AUTHENTICATE_FILL_RESPONSE); -            msg.arg1 = flags; -            msg.obj = extras; -            mHandlerCaller.sendMessage(msg); -        } - -        @Override -        public void authenticateDataset(Bundle extras, int flags) { -            final Message msg = mHandlerCaller.obtainMessage(MSG_AUTHENTICATE_DATASET); -            msg.arg1 = flags; -            msg.obj = extras; -            mHandlerCaller.sendMessage(msg); +        public void onSaveRequest(AssistStructure structure, Bundle extras, +                ISaveCallback callback) { +            mHandlerCaller.obtainMessageOOO(MSG_ON_SAVE_REQUEST, structure, +                    extras, callback).sendToTarget();          }          @Override @@ -171,63 +109,41 @@ public abstract class AutoFillService extends Service {          }      }; -    private final HandlerCaller.Callback mHandlerCallback = new HandlerCaller.Callback() { - -        @Override -        public void executeMessage(Message msg) { -            switch (msg.what) { -                case MSG_CONNECT: { -                    onConnected(); -                    break; -                } case MSG_AUTO_FILL_ACTIVITY: { -                    final SomeArgs args = (SomeArgs) msg.obj; -                    try { -                        final AssistStructure structure = (AssistStructure) args.arg1; -                        final IAutoFillServerCallback callback = -                                (IAutoFillServerCallback) args.arg2; -                        handleAutoFill(structure, callback); -                    } finally { -                        args.recycle(); -                    } -                    break; -                } case MSG_SAVE: { -                    final SomeArgs args = (SomeArgs) msg.obj; -                    try { -                        final AssistStructure structure = (AssistStructure) args.arg1; -                        final IAutoFillServerCallback callback = -                                (IAutoFillServerCallback) args.arg2; -                        final Bundle extras = (Bundle) args.arg3; -                        handleSave(structure, callback, extras); -                    } finally { -                        args.recycle(); -                    } -                    break; -                } case MSG_AUTHENTICATE_FILL_RESPONSE: { -                    final int flags = msg.arg1; -                    final Bundle extras = (Bundle) msg.obj; -                    onFillResponseAuthenticationRequest(extras, flags); -                    break; -                } case MSG_AUTHENTICATE_DATASET: { -                    final int flags = msg.arg1; -                    final Bundle extras = (Bundle) msg.obj; -                    onDatasetAuthenticationRequest(extras, flags); -                    break; -                } case MSG_DISCONNECT: { -                    onDisconnected(); -                    break; -                } default: { -                    Log.w(TAG, "MyCallbacks received invalid message type: " + msg); -                } +    private final HandlerCaller.Callback mHandlerCallback = (msg) -> { +        switch (msg.what) { +            case MSG_CONNECT: { +                onConnected(); +                break; +            } case MSG_ON_FILL_REQUEST: { +                final SomeArgs args = (SomeArgs) msg.obj; +                final AssistStructure structure = (AssistStructure) args.arg1; +                final CancellationSignal cancellation = (CancellationSignal) args.arg2; +                final Bundle extras = (Bundle) args.arg3; +                final IFillCallback callback = (IFillCallback) args.arg4; +                final FillCallback fillCallback = new FillCallback(callback); +                args.recycle(); +                onFillRequest(structure, extras, cancellation, fillCallback); +                break; +            } case MSG_ON_SAVE_REQUEST: { +                final SomeArgs args = (SomeArgs) msg.obj; +                final AssistStructure structure = (AssistStructure) args.arg1; +                final Bundle extras = (Bundle) args.arg2; +                final ISaveCallback callback = (ISaveCallback) args.arg3; +                final SaveCallback saveCallback = new SaveCallback(callback); +                args.recycle(); +                onSaveRequest(structure, extras, saveCallback); +                break; +            } case MSG_DISCONNECT: { +                onDisconnected(); +                break; +            } default: { +                Log.w(TAG, "MyCallbacks received invalid message type: " + msg);              }          }      };      private HandlerCaller mHandlerCaller; -    // User for debugging purposes -    private final List<CallbackHelper.Dumpable> mPendingCallbacks = -            DEBUG_PENDING_CALLBACKS ? new ArrayList<>() : null; -      /**       * {@inheritDoc}       * @@ -236,7 +152,6 @@ public abstract class AutoFillService extends Service {      @Override      public void onCreate() {          super.onCreate(); -          mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), mHandlerCallback, true);      } @@ -255,7 +170,7 @@ public abstract class AutoFillService extends Service {       * <p>You should generally do initialization here rather than in {@link #onCreate}.       */      public void onConnected() { -        if (DEBUG) Log.d(TAG, "onConnected()"); +      }      /** @@ -267,14 +182,18 @@ public abstract class AutoFillService extends Service {       * to notify the result of the request.       *       * @param structure {@link Activity}'s view structure. -     * @param data bundle containing additional arguments set by the Android system (currently none) -     * or data passed by the service on previous calls to fullfill other sections of this activity -     * (see {@link FillResponse} Javadoc for examples of multiple-sections requests). -     * @param cancellationSignal signal for observing cancel requests. +     * @param data bundle containing data passed by the service on previous calls to fill. +     *     This bundle allows your service to keep state between fill and save requests +     *     as well as when filling different sections of the UI as the system will try to +     *     aggressively unbind from the service to conserve resources. See {@link FillResponse} +     *     Javadoc for examples of multiple-sections requests. +     * @param cancellationSignal signal for observing cancellation requests. The system will use +     *     this to notify you that the fill result is no longer needed and you should stop +     *     handling this fill request in order to save resources.       * @param callback object used to notify the result of the request.       */ -    public abstract void onFillRequest(AssistStructure structure, Bundle data, -            CancellationSignal cancellationSignal, FillCallback callback); +    public abstract void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle data, +            @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);      /**       * Called when user requests service to save the fields of an {@link Activity}. @@ -284,108 +203,15 @@ public abstract class AutoFillService extends Service {       * to notify the result of the request.       *       * @param structure {@link Activity}'s view structure. -     * @param data bundle containing additional arguments set by the Android system (currently none) -     * or data passed by the service in the {@link FillResponse} that originated this call. +     * @param data bundle containing data passed by the service on previous calls to fill. +     *     This bundle allows your service to keep state between fill and save requests +     *     as well as when filling different sections of the UI as the system will try to +     *     aggressively unbind from the service to conserve resources. See {@link FillResponse} +     *     Javadoc for examples of multiple-sections requests.       * @param callback object used to notify the result of the request.       */ -    public abstract void onSaveRequest(AssistStructure structure, Bundle data, -            SaveCallback callback); - -    /** -     * Called as result of the user action for a {@link FillResponse} that required authentication. -     * -     * <p>When the {@link FillResponse} required authentication through -     * {@link android.view.autofill.FillResponse.Builder#requiresCustomAuthentication(Bundle, int)}, -     * this call indicates the user is requesting the service to authenticate him/her (and -     * {@code flags} contains {@link #FLAG_AUTHENTICATION_REQUESTED}), and {@code extras} contains -     * the {@link Bundle} passed to that method. -     * -     * <p>When the {@link FillResponse} required authentication through -     * {@link android.view.autofill.FillResponse.Builder#requiresFingerprintAuthentication( -     * android.hardware.fingerprint.FingerprintManager.CryptoObject, Bundle, int)}, -     * {@code flags} this call contains the result of the fingerprint authentication (such as -     * {@link #FLAG_AUTHENTICATION_SUCCESS}, {@link #FLAG_AUTHENTICATION_ERROR}, and -     * {@link #FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}) and {@code extras} contains the -     * {@link Bundle} passed to that method. -     */ -    public void onFillResponseAuthenticationRequest(@SuppressWarnings("unused") Bundle extras, -            int flags) { -        if (DEBUG) Log.d(TAG, "onFillResponseAuthenticationRequest(): flags=" + flags); -    } - -    /** -     * Called as result of the user action for a {@link Dataset} that required authentication. -     * -     * <p>When the {@link Dataset} required authentication through -     * {@link android.view.autofill.Dataset.Builder#requiresCustomAuthentication(Bundle, int)}, this -     * call indicates the user is requesting the service to authenticate him/her (and {@code flags} -     * contains {@link #FLAG_AUTHENTICATION_REQUESTED}), and {@code extras} contains the -     * {@link Bundle} passed to that method. -     * -     * <p>When the {@link Dataset} required authentication through -     * {@link android.view.autofill.Dataset.Builder#requiresFingerprintAuthentication( -     * android.hardware.fingerprint.FingerprintManager.CryptoObject, Bundle, int)}, -     * {@code flags} this call contains the result of the fingerprint authentication (such as -     * {@link #FLAG_AUTHENTICATION_SUCCESS}, {@link #FLAG_AUTHENTICATION_ERROR}, and -     * {@link #FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}) and {@code extras} contains the -     * {@link Bundle} passed to that method. -     */ -    public void onDatasetAuthenticationRequest(@SuppressWarnings("unused") Bundle extras, -            int flags) { -        if (DEBUG) Log.d(TAG, "onDatasetAuthenticationRequest(): flags=" + flags); -    } - -    @Override -    @CallSuper -    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { -        if (mPendingCallbacks != null) { -            pw.print("Number of pending callbacks: "); pw.println(mPendingCallbacks.size()); -            final String prefix = "  "; -            for (int i = 0; i < mPendingCallbacks.size(); i++) { -                final CallbackHelper.Dumpable cb = mPendingCallbacks.get(i); -                pw.print('#'); pw.print(i + 1); pw.println(':'); -                cb.dump(prefix, pw); -            } -            pw.println(); -        } else { -            pw.println("Dumping disabled"); -        } -    } - -    private void handleAutoFill(AssistStructure structure, IAutoFillServerCallback callback) { -        final FillCallback fillCallback = new FillCallback(callback); -        if (DEBUG_PENDING_CALLBACKS) { -            addPendingCallback(fillCallback); -        } -        // TODO(b/33197203): hook up the cancelationSignal -        onFillRequest(structure, null, new CancellationSignal(), fillCallback); -        return; -    } - -    private void handleSave(AssistStructure structure, IAutoFillServerCallback callback, -            Bundle extras) { -        final SaveCallback saveCallback = new SaveCallback(callback); -        if (DEBUG_PENDING_CALLBACKS) { -            addPendingCallback(saveCallback); -        } -        onSaveRequest(structure, extras, saveCallback); -    } - -    private void addPendingCallback(CallbackHelper.Dumpable callback) { -        if (mPendingCallbacks == null) { -            // Shouldn't happend since call is controlled by DEBUG_PENDING_CALLBACKS guard. -            Log.wtf(TAG, "addPendingCallback(): mPendingCallbacks not set"); -            return; -        } - -        if (DEBUG) Log.d(TAG, "Adding pending callback: " + callback); - -        callback.setFinalizer(() -> { -            if (DEBUG) Log.d(TAG, "Removing pending callback: " + callback); -            mPendingCallbacks.remove(callback); -        }); -        mPendingCallbacks.add(callback); -    } +    public abstract void onSaveRequest(@NonNull AssistStructure structure, @Nullable Bundle data, +            @NonNull SaveCallback callback);      /**       * Called when the Android system disconnects from the service. @@ -393,6 +219,6 @@ public abstract class AutoFillService extends Service {       * <p> At this point this service may no longer be an active {@link AutoFillService}.       */      public void onDisconnected() { -        if (DEBUG) Log.d(TAG, "onDisconnected()"); +      }  } diff --git a/core/java/android/service/autofill/AutoFillServiceInfo.java b/core/java/android/service/autofill/AutoFillServiceInfo.java index fd957f179443..985e32fcd966 100644 --- a/core/java/android/service/autofill/AutoFillServiceInfo.java +++ b/core/java/android/service/autofill/AutoFillServiceInfo.java @@ -16,6 +16,7 @@  package android.service.autofill;  import android.Manifest; +import android.annotation.NonNull;  import android.annotation.Nullable;  import android.app.AppGlobals;  import android.content.ComponentName; @@ -25,7 +26,6 @@ import android.content.res.Resources;  import android.content.res.TypedArray;  import android.content.res.XmlResourceParser;  import android.os.RemoteException; -import android.util.AndroidException;  import android.util.AttributeSet;  import android.util.Log;  import android.util.Xml; @@ -40,13 +40,10 @@ import java.io.IOException;  /**   * {@link ServiceInfo} and meta-data about an {@link AutoFillService}.   * - * <p>Upon construction, if {@link #getParseError()} is {@code null}, then the service is configured - * correctly. Otherwise, {@link #getParseError()} indicates the parsing error. - *   * @hide   */  public final class AutoFillServiceInfo { -    static final String TAG = "AutoFillServiceInfo"; +    private static final String TAG = "AutoFillServiceInfo";      private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, int userHandle)              throws PackageManager.NameNotFoundException { @@ -63,10 +60,9 @@ public final class AutoFillServiceInfo {          throw new PackageManager.NameNotFoundException(comp.toString());      } -    @Nullable -    private final String mParseError; - +    @NonNull      private final ServiceInfo mServiceInfo; +      @Nullable      private final String mSettingsActivity; @@ -77,17 +73,7 @@ public final class AutoFillServiceInfo {      public AutoFillServiceInfo(PackageManager pm, ServiceInfo si) {          mServiceInfo = si; -        TypedArray metaDataArray; -        try { -            metaDataArray = getMetaDataArray(pm, si); -        } catch (AndroidException e) { -            mParseError = e.getMessage(); -            mSettingsActivity = null; -            Log.w(TAG, mParseError, e); -            return; -        } - -        mParseError = null; +        final TypedArray metaDataArray = getMetaDataArray(pm, si);          if (metaDataArray != null) {              mSettingsActivity =                      metaDataArray.getString(R.styleable.AutoFillService_settingsActivity); @@ -101,12 +87,11 @@ public final class AutoFillServiceInfo {       * Gets the meta-data as a TypedArray, or null if not provided, or throws if invalid.       */      @Nullable -    private static TypedArray getMetaDataArray(PackageManager pm, ServiceInfo si) -            throws AndroidException { +    private static TypedArray getMetaDataArray(PackageManager pm, ServiceInfo si) {          // Check for permissions.          if (!Manifest.permission.BIND_AUTO_FILL.equals(si.permission)) { -            throw new AndroidException( -                "Service does not require permission " + Manifest.permission.BIND_AUTO_FILL); +            Log.e(TAG, "Service does not require permission " + Manifest.permission.BIND_AUTO_FILL); +            return null;          }          // Get the AutoFill metadata, if declared. @@ -125,11 +110,13 @@ public final class AutoFillServiceInfo {                          && type != XmlPullParser.START_TAG) {                  }              } catch (XmlPullParserException | IOException e) { -                throw new AndroidException("Error parsing auto fill service meta-data: " + e, e); +                Log.e(TAG, "Error parsing auto fill service meta-data", e); +                return null;              }              if (!"autofill-service".equals(parser.getName())) { -                throw new AndroidException("Meta-data does not start with autofill-service tag"); +                Log.e(TAG, "Meta-data does not start with autofill-service tag"); +                return null;              }              attrs = Xml.asAttributeSet(parser); @@ -138,7 +125,8 @@ public final class AutoFillServiceInfo {              try {                  res = pm.getResourcesForApplication(si.applicationInfo);              } catch (PackageManager.NameNotFoundException e) { -                throw new AndroidException("Error getting application resources: " + e, e); +                Log.e(TAG, "Error getting application resources", e); +                return null;              }              return res.obtainAttributes(attrs, R.styleable.AutoFillService); @@ -147,11 +135,6 @@ public final class AutoFillServiceInfo {          }      } -    @Nullable -    public String getParseError() { -        return mParseError; -    } -      public ServiceInfo getServiceInfo() {          return mServiceInfo;      } diff --git a/core/java/android/service/autofill/FillCallback.java b/core/java/android/service/autofill/FillCallback.java index 7cab7ae1c479..a306809886a7 100644 --- a/core/java/android/service/autofill/FillCallback.java +++ b/core/java/android/service/autofill/FillCallback.java @@ -16,56 +16,32 @@  package android.service.autofill; -import static android.service.autofill.AutoFillService.DEBUG; -import static android.util.DebugUtils.flagsToString; -  import android.annotation.Nullable;  import android.app.Activity;  import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable;  import android.os.RemoteException; -import android.service.autofill.CallbackHelper.Dumpable; -import android.service.autofill.CallbackHelper.Finalizer; -import android.util.Log; -import android.view.autofill.Dataset;  import android.view.autofill.FillResponse; -import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; - -import java.io.PrintWriter; -  /**   * Handles auto-fill requests from the {@link AutoFillService} into the {@link Activity} being   * auto-filled. - * - * <p>This class is thread safe.   */ -public final class FillCallback implements Dumpable { - -    private static final String TAG = "FillCallback"; - -    // NOTE: constants below are public so they can be used by flagsToString() -    /** @hide */ public static final int STATE_INITIAL = 1 << 0; -    /** @hide */ public static final int STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE = 1 << 1; -    /** @hide */ public static final int STATE_WAITING_DATASET_AUTH_RESPONSE = 1 << 2; -    /** @hide */ public static final int STATE_FINISHED_OK = 1 << 3; -    /** @hide */ public static final int STATE_FINISHED_FAILURE = 1 << 4; -    /** @hide */ public static final int STATE_FINISHED_ERROR = 1 << 5; -    /** @hide */ public static final int STATE_FINISHED_AUTHENTICATED = 1 << 6; - -    private final IAutoFillServerCallback mCallback; - -    @GuardedBy("mCallback") -    private int mState = STATE_INITIAL; - -    @GuardedBy("mCallback") -    private Finalizer mFinalizer; +public final class FillCallback implements Parcelable { +    private final IFillCallback mCallback; +    private boolean mCalled;      /** @hide */ -    FillCallback(IAutoFillServerCallback callback) { +    public FillCallback(IFillCallback callback) {          mCallback = callback;      } +    /** @hide */ +    private FillCallback(Parcel parcel) { +        mCallback = IFillCallback.Stub.asInterface(parcel.readStrongBinder()); +    } +      /**       * Notifies the Android System that an       * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle, @@ -76,43 +52,12 @@ public final class FillCallback implements Dumpable {       * {@link FillResponse} for examples.       */      public void onSuccess(@Nullable FillResponse response) { -        final boolean authRequired = response != null && response.isAuthRequired(); - -        if (DEBUG) Log.d(TAG, "onSuccess(): authReq= " + authRequired + ", resp=" + response); - -        synchronized (mCallback) { -            if (authRequired) { -                assertOnStateLocked(STATE_INITIAL); -            } else { -                assertOnStateLocked(STATE_INITIAL | STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE -                        | STATE_WAITING_DATASET_AUTH_RESPONSE); -            } - -            try { -                mCallback.showResponse(response); -                if (authRequired) { -                    mState = STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE; -                } else { -                    // Check if at least one dataset requires authentication. -                    boolean waitingAuth = false; -                    if (response != null) { -                        for (Dataset dataset : response.getDatasets()) { -                            if (dataset.isAuthRequired()) { -                                waitingAuth = true; -                                break; -                            } -                        } -                    } -                    if (waitingAuth) { -                        mState = STATE_WAITING_DATASET_AUTH_RESPONSE; -                    } else { -                        setFinalStateLocked(STATE_FINISHED_OK); -                    } -                } -            } catch (RemoteException e) { -                setFinalStateLocked(STATE_FINISHED_ERROR); -                e.rethrowAsRuntimeException(); -            } +        assertNotCalled(); +        mCalled = true; +        try { +            mCallback.onSuccess(response); +        } catch (RemoteException e) { +            e.rethrowAsRuntimeException();          }      } @@ -124,118 +69,43 @@ public final class FillCallback implements Dumpable {       *       * @param message error message to be displayed to the user.       */ -    public void onFailure(CharSequence message) { -        if (DEBUG) Log.d(TAG, "onFailure(): message=" + message); - -        Preconditions.checkArgument(message != null, "message cannot be null"); - -        synchronized (mCallback) { -            assertOnStateLocked(STATE_INITIAL | STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE -                    | STATE_WAITING_DATASET_AUTH_RESPONSE); - -            try { -                mCallback.showError(message); -                setFinalStateLocked(STATE_FINISHED_FAILURE); -            } catch (RemoteException e) { -                setFinalStateLocked(STATE_FINISHED_ERROR); -                e.rethrowAsRuntimeException(); -            } -        } -    } - -    /** -     * Notifies the Android System when the user authenticated a {@link FillResponse} previously -     * passed to {@link #onSuccess(FillResponse)}. -     * -     * @param flags must contain either -     * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} or -     * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}. -     */ -    public void onFillResponseAuthentication(int flags) { -        if (DEBUG) Log.d(TAG, "onFillResponseAuthentication(): flags=" + flags); - -        synchronized (mCallback) { -            assertOnStateLocked(STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE); - -            try { -                mCallback.unlockFillResponse(flags); -                setFinalStateLocked(STATE_FINISHED_AUTHENTICATED); -            } catch (RemoteException e) { -                setFinalStateLocked(STATE_FINISHED_ERROR); -                e.rethrowAsRuntimeException(); -            } -        } -    } - -    /** -     * Notifies the Android System when the user authenticated a {@link Dataset} previously passed -     * to {@link #onSuccess(FillResponse)}. -     * -     * @param dataset values to fill the activity with in case of successful authentication of a -     * previously locked (and empty) dataset). -     * @param flags must contain either -     * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} or -     * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}. -     */ -    public void onDatasetAuthentication(@Nullable Dataset dataset, int flags) { -        if (DEBUG) Log.d(TAG, "onDatasetAuthentication(): dataset=" + dataset + ", flags=" + flags); - -        synchronized (mCallback) { -            assertOnStateLocked(STATE_WAITING_DATASET_AUTH_RESPONSE); - -            try { -                mCallback.unlockDataset(dataset, flags); -                setFinalStateLocked(STATE_FINISHED_AUTHENTICATED); -            } catch (RemoteException e) { -                setFinalStateLocked(STATE_FINISHED_ERROR); -                e.rethrowAsRuntimeException(); -            } +    public void onFailure(@Nullable CharSequence message) { +        assertNotCalled(); +        mCalled = true; +        try { +            mCallback.onFailure(message); +        } catch (RemoteException e) { +            e.rethrowAsRuntimeException();          }      } -    @Override -    public String toString() { -        if (!DEBUG) return super.toString(); - -        return "FillCallback: [mState = " + mState + "]"; -    } -      /** @hide */      @Override -    public void dump(String prefix, PrintWriter pw) { -        pw.print(prefix); pw.print("FillCallback: mState="); pw.println(mState); +    public int describeContents() { +        return 0;      }      /** @hide */      @Override -    public void setFinalizer(Finalizer f) { -        synchronized (mCallback) { -            mFinalizer = f; -        } +    public void writeToParcel(Parcel parcel, int flags) { +        parcel.writeStrongBinder(mCallback.asBinder());      } -    /** -     * Sets a final state (where the callback cannot be used anymore) and notifies the -     * {@link Finalizer} (if any). -     */ -    private void setFinalStateLocked(int state) { -        if (DEBUG) Log.d(TAG, "setFinalState(): " + state); -        mState = state; - -        if (mFinalizer != null) { -            mFinalizer.gone(); +    private void assertNotCalled() { +        if (mCalled) { +            throw new IllegalStateException("Already called");          }      } -    // TODO(b/33197203): move and/or re-add state check logic on server side to avoid malicious app -    // calling the callback on wrong state. - -    // Make sure callback method is called during the proper lifecycle state. -    private void assertOnStateLocked(int flags) { -        if (DEBUG) Log.d(TAG, "assertOnState(): current=" + mState + ", required=" + flags); +    public static final Creator<FillCallback> CREATOR = new Creator<FillCallback>() { +        @Override +        public FillCallback createFromParcel(Parcel parcel) { +            return new FillCallback(parcel); +        } -        Preconditions.checkState((flags & mState) != 0, -                "invalid state: required " + flagsToString(FillCallback.class, "STATE_", flags) -                + ", current is " + flagsToString(FillCallback.class, "STATE_", mState)); -    } +        @Override +        public FillCallback[] newArray(int size) { +            return new FillCallback[size]; +        } +    };  } diff --git a/core/java/android/service/autofill/IAutoFillAppCallback.aidl b/core/java/android/service/autofill/IAutoFillAppCallback.aidl index 8c3898ab2b15..d9c161c1994b 100644 --- a/core/java/android/service/autofill/IAutoFillAppCallback.aidl +++ b/core/java/android/service/autofill/IAutoFillAppCallback.aidl @@ -18,6 +18,8 @@ package android.service.autofill;  import java.util.List; +import android.content.Intent; +import android.content.IntentSender;  import android.view.autofill.Dataset;  /** @@ -31,4 +33,9 @@ oneway interface IAutoFillAppCallback {        * Auto-fills the activity with the contents of a dataset.        */      void autoFill(in Dataset dataset); + +    /** +      * Start an intent sender from the context of the filled app +      */ +    void startIntentSender(in IntentSender intent, in Intent fillInIntent);  } diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl index a4e6ebc996d3..fa1ea65e30b0 100644 --- a/core/java/android/service/autofill/IAutoFillService.aidl +++ b/core/java/android/service/autofill/IAutoFillService.aidl @@ -18,19 +18,20 @@ package android.service.autofill;  import android.app.assist.AssistStructure;  import android.os.Bundle; -import android.service.autofill.IAutoFillServerCallback; +import android.service.autofill.IFillCallback; +import android.service.autofill.ISaveCallback;  import com.android.internal.os.IResultReceiver;  /** + * Interface from the system to an auto fill service. + *   * @hide   */ -// TODO(b/33197203): document class and methods  oneway interface IAutoFillService { -    // TODO(b/33197203): rename method to make them more consistent -    void autoFill(in AssistStructure structure, in IAutoFillServerCallback callback); -    void save(in AssistStructure structure, in IAutoFillServerCallback callback, in Bundle extras); -    void authenticateFillResponse(in Bundle extras, int flags); -    void authenticateDataset(in Bundle extras, int flags); +    void onFillRequest(in AssistStructure structure, in Bundle extras, +            in IFillCallback callback); +    void onSaveRequest(in AssistStructure structure, in Bundle extras, +            in ISaveCallback callback);      void onConnected();      void onDisconnected();  } diff --git a/core/java/android/service/autofill/IAutoFillServerCallback.aidl b/core/java/android/service/autofill/IFillCallback.aidl index 480438ac44ea..537403e72fc5 100644 --- a/core/java/android/service/autofill/IAutoFillServerCallback.aidl +++ b/core/java/android/service/autofill/IFillCallback.aidl @@ -1,5 +1,5 @@  /* - * Copyright (C) 2016 The Android Open Source Project + * 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. @@ -16,24 +16,17 @@  package android.service.autofill; -import java.util.List; +import android.os.ICancellationSignal; -import android.os.Bundle; -import android.view.autofill.AutoFillId; -import android.view.autofill.Dataset;  import android.view.autofill.FillResponse;  /** - * Object running in the AutoFillService process and used to communicate back with system_server. + * Interface to receive the result of a save request.   *   * @hide   */ -// TODO(b/33197203): rename to IAutoFillServerSession -oneway interface IAutoFillServerCallback { -    // TODO(b/33197203): document methods -    void showResponse(in FillResponse response); -    void showError(CharSequence message); -    void onSaved(); -    void unlockFillResponse(int flags); -    void unlockDataset(in Dataset dataset, int flags); +interface IFillCallback { +    void onCancellable(in ICancellationSignal cancellation); +    void onSuccess(in FillResponse response); +    void onFailure(CharSequence message);  } diff --git a/core/java/android/service/autofill/CallbackHelper.java b/core/java/android/service/autofill/ISaveCallback.aidl index ded8f97847c5..e260c7375cc5 100644 --- a/core/java/android/service/autofill/CallbackHelper.java +++ b/core/java/android/service/autofill/ISaveCallback.aidl @@ -13,18 +13,15 @@   * See the License for the specific language governing permissions and   * limitations under the License.   */ -package android.service.autofill; - -import java.io.PrintWriter; - -final class CallbackHelper { -    static interface Dumpable { -        void dump(String prefix, PrintWriter pw); -        void setFinalizer(Finalizer f); -    } +package android.service.autofill; -    static interface Finalizer { -        void gone(); -    } +/** + * Interface to receive the result of a save request. + * + * @hide + */ +interface ISaveCallback { +    void onSuccess(); +    void onFailure(CharSequence message);  } diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java index 9dd979599f5d..46b307233e88 100644 --- a/core/java/android/service/autofill/SaveCallback.java +++ b/core/java/android/service/autofill/SaveCallback.java @@ -16,21 +16,9 @@  package android.service.autofill; -import static android.service.autofill.AutoFillService.DEBUG; -  import android.app.Activity; -import android.app.assist.AssistStructure.ViewNode;  import android.os.Bundle;  import android.os.RemoteException; -import android.service.autofill.CallbackHelper.Dumpable; -import android.service.autofill.CallbackHelper.Finalizer; -import android.util.Log; -import android.view.autofill.AutoFillId; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; - -import java.io.PrintWriter;  /**   * Handles save requests from the {@link AutoFillService} into the {@link Activity} being @@ -38,20 +26,12 @@ import java.io.PrintWriter;   *   * <p>This class is thread safe.   */ -public final class SaveCallback implements Dumpable { - -    private static final String TAG = "SaveCallback"; - -    private final IAutoFillServerCallback mCallback; - -    @GuardedBy("mCallback") -    private boolean mReplied = false; - -    @GuardedBy("mCallback") -    private Finalizer mFinalizer; +public final class SaveCallback { +    private final ISaveCallback mCallback; +    private boolean mCalled;      /** @hide */ -    SaveCallback(IAutoFillServerCallback callback) { +    SaveCallback(ISaveCallback callback) {          mCallback = callback;      } @@ -63,17 +43,12 @@ public final class SaveCallback implements Dumpable {       * @throws RuntimeException if an error occurred while calling the Android System.       */      public void onSuccess() { -        if (DEBUG) Log.d(TAG, "onSuccess()"); - -        synchronized (mCallback) { -            checkNotRepliedYetLocked(); -            try { -                mCallback.onSaved(); -            } catch (RemoteException e) { -                e.rethrowAsRuntimeException(); -            } finally { -                setRepliedLocked(); -            } +        assertNotCalled(); +        mCalled = true; +        try { +            mCallback.onSuccess(); +        } catch (RemoteException e) { +            e.rethrowAsRuntimeException();          }      } @@ -87,54 +62,18 @@ public final class SaveCallback implements Dumpable {       * @throws RuntimeException if an error occurred while calling the Android System.       */      public void onFailure(CharSequence message) { -        if (DEBUG) Log.d(TAG, "onFailure(): message=" + message); - -        synchronized (mCallback) { -            checkNotRepliedYetLocked(); - -            try { -                mCallback.showError(message); -            } catch (RemoteException e) { -                e.rethrowAsRuntimeException(); -            } finally { -                setRepliedLocked(); -            } -        } -    } - -    /** @hide */ -    @Override -    public void dump(String prefix, PrintWriter pw) { -        pw.print(prefix); pw.print("SaveCallback: mReplied="); pw.println(mReplied); -    } - -    /** @hide */ -    @Override -    public void setFinalizer(Finalizer f) { -        synchronized (mCallback) { -            mFinalizer = f; +        assertNotCalled(); +        mCalled = true; +        try { +            mCallback.onFailure(message); +        } catch (RemoteException e) { +            e.rethrowAsRuntimeException();          }      } -    @Override -    public String toString() { -        if (!DEBUG) return super.toString(); - -        return "SaveCallback: [mReplied= " + mReplied + "]"; -    } - -    // There can be only one!! -    private void checkNotRepliedYetLocked() { -        Preconditions.checkState(!mReplied, "already replied"); -    } - -    private void setRepliedLocked() { -        if (DEBUG) Log.d(TAG, "setReplied()"); - -        mReplied = true; - -        if (mFinalizer != null) { -            mFinalizer.gone(); +    private void assertNotCalled() { +        if (mCalled) { +            throw new IllegalStateException("Already called");          }      }  } diff --git a/core/java/android/view/autofill/AutoFillManager.java b/core/java/android/view/autofill/AutoFillManager.java index f2f522daa707..147d72a09f14 100644 --- a/core/java/android/view/autofill/AutoFillManager.java +++ b/core/java/android/view/autofill/AutoFillManager.java @@ -16,6 +16,8 @@  package android.view.autofill; +import static android.view.autofill.Helper.DEBUG; +  import android.annotation.Nullable;  import android.content.Context;  import android.graphics.Rect; @@ -31,7 +33,6 @@ import android.view.View;  public final class AutoFillManager {      private static final String TAG = "AutoFillManager"; -    private static final boolean DEBUG = true; // TODO(b/33197203): change to false once stable      /**       * Flag used to show the auto-fill UI affordance for a view. diff --git a/core/java/android/view/autofill/AutoFillSession.java b/core/java/android/view/autofill/AutoFillSession.java index eec7a823a841..efc1df6c6e84 100644 --- a/core/java/android/view/autofill/AutoFillSession.java +++ b/core/java/android/view/autofill/AutoFillSession.java @@ -19,7 +19,8 @@ package android.view.autofill;  import static android.view.autofill.Helper.DEBUG;  import android.app.Activity; -import android.os.RemoteException; +import android.content.Intent; +import android.content.IntentSender;  import android.service.autofill.IAutoFillAppCallback;  import android.util.Log;  import android.view.View; @@ -39,7 +40,7 @@ public final class AutoFillSession {      private final IAutoFillAppCallback mCallback = new IAutoFillAppCallback.Stub() {          @Override -        public void autoFill(Dataset dataset) throws RemoteException { +        public void autoFill(Dataset dataset) {              final Activity activity = mActivity.get();              if (activity == null) {                  if (DEBUG) Log.d(TAG, "autoFill(): activity already GCed"); @@ -49,12 +50,10 @@ public final class AutoFillSession {              // dataset.extras to service              activity.runOnUiThread(() -> {                  final View root = activity.getWindow().getDecorView().getRootView(); -                for (DatasetField field : dataset.getFields()) { -                    final AutoFillId id = field.getId(); -                    if (id == null) { -                        Log.w(TAG, "autoFill(): null id on " + field); -                        continue; -                    } +                final int itemCount = dataset.getFieldIds().size(); +                for (int i = 0; i < itemCount; i++) { +                    final AutoFillId id = dataset.getFieldIds().get(i); +                    final AutoFillValue value = dataset.getFieldValues().get(i);                      final int viewId = id.getViewId();                      final View view = root.findViewByAccessibilityIdTraversal(viewId);                      if (view == null) { @@ -79,14 +78,28 @@ public final class AutoFillSession {                              Log.d(TAG, "autoFill(): delegating " + id                                      + " to VirtualViewDelegate  " + delegate);                          } -                        delegate.autoFill(id.getVirtualChildId(), field.getValue()); +                        delegate.autoFill(id.getVirtualChildId(), value);                      } else {                          // Handle non-virtual fields itself. -                        view.autoFill(field.getValue()); +                        view.autoFill(value);                      }                  }              });          } + +        @Override +        public void startIntentSender(IntentSender intent, Intent fillInIntent) { +            final Activity activity = mActivity.get(); +            if (activity != null) { +                activity.runOnUiThread(() -> { +                    try { +                        activity.startIntentSender(intent, fillInIntent, 0, 0, 0); +                    } catch (IntentSender.SendIntentException e) { +                        Log.e(TAG, "startIntentSender() failed for intent:" + intent, e); +                    } +                }); +            } +        }      };      private final WeakReference<Activity> mActivity; @@ -114,5 +127,4 @@ public final class AutoFillSession {              }          }      } -  } diff --git a/core/java/android/view/autofill/Dataset.java b/core/java/android/view/autofill/Dataset.java index 18a08f930703..2708358c3143 100644 --- a/core/java/android/view/autofill/Dataset.java +++ b/core/java/android/view/autofill/Dataset.java @@ -17,12 +17,12 @@  package android.view.autofill;  import static android.view.autofill.Helper.DEBUG; -import static android.view.autofill.Helper.append; +import android.annotation.NonNull;  import android.annotation.Nullable;  import android.app.Activity;  import android.app.assist.AssistStructure.ViewNode; -import android.hardware.fingerprint.FingerprintManager.CryptoObject; +import android.content.IntentSender;  import android.os.Bundle;  import android.os.Parcel;  import android.os.Parcelable; @@ -30,8 +30,6 @@ import android.os.Parcelable;  import com.android.internal.util.Preconditions;  import java.util.ArrayList; -import java.util.List; -import java.util.Objects;  /**   * A set of data that can be used to auto-fill an {@link Activity}. @@ -44,221 +42,184 @@ import java.util.Objects;   *   <li>An optional {@link Bundle} with extras (used only by the service creating it).   * </ol>   * - * See {@link FillResponse} for examples. + * @see FillResponse for examples.   */  public final class Dataset implements Parcelable { - +    private final String mId;      private final CharSequence mName; -    private final ArrayList<DatasetField> mFields; +    private final ArrayList<AutoFillId> mFieldIds; +    private final ArrayList<AutoFillValue> mFieldValues;      private final Bundle mExtras; -    private final int mFlags; -    private final boolean mRequiresAuth; -    private final boolean mHasCryptoObject; -    private final long mCryptoOpId; +    private final IntentSender mAuthentication; -    private Dataset(Dataset.Builder builder) { +    private Dataset(Builder builder) { +        mId = builder.mId;          mName = builder.mName; -        // TODO(b/33197203): make an immutable copy of mFields? -        mFields = builder.mFields; +        mFieldIds = builder.mFieldIds; +        mFieldValues = builder.mFieldValues;          mExtras = builder.mExtras; -        mFlags = builder.mFlags; -        mRequiresAuth = builder.mRequiresAuth; -        mHasCryptoObject = builder.mHasCryptoObject; -        mCryptoOpId = builder.mCryptoOpId; +        mAuthentication = builder.mAuthentication;      }      /** @hide */ -    public CharSequence getName() { -        return mName; +    public @NonNull String getId() { +        return mId;      }      /** @hide */ -    public List<DatasetField> getFields() { -        return mFields; -    } - -    /** @hide */ -    public Bundle getExtras() { -        return mExtras; +    public @NonNull CharSequence getName() { +        return mName;      }      /** @hide */ -    public int getFlags() { -        return mFlags; +    public @Nullable ArrayList<AutoFillId> getFieldIds() { +        return mFieldIds;      }      /** @hide */ -    public boolean isAuthRequired() { -        return mRequiresAuth; +    public @Nullable ArrayList<AutoFillValue> getFieldValues() { +        return mFieldValues;      }      /** @hide */ -    public boolean isEmpty() { -        return mFields.isEmpty(); +    public @Nullable Bundle getExtras() { +        return mExtras;      }      /** @hide */ -    public boolean hasCryptoObject() { -        return mHasCryptoObject; +    public @Nullable IntentSender getAuthentication() { +        return mAuthentication;      }      /** @hide */ -    public long getCryptoObjectOpId() { -        return mCryptoOpId; +    public boolean isEmpty() { +        return mFieldIds == null || mFieldIds.isEmpty();      }      @Override      public String toString() {          if (!DEBUG) return super.toString(); -        final StringBuilder builder = new StringBuilder("Dataset [name=").append(mName) -                .append(", fields=").append(mFields).append(", extras="); -        append(builder, mExtras) -            .append(", flags=").append(mFlags) -            .append(", requiresAuth: ").append(mRequiresAuth) -            .append(", hasCrypto: ").append(mHasCryptoObject); +        final StringBuilder builder = new StringBuilder("Dataset [id=").append(mId) +                .append(", name=").append(mName) +                .append(", fieldIds=").append(mFieldIds) +                .append(", fieldValues=").append(mFieldValues) +                .append(", hasAuthentication=").append(mAuthentication != null) +                .append(", hasExtras=").append(mExtras != null);          return builder.append(']').toString();      } +    @Override +    public boolean equals(Object obj) { +        if (this == obj) { +            return true; +        } +        if (obj == null) { +            return false; +        } +        if (getClass() != obj.getClass()) { +            return false; +        } +        final Dataset other = (Dataset) obj; +        if (mId == null) { +            if (other.mId != null) { +                return false; +            } +        } else if (!mId.equals(other.mId)) { +            return false; +        } +        return true; +    } + +    @Override +    public int hashCode() { +        return mId != null ? mId.hashCode() : 0; +    } +      /** -     * A builder for {@link Dataset} objects. +     * A builder for {@link Dataset} objects. You must to provide at least +     * one value for a field or set an authentication intent.       */      public static final class Builder { +        private String mId;          private CharSequence mName; -        private final ArrayList<DatasetField> mFields = new ArrayList<>(); +        private ArrayList<AutoFillId> mFieldIds; +        private ArrayList<AutoFillValue> mFieldValues;          private Bundle mExtras; -        private int mFlags; -        private boolean mRequiresAuth; -        private boolean mHasCryptoObject; -        private long mCryptoOpId; +        private IntentSender mAuthentication; +        private boolean mDestroyed; + +        /** @hide */ +        // TODO(b/33197203): Remove once GCore migrates +        public Builder(@NonNull CharSequence name) { +            this(String.valueOf(System.currentTimeMillis()), name); +        }          /**           * Creates a new builder.           * +         * @param id A required id to identify this dataset for future interactions related to it.           * @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 an user-provided name +         * the first field in the dataset (like username or email address) or a user-provided name           * (like "My Work Address").           */ -        public Builder(CharSequence name) { +        public Builder(@NonNull String id, @NonNull CharSequence name) { +            mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty or null");              mName = Preconditions.checkStringNotEmpty(name, "name cannot be empty or null");          }          /** -         * Requires dataset authentication through the {@link -         * android.service.autofill.AutoFillService} before auto-filling the activity with this -         * dataset. +         * Requires a dataset authentication before auto-filling the activity with this dataset.           * -         * <p>This method is typically called when the device (or the service) does not support -         * fingerprint authentication (and hence it cannot use {@link -         * #requiresFingerprintAuthentication(CryptoObject, Bundle, int)}) or when the service needs -         * to use a custom authentication UI for the dataset. For example, when a dataset contains -         * credit card information (such as number, expiration date, and verification code), the -         * service displays an authentication dialog asking for the verification code to unlock the -         * rest of the data). +         * <p>This method is called when you need to provide an authentication +         * UI for the dataset. For example, when a dataset 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 +         * dataset is completely populated the system will launch the specified authentication +         * intent and will need your approval to fill it in. Since the dataset is "locked" +         * until the user authenticates it, typically this dataset name is masked +         * (for example, "VISA....1234"). Typically you would want to store the dataset +         * labels non-encypted and the actual sensitive data encrypted and not in memory. +         * This allows showing the labels in the UI while involving the user if one of +         * the items with these labels is chosen. Note that if you use sensitive data as +         * a label, for example an email address, then it should also be encrypted. +         *</p>           * -         * <p>Since the dataset is "locked" until the user authenticates it, typically this dataset -         * name is masked (for example, "VISA....1234"). +         * <p>When a user selects this dataset, the system triggers the provided intent +         * whose extras will have the {@link android.content.Intent#EXTRA_AUTO_FILL_ITEM_ID id} +         * of the {@link android.view.autofill.Dataset dataset} to authenticate, the {@link +         * android.content.Intent#EXTRA_AUTO_FILL_EXTRAS extras} associated with this +         * dataset, and a {@link android.content.Intent#EXTRA_AUTO_FILL_CALLBACK callback} +         * to dispatch the authentication result.</p>           * -         * <p>When the user selects this dataset, the Android System calls {@link -         * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)} -         * passing {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_REQUESTED} in -         * the flags and the same {@code extras} passed to this method. The service can then -         * displays its custom authentication UI, and then call the proper method on {@link -         * android.service.autofill.FillCallback} depending on the authentication result and whether -         * this dataset already contains the fields needed to auto-fill the activity: +         * <p>Once you complete your authentication flow you should use the provided callback +         * to notify for a failure or a success. In case of a success you need to provide +         * only the fully populated dataset that is being authenticated. For example, if you +         * provided a {@link FillResponse} with two {@link Dataset}s and marked that +         * only the first dataset needs an authentication then in the provided response +         * you need to provide only the fully populated dataset being authenticated instead +         * of both of them. +         * </p>           * -         * <ul> -         *   <li>If authentication failed, call -         *   {@link android.service.autofill.FillCallback#onDatasetAuthentication(Dataset, -         *       int)} passing {@link -         *       android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} in the flags. -         *   <li>If authentication succeeded and this datast is empty (no fields), call {@link -         *       android.service.autofill.FillCallback#onSuccess(FillResponse)} with a new dataset -         *       (with the proper fields). -         *   <li>If authentication succeeded and this response is not empty, call {@link -         *       android.service.autofill.FillCallback#onDatasetAuthentication(Dataset, int)} -         *       passing -         *       {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS} in the -         *       {@code flags} and {@code null} as the {@code dataset}. -         * </ul> +         * <p>The indent sender mechanism allows you to have your authentication UI +         * implemented as an activity or a service or a receiver. However, the recommended +         * way is to do this is with an activity which the system will start in the +         * filled activity's task meaning it will properly work with back, recent apps, and +         * free-form multi-window, while avoiding the need for the "draw on top of other" +         * apps special permission. You can still theme your authentication activity's +         * UI to look like a dialog if desired.</p>           * -         * @param extras when set, will be passed back in the {@link -         *     android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, -         *     int)}, call so it could be used by the service to handle state. -         * @param flags optional parameters, currently ignored. -         */ -        public Builder requiresCustomAuthentication(@Nullable Bundle extras, int flags) { -            return requiresAuthentication(null, extras, flags); -        } - -        /** -         * Requires dataset authentication through the Fingerprint sensor before auto-filling the -         * activity with this dataset. -         * -         * <p>This method is typically called when the dataset contains sensitive information (for -         * example, credit card information) and the provider requires the user to re-authenticate -         * before using it. -         * -         * <p>Since the dataset is "locked" until the user authenticates it, typically this dataset -         * name is masked (for example, "VISA....1234"). -         * -         * <p>When the user selects this dataset, the Android System displays an UI affordance -         * asking the user to use the fingerprint sensor unlock the dataset, and what happens after -         * a successful fingerprint authentication depends on whether the dataset is empty (no -         * fields, only the masked name) or not: +         * <p></><strong>Note:</strong> Do not make the provided intent sender +         * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the +         * platform needs to fill in the authentication arguments.</p>           * -         * <ul> -         *   <li>If it's empty, the Android System will call {@link -         *       android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, -         *       int)} passing {@link -         *       android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}} in the -         *       flags. -         *   <li>If it's not empty, the activity will be auto-filled with its data. -         * </ul> +         * @param authentication Intent to trigger your authentication flow.           * -         * <p>If the fingerprint authentication fails, the Android System will call {@link -         * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)} -         * passing {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} in the -         * flags. -         * -         * <p><strong>NOTE: </note> the {@link android.service.autofill.AutoFillService} should use -         * the {@link android.hardware.fingerprint.FingerprintManager} to check if fingerpint -         * authentication is available before using this method, and use other alternatives (such as -         * {@link #requiresCustomAuthentication(Bundle, int)}) if it is not: if this method is -         * called when fingerprint is not available, Android System will call {@link -         * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)} -         * passing {@link -         * android.service.autofill.AutoFillService#FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE} -         * in the flags, but it would be wasting system resources (and worsening the user -         * experience) in the process. -         * -         * @param crypto object that will be authenticated. -         * @param extras when set, will be passed back in the {@link -         *     android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)} -         *     call so it could be used by the service to handle state. -         * @param flags optional parameters, currently ignored. +         * @see android.app.PendingIntent#getIntentSender()           */ -        public Builder requiresFingerprintAuthentication(CryptoObject crypto, -                @Nullable Bundle extras, int flags) { -            // TODO(b/33197203): should we allow crypto to be null? -            Preconditions.checkArgument(crypto != null, "must pass a CryptoObject"); -            return requiresAuthentication(crypto, extras, flags); -        } - -        private Builder requiresAuthentication(CryptoObject cryptoObject, Bundle extras, -                int flags) { -            // There can be only one! -            Preconditions.checkState(!mRequiresAuth, -                    "requires-authentication methods already called"); -            // TODO(b/33197203): make sure that either this method or setExtras() is called, but -            // not both -            mExtras = extras; -            mFlags = flags; -            mRequiresAuth = true; -            if (cryptoObject != null) { -                mHasCryptoObject = true; -                mCryptoOpId = cryptoObject.getOpId(); -            } +        public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) { +            throwIfDestroyed(); +            mAuthentication = authentication;              return this;          } @@ -268,41 +229,55 @@ public final class Dataset implements Parcelable {           * @param id id returned by {@link ViewNode#getAutoFillId()}.           * @param value value to be auto filled.           */ -        public Dataset.Builder setValue(AutoFillId id, AutoFillValue value) { -            putField(new DatasetField(id, value)); +        public @NonNull Builder setValue(@NonNull AutoFillId id, @NonNull AutoFillValue value) { +            throwIfDestroyed(); +            Preconditions.checkNotNull(id, "id cannot be null"); +            Preconditions.checkNotNull(value, "value cannot be null"); +            if (mFieldIds != null) { +                final int existingIdx = mFieldIds.indexOf(id); +                if (existingIdx >= 0) { +                    mFieldValues.set(existingIdx, value); +                    return this; +                } +            } else { +                mFieldIds = new ArrayList<>(); +                mFieldValues = new ArrayList<>(); +            } +            mFieldIds.add(id); +            mFieldValues.add(value);              return this;          }          /** -         * Creates a new {@link Dataset} instance. +         * Sets a {@link Bundle} that will be passed to subsequent APIs that +         * manipulate this dataset. For example, they are passed in as {@link +         * android.content.Intent#EXTRA_AUTO_FILL_EXTRAS extras} to your +         * authentication flow.           */ -        public Dataset build() { -            return new Dataset(this); +        public @NonNull Builder setExtras(@Nullable Bundle extras) { +            throwIfDestroyed(); +            mExtras = extras; +            return this;          }          /** -         * Sets a {@link Bundle} that will be passed to subsequent calls to -         * {@link android.service.autofill.AutoFillService} methods such as - * {@link android.service.autofill.AutoFillService#onSaveRequest(android.app.assist.AssistStructure, -         * Bundle, android.service.autofill.SaveCallback)}, using -         * {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} as the key. -         * -         * <p>It can be used to keep service state in between calls. +         * Creates a new {@link Dataset} instance. You should not interact +         * with this builder once this method is called.           */ -        public Builder setExtras(Bundle extras) { -            // TODO(b/33197203): make sure that either this method or the requires-Authentication -            // ones are called, but not both -            mExtras = Objects.requireNonNull(extras, "extras cannot be null"); -            return this; +        public @NonNull Dataset build() { +            throwIfDestroyed(); +            mDestroyed = true; +            if (mFieldIds == null && mAuthentication == null) { +                throw new IllegalArgumentException( +                        "at least one value or an authentication must be set"); +            } +            return new Dataset(this);          } -        /** -         * Emulates {@code Map.put()} by adding a new field to the list if its id is not the yet, -         * or replacing the existing one. -         */ -        private void putField(DatasetField field) { -            // TODO(b/33197203): check if already exists and replaces it if so -            mFields.add(field); +        private void throwIfDestroyed() { +            if (mDestroyed) { +                throw new IllegalStateException("Already called #build()"); +            }          }      } @@ -317,32 +292,33 @@ public final class Dataset implements Parcelable {      @Override      public void writeToParcel(Parcel parcel, int flags) { +        parcel.writeString(mId);          parcel.writeCharSequence(mName); -        parcel.writeList(mFields); +        parcel.writeTypedArrayList(mFieldIds, 0); +        parcel.writeTypedArrayList(mFieldValues, 0);          parcel.writeBundle(mExtras); -        parcel.writeInt(mFlags); -        parcel.writeInt(mRequiresAuth ? 1 : 0); -        parcel.writeInt(mHasCryptoObject ? 1 : 0); -        if (mHasCryptoObject) { -            parcel.writeLong(mCryptoOpId); -        } -    } - -    @SuppressWarnings("unchecked") -    private Dataset(Parcel parcel) { -        mName = parcel.readCharSequence(); -        mFields = parcel.readArrayList(null); -        mExtras = parcel.readBundle(); -        mFlags = parcel.readInt(); -        mRequiresAuth = parcel.readInt() == 1; -        mHasCryptoObject = parcel.readInt() == 1; -        mCryptoOpId = mHasCryptoObject ? parcel.readLong() : 0; +        parcel.writeParcelable(mAuthentication, flags);      }      public static final Parcelable.Creator<Dataset> CREATOR = new Parcelable.Creator<Dataset>() {          @Override -        public Dataset createFromParcel(Parcel source) { -            return new Dataset(source); +        public Dataset 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.readString(), parcel.readCharSequence()); +            final ArrayList<AutoFillId> ids = parcel.readTypedArrayList(null); +            final ArrayList<AutoFillValue> values = parcel.readTypedArrayList(null); +            final int idCount = (ids != null) ? ids.size() : 0; +            final int valueCount = (values != null) ? values.size() : 0; +            for (int i = 0; i < idCount; i++) { +                AutoFillId id = ids.get(i); +                AutoFillValue value = (valueCount > i) ? values.get(i) : null; +                builder.setValue(id, value); +            } +            builder.setExtras(parcel.readBundle()); +            builder.setAuthentication(parcel.readParcelable(null)); +            return builder.build();          }          @Override diff --git a/core/java/android/view/autofill/DatasetField.java b/core/java/android/view/autofill/DatasetField.java deleted file mode 100644 index c6b92acd5759..000000000000 --- a/core/java/android/view/autofill/DatasetField.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2016 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.view.autofill; - -import static android.view.autofill.Helper.DEBUG; - -import android.os.Parcel; -import android.os.Parcelable; - -/** @hide */ -public final class DatasetField implements Parcelable { - -    private final AutoFillId mId; -    private final AutoFillValue mValue; - -    DatasetField(AutoFillId id, AutoFillValue value) { -        mId = id; -        mValue = value; -    } - -    public AutoFillId getId() { -        return mId; -    } - -    public AutoFillValue getValue() { -        return mValue; -    } - -    ///////////////////////////////// -    //  Object "contract" methods. // -    ///////////////////////////////// - -    @Override -    public String toString() { -        if (!DEBUG) return super.toString(); - -        return "DatasetField [id=" + mId + ", value=" + mValue + "]"; -    } - -    ///////////////////////////////////// -    //  Parcelable "contract" methods. // -    ///////////////////////////////////// - -    @Override -    public int describeContents() { -        return 0; -    } - -    @Override -    public void writeToParcel(Parcel parcel, int flags) { -        parcel.writeParcelable(mId, 0); -        parcel.writeParcelable(mValue, 0); -    } - -    private DatasetField(Parcel parcel) { -        mId = parcel.readParcelable(null); -        mValue = parcel.readParcelable(null); -    } - -    public static final Parcelable.Creator<DatasetField> CREATOR = -            new Parcelable.Creator<DatasetField>() { -        @Override -        public DatasetField createFromParcel(Parcel source) { -            return new DatasetField(source); -        } - -        @Override -        public DatasetField[] newArray(int size) { -            return new DatasetField[size]; -        } -    }; -} diff --git a/core/java/android/view/autofill/FillResponse.java b/core/java/android/view/autofill/FillResponse.java index 48dbb8415ec4..596a06c28d57 100644 --- a/core/java/android/view/autofill/FillResponse.java +++ b/core/java/android/view/autofill/FillResponse.java @@ -16,36 +16,30 @@  package android.view.autofill;  import static android.view.autofill.Helper.DEBUG; -import static android.view.autofill.Helper.append; +import android.annotation.NonNull;  import android.annotation.Nullable;  import android.app.Activity; -import android.hardware.fingerprint.FingerprintManager.CryptoObject; +import android.content.IntentSender;  import android.os.Bundle;  import android.os.Parcel;  import android.os.Parcelable; -import android.service.autofill.FillCallback; +import android.util.ArraySet;  import com.android.internal.util.Preconditions; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -  /**   * Response for a {@link   * android.service.autofill.AutoFillService#onFillRequest(android.app.assist.AssistStructure, - * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)} request. + * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)} and + * authentication requests.   *   * <p>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 + * fields that can be auto-filled together, and the Android system displays a dataset picker UI   * affordance that the user must use before the {@link Activity} is filled with the dataset.   *   * <p>For example, for a login page with username/password where the user only has one account in - * the service, the response could be: + * the response could be:   *   * <pre class="prettyprint">   *  new FillResponse.Builder() @@ -102,12 +96,17 @@ import java.util.Set;   * <p>Notice that the ids that are part of a dataset (ids 1 to 4, in this example) are automatically   * added to the {@code savableIds} list.   * - * <p>If the service has multiple {@link Dataset}s with multiple options for some fields on each - * dataset (for example, multiple accounts with both a home and work address), then it should - * "partition" the {@link Activity} in sections and populate the response with just a subset of the - * data that would fulfill the first section; then once the user fills the first section and taps a - * field from the next section, the Android system would issue another request for that section, and - * so on. For example, the first response could be: + * <p>If the service has multiple {@link Dataset}s for different sections of the activity, + * for example, a user section for which there are two datasets followed by an address + * section for which there are two datasets for each user user, then it should "partition" + * the activity in sections and populate the response with just a subset of the data that would + * fulfill the first section (the name in our example); then once the user fills the first + * section and taps a field from the next section (the address in our example), the Android + * system would issue another request for that section, and so on. Note that if the user + * chooses to populate the first section with a service provided dataset, the subsequent request + * would contain the populated values so you don't try to provide suggestions for the first + * section but ony for the second one based on the context of what was already filled. For + * example, the first response could be:   *   * <pre class="prettyprint">   *  new FillResponse.Builder() @@ -132,236 +131,182 @@ import java.util.Set;   *          .setTextFieldValue(id4, "Springfield")   *          .build())   *      .add(new Dataset.Builder("Work") - *          .setTextFieldValue(id3, "Springfield Nuclear Power Plant") + *          .setTextFieldValue(id3, "Springfield Power Plant")   *          .setTextFieldValue(id4, "Springfield")   *          .build())   *      .build();   * </pre>   * - * <p>The service could require user authentication, either at the {@link FillResponse} or {@link - * Dataset} levels, prior to auto-filling the activity - see {@link - * FillResponse.Builder#requiresFingerprintAuthentication(CryptoObject, Bundle, int)}, {@link - * FillResponse.Builder#requiresCustomAuthentication(Bundle, int)}, {@link - * Dataset.Builder#requiresFingerprintAuthentication(CryptoObject, Bundle, int)}, and {@link - * Dataset.Builder#requiresCustomAuthentication(Bundle, int)} for details. + * <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 + * which would allow you to provide the dataset names to the user and if they choose one + * 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 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.</p>   * - * <p>Finally, the service can use the {@link FillResponse.Builder#setExtras(Bundle)} and/or {@link - * Dataset.Builder#setExtras(Bundle)} methods to pass {@link Bundle}s with service-specific data use - * to identify this response on future calls (like {@link - * android.service.autofill.AutoFillService#onSaveRequest(android.app.assist.AssistStructure, - * Bundle, android.service.autofill.SaveCallback)}) - such bundles will be available as the - * {@link android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS} and - * {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} extras in that method's - * {@code extras} argument. + * <p>Finally, the service can use {@link Dataset.Builder#setExtras(Bundle)} methods + * to pass {@link Bundle extras} provided to all future calls related to a dataset, + * for example during authentication and saving.</p>   */  public final class FillResponse implements Parcelable { - -    private final List<Dataset> mDatasets; -    private final AutoFillId[] mSavableIds; +    private final String mId; +    private final ArraySet<Dataset> mDatasets; +    private final ArraySet<AutoFillId> mSavableIds;      private final Bundle mExtras; -    private final int mFlags; -    private final boolean mRequiresAuth; -    private final boolean mHasCryptoObject; -    private final long mCryptoOpId; +    private final IntentSender mAuthentication; -    private FillResponse(Builder builder) { -        // TODO(b/33197203): make it immutable? +    private FillResponse(@NonNull Builder builder) { +        mId = builder.mId;          mDatasets = builder.mDatasets; -        final int size = builder.mSavableIds.size(); -        mSavableIds = new AutoFillId[size]; -        int i = 0; -        for (AutoFillId id : builder.mSavableIds) { -            mSavableIds[i++] = id; -        } +        mSavableIds = builder.mSavableIds;          mExtras = builder.mExtras; -        mFlags = builder.mFlags; -        mRequiresAuth = builder.mRequiresAuth; -        mHasCryptoObject = builder.mHasCryptoObject; -        mCryptoOpId = builder.mCryptoOpId; -    } - -    /** @hide */ -    public List<Dataset> getDatasets() { -        return mDatasets; +        mAuthentication = builder.mAuthentication;      }      /** @hide */ -    public AutoFillId[] getSavableIds() { -        return mSavableIds; +    public @NonNull String getId() { +        return mId;      }      /** @hide */ -    public Bundle getExtras() { +    public @Nullable Bundle getExtras() {          return mExtras;      }      /** @hide */ -    public int getFlags() { -        return mFlags; -    } - -    /** @hide */ -    public boolean isAuthRequired() { -        return mRequiresAuth; +    public @Nullable ArraySet<Dataset> getDatasets() { +        return mDatasets;      }      /** @hide */ -    public boolean hasCryptoObject() { -        return mHasCryptoObject; +    public @Nullable ArraySet<AutoFillId> getSavableIds() { +        return mSavableIds;      }      /** @hide */ -    public long getCryptoObjectOpId() { -        return mCryptoOpId; +    public @Nullable IntentSender getAuthentication() { +        return mAuthentication;      }      /** -     * Builder for {@link FillResponse} objects. +     * Builder for {@link FillResponse} objects. You must to provide at least +     * one dataset or set an authentication intent.       */      public static final class Builder { -        private final List<Dataset> mDatasets = new ArrayList<>(); -        private final Set<AutoFillId> mSavableIds = new HashSet<>(); +        private final String mId; +        private ArraySet<Dataset> mDatasets; +        private ArraySet<AutoFillId> mSavableIds;          private Bundle mExtras; -        private int mFlags; -        private boolean mRequiresAuth; -        private boolean mHasCryptoObject; -        private long mCryptoOpId; +        private IntentSender mAuthentication; +        private boolean mDestroyed; + +        /** @hide */ +        // TODO(b/33197203): Remove once GCore migrates +        public Builder() { +            this(String.valueOf(System.currentTimeMillis())); +        }          /** -         * Requires user authentication through the {@link android.service.autofill.AutoFillService} -         * before handling an auto-fill request. +         * Creates a new {@link FillResponse} builder.           * -         * <p>This method is typically called when the device (or the service) does not support -         * fingerprint authentication (and hence it cannot use {@link -         * #requiresFingerprintAuthentication(CryptoObject, Bundle, int)}) or when the service needs -         * to use a custom authentication UI and is used in 2 scenarios: -         * -         * <ol> -         *   <li>When the user data is encrypted and the service must authenticate an object that -         *       will be used to decrypt it. -         *   <li>When the service already acquired the user data but wants to confirm the user's -         *       identity before the activity is filled with it. -         * </ol> -         * -         * <p>When this method is called, the Android System displays an UI affordance asking the -         * user to tap it to auto-fill the activity; if the user taps it, the Android System calls -         * {@link -         * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle, -         * int)} passing {@link -         * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_REQUESTED} in the flags and -         * the same {@code extras} passed to this method. The service can then displays its custom -         * authentication UI, and then call the proper method on {@link FillCallback} depending on -         * the authentication result and whether this response already contains the {@link Dataset}s -         * need to auto-fill the activity: -         * -         * <ul> -         *   <li>If authentication failed, call {@link -         *       FillCallback#onFillResponseAuthentication(int)} passing {@link -         *       android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} in the flags. -         *   <li>If authentication succeeded and this response is empty (no datasets), call {@link -         *       FillCallback#onSuccess(FillResponse)} with a new dataset (that does not require -         *       authentication). -         *   <li>If authentication succeeded and this response is not empty, call {@link -         *       FillCallback#onFillResponseAuthentication(int)} passing {@link -         *       android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS} in the flags. -         * </ul> -         * -         * @param extras when set, will be passed back in the {@link -         *     android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle, -         *     int)} call so it could be used by the service to handle state. -         * @param flags optional parameters, currently ignored. +         * @param id A required id to identify this dataset for future interactions related to it.           */ -        public Builder requiresCustomAuthentication(@Nullable Bundle extras, int flags) { -            return requiresAuthentication(null, extras, flags); +        public Builder(@NonNull String id) { +            mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty or null");          }          /** -         * Requires user authentication through the Fingerprint sensor before handling an auto-fill -         * request. +         * Requires a fill response authentication before auto-filling the activity with +         * any dataset in this response. This is typically useful when a user interaction +         * is required to unlock their data vault if you encrypt the dataset labels and +         * dataset data. It is recommended to encrypt only the sensitive data and not the +         * dataset labels which would allow auth on the dataset 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.           * -         * <p>The {@link android.service.autofill.AutoFillService} typically uses this method in 2 -         * situations: +         * <p>This method is called when you need to provide an authentication +         * UI for the fill response. For example, when the user's data is stored +         * encrypted and needs a user interaction to decrypt before offering fill +         * suggestions.</p>           * -         * <ol> -         *   <li>When the user data is encrypted and the service must authenticate an object that -         *       will be used to decrypt it. -         *   <li>When the service already acquired the user data but wants to confirm the user's -         *       identity before the activity is filled with it. -         * </ol> +         * <p>When a user initiates an auto fill, the system triggers the provided +         * intent whose extras will have the {@link android.content.Intent +         * #EXTRA_AUTO_FILL_ITEM_ID id} of the {@link android.view.autofill.FillResponse}) +         * to authenticate, the {@link android.content.Intent#EXTRA_AUTO_FILL_EXTRAS extras} +         * associated with this response, and a {@link android.content.Intent +         * #EXTRA_AUTO_FILL_CALLBACK callback} to dispatch the authentication result.</p>           * -         * <p>When this method is called, the Android System displays an UI affordance asking the -         * user to use the fingerprint sensor to auto-fill the activity, and what happens after a -         * successful fingerprint authentication depends on the number of {@link Dataset}s included -         * in this response: +         * <p>Once you complete your authentication flow you should use the provided callback +         * to notify for a failure or a success. In case of a success you need to provide +         * the fully populated response that is being authenticated. For example, if you +         * provided an empty {@link FillResponse} because the user's data was locked and +         * marked that the response needs an authentication then in the response returned +         * if authentication succeeds you need to provide all available datasets some of +         * which may need to be further authenticated, for example a credit card whose +         * CVV needs to be entered.</p>           * -         * <ul> -         *   <li>If it's empty (scenario #1 above), the Android System will call {@link -         *     android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle, -         *       int)} passing {@link -         *       android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}} in the -         *       flags. -         *   <li>If it contains one dataset, the activity will be auto-filled right away. -         *   <li>If it contains many datasets, the Android System will show dataset picker UI, and -         *       then auto-fill the activity once the user select the proper datased. -         * </ul> +         * <p>The indent sender mechanism allows you to have your authentication UI +         * implemented as an activity or a service or a receiver. However, the recommended +         * way is to do this is with an activity which the system will start in the +         * filled activity's task meaning it will properly work with back, recent apps, and +         * free-form multi-window, while avoiding the need for the "draw on top of other" +         * apps special permission. You can still theme your authentication activity's +         * UI to look like a dialog if desired.</p>           * -         * <p>If the fingerprint authentication fails, the Android System will call {@link -         * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle, -         * int)} passing {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} -         * in the flags. +         * <p></><strong>Note:</strong> Do not make the provided intent sender +         * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the +         * platform needs to fill in the authentication arguments.</p>           * -         * <p><strong>NOTE: </note> the {@link android.service.autofill.AutoFillService} should use -         * the {@link android.hardware.fingerprint.FingerprintManager} to check if fingerpint -         * authentication is available before using this method, and use other alternatives (such as -         * {@link #requiresCustomAuthentication(Bundle, int)}) if it is not: if this method is -         * called when fingerprint is not available, Android System will call {@link -         * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle, -         * int)} passing {@link -         * android.service.autofill.AutoFillService#FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE} -         * in the flags, but it would be wasting system resources (and worsening the user -         * experience) in the process. +         * @param authentication Intent to trigger your authentication flow.           * -         * @param crypto object that will be authenticated. -         * @param extras when set, will be passed back in the {@link -         *     android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle, -         *     int)} call so it could be used by the service to handle state. -         * @param flags optional parameters, currently ignored. +         * @see android.app.PendingIntent#getIntentSender()           */ -        public Builder requiresFingerprintAuthentication(CryptoObject crypto, -                @Nullable Bundle extras, int flags) { -            // TODO(b/33197203): should we allow crypto to be null? -            Preconditions.checkArgument(crypto != null, "must pass a CryptoObject"); -            return requiresAuthentication(crypto, extras, flags); -        } - -        private Builder requiresAuthentication(CryptoObject cryptoObject, Bundle extras, -                int flags) { -            // There can be only one! -            Preconditions.checkState(!mRequiresAuth, -                    "requires-authentication methods already called"); -            // TODO(b/33197203): make sure that either this method or setExtras() is called, but -            // not both -            mExtras = extras; -            mFlags = flags; -            mRequiresAuth = true; -            if (cryptoObject != null) { -                mHasCryptoObject = true; -                mCryptoOpId = cryptoObject.getOpId(); -            } +        public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) { +            throwIfDestroyed(); +            mAuthentication = authentication;              return this;          }          /** -         * Adds a new {@link Dataset} to this response. +         * Adds a new {@link Dataset} to this response. Adding a dataset with the +         * same id updates the existing one.           *           * @throws IllegalArgumentException if a dataset with same {@code name} already exists.           */ -        public Builder addDataset(Dataset dataset) { -            Preconditions.checkNotNull(dataset, "dataset cannot be null"); -            // TODO(b/33197203): check if name already exists -            mDatasets.add(dataset); -            for (DatasetField field : dataset.getFields()) { -                mSavableIds.add(field.getId()); +        public@NonNull Builder addDataset(@Nullable Dataset dataset) { +            throwIfDestroyed(); +            if (dataset == null) { +                return this; +            } +            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<>(); +                } +                mSavableIds.add(id);              }              return this;          } @@ -374,27 +319,35 @@ public final class FillResponse implements Parcelable {           *           * <p>See {@link FillResponse} for examples.           */ -        public Builder addSavableFields(AutoFillId... ids) { +        public @NonNull Builder addSavableFields(@Nullable AutoFillId... ids) { +            throwIfDestroyed(); +            if (ids == null) { +                return this; +            }              for (AutoFillId id : ids) { +                if (mSavableIds == null) { +                    mSavableIds = new ArraySet<>(); +                }                  mSavableIds.add(id);              }              return this;          }          /** -         * Sets a {@link Bundle} that will be passed to subsequent calls to {@link -         * android.service.autofill.AutoFillService} methods such as {@link +         * Sets a {@link Bundle} that will be passed to subsequent APIs that +         * manipulate this response. For example, they are passed in as {@link +         * android.content.Intent#EXTRA_AUTO_FILL_EXTRAS extras} to your +         * authentication flow and to subsequent calls to {@link +         * android.service.autofill.AutoFillService#onFillRequest( +         * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, +         * android.service.autofill.FillCallback)} and {@link           * android.service.autofill.AutoFillService#onSaveRequest( -         * android.app.assist.AssistStructure, Bundle, android.service.autofill.SaveCallback)}, -         * using {@link -         * android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS} as the key. -         * -         * <p>It can be used when to keep service state in between calls. +         * android.app.assist.AssistStructure, Bundle, +         * android.service.autofill.SaveCallback)}.           */          public Builder setExtras(Bundle extras) { -            // TODO(b/33197203): make sure that either this method or the requires-Authentication -            // ones are called, but not both -            mExtras = Objects.requireNonNull(extras, "extras cannot be null"); +            throwIfDestroyed(); +            mExtras = extras;              return this;          } @@ -402,8 +355,16 @@ public final class FillResponse implements Parcelable {           * Builds a new {@link FillResponse} instance.           */          public FillResponse build() { +            throwIfDestroyed(); +            mDestroyed = true;              return new FillResponse(this);          } + +        private void throwIfDestroyed() { +            if (mDestroyed) { +                throw new IllegalStateException("Already called #build()"); +            } +        }      }      ///////////////////////////////////// @@ -412,14 +373,12 @@ public final class FillResponse implements Parcelable {      @Override      public String toString() {          if (!DEBUG) return super.toString(); - -        final StringBuilder builder = new StringBuilder("FillResponse: [datasets=") -                .append(mDatasets).append(", savableIds=").append(Arrays.toString(mSavableIds)) -                .append(", extras="); -        append(builder, mExtras) -            .append(", flags=").append(mFlags) -            .append(", requiresAuth: ").append(mRequiresAuth) -            .append(", hasCrypto: ").append(mHasCryptoObject); +        final StringBuilder builder = new StringBuilder( +                "FillResponse: [id=").append(mId) +                .append(", datasets=").append(mDatasets) +                .append(", savableIds=").append(mSavableIds) +                .append(", hasExtras=").append(mExtras != null) +                .append(", hasAuthentication=").append(mAuthentication != null);          return builder.append(']').toString();      } @@ -434,33 +393,34 @@ public final class FillResponse implements Parcelable {      @Override      public void writeToParcel(Parcel parcel, int flags) { -        parcel.writeList(mDatasets); -        parcel.writeParcelableArray(mSavableIds, 0); -        parcel.writeBundle(mExtras); -        parcel.writeInt(mFlags); -        parcel.writeInt(mRequiresAuth ? 1 : 0); -        parcel.writeInt(mHasCryptoObject ? 1 : 0); -        if (mHasCryptoObject) { -            parcel.writeLong(mCryptoOpId); -        } -    } - -    private FillResponse(Parcel parcel) { -        mDatasets = new ArrayList<>(); -        parcel.readList(mDatasets, null); -        mSavableIds = parcel.readParcelableArray(null, AutoFillId.class); -        mExtras = parcel.readBundle(); -        mFlags = parcel.readInt(); -        mRequiresAuth = parcel.readInt() == 1; -        mHasCryptoObject = parcel.readInt() == 1; -        mCryptoOpId = mHasCryptoObject ? parcel.readLong() : 0; +        parcel.writeString(mId); +        parcel.writeTypedArraySet(mDatasets, 0); +        parcel.writeTypedArraySet(mSavableIds, 0); +        parcel.writeParcelable(mExtras, 0); +        parcel.writeParcelable(mAuthentication, 0);      }      public static final Parcelable.Creator<FillResponse> CREATOR =              new Parcelable.Creator<FillResponse>() {          @Override -        public FillResponse createFromParcel(Parcel source) { -            return new FillResponse(source); +        public FillResponse 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.readString()); +            final ArraySet<Dataset> datasets = parcel.readTypedArraySet(null); +            final int datasetCount = (datasets != null) ? datasets.size() : 0; +            for (int i = 0; i < datasetCount; i++) { +                builder.addDataset(datasets.valueAt(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.setExtras(parcel.readParcelable(null)); +            builder.setAuthentication(parcel.readParcelable(null)); +            return builder.build();          }          @Override diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java index 78436f796bf6..178a6973a7b1 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java @@ -34,7 +34,6 @@ import android.os.Binder;  import android.os.Handler;  import android.os.IBinder;  import android.os.Looper; -import android.os.Message;  import android.os.Process;  import android.os.RemoteException;  import android.os.ResultReceiver; @@ -43,7 +42,6 @@ import android.os.UserHandle;  import android.provider.Settings;  import android.service.autofill.IAutoFillManagerService;  import android.text.TextUtils; -import android.text.format.DateUtils;  import android.util.LocalLog;  import android.util.Log;  import android.util.Slog; @@ -72,66 +70,47 @@ import java.util.List;  public final class AutoFillManagerService extends SystemService {      private static final String TAG = "AutoFillManagerService"; -    static final boolean DEBUG = true; // TODO(b/33197203): change to false once stable +    static final boolean DEBUG = false; -    private static final long SERVICE_BINDING_LIFETIME_MS = 5 * DateUtils.MINUTE_IN_MILLIS; - -    private static final int MSG_UNBIND = 1; -    private static final int MSG_REQUEST_AUTO_FILL_FOR_USER = 2; -    private static final int MSG_REQUEST_AUTO_FILL = 3; +    protected static final int MSG_REQUEST_AUTO_FILL_FOR_USER = 1; +    protected static final int MSG_REQUEST_AUTO_FILL = 2; +    private static final int MSG_REQUEST_SAVE_FOR_USER = 3;      private static final int MSG_ON_VALUE_CHANGED = 4; -    private static final int MSG_REQUEST_SAVE_FOR_USER = 5; -    private final AutoFillManagerServiceStub mServiceStub;      private final Context mContext; -    private final ContentResolver mResolver; +    private final AutoFillUI mUi;      private final Object mLock = new Object(); -    private final HandlerCaller.Callback mHandlerCallback = new HandlerCaller.Callback() { - -        @Override -        public void executeMessage(Message msg) { -            switch (msg.what) { -                case MSG_UNBIND: { -                    synchronized (mLock) { -                        removeCachedServiceLocked(msg.arg1); -                    } -                    return; -                } case MSG_REQUEST_AUTO_FILL_FOR_USER: { -                    handleAutoFillForUser(msg.arg1); -                    return; -                } case MSG_REQUEST_SAVE_FOR_USER: { -                    handleSaveForUser(msg.arg1); -                    return; -                } case MSG_REQUEST_AUTO_FILL: { -                    final SomeArgs args = (SomeArgs) msg.obj; -                    try { -                        final int userId = msg.arg1; -                        final int flags = msg.arg2; -                        final IBinder activityToken = (IBinder) args.arg1; -                        final AutoFillId autoFillId = (AutoFillId) args.arg2; -                        final Rect bounds = (Rect) args.arg3; -                        handleAutoFill(activityToken, userId, autoFillId, bounds, flags); -                    } finally { -                        args.recycle(); -                    } -                    return; -                } case MSG_ON_VALUE_CHANGED: { -                    final SomeArgs args = (SomeArgs) msg.obj; -                    try { -                        final int userId = msg.arg1; -                        final IBinder activityToken = (IBinder) args.arg1; -                        final AutoFillId autoFillId = (AutoFillId) args.arg2; -                        final AutoFillValue newValue = (AutoFillValue) args.arg3; -                        handleValueChanged(activityToken, userId, autoFillId, newValue); -                    } finally { -                        args.recycle(); -                    } -                    return; -                } default: { -                    Slog.w(TAG, "Invalid message: " + msg); -                } +    private final HandlerCaller.Callback mHandlerCallback = (msg) -> { +        switch (msg.what) { +            case MSG_REQUEST_AUTO_FILL_FOR_USER: { +                handleAutoFillForUser(msg.arg1); +                return; +            } case MSG_REQUEST_SAVE_FOR_USER: { +                handleSaveForUser(msg.arg1); +                return; +            } case MSG_REQUEST_AUTO_FILL: { +                final SomeArgs args = (SomeArgs) msg.obj; +                final int userId = msg.arg1; +                final int flags = msg.arg2; +                final IBinder activityToken = (IBinder) args.arg1; +                final AutoFillId autoFillId = (AutoFillId) args.arg2; +                final Rect bounds = (Rect) args.arg3; +                args.recycle(); +                handleAutoFill(activityToken, userId, autoFillId, bounds, flags); +                return; +            } case MSG_ON_VALUE_CHANGED: { +                final SomeArgs args = (SomeArgs) msg.obj; +                final int userId = msg.arg1; +                final IBinder activityToken = (IBinder) args.arg1; +                final AutoFillId autoFillId = (AutoFillId) args.arg2; +                final AutoFillValue newValue = (AutoFillValue) args.arg3; +                args.recycle(); +                handleValueChanged(activityToken, userId, autoFillId, newValue); +                return; +            } default: { +                Slog.w(TAG, "Invalid message: " + msg);              }          }      }; @@ -148,10 +127,10 @@ public final class AutoFillManagerService extends SystemService {       * Entries on this cache are added on demand and removed when:       * <ol>       *   <li>An auto-fill service app is removed. -     *   <li>The {@link android.provider.Settings.Secure#AUTO_FILL_SERVICE} for an user change. -     *   <li>It has not been interacted with for {@link #SERVICE_BINDING_LIFETIME_MS} ms. +     *   <li>The {@link android.provider.Settings.Secure#AUTO_FILL_SERVICE} for an user change.\       * </ol>       */ +    // TODO(b/33197203): Update the above comment      @GuardedBy("mLock")      private SparseArray<AutoFillManagerServiceImpl> mServicesCache = new SparseArray<>(); @@ -160,19 +139,14 @@ public final class AutoFillManagerService extends SystemService {      public AutoFillManagerService(Context context) {          super(context); -          mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), mHandlerCallback, true); -          mContext = context; - -        mResolver = context.getContentResolver(); -        mServiceStub = new AutoFillManagerServiceStub(); +        mUi = new AutoFillUI(mContext);      }      @Override      public void onStart() { -        if (DEBUG) Slog.d(TAG, "onStart(): binding as " + AUTO_FILL_MANAGER_SERVICE); -        publishBinderService(AUTO_FILL_MANAGER_SERVICE, mServiceStub); +        publishBinderService(AUTO_FILL_MANAGER_SERVICE, new AutoFillManagerServiceStub());      }      @Override @@ -186,61 +160,41 @@ public final class AutoFillManagerService extends SystemService {          ComponentName serviceComponent = null;          ServiceInfo serviceInfo = null;          final String componentName = Settings.Secure.getStringForUser( -                mResolver, Settings.Secure.AUTO_FILL_SERVICE, userId); +                mContext.getContentResolver(), Settings.Secure.AUTO_FILL_SERVICE, userId);          if (!TextUtils.isEmpty(componentName)) {              try {                  serviceComponent = ComponentName.unflattenFromString(componentName);                  serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0,                          userId);              } catch (RuntimeException | RemoteException e) { -                Slog.wtf(TAG, "Bad auto-fill service name " + componentName, e); +                Slog.e(TAG, "Bad auto-fill service name " + componentName, e);                  return null;              }          } -        if (DEBUG) { -            Slog.d(TAG, "getServiceComponentForUser(" + userId + "): component=" -                    + serviceComponent + ", info: " + serviceInfo); -        }          if (serviceInfo == null) { -            if (DEBUG) Slog.d(TAG, "no service info for " + serviceComponent);              return null;          } -        return new AutoFillManagerServiceImpl(this, mContext, mLock, mRequestsHistory, -                userId, serviceInfo.applicationInfo.uid, serviceComponent, -                SERVICE_BINDING_LIFETIME_MS); + +        try { +            return new AutoFillManagerServiceImpl(mContext, mLock, mRequestsHistory, +                    userId, serviceComponent, mUi); +        } catch (PackageManager.NameNotFoundException e) { +            Slog.w(TAG, "Auto-fill service not found: " + serviceComponent, e); +        } + +        return null;      }      /**       * Gets the service instance for an user. -     * <p> -     * First it tries to return the existing instance from the cache; if it's not cached, it creates -     * a new instance and caches it.       */ -    // TODO(b/33197203): make private once AutoFillUi does not uses notifications -    AutoFillManagerServiceImpl getServiceForUserLocked(int userId) { +    AutoFillManagerServiceImpl getOrCreateServiceForUserLocked(int userId) {          AutoFillManagerServiceImpl service = mServicesCache.get(userId); -        if (service != null) { -            if (DEBUG) -                Log.d(TAG, "reusing cached service for userId " + userId); -            service.setLifeExpectancy(SERVICE_BINDING_LIFETIME_MS); -        } else { +        if (service == null) {              service = newServiceForUser(userId); -            if (service == null) { -                // Already logged -                return null; -            } -            if (DEBUG) Log.d(TAG, "creating new cached service for userId " + userId); -            service.startLocked();              mServicesCache.put(userId, service);          } -        // Keep service connection alive for a while, in case user needs to interact with it -        // (for example, to save the data that was inputted in) -        if (mHandlerCaller.hasMessages(MSG_UNBIND)) { -            mHandlerCaller.removeMessages(MSG_UNBIND); -        } -        mHandlerCaller.sendMessageDelayed(mHandlerCaller.obtainMessageI(MSG_UNBIND, userId), -                SERVICE_BINDING_LIFETIME_MS);          return service;      } @@ -248,22 +202,17 @@ public final class AutoFillManagerService extends SystemService {       * Removes a cached service for a given user.       */      void removeCachedServiceLocked(int userId) { -        if (DEBUG) Log.d(TAG, "removing cached service for userId " + userId);          final AutoFillManagerServiceImpl service = mServicesCache.get(userId); -        if (service == null) { -            if (DEBUG) { -                Log.d(TAG, "removeCachedServiceForUser(): no cached service for userId " + userId); -            } -            return; +        if (service != null) { +            mServicesCache.delete(userId); +            service.destroyLocked();          } -        mServicesCache.delete(userId); -        service.stopLocked();      }      private void handleAutoFill(IBinder activityToken, int userId, AutoFillId autoFillId,              Rect bounds, int flags) {          synchronized (mLock) { -            final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId); +            final AutoFillManagerServiceImpl service = getOrCreateServiceForUserLocked(userId);              if (service != null) {                  service.requestAutoFillLocked(activityToken, autoFillId, bounds, flags);              } @@ -273,7 +222,7 @@ public final class AutoFillManagerService extends SystemService {      private void handleValueChanged(IBinder activityToken, int userId, AutoFillId autoFillId,              AutoFillValue newValue) {          synchronized (mLock) { -            final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId); +            final AutoFillManagerServiceImpl service = getOrCreateServiceForUserLocked(userId);              if (service != null) {                  service.onValueChangeLocked(activityToken, autoFillId, newValue);              } @@ -283,45 +232,32 @@ public final class AutoFillManagerService extends SystemService {      private IBinder getTopActivityForUser() {          final List<IBinder> topActivities = LocalServices                  .getService(ActivityManagerInternal.class).getTopVisibleActivities(); -        if (DEBUG) Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);          if (topActivities.isEmpty()) { -            Slog.w(TAG, "Could not get top activity");              return null;          }          return topActivities.get(0);      }      private void handleAutoFillForUser(int userId) { -        if (DEBUG) Slog.d(TAG, "handler.requestAutoFillForUser(): id=" + userId);          final IBinder activityToken = getTopActivityForUser(); -        if (activityToken == null) { -            return; -        } - -        synchronized (mLock) { -            final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId); -            if (service == null) { -                Slog.w(TAG, "no service for user " + userId); -                return; +        if (activityToken != null) { +            synchronized (mLock) { +                final AutoFillManagerServiceImpl service = +                        getOrCreateServiceForUserLocked(userId); +                service.requestAutoFillLocked(activityToken, null, null, 0);              } -            service.requestAutoFillLocked(activityToken, null, null, 0);          } +      }      private void handleSaveForUser(int userId) { -        if (DEBUG) Slog.d(TAG, "handler.handleSaveForUser(): id=" + userId);          final IBinder activityToken = getTopActivityForUser(); -        if (activityToken == null) { -            return; -        } - -        synchronized (mLock) { -            final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId); -            if (service == null) { -                Slog.w(TAG, "no service for user " + userId); -                return; +        if (activityToken != null) { +            synchronized (mLock) { +                final AutoFillManagerServiceImpl service = +                        getOrCreateServiceForUserLocked(userId); +                service.requestSaveForUserLocked(activityToken);              } -            service.requestSaveForUserLocked(activityToken);          }      } @@ -347,9 +283,6 @@ public final class AutoFillManagerService extends SystemService {          @Override          public void requestAutoFill(AutoFillId id, Rect bounds, int flags) { -            if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", autoFillId=" + id -                    + ", bounds=" + bounds); -              final IBinder activityToken = getTopActivity();              if (activityToken != null) {                  mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIIOOO(MSG_REQUEST_AUTO_FILL, @@ -360,25 +293,19 @@ public final class AutoFillManagerService extends SystemService {          @Override          public void requestAutoFillForUser(int userId) {              mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); - -            mHandlerCaller.sendMessage( -                    mHandlerCaller.obtainMessageI(MSG_REQUEST_AUTO_FILL_FOR_USER, userId)); +            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageI( +                    MSG_REQUEST_AUTO_FILL_FOR_USER, userId));          }          @Override          public void requestSaveForUser(int userId) {              mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); - -            mHandlerCaller.sendMessage( -                    mHandlerCaller.obtainMessageI(MSG_REQUEST_SAVE_FOR_USER, userId)); +            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageI(MSG_REQUEST_SAVE_FOR_USER, userId));          }          @Override          public void onValueChanged(AutoFillId id, AutoFillValue value) { -            if (DEBUG) Slog.d(TAG, "onValueChanged(): id=" + id + ", value=" + value); -              final IBinder activityToken = getTopActivity(); -              if (activityToken != null) {                  mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOOO(MSG_ON_VALUE_CHANGED,                          UserHandle.getCallingUserId(), activityToken, id, value)); @@ -407,6 +334,7 @@ public final class AutoFillManagerService extends SystemService {                          impl.dumpLocked("  ", pw);                      }                  } +                mUi.dump(pw);              }              pw.println("Requests history:");              mRequestsHistory.reverseDump(fd, pw, args); @@ -430,7 +358,6 @@ public final class AutoFillManagerService extends SystemService {          @Override          public void onChange(boolean selfChange, Uri uri, int userId) { -            if (DEBUG) Slog.d(TAG, "settings (" + uri + " changed for " + userId);              synchronized (mLock) {                  removeCachedServiceLocked(userId);              } diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java index 42e4fd3b2b7f..e32e21d54b56 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java @@ -16,15 +16,11 @@  package com.android.server.autofill; -import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_ERROR; -import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_REQUESTED; -import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_SUCCESS;  import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_SHOW;  import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_HIDE;  import static com.android.server.autofill.Helper.DEBUG;  import static com.android.server.autofill.Helper.VERBOSE; -import static com.android.server.autofill.Helper.bundleToString;  import android.annotation.Nullable;  import android.app.Activity; @@ -38,33 +34,26 @@ import android.content.ComponentName;  import android.content.Context;  import android.content.Intent;  import android.content.IntentFilter; -import android.content.ServiceConnection; +import android.content.IntentSender;  import android.content.pm.PackageManager;  import android.graphics.Rect; -import android.hardware.fingerprint.Fingerprint; -import android.hardware.fingerprint.IFingerprintService; -import android.hardware.fingerprint.IFingerprintServiceReceiver; -import android.os.Binder;  import android.os.Bundle; -import android.os.DeadObjectException;  import android.os.IBinder; +import android.os.ICancellationSignal;  import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.UserHandle;  import android.service.autofill.AutoFillService;  import android.service.autofill.AutoFillServiceInfo; +import android.service.autofill.FillCallback;  import android.service.autofill.IAutoFillAppCallback; -import android.service.autofill.IAutoFillServerCallback;  import android.service.autofill.IAutoFillService; +import android.service.autofill.IFillCallback;  import android.service.voice.VoiceInteractionSession;  import android.util.ArrayMap; +import android.util.ArraySet;  import android.util.LocalLog; -import android.util.Log;  import android.util.PrintWriterPrinter;  import android.util.Slog;  import android.util.SparseArray; -import android.util.TimeUtils;  import android.view.autofill.AutoFillId;  import android.view.autofill.AutoFillValue;  import android.view.autofill.Dataset; @@ -77,8 +66,6 @@ import com.android.server.FgThread;  import java.io.PrintWriter;  import java.lang.ref.WeakReference;  import java.util.HashMap; -import java.util.LinkedList; -import java.util.List;  import java.util.Map;  import java.util.Map.Entry; @@ -95,24 +82,13 @@ final class AutoFillManagerServiceImpl {      private static int sSessionIdCounter = 0;      private final int mUserId; -    private final int mUid;      private final ComponentName mComponent;      private final String mComponentName;      private final Context mContext;      private final IActivityManager mAm;      private final Object mLock;      private final AutoFillServiceInfo mInfo; -    private final AutoFillManagerService mManagerService; - -    // Token used for fingerprint authentication -    // TODO(b/33197203): create on demand? -    private final IBinder mAuthToken = new Binder(); - -    private final IFingerprintService mFingerprintService = -            IFingerprintService.Stub.asInterface(ServiceManager.getService("fingerprint")); - -    @GuardedBy("mLock") -    private final List<QueuedRequest> mQueuedRequests = new LinkedList<>(); +    private final AutoFillUI mUi;      private final LocalLog mRequestsHistory; @@ -122,15 +98,7 @@ final class AutoFillManagerServiceImpl {              if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {                  final String reason = intent.getStringExtra("reason");                  if (DEBUG) Slog.d(TAG, "close system dialogs: " + reason); - -                synchronized (mLock) { -                    final int size = mSessions.size(); -                    for (int i = 0; i < size; i++) { -                        final Session session = mSessions.valueAt(i); -                        // TODO(b/33197203): invalidate the sessions instead? -                        session.mUi.closeAll(); -                    } -                } +                mUi.hideAll();              }          }      }; @@ -144,40 +112,7 @@ final class AutoFillManagerServiceImpl {      // TODO(b/33197203): need to make sure service is bound while callback is pending and/or      // use WeakReference      @GuardedBy("mLock") -    private static final SparseArray<Session> mSessions = new SparseArray<>(); - -    private final ServiceConnection mConnection = new ServiceConnection() { -        @Override -        public void onServiceConnected(ComponentName name, IBinder service) { -            if (DEBUG) Slog.d(TAG, "onServiceConnected():" + name); -            synchronized (mLock) { -                mService = IAutoFillService.Stub.asInterface(service); -                try { -                    mService.onConnected(); -                } catch (RemoteException e) { -                    Slog.w(TAG, "Exception on service.onConnected(): " + e); -                    return; -                } -                if (!mQueuedRequests.isEmpty()) { -                    if (DEBUG) Slog.d(TAG, "queued requests:" + mQueuedRequests.size()); -                } -                for (final QueuedRequest request: mQueuedRequests) { -                    requestAutoFillLocked(request.activityToken, request.autoFillId, -                            request.bounds, request.flags, false); -                } -                mQueuedRequests.clear(); -            } -        } - -        @Override -        public void onServiceDisconnected(ComponentName name) { -            if (DEBUG) Slog.d(TAG, name + " disconnected"); -            synchronized (mLock) { -                mService = null; -                mManagerService.removeCachedServiceLocked(mUserId); -            } -        } -    }; +    private final SparseArray<Session> mSessions = new SparseArray<>();      /**       * Receiver of assist data from the app's {@link Activity}, uses the {@code resultData} as @@ -193,8 +128,13 @@ final class AutoFillManagerServiceImpl {                  Slog.w(TAG, "no app callback on mAssistReceiver's resultData");                  return;              } +              final AssistStructure structure = resultData                      .getParcelable(VoiceInteractionSession.KEY_STRUCTURE); +            if (structure == null) { +                Slog.w(TAG, "no assist structure for id " + resultCode); +                return; +            }              final Session session;              synchronized (mLock) { @@ -203,91 +143,55 @@ final class AutoFillManagerServiceImpl {                      Slog.w(TAG, "no server callback for id " + resultCode);                      return;                  } -                session.setAppCallbackLocked(appBinder); -                // TODO(b/33197203): since service is fetching the data (to use for save later), -                // we should optimize what's sent (for example, remove layout containers, -                // color / font info, etc...) -                session.mStructure = structure; - -                // TODO(b/33197203, b/33269702): Must fetch the data so it's available later on -                // handleSave(), even if if the activity is gone by then, but structure.ensureData() -                // gives a ONE_WAY warning because system_service could block on app calls. -                // We need to change AssistStructure so it provides a "one-way" writeToParcel() -                // method that sends all the data -                structure.ensureData(); - -                structure.sanitizeForParceling(true); -                if (VERBOSE) { -                    Slog.v(TAG, "Dumping " + structure + " before calling service.autoFill()"); -                    structure.dump(); -                } -                mService.autoFill(structure, session.mServerCallback);              } -        } -    }; -    @GuardedBy("mLock") -    private IAutoFillService mService; -    @GuardedBy("mLock") -    private boolean mBound; -    @GuardedBy("mLock") -    private boolean mValid; +            // TODO(b/33197203): since service is fetching the data (to use for save later), +            // we should optimize what's sent (for example, remove layout containers, +            // color / font info, etc...) -    // Estimated time when the service will be evicted from the cache. -    long mEstimateTimeOfDeath; +            // TODO(b/33197203, b/33269702): Must fetch the data so it's available later on +            // handleSave(), even if if the activity is gone by then, but structure.ensureData() +            // gives a ONE_WAY warning because system_service could block on app calls. +            // We need to change AssistStructure so it provides a "one-way" writeToParcel() +            // method that sends all the data +            structure.ensureData(); -    AutoFillManagerServiceImpl(AutoFillManagerService managerService, Context context, Object lock, -            LocalLog requestsHistory, int userId, int uid, ComponentName component, long ttl) { -        mManagerService = managerService; +            structure.sanitizeForParceling(true); + +            if (VERBOSE) { +                Slog.v(TAG, "Dumping " + structure + " before calling service.autoFill()"); +                structure.dump(); +            } + +            session.onApplicationDataAvailable(structure, appBinder); +        } +    }; + +    AutoFillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory, +            int userId, ComponentName component, AutoFillUI ui) +            throws PackageManager.NameNotFoundException {          mContext = context;          mLock = lock;          mRequestsHistory = requestsHistory;          mUserId = userId; -        mUid = uid;          mComponent = component;          mComponentName = mComponent.flattenToShortString();          mAm = ActivityManager.getService(); -        setLifeExpectancy(ttl); - -        final AutoFillServiceInfo info; -        try { -            info = new AutoFillServiceInfo(context.getPackageManager(), component, mUserId); -        } catch (PackageManager.NameNotFoundException e) { -            Slog.w(TAG, "Auto-fill service not found: " + component, e); -            mInfo = null; -            mValid = false; -            return; -        } -        mInfo = info; -        if (mInfo.getParseError() != null) { -            Slog.w(TAG, "Bad auto-fill service: " + mInfo.getParseError()); -            mValid = false; -            return; -        } +        mUi = ui; +        mInfo = new AutoFillServiceInfo(context.getPackageManager(), component, mUserId); -        mValid = true;          IntentFilter filter = new IntentFilter();          filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);          mContext.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler());      } -    void setLifeExpectancy(long ttl) { -        mEstimateTimeOfDeath = SystemClock.uptimeMillis() + ttl; -    } - -    void startLocked() { -        if (DEBUG) Slog.d(TAG, "startLocked()"); -        final Intent intent = new Intent(AutoFillService.SERVICE_INTERFACE); -        intent.setComponent(mComponent); -        mBound = mContext.bindServiceAsUser(intent, mConnection, -                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, new UserHandle(mUserId)); - -        if (!mBound) { -            Slog.w(TAG, "Failed binding to auto-fill service " + mComponent); -            return; -        } -        if (DEBUG) Slog.d(TAG, "Bound to " + mComponent); +    /** +     * Used by {@link AutoFillManagerServiceShellCommand} to request save for the current top app. +     */ +    void requestSaveForUserLocked(IBinder activityToken) { +        final Session session = getOrCreateSessionByTokenLocked(activityToken); +        session.onSaveLocked();      }      /** @@ -299,66 +203,17 @@ final class AutoFillManagerServiceImpl {       * @param flags optional flags.       */      void requestAutoFillLocked(IBinder activityToken, @Nullable AutoFillId autoFillId, -            @Nullable Rect bounds, int flags) { -        if (!mBound) { -            Slog.w(TAG, "requestAutoFillLocked() failed because it's not bound to service"); -            return; -        } - -        requestAutoFillLocked(activityToken, autoFillId, bounds, flags, true); -    } - -    /** -     * Used by {@link AutoFillManagerServiceShellCommand} to request save for the current top app. -     */ -    void requestSaveForUserLocked(IBinder activityToken) { -        if (!mBound) { -            Slog.w(TAG, "requestSaveForUserLocked() failed because it's not bound to service"); -            return; -        } -        if (mService == null) { -            Slog.w(TAG, "requestSaveForUserLocked: service not set"); -            return; -        } - -        final Session session = getSessionByTokenLocked(activityToken); -        if (session == null) { -            Slog.w(TAG, "requestSaveForUserLocked(): no session for " + activityToken); -            return; -        } - -        session.onSaveLocked(); -    } - -    private void requestAutoFillLocked(IBinder activityToken, @Nullable AutoFillId autoFillId, -            @Nullable Rect bounds, int flags, boolean queueIfNecessary) { -        if (mService == null) { -            if (!queueIfNecessary) { -                Slog.w(TAG, "requestAutoFillLocked(): service is null"); -                return; -            } -            if (DEBUG) Slog.d(TAG, "requestAutoFillLocked(): service not set yet, queuing it"); -            mQueuedRequests.add(new QueuedRequest(activityToken, autoFillId, bounds, flags)); -            return; -        } - +          @Nullable Rect bounds, int flags) {          final String historyItem = "s=" + mComponentName + " u=" + mUserId + " f=" + flags                  + " a=" + activityToken + " i=" + autoFillId + " b=" + bounds;          mRequestsHistory.log(historyItem);          // TODO(b/33197203): Handle partitioning -        Session session = getSessionByTokenLocked(activityToken); - -        if (session == null) { -            session = createSessionByTokenLocked(activityToken); -        } else { -            if (DEBUG) Slog.d(TAG, "reusing session for " + activityToken + ": " + session.mId); -        } - +        Session session = getOrCreateSessionByTokenLocked(activityToken);          session.updateAutoFillInput(flags, autoFillId, null, bounds);      } -    private Session getSessionByTokenLocked(IBinder activityToken) { +    private Session getOrCreateSessionByTokenLocked(IBinder activityToken) {          final int size = mSessions.size();          for (int i = 0; i < size; i++) {              final Session session = mSessions.valueAt(i); @@ -366,14 +221,14 @@ final class AutoFillManagerServiceImpl {                  return session;              }          } -        return null; +        return createSessionByTokenLocked(activityToken);      }      private Session createSessionByTokenLocked(IBinder activityToken) {          final int sessionId = ++sSessionIdCounter;          if (DEBUG) Slog.d(TAG, "creating session for " + activityToken + ": " + sessionId); -        final Session newSession = new Session(sessionId, activityToken); +        final Session newSession = new Session(mContext, activityToken, sessionId);          mSessions.put(sessionId, newSession);          /* @@ -401,88 +256,33 @@ final class AutoFillManagerServiceImpl {       */      void onValueChangeLocked(IBinder activityToken, AutoFillId autoFillId, AutoFillValue newValue) {          // TODO(b/33197203): add MetricsLogger call -        final Session session = getSessionByTokenLocked(activityToken); -        if (session == null) { -            Slog.w(TAG, "onValueChangeLocked(): session gone for " + activityToken); -            return; -        } - +        final Session session = getOrCreateSessionByTokenLocked(activityToken);          session.updateValueLocked(autoFillId, newValue);      } -    void stopLocked() { -        if (DEBUG) Slog.d(TAG, "stopLocked()"); - -        // Sanity check. -        if (mService == null) { -            Slog.w(TAG, "service already null on shutdown"); -            return; -        } -        try { -            mService.onDisconnected(); -        } catch (RemoteException e) { -            if (! (e instanceof DeadObjectException)) { -                Slog.w(TAG, "Exception calling service.onDisconnected(): " + e); -            } -        } finally { -            mService = null; -        } - -        if (mBound) { -            mContext.unbindService(mConnection); -            mBound = false; -        } -        if (mValid) { -            mContext.unregisterReceiver(mBroadcastReceiver); -        } -    } -      void removeSessionLocked(int id) {          if (DEBUG) Slog.d(TAG, "Removing session " + id); -        mSessions.remove(id); - -        // TODO(b/33197203): notify mService so it can invalidate the FillCallback / SaveCallback -        // and cached AssistStructures +        mSessions.get(id);      } -    void dumpLocked(String prefix, PrintWriter pw) { -        if (!mValid) { -            pw.print("  NOT VALID: "); -            if (mInfo == null) { -                pw.println("no info"); -            } else { -                pw.println(mInfo.getParseError()); -            } -            return; +    void destroyLocked() { +        mContext.unregisterReceiver(mBroadcastReceiver); +        final int sessionCount = mSessions.size(); +        for (int i = sessionCount - 1; i >= 0; i--) { +            Session session = mSessions.valueAt(i); +            session.destroy(); +            mSessions.removeAt(i);          } +    } +    void dumpLocked(String prefix, PrintWriter pw) {          final String prefix2 = prefix + "  "; - -        pw.print(prefix); pw.print("mUserId="); pw.println(mUserId); -        pw.print(prefix); pw.print("mUid="); pw.println(mUid); -        pw.print(prefix); pw.print("mComponent="); pw.println(mComponentName); -        pw.print(prefix); pw.print("mService: "); pw.println(mService); -        pw.print(prefix); pw.print("mBound="); pw.println(mBound); -        pw.print(prefix); pw.print("mEstimateTimeOfDeath="); -            TimeUtils.formatDuration(mEstimateTimeOfDeath, SystemClock.uptimeMillis(), pw); -            pw.println(); -        pw.print(prefix); pw.print("mAuthToken: "); pw.println(mAuthToken); -          if (DEBUG) {              // ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)              pw.print(prefix); pw.println("ServiceInfo:");              mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix);          } -        if (mQueuedRequests.isEmpty()) { -            pw.print(prefix); pw.println("No queued requests"); -        } else { -            pw.print(prefix); pw.println("Queued requests:"); -            for (int i = 0; i < mQueuedRequests.size(); i++) { -                pw.print(prefix2); pw.print(i); pw.print(": "); pw.println(mQueuedRequests.get(i)); -            } -        } -          pw.print(prefix); pw.print("sSessionIdCounter="); pw.println(sSessionIdCounter);          final int size = mSessions.size();          if (size == 0) { @@ -498,32 +298,10 @@ final class AutoFillManagerServiceImpl {      @Override      public String toString() { -        return "AutoFillManagerServiceImpl: [userId=" + mUserId + ", uid=" + mUid +        return "AutoFillManagerServiceImpl: [userId=" + mUserId                  + ", component=" + mComponentName + "]";      } -    private static final class QueuedRequest { -        final IBinder activityToken; -        final AutoFillId autoFillId; -        final Rect bounds; -        final int flags; - -        QueuedRequest(IBinder activityToken, AutoFillId autoFillId, Rect bounds, int flags) { -            this.activityToken = activityToken; -            this.autoFillId = autoFillId; -            this.bounds = bounds; -            this.flags = flags; -        } - -        @Override -        public String toString() { -            if (!DEBUG) return super.toString(); - -            return "QueuedRequest: [flags=" + flags + ", token=" + activityToken -                    + ", id=" + autoFillId + ", bounds=" + bounds; -        } -    } -      /**       * State for a given view with a AutoFillId.       * @@ -553,12 +331,7 @@ final class AutoFillManagerServiceImpl {           * Response should only be set once.           */          void setResponse(FillResponse response) { -            if (mResponse != null) { -                Slog.e(TAG, "ViewState response set more than once"); -                return; -            }              mResponse = response; -              maybeCallOnFillReady();          } @@ -608,39 +381,27 @@ final class AutoFillManagerServiceImpl {      // - On all authentication scenarios.      // - When user does not interact back after a while.      // - When service is unbound. -    final class Session implements ViewState.Listener { +    final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener, +            AutoFillUI.AutoFillUiCallback { +        private final int mId; -        private final AutoFillUI mUi;          private final WeakReference<IBinder> mActivityToken;          @GuardedBy("mLock")          private final Map<AutoFillId, ViewState> mViewStates = new ArrayMap<>(); +          @GuardedBy("mLock")          @Nullable          private ViewState mCurrentViewState;          private IAutoFillAppCallback mAppCallback; -        // TODO(b/33197203): Get a response per view instead of per activity. -        @GuardedBy("mLock") -        private FillResponse mCurrentResponse; -        @GuardedBy("mLock") -        private FillResponse mResponseRequiringAuth; -        @GuardedBy("mLock") -        private Dataset mDatasetRequiringAuth; - -        /** -         * Used to auto-fill the activity directly when the FillCallback.onResponse() is called as -         * the result of a successful user authentication on service's side. -         */          @GuardedBy("mLock") -        private boolean mAutoFillDirectly; +        RemoteFillService mRemoteFillService; -        /** -         * Used to remember which {@link Dataset} filled the session. -         */ +        // TODO(b/33197203): Get a response per view instead of per activity.          @GuardedBy("mLock") -        private Dataset mAutoFilledDataset; +        private FillResponse mCurrentResponse;          /**           * Map of ids that must be updated so they're send to {@link #onSaveLocked()}. @@ -652,168 +413,77 @@ final class AutoFillManagerServiceImpl {           * Assist structure sent by the app; it will be updated (sanitized, change values for save)           * before sent to {@link AutoFillService}.           */ +        @GuardedBy("mLock")          private AssistStructure mStructure; -        // TODO(b/33197203): use handler to handle results? -        // TODO(b/33197203): handle all callback methods and/or cancelation? -        private IFingerprintServiceReceiver mServiceReceiver = -                new IFingerprintServiceReceiver.Stub() { - -            @Override -            public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { -                if (DEBUG) Slog.d(TAG, "onEnrollResult()"); -            } - -            @Override -            public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) { -                if (DEBUG) Slog.d(TAG, "onAcquired()"); -            } - -            @Override -            public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) { -                if (DEBUG) Slog.d(TAG, "onAuthenticationSucceeded(): " + fp.getGroupId()); - -                // First, check what was authenticated, a response or a dataset. -                // Then, decide how to handle it: -                // - If service provided data, handle them directly. -                // - Otherwise, notify service. - -                mAutoFillDirectly = true; - -                if (mDatasetRequiringAuth != null) { -                    if (mDatasetRequiringAuth.isEmpty()) { -                        notifyDatasetAuthenticationResult(mDatasetRequiringAuth.getExtras(), -                                FLAG_AUTHENTICATION_SUCCESS); -                    } else { -                        autoFillApp(mDatasetRequiringAuth); -                    } -                } else if (mResponseRequiringAuth != null) { -                    final List<Dataset> datasets = mResponseRequiringAuth.getDatasets(); -                    if (datasets.isEmpty()) { -                        notifyResponseAuthenticationResult(mResponseRequiringAuth.getExtras(), -                                FLAG_AUTHENTICATION_SUCCESS); -                    } else { -                        showResponseLocked(mResponseRequiringAuth, true); -                    } -                } else { -                    Slog.w(TAG, "onAuthenticationSucceeded(): no response or dataset"); -                } - -                mUi.dismissFingerprintRequest(true); -            } - -            @Override -            public void onAuthenticationFailed(long deviceId) { -                if (DEBUG) Slog.d(TAG, "onAuthenticationFailed()"); -                // Do nothing - onError() will be called after a few failures... -            } - -            @Override -            public void onError(long deviceId, int error, int vendorCode) { -                if (DEBUG) Slog.d(TAG, "onError()"); - -                // Notify service so it can fallback to its own authentication -                if (mDatasetRequiringAuth != null) { -                    notifyDatasetAuthenticationResult(mDatasetRequiringAuth.getExtras(), -                            FLAG_AUTHENTICATION_ERROR); -                } else if (mResponseRequiringAuth != null) { -                    notifyResponseAuthenticationResult(mResponseRequiringAuth.getExtras(), -                            FLAG_AUTHENTICATION_ERROR); -                } else { -                    Slog.w(TAG, "onError(): no response or dataset"); -                } - -                mUi.dismissFingerprintRequest(false); -            } - -            @Override -            public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) { -                if (DEBUG) Slog.d(TAG, "onRemoved()"); -            } +        private Session(Context context, IBinder activityToken, int id) { +            mActivityToken = new WeakReference<>(activityToken); +            mRemoteFillService = new RemoteFillService(context, mComponent, mUserId, this); +            mId = id; +        } -            @Override -            public void onEnumerated(long deviceId, int fingerId, int groupId, int remaining) { -                if (DEBUG) Slog.d(TAG, "onEnumerated()"); +        // FillServiceCallbacks +        @Override +        public void onFillRequestSuccess(FillResponse response) { +            // TODO(b/33197203): add MetricsLogger call +            if (response == null) { +                destroy(); +                return;              } -        }; - -        private IAutoFillServerCallback mServerCallback = new IAutoFillServerCallback.Stub() { -            @Override -            public void showResponse(FillResponse response) { -                // TODO(b/33197203): add MetricsLogger call -                if (response == null) { -                    if (DEBUG) Slog.d(TAG, "showResponse(): null response"); - -                    removeSelf(); -                    return; -                } - -                synchronized (mLock) { -                    showResponseLocked(response, response.isAuthRequired()); -                } +            synchronized (mLock) { +                processResponseLocked(response);              } +        } -            @Override -            public void showError(CharSequence message) { -                // TODO(b/33197203): add MetricsLogger call -                if (DEBUG) Slog.d(TAG, "showError(): " + message); +        // FillServiceCallbacks +        @Override +        public void onFillRequestFailure(CharSequence message) { +            // TODO(b/33197203): add MetricsLogger call +            getUiForShowing().showError(message); +            destroy(); +        } -                mUi.showError(message); -                removeSelf(); -            } +        // FillServiceCallbacks +        @Override +        public void onSaveRequestSuccess() { +            // TODO: Implement +        } -            @Override -            public void onSaved() { -                // TODO(b/33197203): add MetricsLogger call -                if (DEBUG) Slog.d(TAG, "onSaved()"); +        // FillServiceCallbacks +        @Override +        public void onSaveRequestFailure(CharSequence message) { +            // TODO(b/33197203): add MetricsLogger call +            getUiForShowing().showError(message); +            destroy(); +        } -                removeSelf(); -            } +        // FillServiceCallbacks +        @Override +        public void authenticate(IntentSender intent, Intent fillInIntent) { +            startAuthIntent(intent, fillInIntent); +        } -            @Override -            public void unlockFillResponse(int flags) { -                // TODO(b/33197203): add proper MetricsLogger calls? -                if (DEBUG) Log.d(TAG, "unlockUser(): flags=" + flags); - -                synchronized (mLock) { -                    if ((flags & FLAG_AUTHENTICATION_SUCCESS) != 0) { -                        if (mResponseRequiringAuth == null) { -                            Log.wtf(TAG, "unlockUser(): no mResponseRequiringAuth on flags " -                                    + flags); -                            removeSelf(); -                            return; -                        } -                        final List<Dataset> datasets = mResponseRequiringAuth.getDatasets(); -                        if (datasets.isEmpty()) { -                            Log.w(TAG, "unlockUser(): no dataset on previous response: " -                                    + mResponseRequiringAuth); -                            removeSelf(); -                            return; -                        } -                        mAutoFillDirectly = true; -                        showResponseLocked(mResponseRequiringAuth, false); -                    } -                    // TODO(b/33197203): show UI error on authentication failure? -                    // Or let service handle it? -                } -            } +        // FillServiceCallbacks +        @Override +        public void onServiceDied(RemoteFillService service) { +            // TODO: Implement +        } -            @Override -            public void unlockDataset(Dataset dataset, int flags) { -                // TODO(b/33197203): add proper MetricsLogger calls? -                if (DEBUG) Log.d(TAG, "unlockDataset(): dataset=" + dataset + ", flags=" + flags); +        // AutoFillUiCallback +        @Override +        public void fill(Dataset dataset) { +            autoFill(dataset); +        } -                if ((flags & FLAG_AUTHENTICATION_SUCCESS) != 0) { -                    autoFillApp(dataset != null ? dataset : mDatasetRequiringAuth); -                    return; -                } +        // AutoFillUiCallback +        @Override +        public void save() { +            synchronized (mLock) { +                onSaveLocked();              } -        }; - -        final int mId; +        }          private Session(int id, IBinder activityToken) { -            mUi = new AutoFillUI(mContext, this);              mId = id;              mActivityToken = new WeakReference<>(activityToken);          } @@ -826,7 +496,7 @@ final class AutoFillManagerServiceImpl {            // TODO(b/33197203): ignore if not part of the savable ids.            if (mUpdatedValues == null) { -                // Lazy intializes it +                // Lazy initializes it                  mUpdatedValues = new HashMap<>();              }              mUpdatedValues.put(id, newValue); @@ -847,32 +517,10 @@ final class AutoFillManagerServiceImpl {              if (mUpdatedValues == null || mUpdatedValues.isEmpty()) {                  // Nothing changed                  if (DEBUG) Slog.d(TAG, "onSave(): when no changes, comes no responsibilities"); -                  return;              }              // TODO(b/33197203): make sure the extras are tested by CTS -            final Bundle responseExtras = mCurrentResponse == null ? null -                    : mCurrentResponse.getExtras(); -            final Bundle datasetExtras = mAutoFilledDataset == null ? null -                    : mAutoFilledDataset.getExtras(); -            final Bundle extras = (responseExtras == null && datasetExtras == null) -                    ? null : new Bundle(); -            if (responseExtras != null) { -                if (DEBUG) { -                    Slog.d(TAG, "response extras on save extras: " -                            + bundleToString(responseExtras)); -                } -                extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, responseExtras); -            } -            if (datasetExtras != null) { -                if (DEBUG) { -                    Slog.d(TAG, "dataset extras on save extras: " + bundleToString(datasetExtras)); -                } -                extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetExtras); -            } - -              for (Entry<AutoFillId, AutoFillValue> entry : mUpdatedValues.entrySet()) {                  final AutoFillId id = entry.getKey();                  final ViewNode node = findViewNodeByIdLocked(id); @@ -891,20 +539,24 @@ final class AutoFillManagerServiceImpl {                  Slog.v(TAG, "Dumping " + mStructure + " before calling service.save()");                  mStructure.dump();              } -            try { -                mService.save(mStructure, mServerCallback, extras); -            } catch (RemoteException e) { -                Slog.w(TAG, "Error calling save on service: " + e); -                // TODO(b/33197203): invalidate session? -            } + +            mRemoteFillService.onSaveRequest(mStructure, mCurrentResponse.getExtras()); +        } + +        void onApplicationDataAvailable(AssistStructure structure, IBinder appCallback) { +            setAppCallback(appCallback); +            mStructure = structure; +            // TODO(b/33197203): Need to pipe the bundle +            mRemoteFillService.onFillRequest(structure, null);          } -        void setAppCallbackLocked(IBinder appBinder) { +        private void setAppCallback(IBinder appBinder) {              try {                  appBinder.linkToDeath(() -> {                      if (DEBUG) Slog.d(TAG, "app callback died");                      // TODO(b/33197203): more cleanup here?                      mAppCallback = null; +                    destroy();                  }, 0);              } catch (RemoteException e) {                  Slog.w(TAG, "linkToDeath() failed: " + e); @@ -957,78 +609,105 @@ final class AutoFillManagerServiceImpl {                      filterText = text.toString();                  }              } -            mUi.showFillUi(viewState, response.getDatasets(), bounds, filterText); +            getUiForShowing().showFillUi(viewState, response.getDatasets(), bounds, filterText);          } -        private void showResponseLocked(FillResponse response, boolean authRequired) { -            if (DEBUG) Slog.d(TAG, "showResponse(directly=" + mAutoFillDirectly -                    + ", authRequired=" + authRequired +"):" + response); +        private void processResponseLocked(FillResponse response) { +            if (DEBUG) Slog.d(TAG, "showResponse(authRequired=" +                    + response.getAuthentication() +"):" + response); -            if (mAutoFillDirectly && response != null) { -                final List<Dataset> datasets = response.getDatasets(); -                if (datasets.size() == 1) { -                    // User authenticated and provider returned just 1 dataset - auto-fill it now! -                    final Dataset dataset = datasets.get(0); -                    if (DEBUG) Slog.d(TAG, "auto-filling directly from auth: " + dataset); +            // TODO(b/33197203): add MetricsLogger calls -                    autoFillApp(dataset); -                    return; -                } -            } +            mCurrentResponse = response; -            if (!authRequired) { -                // TODO(b/33197203): add MetricsLogger call -                mCurrentResponse = response; +            if (mCurrentResponse.getAuthentication() != null) { +                // ...or handle authentication. +                Intent fillInIntent = createAuthFillInIntent(response.getId(), mStructure, +                        new Bundle(), new FillCallback(new IFillCallback.Stub() { +                            @Override +                            public void onCancellable(ICancellationSignal cancellation) { +                                // TODO(b/33197203): Handle cancellation +                            } + +                            @Override +                            public void onSuccess(FillResponse response) { +                                mCurrentResponse = createAuthenticatedResponse( +                                        mCurrentResponse, response); +                                processResponseLocked(mCurrentResponse); +                            } + +                            @Override +                            public void onFailure(CharSequence message) { +                                getUiForShowing().showError(message); +                                destroy(); +                            } +                        })); + +                 getUiForShowing().showFillResponseAuthRequest( +                         mCurrentResponse.getAuthentication(), fillInIntent); +            } else {                  // TODO(b/33197203): Consider using mCurrentResponse, depends on partitioning design                  if (mCurrentViewState != null) {                      mCurrentViewState.setResponse(mCurrentResponse);                  } -                return; -            } - -            // Handles response that requires authentication. -            // TODO(b/33197203): add MetricsLogger call, including if fingerprint requested - -            mResponseRequiringAuth = response; -            final boolean requiresFingerprint = response.hasCryptoObject(); -            if (requiresFingerprint) { -                // TODO(b/33197203): check if fingerprint is available first and call error callback -                // with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not. -                // Start scanning for the fingerprint. -                scanFingerprint(response.getCryptoObjectOpId());              } -            // Displays the message asking the user to tap (or fingerprint) for AutoFill. -            mUi.showFillResponseAuthenticationRequest(requiresFingerprint, -                    response.getExtras(), response.getFlags());          }          void autoFill(Dataset dataset) {              synchronized (mLock) {                  // Autofill it directly... -                if (!dataset.isAuthRequired()) { +                if (dataset.getAuthentication() == null) {                      autoFillApp(dataset); +                    // For now just show this on every fill +                    getUiForShowing().showSaveUi();                      return;                  }                  // ...or handle authentication. +                Intent fillInIntent = createAuthFillInIntent(dataset.getId(), mStructure, +                        new Bundle(), new FillCallback(new IFillCallback.Stub() { +                    @Override +                    public void onCancellable(ICancellationSignal cancellation) { +                        // TODO(b/33197203): Handle cancellation +                    } -                mDatasetRequiringAuth = dataset; -                final boolean requiresFingerprint = dataset.hasCryptoObject(); -                if (requiresFingerprint) { -                    // TODO(b/33197203): check if fingerprint is available first and call error -                    // callback with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not. -                    // Start scanning for the fingerprint. -                    scanFingerprint(dataset.getCryptoObjectOpId()); -                    // Displays the message asking the user to tap (or fingerprint) for AutoFill. -                    mUi.showDatasetFingerprintAuthenticationRequest(dataset); -                } else { -                    try { -                        mService.authenticateDataset(dataset.getExtras(), -                                FLAG_AUTHENTICATION_REQUESTED); -                    } catch (RemoteException e) { -                        Slog.w(TAG, "Error authenticating dataset: " + e); +                    @Override +                    public void onSuccess(FillResponse response) { +                        mCurrentResponse = createAuthenticatedResponse( +                                mCurrentResponse, response); +                        Dataset augmentedDataset = Helper.findDatasetById(dataset.getId(), +                                mCurrentResponse); +                        if (augmentedDataset != null) { +                            autoFill(augmentedDataset); +                        }                      } -                } + +                    @Override +                    public void onFailure(CharSequence message) { +                        getUiForShowing().showError(message); +                        destroy(); +                    } +                })); + +                startAuthIntent(dataset.getAuthentication(), fillInIntent); +            } +        } + +        private Intent createAuthFillInIntent(String itemId, AssistStructure structure, +                Bundle extras, FillCallback fillCallback) { +            Intent fillInIntent = new Intent(); +            fillInIntent.putExtra(Intent.EXTRA_AUTO_FILL_ITEM_ID, itemId); +            fillInIntent.putExtra(Intent.EXTRA_AUTO_FILL_ASSIST_STRUCTURE, structure); +            fillInIntent.putExtra(Intent.EXTRA_AUTO_FILL_EXTRAS, extras); +            fillInIntent.putExtra(Intent.EXTRA_AUTO_FILL_CALLBACK, fillCallback); +            return fillInIntent; +        } + +        private void startAuthIntent(IntentSender intent, Intent fillInIntent) { +            try { +                mAppCallback.startIntentSender(intent, fillInIntent); +            } catch (RemoteException e) { +                Slog.e(TAG, "Error launching auth intent", e);              }          } @@ -1036,11 +715,6 @@ final class AutoFillManagerServiceImpl {              pw.print(prefix); pw.print("mId: "); pw.println(mId);              pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken.get());              pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse); -            pw.print(prefix); -                pw.print("mResponseRequiringAuth: "); pw.println(mResponseRequiringAuth); -            pw.print(prefix); -                pw.print("mDatasetRequiringAuth: "); pw.println(mDatasetRequiringAuth); -            pw.print(prefix); pw.print("mAutoFillDirectly: "); pw.println(mAutoFillDirectly);              pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState);              pw.print(prefix); pw.print("mViewStates: "); pw.println(mViewStates.size());              final String prefix2 = prefix + "  "; @@ -1057,42 +731,8 @@ final class AutoFillManagerServiceImpl {              } else {                  pw.println("null");              } -        } -        /** -         * Notifies the result of a {@link FillResponse} authentication request to the service. -         * -         * <p>Typically called by the UI after user taps the "Tap to autofill" affordance, or after user -         * used the fingerprint sensors to authenticate. -         */ -        void notifyResponseAuthenticationResult(Bundle extras, int flags) { -            if (DEBUG) Slog.d(TAG, "notifyResponseAuthenticationResult(): flags=" + flags -                    + ", extras=" + bundleToString(extras)); -            synchronized (mLock) { -                try { -                    mService.authenticateFillResponse(extras, flags); -                } catch (RemoteException e) { -                    Slog.w(TAG, "Error sending authentication result back to service: " + e); -                } -            } -        } - -        /** -         * Notifies the result of a {@link Dataset} authentication request to the service. -         * -         * <p>Typically called by the UI after user taps the "Tap to autofill" affordance, or after -         * it gets the results from a fingerprint authentication. -         */ -        void notifyDatasetAuthenticationResult(Bundle extras, int flags) { -            if (DEBUG) Slog.d(TAG, "notifyDatasetAuthenticationResult(): flags=" + flags -                    + ", extras=" + bundleToString(extras)); -            synchronized (mLock) { -                try { -                    mService.authenticateDataset(extras, flags); -                } catch (RemoteException e) { -                    Slog.w(TAG, "Error sending authentication result back to service: " + e); -                } -            } +            mRemoteFillService.dump(prefix, pw);          }          void autoFillApp(Dataset dataset) { @@ -1106,32 +746,9 @@ final class AutoFillManagerServiceImpl {              }          } -        /** -         * Called by UI to trigger a save request to the service. -         */ -        void requestSave() { -            synchronized (mLock) { -                onSaveLocked(); -            } -        } - -        private void scanFingerprint(long opId) { -            // TODO(b/33197203): add MetricsLogger call -            if (DEBUG) Slog.d(TAG, "Starting fingerprint scan for op id: " + opId); - -            // TODO(b/33197203): since we're clearing the AutoFillService's identity, make sure -            // this method is only called at the proper times, otherwise a malicious provider could -            // keep the callback refence to bypass the check -            final long token = Binder.clearCallingIdentity(); -            try { -                // TODO(b/33197203): set a timeout? -                mFingerprintService.authenticate(mAuthToken, opId, mUserId, mServiceReceiver, 0, -                        null); -            } catch (RemoteException e) { -                // Local call, shouldn't happen. -            } finally { -                Binder.restoreCallingIdentity(token); -            } +        private AutoFillUI getUiForShowing() { +            mUi.setCallback(this, mId); +            return mUi;          }          private ViewNode findViewNodeByIdLocked(AutoFillId id) { @@ -1167,10 +784,89 @@ final class AutoFillManagerServiceImpl {              return null;          } -        private void removeSelf() { +        private void destroy() {              synchronized (mLock) { +                mRemoteFillService.destroy(); +                mUi.hideAll(); +                mUi.setCallback(null, 0);                  removeSessionLocked(mId);              }          } + +        /** +         * Creates a response from the {@code original} and an {@code update} by +         * replacing all items that needed authentication (response or datasets) +         * with their updated version if the latter does not need authentication. +         * New datasets that don't require auth are appended. +         * +         * @param original The original response requiring auth at some level. +         * @param update An updated response with auth not needed anymore at some level. +         * @return A new response with updated items where auth is not needed anymore. +         */ +        // TODO(b/33197203) Unit test +        FillResponse createAuthenticatedResponse(FillResponse original, FillResponse update) { +            // Can update only if ids match +            if (!original.getId().equals(update.getId())) { +                return original; +            } + +            // If the original required auth and the update doesn't, the update wins +            // but only if none of the update's datasets requires authentication. +            if (original.getAuthentication() != null && update.getAuthentication() == null) { +                ArraySet<Dataset> updateDatasets = update.getDatasets(); +                final int udpateDatasetCount = updateDatasets.size(); +                for (int i = 0; i < udpateDatasetCount; i++) { +                    Dataset updateDataset = updateDatasets.valueAt(i); +                    if (updateDataset.getAuthentication() != null) { +                        return original; +                    } +                } +                return update; +            } + +            // If no auth on response level we create a response that has all +            // datasets from the original with the ones that required auth but +            // not anymore updated and new ones not requiring auth appended. + +            // The update shouldn't require auth +            if (update.getAuthentication() != null) { +                return original; +            } + +            final FillResponse.Builder builder = new FillResponse.Builder(original.getId()); + +            // Update existing datasets +            final ArraySet<Dataset> origDatasets = original.getDatasets(); +            final int origDatasetCount = origDatasets.size(); +            for (int i = 0; i < origDatasetCount; i++) { +                Dataset origDataset = origDatasets.valueAt(i); +                ArraySet<Dataset> updateDatasets = update.getDatasets(); +                final int updateDatasetCount = updateDatasets.size(); +                for (int j = 0; j < updateDatasetCount; j++) { +                    Dataset updateDataset = updateDatasets.valueAt(j); +                    if (origDataset.getId().equals(updateDataset.getId())) { +                        // The update shouldn't require auth +                        if (updateDataset.getAuthentication() == null) { +                            origDataset = updateDataset; +                            updateDatasets.removeAt(j); +                        } +                        break; +                    } +                } +                builder.addDataset(origDataset); +            } + +            // Add new datasets +            final ArraySet<Dataset> updateDatasets = update.getDatasets(); +            final int updateDatasetCount = updateDatasets.size(); +            for (int i = 0; i < updateDatasetCount; i++) { +                final Dataset updateDataset = updateDatasets.valueAt(i); +                builder.addDataset(updateDataset); +            } + +            // For now no extras and savable id updates. + +            return builder.build(); +        }      }  } diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java index 76c291687ec3..da54d853135b 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java @@ -18,10 +18,7 @@ package com.android.server.autofill;  import static com.android.server.autofill.Helper.DEBUG; -import android.annotation.Nullable; -import android.app.Activity;  import android.app.Notification; -import android.app.Notification.Action;  import android.app.NotificationManager;  import android.app.PendingIntent;  import android.app.StatusBarManager; @@ -29,12 +26,11 @@ 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.graphics.PixelFormat;  import android.os.Binder; -import android.os.Bundle; +import android.util.ArraySet;  import android.util.Slog; -import android.view.autofill.AutoFillId;  import android.view.autofill.Dataset;  import android.view.autofill.FillResponse;  import android.view.Gravity; @@ -44,27 +40,28 @@ import android.view.WindowManager;  import android.view.WindowManager.LayoutParams;  import android.widget.Toast; -import com.android.internal.annotations.GuardedBy;  import com.android.server.UiThread; -import com.android.server.autofill.AutoFillManagerServiceImpl.Session;  import com.android.server.autofill.AutoFillManagerServiceImpl.ViewState;  import java.io.PrintWriter; -import java.util.Arrays; -import java.util.List;  /**   * Handles all auto-fill related UI tasks.   */  // 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 String EXTRA_AUTH_INTENT_SENDER = +            "com.android.server.autofill.extra.AUTH_INTENT_SENDER"; +    private static final String EXTRA_AUTH_FILL_IN_INTENT = +            "com.android.server.autofill.extra.AUTH_FILL_IN_INTENT"; +      private final Context mContext; -    private final Session mSession;      private final WindowManager mWm; +    // TODO(b/33197203) Fix locking - some state requires lock and some not - requires refactoring +      // Fill UI variables      private AnchoredWindow mFillWindow;      private DatasetPicker mFillView; @@ -72,21 +69,39 @@ final class AutoFillUI {      private Rect mBounds;      private String mFilterText; +    private AutoFillUiCallback mCallback; +    private int mClientId; + +    public interface AutoFillUiCallback { +        void authenticate(IntentSender intent, Intent fillInIntent); +        void fill(Dataset dataset); +        void save(); +    } +      /**       * Custom snackbar UI used for saving autofill or other informational messages.       */      private View mSnackbar; -    AutoFillUI(Context context, Session session) { +    AutoFillUI(Context context) {          mContext = context; -        mSession = session;          mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);      } +    void setCallback(AutoFillUiCallback callback, int clientId) { +        hideAll(); +        mCallback = callback; +        mClientId = clientId; +    } +      /**       * Displays an error message to the user.       */      void showError(CharSequence message) { +        if (!hasCallback()) { +            return; +        } +        hideAll();          // TODO(b/33197203): proper implementation          UiThread.getHandler().runWithScissors(() -> {              Toast.makeText(mContext, "AutoFill error: " + message, Toast.LENGTH_LONG).show(); @@ -95,21 +110,17 @@ final class AutoFillUI {      /**       * Hides the fill UI. -     * Shows the options from a {@link FillResponse} so the user can pick up the proper -     * {@link Dataset} (when the response has one) for a given view (identified by -     * {@code autoFillId}).       */      void hideFillUi() {          UiThread.getHandler().runWithScissors(() -> { -            hideFillUiLocked(); +            hideFillUiUiThread();          }, 0);      } -    // Must be called in inside UI Thread -    private void hideFillUiLocked() { +    @android.annotation.UiThread +    private void hideFillUiUiThread() {          if (mFillWindow != null) { -            if (DEBUG) Slog.d(TAG, "hideFillUiLocked(): hide" + mFillWindow); - +            if (DEBUG) Slog.d(TAG, "hideFillUiUiThread(): hide" + mFillWindow);              mFillWindow.hide();          } @@ -120,7 +131,6 @@ final class AutoFillUI {          mFillWindow = null;      } -      /**       * Shows the fill UI, removing the previous fill UI if the has changed.       * @@ -129,26 +139,31 @@ final class AutoFillUI {       * @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(ViewState viewState, List<Dataset> datasets, Rect bounds, +    void showFillUi(ViewState viewState, ArraySet<Dataset> datasets, Rect bounds,              String filterText) { +        if (!hasCallback()) { +            return; +        } +        hideAll();          UiThread.getHandler().runWithScissors(() -> {              if (mViewState != viewState) { -                // new -                hideFillUi(); -                  mViewState = viewState;                  mFillView = new DatasetPicker(mContext, datasets,                          (dataset) -> { -                            mSession.autoFillApp(dataset); +                            final AutoFillUiCallback callback; +                            synchronized (mLock) { +                                callback = mCallback; +                            } +                            callback.fill(dataset);                              hideFillUi();                          }); +                // TODO: No magical numbers                  mFillWindow = new AnchoredWindow(                          mWm, mFillView, 800, ViewGroup.LayoutParams.WRAP_CONTENT);                  if (DEBUG) Slog.d(TAG, "show FillUi");              } -              if (!bounds.equals(mBounds)) {                  if (DEBUG) Slog.d(TAG, "update FillUi bounds: " + mBounds);                  mBounds = bounds; @@ -170,27 +185,14 @@ final class AutoFillUI {       * <p>It typically replaces the auto-fill bar with a message saying "Press fingerprint or tap to       * autofill" or "Tap to autofill", depending on the value of {@code usesFingerprint}.       */ -    void showFillResponseAuthenticationRequest(boolean usesFingerprint, -            Bundle extras, int flags) { -        // TODO(b/33197203): proper implementation -        showAuthNotification(usesFingerprint, extras, flags); -    } - -    /** -     * Shows an UI affordance asking indicating that user action is required before a -     * {@link Dataset} can be used. -     * -     * <p>It typically replaces the auto-fill bar with a message saying "Press fingerprint to -     * autofill". -     */ -    void showDatasetFingerprintAuthenticationRequest(Dataset dataset) { -        if (DEBUG) Slog.d(TAG, "showDatasetAuthenticationRequest(): dataset=" + dataset); - -        // TODO(b/33197203): proper implementation (either pop up a fingerprint dialog or replace -        // the auto-fill bar with a new message. +    void showFillResponseAuthRequest(IntentSender intent, Intent fillInIntent) { +        if (!hasCallback()) { +            return; +        } +        hideAll();          UiThread.getHandler().runWithScissors(() -> { -            Toast.makeText(mContext, "AutoFill: press fingerprint to unlock " + dataset.getName(), -                    Toast.LENGTH_LONG).show(); +            // TODO(b/33197203): proper implementation +            showFillResponseAuthUiUiThread(intent, fillInIntent);          }, 0);      } @@ -198,46 +200,36 @@ final class AutoFillUI {       * Shows the UI asking the user to save for auto-fill.       */      void showSaveUi() { -        showSnackbar(new SavePrompt(mContext, new SavePrompt.OnSaveListener() { -            @Override -            public void onSaveClick() { -                hideSnackbar(); - -                // TODO(b/33197203): add MetricsLogger call -                mSession.requestSave(); -            } -            @Override -            public void onCancelClick() { -                hideSnackbar(); -            } -        })); -    } - -    /** -     * Called by service after the user user the fingerprint sensors to authenticate. -     */ -    void dismissFingerprintRequest(boolean success) { -        if (DEBUG) Slog.d(TAG, "dismissFingerprintRequest(): ok=" + success); - -        dismissAuthNotification(); - -        if (!success) { -            // TODO(b/33197203): proper implementation (snack bar / i18n string) -            UiThread.getHandler().runWithScissors(() -> { -                Toast.makeText(mContext, "AutoFill: fingerprint failed", Toast.LENGTH_LONG).show(); -            }, 0); +        if (!hasCallback()) { +            return;          } +        hideAll(); +        UiThread.getHandler().runWithScissors(() -> { +            showSnackbarUiThread(new SavePrompt(mContext, +                    new SavePrompt.OnSaveListener() { +                @Override +                public void onSaveClick() { +                    hideSnackbarUiThread(); +                    // TODO(b/33197203): add MetricsLogger call +                    mCallback.save(); +                } + +                @Override +                public void onCancelClick() { +                    hideSnackbarUiThread(); +                } +            })); +        }, 0);      }      /** -     * Closes all UI affordances. +     * Hides all UI affordances.       */ -    void closeAll() { -        if (DEBUG) Slog.d(TAG, "closeAll()"); - +    void hideAll() {          UiThread.getHandler().runWithScissors(() -> { -            hideSnackbarLocked(); -            hideFillUiLocked(); +            hideSnackbarUiThread(); +            hideFillUiUiThread(); +            hideFillResponseAuthUiUiThread();          }, 0);      } @@ -245,7 +237,7 @@ final class AutoFillUI {          pw.println("AufoFill UI");          final String prefix = "  ";          pw.print(prefix); pw.print("sResultCode: "); pw.println(sResultCode); -        pw.print(prefix); pw.print("mSessionId: "); pw.println(mSession.mId); +        pw.print(prefix); pw.print("mClientId: "); pw.println(mClientId);          pw.print(prefix); pw.print("mSnackBar: "); pw.println(mSnackbar);          pw.print(prefix); pw.print("mViewState: "); pw.println(mViewState);          pw.print(prefix); pw.print("mBounds: "); pw.println(mBounds); @@ -254,7 +246,7 @@ final class AutoFillUI {      //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. -    private void showSnackbar(View snackBar) { +    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 @@ -274,20 +266,19 @@ final class AutoFillUI {          }, 0);      } -    private void hideSnackbar() { -        UiThread.getHandler().runWithScissors(() -> { -            hideSnackbarLocked(); -        }, 0); -    } - -    // Must be called in inside UI Thread -    private void hideSnackbarLocked() { +    private void hideSnackbarUiThread() {          if (mSnackbar != null) {              mWm.removeView(mSnackbar);              mSnackbar = null;          }      } +    private boolean hasCallback() { +        synchronized (mLock) { +            return mCallback != null; +        } +    } +      /////////////////////////////////////////////////////////////////////////////////      // TODO(b/33197203): temporary code using a notification to request auto-fill. //      // Will be removed once UX decide the right way to present it to the user.     // @@ -297,25 +288,13 @@ final class AutoFillUI {      private static final String NOTIFICATION_AUTO_FILL_INTENT =              "com.android.internal.autofill.action.REQUEST_AUTOFILL"; -    // Extras used in the notification intents -    private static final String EXTRA_USER_ID = "user_id"; -    private static final String EXTRA_NOTIFICATION_TYPE = "notification_type"; -    private static final String EXTRA_SESSION_ID = "session_id"; -    private static final String EXTRA_FILL_RESPONSE = "fill_response"; -    private static final String EXTRA_DATASET = "dataset"; -    private static final String EXTRA_AUTH_REQUIRED_EXTRAS = "auth_required_extras"; -    private static final String EXTRA_FLAGS = "flags"; - -    private static final String TYPE_OPTIONS = "options"; -    private static final String TYPE_AUTH_RESPONSE = "auth_response"; -      private BroadcastReceiver mNotificationReceiver;      private final Object mLock = new Object();      // Hack used to generate unique pending intents      static int sResultCode = 0; -    private void setNotificationListener() { +    private void ensureNotificationListener() {          synchronized (mLock) {              if (mNotificationReceiver == null) {                  mNotificationReceiver = new NotificationReceiver(); @@ -328,83 +307,58 @@ final class AutoFillUI {      final class NotificationReceiver extends BroadcastReceiver {          @Override          public void onReceive(Context context, Intent intent) { -            final String type = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE); -            if (type == null) { -                Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent); -                return; -            } -            final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET); -            final int flags = intent.getIntExtra(EXTRA_FLAGS, 0); - -            if (DEBUG) Slog.d(TAG, "Notification received: type=" + type -                    + ", sessionId=" + mSession.mId); +            final AutoFillUiCallback callback;              synchronized (mLock) { -                switch (type) { -                    case TYPE_AUTH_RESPONSE: -                        mSession.notifyResponseAuthenticationResult( -                                intent.getBundleExtra(EXTRA_AUTH_REQUIRED_EXTRAS), flags); -                        break; -                    default: { -                        Slog.w(TAG, "Unknown notification type: " + type); -                    } -                } +                callback = mCallback; +            } +            if (callback != null) { +                IntentSender intentSender = intent.getParcelableExtra(EXTRA_AUTH_INTENT_SENDER); +                Intent fillInIntent = intent.getParcelableExtra(EXTRA_AUTH_FILL_IN_INTENT); +                callback.authenticate(intentSender, fillInIntent);              }              collapseStatusBar();          }      } -    private static Intent newNotificationIntent(String type) { -        final Intent intent = new Intent(NOTIFICATION_AUTO_FILL_INTENT); -        intent.putExtra(EXTRA_NOTIFICATION_TYPE, type); -        return intent; -    } - -    private void showAuthNotification(boolean usesFingerprint, -            Bundle extras, int flags) { -        final long token = Binder.clearCallingIdentity(); -        try { -            showAuthNotificationAsSystem(usesFingerprint, extras, flags); -        } finally { -            Binder.restoreCallingIdentity(token); -        } -    } - -    private void showAuthNotificationAsSystem( -            boolean usesFingerprint, Bundle extras, int flags) { +    @android.annotation.UiThread +    private void showFillResponseAuthUiUiThread(IntentSender intent, Intent fillInIntent) {          final String title = "AutoFill Authentication";          final StringBuilder subTitle = new StringBuilder("Provider require user authentication.\n"); -        final Intent authIntent = newNotificationIntent(TYPE_AUTH_RESPONSE); -        if (extras != null) { -            authIntent.putExtra(EXTRA_AUTH_REQUIRED_EXTRAS, extras); -        } -        if (flags != 0) { -            authIntent.putExtra(EXTRA_FLAGS, flags); -        } -        final PendingIntent authPendingIntent = PendingIntent.getBroadcast(mContext, ++sResultCode, -                authIntent, PendingIntent.FLAG_ONE_SHOT); +        final Intent authIntent = new Intent(NOTIFICATION_AUTO_FILL_INTENT); +        authIntent.putExtra(EXTRA_AUTH_INTENT_SENDER, intent); +        authIntent.putExtra(EXTRA_AUTH_FILL_IN_INTENT, fillInIntent); -        if (usesFingerprint) { -            subTitle.append("But kindly accepts your fingerprint instead" -                    + "\n(tap fingerprint sensor to trigger it)"); +        final PendingIntent authPendingIntent = PendingIntent.getBroadcast( +                mContext, ++sResultCode, authIntent, PendingIntent.FLAG_ONE_SHOT); -        } else { -            subTitle.append("Tap notification to launch its authentication UI."); -        } +        subTitle.append("Tap notification to launch its authentication UI.");          final Notification.Builder notification = newNotificationBuilder()                  .setAutoCancel(true)                  .setOngoing(false)                  .setContentTitle(title) -                .setStyle(new Notification.BigTextStyle().bigText(subTitle.toString())); -        if (authPendingIntent != null) { -            notification.setContentIntent(authPendingIntent); +                .setStyle(new Notification.BigTextStyle().bigText(subTitle.toString())) +                .setContentIntent(authPendingIntent); + +        ensureNotificationListener(); + +        final long identity = Binder.clearCallingIdentity(); +        try { +            NotificationManager.from(mContext).notify(mClientId, notification.build()); +        } finally { +            Binder.restoreCallingIdentity(identity);          } -        NotificationManager.from(mContext).notify(mSession.mId, notification.build());      } -    private void dismissAuthNotification() { -        NotificationManager.from(mContext).cancel(mSession.mId); +    @android.annotation.UiThread +    private void hideFillResponseAuthUiUiThread() { +        final long identity = Binder.clearCallingIdentity(); +        try { +            NotificationManager.from(mContext).cancel(mClientId); +        } finally { +            Binder.restoreCallingIdentity(identity); +        }      }      private Notification.Builder newNotificationBuilder() { diff --git a/services/autofill/java/com/android/server/autofill/DatasetPicker.java b/services/autofill/java/com/android/server/autofill/DatasetPicker.java index db516d85efd7..8212cf15d257 100644 --- a/services/autofill/java/com/android/server/autofill/DatasetPicker.java +++ b/services/autofill/java/com/android/server/autofill/DatasetPicker.java @@ -17,6 +17,7 @@ package com.android.server.autofill;  import android.content.Context;  import android.graphics.Color; +import android.util.ArraySet;  import android.view.autofill.Dataset;  import android.view.View;  import android.view.ViewGroup; @@ -44,7 +45,7 @@ final class DatasetPicker extends ListView implements OnItemClickListener {      private final Listener mListener; -    DatasetPicker(Context context, List<Dataset> datasets, Listener listener) { +    DatasetPicker(Context context, ArraySet<Dataset> datasets, Listener listener) {          super(context);          mListener = listener; diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index 9171dac04d81..7fff41008866 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -17,6 +17,9 @@  package com.android.server.autofill;  import android.os.Bundle; +import android.util.ArraySet; +import android.view.autofill.Dataset; +import android.view.autofill.FillResponse;  import java.util.Arrays;  import java.util.Objects; @@ -55,4 +58,26 @@ final class Helper {      private Helper() {          throw new UnsupportedOperationException("contains static members only");      } + +    /** +     * Finds a data set by id in a response. +     * +     * @param id The dataset id. +     * @param response The response to search. +     * @return The dataset if found or null. +     */ +    static Dataset findDatasetById(String id, FillResponse response) { +        ArraySet<Dataset> datasets = response.getDatasets(); +        if (datasets == null || datasets.isEmpty()) { +            return null; +        } +        final int datasetCount = datasets.size(); +        for (int i = 0; i < datasetCount; i++) { +            Dataset dataset = datasets.valueAt(i); +            if (dataset.getId().equals(id)) { +                return dataset; +            } +        } +        return null; +    }  } diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java new file mode 100644 index 000000000000..c070f779c6f9 --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -0,0 +1,521 @@ +/* + * 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.annotation.NonNull; +import android.annotation.Nullable; +import android.app.assist.AssistStructure; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.ICancellationSignal; +import android.os.Message; +import android.os.RemoteException; +import android.os.UserHandle; +import android.service.autofill.AutoFillService; +import android.service.autofill.IAutoFillService; +import android.service.autofill.IFillCallback; +import android.service.autofill.ISaveCallback; +import android.text.format.DateUtils; +import android.util.Slog; +import android.view.autofill.FillResponse; +import com.android.internal.os.HandlerCaller; +import com.android.server.FgThread; + +import java.io.PrintWriter; +import java.lang.ref.WeakReference; + +/** + * This class represents a remote fill service. It abstracts away the binding + * and unbinding from the remote implementation. + * + * <p>Clients can call methods of this class without worrying about when and + * how to bind/unbind/timeout. All state of this class is modified on a handler + * thread. + */ +final class RemoteFillService implements DeathRecipient { +    private static final String LOG_TAG = "RemoteFillService"; + +    private static final boolean DEBUG = Helper.DEBUG; + +    // How long after the last interaction with the service we would unbind +    private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.MINUTE_IN_MILLIS; + +    private final Context mContext; + +    private final ComponentName mComponentName; + +    private final Intent mIntent; + +    private final FillServiceCallbacks mCallbacks; + +    private final int mUserId; + +    private final ServiceConnection mServiceConnection = new RemoteServiceConnection(); + +    private final HandlerCaller mHandler; + +    private IAutoFillService mAutoFillService; + +    private boolean mBinding; + +    private boolean mDestroyed; + +    private boolean mServiceDied; + +    private boolean mCompleted; + +    private PendingRequest mPendingRequest; + +    public interface FillServiceCallbacks { +        void onFillRequestSuccess(FillResponse response); +        void onFillRequestFailure(CharSequence message); +        void onSaveRequestSuccess(); +        void onSaveRequestFailure(CharSequence message); +        void onServiceDied(RemoteFillService service); +    } + +    public RemoteFillService(Context context, ComponentName componentName, +            int userId, FillServiceCallbacks callbacks) { +        mContext = context; +        mCallbacks = callbacks; +        mComponentName = componentName; +        mIntent = new Intent(AutoFillService.SERVICE_INTERFACE) +                .setComponent(mComponentName); +        mUserId = userId; +        mHandler = new MyHandler(context); +    } + +    public void destroy() { +        mHandler.obtainMessage(MyHandler.MSG_DESTROY).sendToTarget(); +    } + +    private void handleDestroy() { +        if (mPendingRequest != null) { +            mPendingRequest.cancel(); +            mPendingRequest = null; +        } +        ensureUnbound(); +        mDestroyed = true; +    } + +    @Override +    public void binderDied() { +        mHandler.obtainMessage(MyHandler.MSG_BINDER_DIED).sendToTarget(); +    } + +    private void handleBinderDied() { +        if (mAutoFillService != null) { +            mAutoFillService.asBinder().unlinkToDeath(this, 0); +        } +        mAutoFillService = null; +        mServiceDied = true; +        mCallbacks.onServiceDied(this); +    } + +    public void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle extras) { +        cancelScheduledUnbind(); +        PendingFillRequest request = new PendingFillRequest(structure, extras, this); +        mHandler.obtainMessageO(MyHandler.MSG_ON_PENDING_REQUEST, request).sendToTarget(); +    } + +    public void onSaveRequest(@NonNull AssistStructure structure, @Nullable Bundle extras) { +        cancelScheduledUnbind(); +        PendingSaveRequest request = new PendingSaveRequest(structure, extras, this); +        mHandler.obtainMessageO(MyHandler.MSG_ON_PENDING_REQUEST, request).sendToTarget(); +    } + +    // Note: we are dumping without a lock held so this is a bit racy but +    // adding a lock to a class that offloads to a handler thread would +    // mean adding a lock adding overhead to normal runtime operation. +    public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { +        String tab = "  "; +        pw.append(prefix).append("service:").println(); +        pw.append(prefix).append(tab).append("userId=") +                .append(String.valueOf(mUserId)).println(); +        pw.append(prefix).append(tab).append("componentName=") +                .append(mComponentName.flattenToString()).println(); +        pw.append(prefix).append(tab).append("destroyed=") +                .append(String.valueOf(mDestroyed)).println(); +        pw.append(prefix).append(tab).append("bound=") +                .append(String.valueOf(isBound())).println(); +        pw.append(prefix).append(tab).append("hasPendingRequest=") +                .append(String.valueOf(mPendingRequest != null)).println(); +        pw.println(); +    } + +    private void cancelScheduledUnbind() { +        mHandler.removeMessages(MyHandler.MSG_UNBIND); +    } + +    private void scheduleUnbind() { +        cancelScheduledUnbind(); +        Message message = mHandler.obtainMessage(MyHandler.MSG_UNBIND); +        mHandler.sendMessageDelayed(message, TIMEOUT_IDLE_BIND_MILLIS); +    } + +    private void handleUnbind() { +        ensureUnbound(); +    } + +    private void handlePendingRequest(PendingRequest pendingRequest) { +        if (mDestroyed || mCompleted) { +            return; +        } +        if (pendingRequest.isFinal()) { +            mCompleted = true; +        } +        if (!isBound()) { +            if (mPendingRequest != null) { +                mPendingRequest.cancel(); +            } +            mPendingRequest = pendingRequest; +            ensureBound(); +        } else { +            if (DEBUG) { +                Slog.d(LOG_TAG, "[user: " + mUserId + "] handleOnFillRequest()"); +            } +            pendingRequest.run(); +        } +    } + +    private boolean isBound() { +        return mAutoFillService != null; +    } + +    private void ensureBound() { +        if (isBound() || mBinding) { +            return; +        } +        if (DEBUG) { +            Slog.d(LOG_TAG, "[user: " + mUserId + "] ensureBound()"); +        } +        mBinding = true; + +        boolean willBind = mContext.bindServiceAsUser(mIntent, mServiceConnection, +                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, +                new UserHandle(mUserId)); + +        if (!willBind) { +            if (DEBUG) { +                Slog.d(LOG_TAG, "[user: " + mUserId + "] could not bind to " + mIntent); +            } +            mBinding = false; + +            if (!mServiceDied) { +                handleBinderDied(); +            } +        } +    } + +    private void ensureUnbound() { +        if (!isBound() && !mBinding) { +            return; +        } +        if (DEBUG) { +            Slog.d(LOG_TAG, "[user: " + mUserId + "] ensureUnbound()"); +        } +        mBinding = false; +        if (isBound()) { +            mAutoFillService.asBinder().unlinkToDeath(this, 0); +            mAutoFillService = null; +        } +        mContext.unbindService(mServiceConnection); +    } + +    private void dispatchOnFillRequestSuccess(PendingRequest pendingRequest, +            FillResponse response) { +        mHandler.getHandler().post(() -> { +            if (handleResponseCallbackCommon(pendingRequest)) { +                mCallbacks.onFillRequestSuccess(response); +            } +        }); +    } + +    private void dispatchOnFillRequestFailure(PendingRequest pendingRequest, +            CharSequence message) { +        mHandler.getHandler().post(() -> { +            if (handleResponseCallbackCommon(pendingRequest)) { +                mCallbacks.onFillRequestFailure(message); +            } +        }); +    } + +    private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest) { +        mHandler.getHandler().post(() -> { +            if (handleResponseCallbackCommon(pendingRequest)) { +                mCallbacks.onSaveRequestSuccess(); +            } +        }); +    } + +    private void dispatchOnSaveRequestFailure(PendingRequest pendingRequest, +            CharSequence message) { +        mHandler.getHandler().post(() -> { +            if (handleResponseCallbackCommon(pendingRequest)) { +                mCallbacks.onSaveRequestFailure(message); +            } +        }); +    } + +    private boolean handleResponseCallbackCommon(PendingRequest pendingRequest) { +        if (mDestroyed) { +            return false; +        } +        if (mPendingRequest == pendingRequest) { +            mPendingRequest = null; +        } +        if (mPendingRequest == null) { +            scheduleUnbind(); +        } +        return true; +    } + +    private class RemoteServiceConnection implements ServiceConnection { +        @Override +        public void onServiceConnected(ComponentName name, IBinder service) { +            if (mDestroyed || !mBinding) { +                mContext.unbindService(mServiceConnection); +                return; +            } +            mBinding = false; +            mAutoFillService = IAutoFillService.Stub.asInterface(service); +            try { +                service.linkToDeath(RemoteFillService.this, 0); +            } catch (RemoteException re) { +                handleBinderDied(); +                return; +            } + +            if (mPendingRequest != null) { +                handlePendingRequest(mPendingRequest); +            } + +            mServiceDied = false; +        } + +        @Override +        public void onServiceDisconnected(ComponentName name) { +            mBinding = true; +            mAutoFillService = null; +        } +    } + +    private final class MyHandler extends HandlerCaller { +        public static final int MSG_DESTROY = 1; +        public static final int MSG_BINDER_DIED = 2; +        public static final int MSG_UNBIND = 3; +        public static final int MSG_ON_PENDING_REQUEST = 4; + +        public MyHandler(Context context) { +            // Cannot use lambda - doesn't compile +            super(context, FgThread.getHandler().getLooper(), new Callback() { +                @Override +                public void executeMessage(Message message) { +                    if (mDestroyed) { +                        Slog.w(LOG_TAG, "Not handling " + message + " as service for " +                                + mComponentName + " is already destroyed"); +                        return; +                    } +                    switch (message.what) { +                        case MSG_DESTROY: { +                            handleDestroy(); +                        } break; + +                        case MSG_BINDER_DIED: { +                            handleBinderDied(); +                        } break; + +                        case MSG_UNBIND: { +                            handleUnbind(); +                        } break; + +                        case MSG_ON_PENDING_REQUEST: { +                            handlePendingRequest((PendingRequest) message.obj); +                        } break; +                    } +                } +            }, false); +        } +    } + +    private static abstract class PendingRequest implements Runnable { +        void cancel() { + +        } + +        /** +         * @return whether this request leads to a final state where no +         * other requests can be made. +         */ +        boolean isFinal() { +            return false; +        } +    } + +    private static final class PendingFillRequest extends PendingRequest { +        private final Object mLock = new Object(); +        private final WeakReference<RemoteFillService> mWeakService; +        private AssistStructure mStructure; +        private Bundle mExtras; +        private final IFillCallback mCallback; +        private ICancellationSignal mCancellation; +        private boolean mCancelled; + +        public PendingFillRequest(AssistStructure structure, +                Bundle extras, RemoteFillService service) { +            mStructure = structure; +            mExtras = extras; +            mWeakService = new WeakReference<>(service); +            mCallback = new IFillCallback.Stub() { +                @Override +                public void onCancellable(ICancellationSignal cancellation) { +                    synchronized (mLock) { +                        final boolean cancelled; +                        synchronized (mLock) { +                            mCancellation = cancellation; +                            cancelled = mCancelled; +                        } +                        if (cancelled) { +                            try { +                                cancellation.cancel(); +                            } catch (RemoteException e) { +                                Slog.e(LOG_TAG, "Error requesting a cancellation", e); +                            } +                        } +                    } +                } + +                @Override +                public void onSuccess(FillResponse response) { +                    RemoteFillService remoteService = mWeakService.get(); +                    if (remoteService != null) { +                        remoteService.dispatchOnFillRequestSuccess( +                                PendingFillRequest.this, response); +                    } +                } + +                @Override +                public void onFailure(CharSequence message) { +                    RemoteFillService remoteService = mWeakService.get(); +                    if (remoteService != null) { +                        remoteService.dispatchOnFillRequestFailure( +                                PendingFillRequest.this, message); +                    } +                } +            }; +        } + +        @Override +        public void run() { +            RemoteFillService remoteService = mWeakService.get(); +            if (remoteService != null) { +                try { +                    remoteService.mAutoFillService.onFillRequest(mStructure, +                            mExtras, mCallback); +                    synchronized (mLock) { +                        mStructure = null; +                        mExtras = null; +                    } +                } catch (RemoteException e) { +                    Slog.e(LOG_TAG, "Error calling on fill request", e); +                    cancel(); +                } +            } +        } + +        @Override +        public void cancel() { +            final ICancellationSignal cancellation; +            synchronized (mLock) { +                if (mCancelled) { +                    return; +                } +                mCancelled = true; +                cancellation = mCancellation; +            } +            if (cancellation == null) { +                return; +            } +            try { +                cancellation.cancel(); +            } catch (RemoteException e) { +                Slog.e(LOG_TAG, "Error cancelling a fill request", e); +            } +        } +    } + +    private static final class PendingSaveRequest extends PendingRequest { +        private final Object mLock = new Object(); +        private final WeakReference<RemoteFillService> mWeakService; +        private AssistStructure mStructure; +        private Bundle mExtras; +        private final ISaveCallback mCallback; + +        public PendingSaveRequest(@NonNull AssistStructure structure, +                @Nullable Bundle extras, @NonNull RemoteFillService service) { +            mStructure = structure; +            mExtras = extras; +            mWeakService = new WeakReference<>(service); +            mCallback = new ISaveCallback.Stub() { +                @Override +                public void onSuccess() { +                    RemoteFillService service = mWeakService.get(); +                    if (service != null) { +                        service.dispatchOnSaveRequestSuccess( +                                PendingSaveRequest.this); +                    } +                } + +                @Override +                public void onFailure(CharSequence message) { +                    RemoteFillService service = mWeakService.get(); +                    if (service != null) { +                        service.dispatchOnSaveRequestFailure( +                                PendingSaveRequest.this, message); +                    } +                } +            }; +        } + +        @Override +        public void run() { +            RemoteFillService service = mWeakService.get(); +            if (service != null) { +                try { +                    service.mAutoFillService.onSaveRequest(mStructure, +                            mExtras, mCallback); +                    synchronized (mLock) { +                        mStructure = null; +                        mExtras = null; +                    } +                } catch (RemoteException e) { +                    Slog.e(LOG_TAG, "Error calling on save request", e); +                } +            } +        } + +        @Override +        public boolean isFinal() { +            return true; +        } +    } +} |