Merge "Refactoring of auto fill - lifecycle, auth, improvements"
This commit is contained in:
committed by
Android (Google) Code Review
commit
85a05cd9b5
@@ -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 \
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -9509,6 +9509,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";
|
||||
@@ -39133,26 +39137,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 {
|
||||
@@ -50165,10 +50162,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);
|
||||
}
|
||||
@@ -50180,12 +50176,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);
|
||||
}
|
||||
|
||||
@@ -50611,7 +50606,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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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);
|
||||
@@ -1375,6 +1376,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.
|
||||
*
|
||||
|
||||
@@ -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)
|
||||
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 save(AssistStructure structure, IAutoFillServerCallback callback,
|
||||
Bundle extras) throws RemoteException {
|
||||
mHandlerCaller
|
||||
.obtainMessageOOO(MSG_SAVE, structure, callback, extras)
|
||||
.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()");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
public void onFailure(@Nullable CharSequence message) {
|
||||
assertNotCalled();
|
||||
mCalled = true;
|
||||
try {
|
||||
mCallback.onFailure(message);
|
||||
} catch (RemoteException e) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
private void assertNotCalled() {
|
||||
if (mCalled) {
|
||||
throw new IllegalStateException("Already called");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
public static final Creator<FillCallback> CREATOR = new Creator<FillCallback>() {
|
||||
@Override
|
||||
public FillCallback createFromParcel(Parcel parcel) {
|
||||
return new FillCallback(parcel);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
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];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
static interface Finalizer {
|
||||
void gone();
|
||||
}
|
||||
/**
|
||||
* Interface to receive the result of a save request.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
interface ISaveCallback {
|
||||
void onSuccess();
|
||||
void onFailure(CharSequence message);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
assertNotCalled();
|
||||
mCalled = true;
|
||||
try {
|
||||
mCallback.onFailure(message);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
/** @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;
|
||||
}
|
||||
}
|
||||
|
||||
@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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
public @NonNull String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public @NonNull CharSequence getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public List<DatasetField> getFields() {
|
||||
return mFields;
|
||||
public @Nullable ArrayList<AutoFillId> getFieldIds() {
|
||||
return mFieldIds;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public Bundle getExtras() {
|
||||
public @Nullable ArrayList<AutoFillValue> getFieldValues() {
|
||||
return mFieldValues;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public @Nullable Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public int getFlags() {
|
||||
return mFlags;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean isAuthRequired() {
|
||||
return mRequiresAuth;
|
||||
public @Nullable IntentSender getAuthentication() {
|
||||
return mAuthentication;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean isEmpty() {
|
||||
return mFields.isEmpty();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean hasCryptoObject() {
|
||||
return mHasCryptoObject;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public long getCryptoObjectOpId() {
|
||||
return mCryptoOpId;
|
||||
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.
|
||||
* <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>
|
||||
*
|
||||
* @param authentication Intent to trigger your authentication flow.
|
||||
*
|
||||
* @see android.app.PendingIntent#getIntentSender()
|
||||
*/
|
||||
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:
|
||||
*
|
||||
* <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>
|
||||
*
|
||||
* <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.
|
||||
*/
|
||||
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() {
|
||||
public @NonNull Builder setExtras(@Nullable Bundle extras) {
|
||||
throwIfDestroyed();
|
||||
mExtras = extras;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Dataset} instance. You should not interact
|
||||
* with this builder once this method is called.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
mAuthentication = builder.mAuthentication;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public List<Dataset> getDatasets() {
|
||||
return mDatasets;
|
||||
public @NonNull String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public AutoFillId[] getSavableIds() {
|
||||
return mSavableIds;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public Bundle getExtras() {
|
||||
public @Nullable Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public int getFlags() {
|
||||
return mFlags;
|
||||
public @Nullable ArraySet<Dataset> getDatasets() {
|
||||
return mDatasets;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean isAuthRequired() {
|
||||
return mRequiresAuth;
|
||||
public @Nullable ArraySet<AutoFillId> getSavableIds() {
|
||||
return mSavableIds;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean hasCryptoObject() {
|
||||
return mHasCryptoObject;
|
||||
}
|
||||
|
||||
/** @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;
|
||||
|
||||
/**
|
||||
* Requires user authentication through the {@link android.service.autofill.AutoFillService}
|
||||
* before handling an auto-fill request.
|
||||
*
|
||||
* <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.
|
||||
*/
|
||||
public Builder requiresCustomAuthentication(@Nullable Bundle extras, int flags) {
|
||||
return requiresAuthentication(null, extras, flags);
|
||||
/** @hide */
|
||||
// TODO(b/33197203): Remove once GCore migrates
|
||||
public Builder() {
|
||||
this(String.valueOf(System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires user authentication through the Fingerprint sensor before handling an auto-fill
|
||||
* request.
|
||||
* Creates a new {@link FillResponse} builder.
|
||||
*
|
||||
* <p>The {@link android.service.autofill.AutoFillService} typically uses this method in 2
|
||||
* situations:
|
||||
*
|
||||
* <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 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:
|
||||
*
|
||||
* <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>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: </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 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.
|
||||
* @param id A required id to identify this dataset for future interactions related to it.
|
||||
*/
|
||||
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);
|
||||
public Builder(@NonNull String id) {
|
||||
mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty or null");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
/**
|
||||
* 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>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>
|
||||
*
|
||||
* <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>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>
|
||||
*
|
||||
* <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></><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>
|
||||
*
|
||||
* @param authentication Intent to trigger your authentication flow.
|
||||
*
|
||||
* @see android.app.PendingIntent#getIntentSender()
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
if (activityToken != null) {
|
||||
synchronized (mLock) {
|
||||
final AutoFillManagerServiceImpl service =
|
||||
getOrCreateServiceForUserLocked(userId);
|
||||
service.requestAutoFillLocked(activityToken, null, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (mLock) {
|
||||
final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
|
||||
if (service == null) {
|
||||
Slog.w(TAG, "no service for user " + userId);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user