diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java index 4c80f54e744..84b7bf0a7a6 100644 --- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java +++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java @@ -29,13 +29,16 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.content.res.Resources; import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; import android.credentials.SetEnabledProvidersException; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Bundle; import android.os.OutcomeReceiver; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.provider.Settings; import android.text.TextUtils; import android.util.IconDrawableFactory; @@ -48,6 +51,7 @@ import androidx.fragment.app.FragmentManager; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.OnLifecycleEvent; +import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreference; @@ -69,6 +73,7 @@ import java.util.concurrent.Executor; /** Queries available credential manager providers and adds preferences for them. */ public class CredentialManagerPreferenceController extends BasePreferenceController implements LifecycleObserver { + public static final String ADD_SERVICE_DEVICE_CONFIG = "credential_manager_service_search_uri"; private static final String TAG = "CredentialManagerPreferenceController"; private static final String ALTERNATE_INTENT = "android.settings.SYNC_SETTINGS"; private static final int MAX_SELECTABLE_PROVIDERS = 5; @@ -84,6 +89,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl private @Nullable FragmentManager mFragmentManager = null; private @Nullable Delegate mDelegate = null; + private @Nullable String mFlagOverrideForTest = null; public CredentialManagerPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); @@ -236,12 +242,16 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl setAvailableServices( lifecycleOwner, mCredentialManager.getCredentialProviderServices( - getUser(), CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY)); + getUser(), CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY), + null); } @VisibleForTesting void setAvailableServices( - LifecycleOwner lifecycleOwner, List availableServices) { + LifecycleOwner lifecycleOwner, + List availableServices, + String flagOverrideForTest) { + mFlagOverrideForTest = flagOverrideForTest; mServices.clear(); mServices.addAll(availableServices); @@ -275,6 +285,65 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl PreferenceGroup group = screen.findPreference(getPreferenceKey()); Context context = screen.getContext(); mPrefs.putAll(buildPreferenceList(context, group)); + + // Add the "add service" button only when there are no providers. + if (mPrefs.isEmpty()) { + String searchUri = getAddServiceUri(context); + if (!TextUtils.isEmpty(searchUri)) { + group.addPreference(newAddServicePreference(searchUri, context)); + } + } + } + + /** + * Returns the "add service" URI to show the play store. It will first try and use the + * credential manager specific search URI and if that is null it will fallback to the autofill + * one. + */ + public @NonNull String getAddServiceUri(@NonNull Context context) { + // Check the credential manager gflag for a link. + String searchUri = + DeviceConfig.getString( + DeviceConfig.NAMESPACE_CREDENTIAL, + ADD_SERVICE_DEVICE_CONFIG, + mFlagOverrideForTest); + if (!TextUtils.isEmpty(searchUri)) { + return searchUri; + } + + // If not fall back on autofill. + return Settings.Secure.getStringForUser( + context.getContentResolver(), + Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI, + getUser()); + } + + /** + * Gets the preference that allows to add a new cred man service. + * + * @return the pref to be added + */ + @VisibleForTesting + public Preference newAddServicePreference(String searchUri, Context context) { + final Intent addNewServiceIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)); + final Preference preference = new Preference(context); + preference.setOnPreferenceClickListener( + p -> { + context.startActivityAsUser(addNewServiceIntent, UserHandle.of(getUser())); + return true; + }); + preference.setTitle(R.string.print_menu_item_add_service); + preference.setOrder(Integer.MAX_VALUE - 1); + preference.setPersistent(false); + + // Try to set the icon this should fail in a test environment but work + // in the actual app. + try { + preference.setIcon(R.drawable.ic_add_24dp); + } catch (Resources.NotFoundException e) { + Log.e(TAG, "Failed to find icon for add services link", e); + } + return preference; } /** Aggregates the list of services and builds a list of UI prefs to show. */ diff --git a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java index 2109f127d34..7f6857bdeef 100644 --- a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java @@ -33,9 +33,11 @@ import android.content.pm.ServiceInfo; import android.credentials.CredentialProviderInfo; import android.net.Uri; import android.os.Looper; +import android.os.UserHandle; import android.provider.Settings; import androidx.lifecycle.Lifecycle; +import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; @@ -120,11 +122,42 @@ public class CredentialManagerPreferenceControllerTest { } @Test - public void displayPreference_noServices_noPreferencesAdded() { + public void displayPreference_noServices_noPreferencesAdded_useAutofillUri() { + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI, + "test", + UserHandle.myUserId()); + CredentialManagerPreferenceController controller = createControllerWithServices(Collections.emptyList()); controller.displayPreference(mScreen); - assertThat(mCredentialsPreferenceCategory.getPreferenceCount()).isEqualTo(0); + assertThat(mCredentialsPreferenceCategory.getPreferenceCount()).isEqualTo(1); + + Preference pref = mCredentialsPreferenceCategory.getPreference(0); + assertThat(pref.getTitle()).isEqualTo("Add service"); + + assertThat(controller.getAddServiceUri(mContext)).isEqualTo("test"); + } + + @Test + public void displayPreference_noServices_noPreferencesAdded_useCredManUri() { + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI, + "test", + UserHandle.myUserId()); + + CredentialManagerPreferenceController controller = + createControllerWithServicesAndAddServiceOverride( + Collections.emptyList(), "credman"); + controller.displayPreference(mScreen); + assertThat(mCredentialsPreferenceCategory.getPreferenceCount()).isEqualTo(1); + + Preference pref = mCredentialsPreferenceCategory.getPreference(0); + assertThat(pref.getTitle()).isEqualTo("Add service"); + + assertThat(controller.getAddServiceUri(mContext)).isEqualTo("credman"); } @Test @@ -134,6 +167,9 @@ public class CredentialManagerPreferenceControllerTest { controller.displayPreference(mScreen); assertThat(controller.isConnected()).isFalse(); assertThat(mCredentialsPreferenceCategory.getPreferenceCount()).isEqualTo(1); + + Preference pref = mCredentialsPreferenceCategory.getPreference(0); + assertThat(pref.getTitle()).isNotEqualTo("Add account"); } @Test @@ -400,10 +436,15 @@ public class CredentialManagerPreferenceControllerTest { private CredentialManagerPreferenceController createControllerWithServices( List availableServices) { + return createControllerWithServicesAndAddServiceOverride(availableServices, null); + } + + private CredentialManagerPreferenceController createControllerWithServicesAndAddServiceOverride( + List availableServices, String addServiceOverride) { CredentialManagerPreferenceController controller = new CredentialManagerPreferenceController( mContext, mCredentialsPreferenceCategory.getKey()); - controller.setAvailableServices(() -> mock(Lifecycle.class), availableServices); + controller.setAvailableServices(() -> mock(Lifecycle.class), availableServices, addServiceOverride); controller.setDelegate(mDelegate); return controller; }