diff --git a/res/layout/preference_credential_manager_with_buttons.xml b/res/layout/preference_credential_manager_with_buttons.xml
new file mode 100644
index 00000000000..1889cea8078
--- /dev/null
+++ b/res/layout/preference_credential_manager_with_buttons.xml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2b5117d7835..e73cf31eea1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -10775,7 +10775,7 @@
Passwords
- Passwords, passkeys, and data services
+ Preferred service
Additional providers
auto, fill, autofill, data, passkey, password
+
+ Change
+
+ Open
@@ -12836,4 +12840,4 @@
-
+
\ No newline at end of file
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8df990b8ed2..fbc6d7fc6e2 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -989,4 +989,22 @@
- true
+
+
+
+
diff --git a/res/xml/accounts_dashboard_settings_credman.xml b/res/xml/accounts_dashboard_settings_credman.xml
index 7bcf62d53e1..7266bda3d1e 100644
--- a/res/xml/accounts_dashboard_settings_credman.xml
+++ b/res/xml/accounts_dashboard_settings_credman.xml
@@ -26,15 +26,14 @@
android:order="10"
android:title="@string/credman_chosen_app_title">
-
-
+
-
-
+
-
@@ -36,7 +35,7 @@
-
+
-
-
+
mCredentialProviderInfos;
private final @Nullable AutofillServiceInfo mAutofillServiceInfo;
private final boolean mIsDefaultAutofillProvider;
@@ -316,4 +325,44 @@ public final class CombinedProviderInfo {
return cmpi;
}
+
+ public static @Nullable Intent createSettingsActivityIntent(
+ @NonNull Context context,
+ @Nullable CharSequence packageName,
+ @Nullable CharSequence settingsActivity,
+ int currentUserId) {
+ if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(settingsActivity)) {
+ return null;
+ }
+
+ ComponentName cn =
+ new ComponentName(String.valueOf(packageName), String.valueOf(settingsActivity));
+ if (cn == null) {
+ Log.e(
+ TAG,
+ "Failed to deserialize settingsActivity attribute, we got: "
+ + String.valueOf(packageName)
+ + " and "
+ + String.valueOf(settingsActivity));
+ return null;
+ }
+
+ Intent intent = new Intent(SETTINGS_ACTIVITY_INTENT_ACTION);
+ intent.addCategory(SETTINGS_ACTIVITY_INTENT_CATEGORY);
+ intent.setComponent(cn);
+
+ int contextUserId = context.getUser().getIdentifier();
+ if (currentUserId != contextUserId && UserManager.isHeadlessSystemUserMode()) {
+ Log.w(
+ TAG,
+ "onLeftSideClicked(): using context for current user ("
+ + currentUserId
+ + ") instead of user "
+ + contextUserId
+ + " on headless system user mode");
+ context = context.createContextAsUser(UserHandle.of(currentUserId), /* flags= */ 0);
+ }
+
+ return intent;
+ }
}
diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
index c7d5a73e1c6..20232991475 100644
--- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
@@ -96,9 +96,6 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
private static final String ALTERNATE_INTENT = "android.settings.SYNC_SETTINGS";
private static final String PRIMARY_INTENT = "android.settings.CREDENTIAL_PROVIDER";
private static final int MAX_SELECTABLE_PROVIDERS = 5;
- private static final String SETTINGS_ACTIVITY_INTENT_ACTION = "android.intent.action.MAIN";
- private static final String SETTINGS_ACTIVITY_INTENT_CATEGORY =
- "android.intent.category.LAUNCHER";
private final PackageManager mPm;
private final List mServices;
@@ -495,6 +492,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
// If this provider is displayed at the top then we should not show it.
if (topProvider != null
+ && topProvider.getApplicationInfo() != null
&& topProvider.getApplicationInfo().packageName.equals(packageName)) {
continue;
}
@@ -504,10 +502,6 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
continue;
}
- // Get the settings activity.
- CharSequence settingsActivity =
- combinedInfo.getCredentialProviderInfos().get(0).getSettingsActivity();
-
Drawable icon = combinedInfo.getAppIcon(context, getUser());
CharSequence title = combinedInfo.getAppName(context);
@@ -519,7 +513,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
icon,
packageName,
combinedInfo.getSettingsSubtitle(),
- settingsActivity);
+ combinedInfo.getSettingsActivity());
output.put(packageName, pref);
group.addPreference(pref);
}
@@ -658,43 +652,12 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
@Override
public void onLeftSideClicked() {
- if (settingsActivity == null) {
- Log.w(TAG, "settingsActivity was null");
- return;
+ Intent settingsIntent =
+ CombinedProviderInfo.createSettingsActivityIntent(
+ mContext, packageName, settingsActivity, getUser());
+ if (settingsIntent != null) {
+ mContext.startActivity(settingsIntent);
}
-
- String settingsActivityStr = String.valueOf(settingsActivity);
- ComponentName cn = ComponentName.unflattenFromString(settingsActivityStr);
- if (cn == null) {
- Log.w(
- TAG,
- "Failed to deserialize settingsActivity attribute, we got: "
- + settingsActivityStr);
- return;
- }
-
- Intent intent = new Intent(SETTINGS_ACTIVITY_INTENT_ACTION);
- intent.addCategory(SETTINGS_ACTIVITY_INTENT_CATEGORY);
- intent.setComponent(cn);
-
- Context context = mContext;
- int currentUserId = getUser();
- int contextUserId = context.getUser().getIdentifier();
-
- if (currentUserId != contextUserId) {
- Log.d(
- TAG,
- "onLeftSideClicked(): using context for current user ("
- + currentUserId
- + ") instead of user "
- + contextUserId
- + " on headless system user mode");
- context =
- context.createContextAsUser(
- UserHandle.of(currentUserId), /* flags= */ 0);
- }
-
- context.startActivity(intent);
}
});
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
index 567bc3141e9..d2400bbbcbb 100644
--- a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
@@ -16,31 +16,32 @@
package com.android.settings.applications.credentials;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ServiceInfo;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
+import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.autofill.AutofillService;
import android.service.autofill.AutofillServiceInfo;
-import android.text.TextUtils;
import android.view.autofill.AutofillManager;
import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.Utils;
import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
import com.android.settingslib.applications.DefaultAppInfo;
+import com.android.settingslib.widget.TwoTargetPreference;
import java.util.ArrayList;
import java.util.List;
-public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController
- implements Preference.OnPreferenceClickListener {
+public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController {
private static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE);
private static final String TAG = "DefaultCombinedPreferenceController";
@@ -78,72 +79,80 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
// Despite this method being called getSettingIntent this intent actually
// opens the primary picker. This is so that we can swap the cog and the left
// hand side presses to align the UX.
- return new Intent(mContext, CredentialsPickerActivity.class);
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
-
- final String prefKey = getPreferenceKey();
- final Preference preference = screen.findPreference(prefKey);
- if (preference != null) {
- preference.setOnPreferenceClickListener((Preference.OnPreferenceClickListener) this);
+ if (PrimaryProviderPreference.shouldUseNewSettingsUi()) {
+ // We need to return an empty intent here since the class we inherit
+ // from will throw an NPE if we return null and we don't want it to
+ // open anything since we added the buttons.
+ return new Intent();
}
+ return createIntentToOpenPicker();
}
@Override
- public boolean onPreferenceClick(Preference preference) {
- // Get the selected provider.
+ public void updateState(@NonNull Preference preference) {
final CombinedProviderInfo topProvider = getTopProvider();
- if (topProvider == null) {
- return false;
+ if (topProvider != null && mContext != null) {
+ updatePreferenceForProvider(
+ preference,
+ topProvider.getAppName(mContext),
+ topProvider.getSettingsSubtitle(),
+ topProvider.getAppIcon(mContext, getUser()),
+ createSettingsActivityIntent(
+ topProvider.getPackageName(), topProvider.getSettingsActivity()));
+ } else {
+ updatePreferenceForProvider(preference, null, null, null, null);
+ }
+ }
+
+ @VisibleForTesting
+ public void updatePreferenceForProvider(
+ Preference preference,
+ @Nullable CharSequence appName,
+ @Nullable String appSubtitle,
+ @Nullable Drawable appIcon,
+ @Nullable Intent settingsActivityIntent) {
+ if (appName == null) {
+ preference.setTitle(R.string.app_list_preference_none);
+ } else {
+ preference.setTitle(appName);
}
- // If the top provider has a defined Credential Manager settings
- // provider then we should open that up.
- final String settingsActivity = topProvider.getSettingsActivity();
- if (!TextUtils.isEmpty(settingsActivity)) {
- final Intent intent =
- new Intent(Intent.ACTION_MAIN)
- .setComponent(
- new ComponentName(
- topProvider.getPackageName(), settingsActivity));
- startActivity(intent);
- return true;
+ if (appIcon == null) {
+ preference.setIcon(null);
+ } else {
+ preference.setIcon(Utils.getSafeIcon(appIcon));
}
- return false;
+ preference.setSummary(appSubtitle);
+
+ if (preference instanceof PrimaryProviderPreference) {
+ PrimaryProviderPreference primaryPref = (PrimaryProviderPreference) preference;
+ primaryPref.setIconSize(TwoTargetPreference.ICON_SIZE_MEDIUM);
+ primaryPref.setDelegate(
+ new PrimaryProviderPreference.Delegate() {
+ public void onOpenButtonClicked() {
+ if (settingsActivityIntent != null) {
+ startActivity(settingsActivityIntent);
+ }
+ }
+
+ public void onChangeButtonClicked() {
+ startActivity(createIntentToOpenPicker());
+ }
+ });
+
+ // Hide the open button if there is no defined settings activity.
+ primaryPref.setOpenButtonVisible(settingsActivityIntent != null);
+ primaryPref.setButtonsVisible(appName != null);
+ }
}
private @Nullable CombinedProviderInfo getTopProvider() {
- List providers = getAllProviders(getUser());
- return CombinedProviderInfo.getTopProvider(providers);
+ return CombinedProviderInfo.getTopProvider(getAllProviders(getUser()));
}
@Override
protected DefaultAppInfo getDefaultAppInfo() {
- CombinedProviderInfo topProvider = getTopProvider();
- if (topProvider != null) {
- ServiceInfo brandingService = topProvider.getBrandingService();
- if (brandingService == null) {
- return new DefaultAppInfo(
- mContext,
- mPackageManager,
- getUser(),
- topProvider.getApplicationInfo(),
- topProvider.getSettingsSubtitle(),
- true);
- } else {
- return new DefaultAppInfo(
- mContext,
- mPackageManager,
- getUser(),
- brandingService,
- topProvider.getSettingsSubtitle(),
- true);
- }
- }
return null;
}
@@ -180,4 +189,16 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
protected int getUser() {
return UserHandle.myUserId();
}
+
+ /** Creates an intent to open the credential picker. */
+ private Intent createIntentToOpenPicker() {
+ return new Intent(mContext, CredentialsPickerActivity.class);
+ }
+
+ /** Creates an intent to open the settings activity of the primary provider (if available). */
+ public @Nullable Intent createSettingsActivityIntent(
+ @Nullable String packageName, @Nullable String settingsActivity) {
+ return CombinedProviderInfo.createSettingsActivityIntent(
+ mContext, packageName, settingsActivity, getUser());
+ }
}
diff --git a/src/com/android/settings/applications/credentials/PrimaryProviderPreference.java b/src/com/android/settings/applications/credentials/PrimaryProviderPreference.java
new file mode 100644
index 00000000000..b8e2529dee7
--- /dev/null
+++ b/src/com/android/settings/applications/credentials/PrimaryProviderPreference.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2024 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 com.android.settings.applications.credentials;
+
+import android.content.Context;
+import android.credentials.flags.Flags;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.widget.GearPreference;
+
+/**
+ * This preference is shown at the top of the "passwords & accounts" screen and allows the user to
+ * pick their primary credential manager provider.
+ */
+public class PrimaryProviderPreference extends GearPreference {
+
+ public static boolean shouldUseNewSettingsUi() {
+ return Flags.newSettingsUi();
+ }
+
+ private @Nullable Button mChangeButton = null;
+ private @Nullable Button mOpenButton = null;
+ private @Nullable View mButtonFrameView = null;
+ private @Nullable View mGearView = null;
+ private @Nullable Delegate mDelegate = null;
+ private boolean mButtonsVisible = false;
+ private boolean mOpenButtonVisible = false;
+
+ /** Called to send messages back to the parent controller. */
+ public static interface Delegate {
+ void onOpenButtonClicked();
+
+ void onChangeButtonClicked();
+ }
+
+ public PrimaryProviderPreference(
+ @NonNull Context context,
+ @NonNull AttributeSet attrs,
+ int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ initializeNewSettingsUi();
+ }
+
+ public PrimaryProviderPreference(
+ @NonNull Context context,
+ @NonNull AttributeSet attrs) {
+ super(context, attrs);
+ initializeNewSettingsUi();
+ }
+
+ private void initializeNewSettingsUi() {
+ if (!shouldUseNewSettingsUi()) {
+ return;
+ }
+
+ // Change the layout to the new settings ui.
+ setLayoutResource(R.layout.preference_credential_manager_with_buttons);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ if (shouldUseNewSettingsUi()) {
+ onBindViewHolderNewSettingsUi(holder);
+ } else {
+ onBindViewHolderOldSettingsUi(holder);
+ }
+ }
+
+ private void onBindViewHolderOldSettingsUi(PreferenceViewHolder holder) {
+ setOnPreferenceClickListener(
+ new Preference.OnPreferenceClickListener() {
+ public boolean onPreferenceClick(@NonNull Preference preference) {
+ if (mDelegate != null) {
+ mDelegate.onOpenButtonClicked();
+ return true;
+ }
+
+ return false;
+ }
+ });
+
+ // Setup the gear icon to handle opening the change provider scenario.
+ mGearView = holder.findViewById(R.id.settings_button);
+ mGearView.setVisibility(View.VISIBLE);
+ mGearView.setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(@NonNull View v) {
+ if (mDelegate != null) {
+ mDelegate.onChangeButtonClicked();
+ }
+ }
+ });
+ }
+
+ private void onBindViewHolderNewSettingsUi(PreferenceViewHolder holder) {
+ mOpenButton = (Button) holder.findViewById(R.id.open_button);
+ mOpenButton.setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(@NonNull View v) {
+ if (mDelegate != null) {
+ mDelegate.onOpenButtonClicked();
+ }
+ }
+ });
+ setVisibility(mOpenButton, mOpenButtonVisible);
+
+ mChangeButton = (Button) holder.findViewById(R.id.change_button);
+ mChangeButton.setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(@NonNull View v) {
+ if (mDelegate != null) {
+ mDelegate.onChangeButtonClicked();
+ }
+ }
+ });
+
+ mButtonFrameView = holder.findViewById(R.id.credman_button_frame);
+ mButtonFrameView.setVisibility(mButtonsVisible ? View.VISIBLE : View.GONE);
+
+ // There is a special case where if the provider == none then we should
+ // hide the buttons and when the preference is tapped we can open the
+ // provider selection dialog.
+ setOnPreferenceClickListener(
+ new Preference.OnPreferenceClickListener() {
+ public boolean onPreferenceClick(@NonNull Preference preference) {
+ return handlePreferenceClickNewSettingsUi();
+ }
+ });
+ }
+
+ private boolean handlePreferenceClickNewSettingsUi() {
+ if (mDelegate != null && !mButtonsVisible) {
+ mDelegate.onChangeButtonClicked();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void setOpenButtonVisible(boolean isVisible) {
+ if (mOpenButton != null) {
+ mOpenButton.setVisibility(isVisible ? View.VISIBLE : View.GONE);
+ setVisibility(mOpenButton, isVisible);
+ }
+
+ mOpenButtonVisible = isVisible;
+ }
+
+ public void setButtonsVisible(boolean isVisible) {
+ if (mButtonFrameView != null) {
+ setVisibility(mButtonFrameView, isVisible);
+ }
+
+ mButtonsVisible = isVisible;
+ }
+
+ public void setDelegate(@NonNull Delegate delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ protected boolean shouldHideSecondTarget() {
+ return shouldUseNewSettingsUi();
+ }
+
+ @VisibleForTesting
+ public @Nullable Button getOpenButton() {
+ return mOpenButton;
+ }
+
+ @VisibleForTesting
+ public @Nullable Button getChangeButton() {
+ return mChangeButton;
+ }
+
+ @VisibleForTesting
+ public @Nullable View getButtonFrameView() {
+ return mButtonFrameView;
+ }
+
+ @VisibleForTesting
+ public @Nullable View getGearView() {
+ return mGearView;
+ }
+
+ private static void setVisibility(View view, boolean isVisible) {
+ view.setVisibility(isVisible ? View.VISIBLE : View.GONE);
+ }
+}
diff --git a/tests/unit/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceControllerTest.java
new file mode 100644
index 00000000000..301fcfa6717
--- /dev/null
+++ b/tests/unit/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceControllerTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 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 com.android.settings.applications.credentials;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.preference.PreferenceViewHolder;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.R;
+import com.android.settings.testutils.ResourcesUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class DefaultCombinedPreferenceControllerTest {
+
+ private Context mContext;
+ private PrimaryProviderPreference.Delegate mDelegate;
+ private AttributeSet mAttributes;
+
+ @Before
+ public void setUp() {
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ if (Looper.myLooper() == null) {
+ Looper.prepare(); // needed to create the preference screen
+ }
+ mDelegate =
+ new PrimaryProviderPreference.Delegate() {
+ public void onOpenButtonClicked() {}
+
+ public void onChangeButtonClicked() {}
+ };
+ }
+
+ @Test
+ public void ensureSettingIntentNullForNewDesign() {
+ if (!PrimaryProviderPreference.shouldUseNewSettingsUi()) {
+ return;
+ }
+
+ // The setting intent should be null for the new design since this
+ // is handled by the delegate for the PrimaryProviderPreference.
+ DefaultCombinedPreferenceController dcpc =
+ new DefaultCombinedPreferenceController(mContext);
+ assertThat(dcpc.getSettingIntent(null).getPackage()).isNull();
+ }
+
+ @Test
+ public void ensureSettingIntentNotNullForOldDesign() {
+ if (PrimaryProviderPreference.shouldUseNewSettingsUi()) {
+ return;
+ }
+
+ // For the old design the setting intent should still be used.
+ DefaultCombinedPreferenceController dcpc =
+ new DefaultCombinedPreferenceController(mContext);
+ assertThat(dcpc.getSettingIntent(null).getPackage()).isNotNull();
+ }
+
+ @Test
+ public void ensureSettingsActivityIntentCreatedSuccessfully() {
+ DefaultCombinedPreferenceController dcpc =
+ new DefaultCombinedPreferenceController(mContext);
+
+ // Ensure that the settings activity is only created if we haved the right combination
+ // of package and class name.
+ assertThat(dcpc.createSettingsActivityIntent(null, null)).isNull();
+ assertThat(dcpc.createSettingsActivityIntent("", null)).isNull();
+ assertThat(dcpc.createSettingsActivityIntent("", "")).isNull();
+ assertThat(dcpc.createSettingsActivityIntent("com.test", "")).isNull();
+ assertThat(dcpc.createSettingsActivityIntent("com.test", "ClassName")).isNotNull();
+ }
+
+ @Test
+ public void ensureUpdatePreferenceForProviderPopulatesInfo() {
+ if (!PrimaryProviderPreference.shouldUseNewSettingsUi()) {
+ return;
+ }
+
+ DefaultCombinedPreferenceController dcpc =
+ new DefaultCombinedPreferenceController(mContext);
+ PrimaryProviderPreference ppp = createTestPreference();
+ Drawable appIcon = mContext.getResources().getDrawable(R.drawable.ic_settings_delete);
+
+ // Update the preference to use the provider and make sure the view
+ // was updated.
+ dcpc.updatePreferenceForProvider(ppp, "App Name", "Subtitle", appIcon, null);
+ assertThat(ppp.getTitle().toString()).isEqualTo("App Name");
+ assertThat(ppp.getSummary().toString()).isEqualTo("Subtitle");
+ assertThat(ppp.getIcon()).isEqualTo(appIcon);
+
+ // Set the preference back to none and make sure the view was updated.
+ dcpc.updatePreferenceForProvider(ppp, null, null, null, null);
+ assertThat(ppp.getTitle().toString()).isEqualTo("None");
+ assertThat(ppp.getSummary()).isNull();
+ assertThat(ppp.getIcon()).isNull();
+ }
+
+ private PrimaryProviderPreference createTestPreference() {
+ int layoutId =
+ ResourcesUtils.getResourcesId(
+ mContext, "layout", "preference_credential_manager_with_buttons");
+ PreferenceViewHolder holder =
+ PreferenceViewHolder.createInstanceForTests(
+ LayoutInflater.from(mContext).inflate(layoutId, null));
+ PreferenceViewHolder holderForTest = spy(holder);
+ View gearView = new View(mContext, null);
+ int gearId = ResourcesUtils.getResourcesId(mContext, "id", "settings_button");
+ when(holderForTest.findViewById(gearId)).thenReturn(gearView);
+
+ PrimaryProviderPreference ppp = new PrimaryProviderPreference(mContext, mAttributes);
+ ppp.setDelegate(mDelegate);
+ ppp.onBindViewHolder(holderForTest);
+ return ppp;
+ }
+}
diff --git a/tests/unit/src/com/android/settings/applications/credentials/PrimaryProviderPreferenceTest.java b/tests/unit/src/com/android/settings/applications/credentials/PrimaryProviderPreferenceTest.java
new file mode 100644
index 00000000000..51a1fc41e30
--- /dev/null
+++ b/tests/unit/src/com/android/settings/applications/credentials/PrimaryProviderPreferenceTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2024 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 com.android.settings.applications.credentials;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.preference.PreferenceViewHolder;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.testutils.ResourcesUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PrimaryProviderPreferenceTest {
+
+ private Context mContext;
+ private PrimaryProviderPreference.Delegate mDelegate;
+ private boolean mReceivedOpenButtonClicked = false;
+ private boolean mReceivedChangeButtonClicked = false;
+ private AttributeSet mAttributes;
+
+ @Before
+ public void setUp() {
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ if (Looper.myLooper() == null) {
+ Looper.prepare(); // needed to create the preference screen
+ }
+ mReceivedOpenButtonClicked = false;
+ mReceivedChangeButtonClicked = false;
+ mDelegate =
+ new PrimaryProviderPreference.Delegate() {
+ public void onOpenButtonClicked() {
+ mReceivedOpenButtonClicked = true;
+ }
+
+ public void onChangeButtonClicked() {
+ mReceivedChangeButtonClicked = true;
+ }
+ };
+ }
+
+ @Test
+ public void ensureButtonsClicksCallDelegate_newDesign() {
+ if (!PrimaryProviderPreference.shouldUseNewSettingsUi()) {
+ return;
+ }
+
+ PrimaryProviderPreference ppp = createTestPreferenceWithNewLayout();
+
+ // Test that all the views & buttons are bound correctly.
+ assertThat(ppp.getOpenButton()).isNotNull();
+ assertThat(ppp.getChangeButton()).isNotNull();
+ assertThat(ppp.getButtonFrameView()).isNotNull();
+
+ // Test that clicking the open button results in the delegate being
+ // called.
+ assertThat(mReceivedOpenButtonClicked).isFalse();
+ ppp.getOpenButton().performClick();
+ assertThat(mReceivedOpenButtonClicked).isTrue();
+
+ // Test that clicking the change button results in the delegate being
+ // called.
+ assertThat(mReceivedChangeButtonClicked).isFalse();
+ ppp.getChangeButton().performClick();
+ assertThat(mReceivedChangeButtonClicked).isTrue();
+ }
+
+ @Test
+ public void ensureButtonsClicksCallDelegate_newDesign_openButtonVisibility() {
+ if (!PrimaryProviderPreference.shouldUseNewSettingsUi()) {
+ return;
+ }
+
+ PrimaryProviderPreference ppp = createTestPreferenceWithNewLayout();
+
+ // Test that the open button is visible.
+ assertThat(ppp.getOpenButton()).isNotNull();
+ assertThat(ppp.getOpenButton().getVisibility()).isEqualTo(View.GONE);
+
+ // Show the button and make sure the view was updated.
+ ppp.setOpenButtonVisible(true);
+ assertThat(ppp.getOpenButton().getVisibility()).isEqualTo(View.VISIBLE);
+
+ // Hide the button and make sure the view was updated.
+ ppp.setOpenButtonVisible(false);
+ assertThat(ppp.getOpenButton().getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void ensureButtonsClicksCallDelegate_newDesign_buttonsHidden() {
+ if (!PrimaryProviderPreference.shouldUseNewSettingsUi()) {
+ return;
+ }
+
+ PrimaryProviderPreference ppp = createTestPreferenceWithNewLayout();
+
+ // Test that the buttons are visible.
+ assertThat(ppp.getButtonFrameView()).isNotNull();
+ assertThat(ppp.getButtonFrameView().getVisibility()).isEqualTo(View.GONE);
+ assertThat(mReceivedChangeButtonClicked).isFalse();
+
+ // If we show the buttons the visiblility should be updated.
+ ppp.setButtonsVisible(true);
+ assertThat(ppp.getButtonFrameView().getVisibility()).isEqualTo(View.VISIBLE);
+
+ // If we hide the buttons the visibility should be updated.
+ ppp.setButtonsVisible(false);
+ assertThat(ppp.getButtonFrameView().getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void ensureButtonsClicksCallDelegate_oldDesign() {
+ if (PrimaryProviderPreference.shouldUseNewSettingsUi()) {
+ return;
+ }
+
+ PrimaryProviderPreference ppp = createTestPreference("preference_widget_gear");
+
+ // Test that clicking the preference results in the delegate being
+ // called.
+ assertThat(mReceivedOpenButtonClicked).isFalse();
+ ppp.getOnPreferenceClickListener().onPreferenceClick(ppp);
+ assertThat(mReceivedOpenButtonClicked).isTrue();
+
+ // Test that the gear button is present and visible.
+ assertThat(ppp.getGearView()).isNotNull();
+ assertThat(ppp.getGearView().getVisibility()).isEqualTo(View.VISIBLE);
+
+ // Test that clicking the gear button results in the delegate being
+ // called.
+ assertThat(mReceivedChangeButtonClicked).isFalse();
+ ppp.getGearView().performClick();
+ assertThat(mReceivedChangeButtonClicked).isTrue();
+ }
+
+ private PrimaryProviderPreference createTestPreferenceWithNewLayout() {
+ return createTestPreference("preference_credential_manager_with_buttons");
+ }
+
+ private PrimaryProviderPreference createTestPreference(String layoutName) {
+ int layoutId = ResourcesUtils.getResourcesId(mContext, "layout", layoutName);
+ PreferenceViewHolder holder =
+ PreferenceViewHolder.createInstanceForTests(
+ LayoutInflater.from(mContext).inflate(layoutId, null));
+ PreferenceViewHolder holderForTest = spy(holder);
+ View gearView = new View(mContext, null);
+ int gearId = ResourcesUtils.getResourcesId(mContext, "id", "settings_button");
+ when(holderForTest.findViewById(gearId)).thenReturn(gearView);
+
+ PrimaryProviderPreference ppp = new PrimaryProviderPreference(mContext, mAttributes);
+ ppp.setDelegate(mDelegate);
+ ppp.onBindViewHolder(holderForTest);
+ return ppp;
+ }
+}