Add AutofillService#onSavedDatasetsInfoRequest.

System settings will use this to display the number of saved passwords
in each AutofillService available on the device.

The API supports some flexibility to make it easier to support a
different settings UX in the future.

Bug: 169455298
CTS-Coverage-Bug: 183499221
Test: manual - check the result displayed in settings
Change-Id: I13dd2f06dad18cfe8ef5de9b44854e44ac8d7365
This commit is contained in:
Ahaan Ugale
2021-03-07 21:29:02 -08:00
parent 624a0f2d0e
commit a451a40f95
6 changed files with 406 additions and 0 deletions

View File

@@ -37468,6 +37468,7 @@ package android.service.autofill {
method public void onDisconnected();
method public abstract void onFillRequest(@NonNull android.service.autofill.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
method public abstract void onSaveRequest(@NonNull android.service.autofill.SaveRequest, @NonNull android.service.autofill.SaveCallback);
method public void onSavedDatasetsInfoRequest(@NonNull android.service.autofill.SavedDatasetsInfoCallback);
field public static final String SERVICE_INTERFACE = "android.service.autofill.AutofillService";
field public static final String SERVICE_META_DATA = "android.autofill";
}
@@ -37743,6 +37744,22 @@ package android.service.autofill {
field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
}
public final class SavedDatasetsInfo {
ctor public SavedDatasetsInfo(@NonNull String, @IntRange(from=0) int);
method @IntRange(from=0) public int getCount();
method @NonNull public String getType();
field public static final String TYPE_OTHER = "other";
field public static final String TYPE_PASSWORDS = "passwords";
}
public interface SavedDatasetsInfoCallback {
method public void onError(int);
method public void onSuccess(@NonNull java.util.Set<android.service.autofill.SavedDatasetsInfo>);
field public static final int ERROR_NEEDS_USER_ACTION = 2; // 0x2
field public static final int ERROR_OTHER = 0; // 0x0
field public static final int ERROR_UNSUPPORTED = 1; // 0x1
}
public final class TextValueSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer {
ctor public TextValueSanitizer(@NonNull java.util.regex.Pattern, @NonNull String);
method public int describeContents();

View File

@@ -38,6 +38,8 @@ import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import com.android.internal.os.IResultReceiver;
/**
* An {@code AutofillService} is a service used to automatically fill the contents of the screen
* on behalf of a given user - for more information about autofill, read
@@ -575,6 +577,20 @@ public abstract class AutofillService extends Service {
*/
public static final String SERVICE_META_DATA = "android.autofill";
/**
* Name of the {@link IResultReceiver} extra used to return the primary result of a request.
*
* @hide
*/
public static final String EXTRA_RESULT = "result";
/**
* Name of the {@link IResultReceiver} extra used to return the error reason of a request.
*
* @hide
*/
public static final String EXTRA_ERROR = "error";
private final IAutoFillService mInterface = new IAutoFillService.Stub() {
@Override
public void onConnectedStateChanged(boolean connected) {
@@ -603,6 +619,14 @@ public abstract class AutofillService extends Service {
AutofillService::onSaveRequest,
AutofillService.this, request, new SaveCallback(callback)));
}
@Override
public void onSavedPasswordCountRequest(IResultReceiver receiver) {
mHandler.sendMessage(obtainMessage(
AutofillService::onSavedDatasetsInfoRequest,
AutofillService.this,
new SavedDatasetsInfoCallbackImpl(receiver, SavedDatasetsInfo.TYPE_PASSWORDS)));
}
};
private Handler mHandler;
@@ -672,6 +696,19 @@ public abstract class AutofillService extends Service {
public abstract void onSaveRequest(@NonNull SaveRequest request,
@NonNull SaveCallback callback);
/**
* Called from system settings to display information about the datasets the user saved to this
* service.
*
* <p>There is no timeout for the request, but it's recommended to return the result within a
* few seconds, or the user may navigate away from the activity that would display the result.
*
* @param callback callback for responding to the request
*/
public void onSavedDatasetsInfoRequest(@NonNull SavedDatasetsInfoCallback callback) {
callback.onError(SavedDatasetsInfoCallback.ERROR_UNSUPPORTED);
}
/**
* Called when the Android system disconnects from the service.
*

View File

@@ -31,4 +31,5 @@ oneway interface IAutoFillService {
void onConnectedStateChanged(boolean connected);
void onFillRequest(in FillRequest request, in IFillCallback callback);
void onSaveRequest(in SaveRequest request, in ISaveCallback callback);
void onSavedPasswordCountRequest(in IResultReceiver receiver);
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.service.autofill;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.StringDef;
import com.android.internal.util.DataClass;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* A result returned from
* {@link AutofillService#onSavedDatasetsInfoRequest(SavedDatasetsInfoCallback)}.
*/
@DataClass(
genToString = true,
genHiddenConstDefs = true,
genEqualsHashCode = true)
public final class SavedDatasetsInfo {
/**
* Any other type of datasets.
*/
public static final String TYPE_OTHER = "other";
/**
* Datasets such as login credentials.
*/
public static final String TYPE_PASSWORDS = "passwords";
/**
* The type of the datasets that this info is about.
*/
@NonNull
@Type
private final String mType;
/**
* The number of datasets of {@link #getType() this type} that the user has saved to the
* service.
*/
@IntRange(from = 0)
private final int mCount;
// Code below generated by codegen v1.0.22.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/SavedDatasetsInfo.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
/** @hide */
@StringDef(prefix = "TYPE_", value = {
TYPE_OTHER,
TYPE_PASSWORDS
})
@Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
public @interface Type {}
/**
* Creates a new SavedDatasetsInfo.
*
* @param type
* The type of the datasets.
* @param count
* The number of datasets of this type that the user has saved to the service.
*/
@DataClass.Generated.Member
public SavedDatasetsInfo(
@NonNull @Type String type,
@IntRange(from = 0) int count) {
this.mType = type;
if (!(java.util.Objects.equals(mType, TYPE_OTHER))
&& !(java.util.Objects.equals(mType, TYPE_PASSWORDS))) {
throw new java.lang.IllegalArgumentException(
"type was " + mType + " but must be one of: "
+ "TYPE_OTHER(" + TYPE_OTHER + "), "
+ "TYPE_PASSWORDS(" + TYPE_PASSWORDS + ")");
}
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mType);
this.mCount = count;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mCount,
"from", 0);
// onConstructed(); // You can define this method to get a callback
}
/**
* The type of the datasets.
*/
@DataClass.Generated.Member
public @NonNull @Type String getType() {
return mType;
}
/**
* The number of datasets of this type that the user has saved to the service.
*/
@DataClass.Generated.Member
public @IntRange(from = 0) int getCount() {
return mCount;
}
@Override
@DataClass.Generated.Member
public String toString() {
// You can override field toString logic by defining methods like:
// String fieldNameToString() { ... }
return "SavedDatasetsInfo { " +
"type = " + mType + ", " +
"count = " + mCount +
" }";
}
@Override
@DataClass.Generated.Member
public boolean equals(@android.annotation.Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(SavedDatasetsInfo other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
SavedDatasetsInfo that = (SavedDatasetsInfo) o;
//noinspection PointlessBooleanExpression
return true
&& java.util.Objects.equals(mType, that.mType)
&& mCount == that.mCount;
}
@Override
@DataClass.Generated.Member
public int hashCode() {
// You can override field hashCode logic by defining methods like:
// int fieldNameHashCode() { ... }
int _hash = 1;
_hash = 31 * _hash + java.util.Objects.hashCode(mType);
_hash = 31 * _hash + mCount;
return _hash;
}
@DataClass.Generated(
time = 1615325704446L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/service/autofill/SavedDatasetsInfo.java",
inputSignatures = "public static final java.lang.String TYPE_OTHER\npublic static final java.lang.String TYPE_PASSWORDS\nprivate final @android.annotation.NonNull @android.service.autofill.SavedDatasetsInfo.Type java.lang.String mType\nprivate final @android.annotation.IntRange int mCount\nclass SavedDatasetsInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.service.autofill;
import android.annotation.IntDef;
import android.annotation.NonNull;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Set;
/**
* Handles the response to
* {@link AutofillService#onSavedDatasetsInfoRequest(SavedDatasetsInfoCallback)}.
* <p>
* Use {@link #onSuccess(Set)} to return the computed info about the datasets the user saved to this
* service. If there was an error querying the info, or if the service is unable to do so at this
* time (for example, if the user isn't logged in), call {@link #onError(int)}.
* <p>
* This callback can be used only once.
*/
public interface SavedDatasetsInfoCallback {
/** @hide */
@IntDef(prefix = {"ERROR_"}, value = {
ERROR_OTHER,
ERROR_UNSUPPORTED,
ERROR_NEEDS_USER_ACTION
})
@Retention(RetentionPolicy.SOURCE)
@interface Error {
}
/**
* The result could not be computed for any other reason.
*/
int ERROR_OTHER = 0;
/**
* The service does not support this request.
*/
int ERROR_UNSUPPORTED = 1;
/**
* The result cannot be computed until the user takes some action, such as setting up their
* account.
*/
int ERROR_NEEDS_USER_ACTION = 2;
/**
* Successfully respond to the request with the info on each type of saved datasets.
*/
void onSuccess(@NonNull Set<SavedDatasetsInfo> results);
/**
* Respond to the request with an error. System settings may display a suitable notice to the
* user.
*/
void onError(@Error int error);
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.service.autofill;
import static android.service.autofill.AutofillService.EXTRA_ERROR;
import static android.service.autofill.AutofillService.EXTRA_RESULT;
import static com.android.internal.util.Preconditions.checkArgumentInRange;
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.os.IResultReceiver;
import java.util.Set;
final class SavedDatasetsInfoCallbackImpl implements SavedDatasetsInfoCallback {
private static final String TAG = "AutofillService";
@NonNull
private final IResultReceiver mReceiver;
@NonNull
private final String mType;
/**
* Creates a {@link SavedDatasetsInfoCallback} that returns the {@link
* SavedDatasetsInfo#getCount() number} of saved datasets of {@code type} to the {@code
* receiver}.
*/
SavedDatasetsInfoCallbackImpl(@NonNull IResultReceiver receiver, @NonNull String type) {
mReceiver = requireNonNull(receiver);
mType = requireNonNull(type);
}
@Override
public void onSuccess(@NonNull Set<SavedDatasetsInfo> results) {
requireNonNull(results);
if (results.isEmpty()) {
send(1, null);
return;
}
int count = -1;
for (SavedDatasetsInfo info : results) {
if (mType.equals(info.getType())) {
count = info.getCount();
}
}
if (count < 0) {
send(1, null);
return;
}
Bundle bundle = new Bundle(/* capacity= */ 1);
bundle.putInt(EXTRA_RESULT, count);
send(0, bundle);
}
@Override
public void onError(@Error int error) {
checkArgumentInRange(error, ERROR_OTHER, ERROR_NEEDS_USER_ACTION, "error");
Bundle bundle = new Bundle(/* capacity= */ 1);
bundle.putInt(EXTRA_ERROR, error);
send(1, bundle);
}
private void send(int resultCode, Bundle bundle) {
try {
mReceiver.send(resultCode, bundle);
} catch (DeadObjectException e) {
Log.w(TAG, "Failed to send onSavedPasswordCountRequest result: " + e);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
}