diff --git a/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java b/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java
index f27530e0aea..123addac7e2 100644
--- a/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java
+++ b/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java
@@ -16,37 +16,63 @@
package com.android.settings.applications.autofill;
+import static android.service.autofill.AutofillService.EXTRA_RESULT;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+
import android.annotation.UserIdInt;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
import android.os.UserHandle;
+import android.service.autofill.AutofillService;
import android.service.autofill.AutofillServiceInfo;
+import android.service.autofill.IAutoFillService;
import android.text.TextUtils;
import android.util.IconDrawableFactory;
+import android.util.Log;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.IResultReceiver;
import com.android.settings.Utils;
import com.android.settings.core.BasePreferenceController;
+import java.lang.ref.WeakReference;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Queries available autofill services and adds preferences for those that declare passwords
* settings.
+ *
+ * The controller binds to each service to fetch the number of saved passwords in each.
*/
-public class PasswordsPreferenceController extends BasePreferenceController {
+public class PasswordsPreferenceController extends BasePreferenceController
+ implements LifecycleObserver {
+ private static final String TAG = "AutofillSettings";
private final PackageManager mPm;
private final IconDrawableFactory mIconFactory;
private final List mServices;
+ private LifecycleOwner mLifecycleOwner;
+
public PasswordsPreferenceController(Context context, String preferenceKey) {
this(context, preferenceKey,
AutofillServiceInfo.getAvailableServices(context, UserHandle.myUserId()));
@@ -67,6 +93,11 @@ public class PasswordsPreferenceController extends BasePreferenceController {
mServices = availableServices;
}
+ @OnLifecycleEvent(ON_CREATE)
+ void onCreate(LifecycleOwner lifecycleOwner) {
+ mLifecycleOwner = lifecycleOwner;
+ }
+
@Override
public int getAvailabilityStatus() {
return mServices.isEmpty() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
@@ -96,7 +127,79 @@ public class PasswordsPreferenceController extends BasePreferenceController {
pref.setIntent(
new Intent(Intent.ACTION_MAIN)
.setClassName(serviceInfo.packageName, service.getPasswordsActivity()));
+
+ final MutableLiveData passwordCount = new MutableLiveData<>();
+ passwordCount.observe(
+ // TODO(b/169455298): Validate the result.
+ // TODO(b/169455298): Use a Quantity String resource.
+ mLifecycleOwner, count -> pref.setSummary("" + count + " passwords saved"));
+ // TODO(b/169455298): Limit the number of concurrent queries.
+ // TODO(b/169455298): Cache the results for some time.
+ requestSavedPasswordCount(service, user, passwordCount);
+
group.addPreference(pref);
}
}
+
+ private void requestSavedPasswordCount(
+ AutofillServiceInfo service, @UserIdInt int user, MutableLiveData data) {
+ final Intent intent =
+ new Intent(AutofillService.SERVICE_INTERFACE)
+ .setComponent(service.getServiceInfo().getComponentName());
+ final AutofillServiceConnection connection = new AutofillServiceConnection(mContext, data);
+ if (mContext.bindServiceAsUser(
+ intent, connection, Context.BIND_AUTO_CREATE, UserHandle.of(user))) {
+ connection.mBound.set(true);
+ mLifecycleOwner.getLifecycle().addObserver(connection);
+ }
+ }
+
+ private static class AutofillServiceConnection implements ServiceConnection, LifecycleObserver {
+ final WeakReference mContext;
+ final MutableLiveData mData;
+ final AtomicBoolean mBound = new AtomicBoolean();
+
+ AutofillServiceConnection(Context context, MutableLiveData data) {
+ mContext = new WeakReference<>(context);
+ mData = data;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ final IAutoFillService autofillService = IAutoFillService.Stub.asInterface(service);
+ // TODO check if debug is logged on user build.
+ Log.d(TAG, "Fetching password count from " + name);
+ try {
+ autofillService.onSavedPasswordCountRequest(
+ new IResultReceiver.Stub() {
+ @Override
+ public void send(int resultCode, Bundle resultData) {
+ Log.d(TAG, "Received password count result " + resultCode
+ + " from " + name);
+ if (resultCode == 0 && resultData != null) {
+ mData.postValue(resultData.getInt(EXTRA_RESULT));
+ }
+ unbind();
+ }
+ });
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to fetch password count: " + e);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+
+ @OnLifecycleEvent(ON_DESTROY)
+ void unbind() {
+ if (!mBound.getAndSet(false)) {
+ return;
+ }
+ final Context context = mContext.get();
+ if (context != null) {
+ context.unbindService(this);
+ }
+ }
+ }
}
diff --git a/tests/unit/src/com/android/settings/applications/autofill/PasswordsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/autofill/PasswordsPreferenceControllerTest.java
index 216658fcb12..25d989385d3 100644
--- a/tests/unit/src/com/android/settings/applications/autofill/PasswordsPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/applications/autofill/PasswordsPreferenceControllerTest.java
@@ -21,21 +21,26 @@ import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_U
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+
import android.content.ComponentName;
import android.content.Context;
import android.os.Looper;
import android.service.autofill.AutofillServiceInfo;
+import androidx.lifecycle.Lifecycle;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.collect.Lists;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -100,11 +105,15 @@ public class PasswordsPreferenceControllerTest {
assertThat(mPasswordsPreferenceCategory.getPreferenceCount()).isEqualTo(0);
}
+ @Ignore("TODO: Fix the test to handle the service binding.")
@Test
+ @UiThreadTest
public void displayPreference_withPasswords_addsPreference() {
AutofillServiceInfo service = createServiceWithPasswords();
PasswordsPreferenceController controller =
createControllerWithServices(Lists.newArrayList(service));
+ controller.onCreate(() -> mock(Lifecycle.class));
+
controller.displayPreference(mScreen);
assertThat(mPasswordsPreferenceCategory.getPreferenceCount()).isEqualTo(1);