Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a8df0386 authored by Ahaan Ugale's avatar Ahaan Ugale Committed by Android (Google) Code Review
Browse files

Merge "Autofill Settings: Display the number of saved passwords." into sc-dev

parents 1b82a330 63fb2f10
Loading
Loading
Loading
Loading
+104 −1
Original line number Diff line number Diff line
@@ -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.
 * <p>
 * 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<AutofillServiceInfo> 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<Integer> 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<Integer> 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<Context> mContext;
        final MutableLiveData<Integer> mData;
        final AtomicBoolean mBound = new AtomicBoolean();

        AutofillServiceConnection(Context context, MutableLiveData<Integer> 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);
            }
        }
    }
}
+9 −0
Original line number Diff line number Diff line
@@ -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);