Merge "Autofill Settings: Display the number of saved passwords." into sc-dev
This commit is contained in:
@@ -16,37 +16,63 @@
|
|||||||
|
|
||||||
package com.android.settings.applications.autofill;
|
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.annotation.UserIdInt;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ServiceInfo;
|
import android.content.pm.ServiceInfo;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
|
import android.service.autofill.AutofillService;
|
||||||
import android.service.autofill.AutofillServiceInfo;
|
import android.service.autofill.AutofillServiceInfo;
|
||||||
|
import android.service.autofill.IAutoFillService;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.IconDrawableFactory;
|
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.Preference;
|
||||||
import androidx.preference.PreferenceGroup;
|
import androidx.preference.PreferenceGroup;
|
||||||
import androidx.preference.PreferenceScreen;
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
import com.android.internal.os.IResultReceiver;
|
||||||
import com.android.settings.Utils;
|
import com.android.settings.Utils;
|
||||||
import com.android.settings.core.BasePreferenceController;
|
import com.android.settings.core.BasePreferenceController;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queries available autofill services and adds preferences for those that declare passwords
|
* Queries available autofill services and adds preferences for those that declare passwords
|
||||||
* settings.
|
* 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 PackageManager mPm;
|
||||||
private final IconDrawableFactory mIconFactory;
|
private final IconDrawableFactory mIconFactory;
|
||||||
private final List<AutofillServiceInfo> mServices;
|
private final List<AutofillServiceInfo> mServices;
|
||||||
|
|
||||||
|
private LifecycleOwner mLifecycleOwner;
|
||||||
|
|
||||||
public PasswordsPreferenceController(Context context, String preferenceKey) {
|
public PasswordsPreferenceController(Context context, String preferenceKey) {
|
||||||
this(context, preferenceKey,
|
this(context, preferenceKey,
|
||||||
AutofillServiceInfo.getAvailableServices(context, UserHandle.myUserId()));
|
AutofillServiceInfo.getAvailableServices(context, UserHandle.myUserId()));
|
||||||
@@ -67,6 +93,11 @@ public class PasswordsPreferenceController extends BasePreferenceController {
|
|||||||
mServices = availableServices;
|
mServices = availableServices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnLifecycleEvent(ON_CREATE)
|
||||||
|
void onCreate(LifecycleOwner lifecycleOwner) {
|
||||||
|
mLifecycleOwner = lifecycleOwner;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAvailabilityStatus() {
|
public int getAvailabilityStatus() {
|
||||||
return mServices.isEmpty() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
|
return mServices.isEmpty() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
|
||||||
@@ -96,7 +127,79 @@ public class PasswordsPreferenceController extends BasePreferenceController {
|
|||||||
pref.setIntent(
|
pref.setIntent(
|
||||||
new Intent(Intent.ACTION_MAIN)
|
new Intent(Intent.ACTION_MAIN)
|
||||||
.setClassName(serviceInfo.packageName, service.getPasswordsActivity()));
|
.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);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,21 +21,26 @@ import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_U
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.service.autofill.AutofillServiceInfo;
|
import android.service.autofill.AutofillServiceInfo;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Lifecycle;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceCategory;
|
import androidx.preference.PreferenceCategory;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import androidx.preference.PreferenceScreen;
|
import androidx.preference.PreferenceScreen;
|
||||||
|
import androidx.test.annotation.UiThreadTest;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
import com.google.android.collect.Lists;
|
import com.google.android.collect.Lists;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
@@ -100,11 +105,15 @@ public class PasswordsPreferenceControllerTest {
|
|||||||
assertThat(mPasswordsPreferenceCategory.getPreferenceCount()).isEqualTo(0);
|
assertThat(mPasswordsPreferenceCategory.getPreferenceCount()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore("TODO: Fix the test to handle the service binding.")
|
||||||
@Test
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
public void displayPreference_withPasswords_addsPreference() {
|
public void displayPreference_withPasswords_addsPreference() {
|
||||||
AutofillServiceInfo service = createServiceWithPasswords();
|
AutofillServiceInfo service = createServiceWithPasswords();
|
||||||
PasswordsPreferenceController controller =
|
PasswordsPreferenceController controller =
|
||||||
createControllerWithServices(Lists.newArrayList(service));
|
createControllerWithServices(Lists.newArrayList(service));
|
||||||
|
controller.onCreate(() -> mock(Lifecycle.class));
|
||||||
|
|
||||||
controller.displayPreference(mScreen);
|
controller.displayPreference(mScreen);
|
||||||
|
|
||||||
assertThat(mPasswordsPreferenceCategory.getPreferenceCount()).isEqualTo(1);
|
assertThat(mPasswordsPreferenceCategory.getPreferenceCount()).isEqualTo(1);
|
||||||
|
Reference in New Issue
Block a user