diff --git a/core/api/current.txt b/core/api/current.txt index f428a56b594e1..585919ebe21cc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -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 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); + 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(); diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 04a4ca47d305b..13274c6f27eee 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -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. + * + *

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. * diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl index 23a1a3fee47c5..d88e0945bdca4 100644 --- a/core/java/android/service/autofill/IAutoFillService.aidl +++ b/core/java/android/service/autofill/IAutoFillService.aidl @@ -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); } diff --git a/core/java/android/service/autofill/SavedDatasetsInfo.java b/core/java/android/service/autofill/SavedDatasetsInfo.java new file mode 100644 index 0000000000000..6a4d2b834cef1 --- /dev/null +++ b/core/java/android/service/autofill/SavedDatasetsInfo.java @@ -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 + +} diff --git a/core/java/android/service/autofill/SavedDatasetsInfoCallback.java b/core/java/android/service/autofill/SavedDatasetsInfoCallback.java new file mode 100644 index 0000000000000..a47105a238169 --- /dev/null +++ b/core/java/android/service/autofill/SavedDatasetsInfoCallback.java @@ -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)}. + *

+ * 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)}. + *

+ * 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 results); + + /** + * Respond to the request with an error. System settings may display a suitable notice to the + * user. + */ + void onError(@Error int error); +} diff --git a/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.java b/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.java new file mode 100644 index 0000000000000..b8a8cde12d3ee --- /dev/null +++ b/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.java @@ -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 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(); + } + } +}