Loading res/layout/user_credential.xml +16 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,22 @@ android:orientation="vertical" android:paddingTop="10dp"> <TextView android:id="@+id/credential_being_used_by_title" android:text="@string/credential_being_used_by" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorSecondary"/> <TextView android:id="@+id/credential_being_used_by_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorTertiary" android:paddingStart="?android:attr/listPreferredItemPaddingStart"/> <TextView android:id="@+id/contents_title" android:text="@string/credential_contains" Loading res/values/strings.xml +9 −5 Original line number Diff line number Diff line Loading @@ -5258,8 +5258,10 @@ <string name="credentials_settings_not_available">Credentials are not available for this user</string> <!-- Sub-heading for a user credential installed to be used by apps and as part of VPN configurations. [CHAR LIMIT=NONE] --> <string name="credential_for_vpn_and_apps">Installed for VPN and apps</string> <!-- Sub-heading for a user credential installed for Wi-Fi configuration. [CHAR LIMIT=NONE]. --> <string name="credential_for_wifi">Installed for Wi\u2011Fi</string> <!-- Sub-heading for a user credential installed to be used as part of a Wi-Fi configuration. [CHAR LIMIT=NONE]. --> <string name="credential_for_wifi">Installed for Wi-Fi</string> <string name="credential_for_wifi_in_use">Installed for Wi\u2011Fi (In use)</string> <!-- Description of dialog to reset credential storage [CHAR LIMIT=NONE] --> <string name="credentials_reset_hint">Remove all the contents?</string> <!-- Toast message [CHAR LIMIT=30] --> Loading Loading @@ -5809,14 +5811,16 @@ <!-- Alert dialog confirmation when removing a user CA certificate. --> <string name="trusted_credentials_remove_confirmation">Permanently remove the user CA certificate?</string> <!-- Header for a list of items that a credential entry is required. For example, a network uses this credential. [CHAR LIMIT=NONE] --> <string name="credential_being_used_by">Being used by</string> <!-- Header for a list of items that a credential entry contains. For example, one private key and one certificate. [CHAR LIMIT=NONE] --> <string name="credential_contains">This entry contains:</string> <string name="credential_contains">This entry contains</string> <!-- Item found in the PKCS12 keystore being investigated [CHAR LIMIT=NONE] --> <string name="one_userkey">one user key</string> <string name="one_userkey">1 user key</string> <!-- Item found in the PKCS12 keystore being investigated [CHAR LIMIT=NONE] --> <string name="one_usercrt">one user certificate</string> <string name="one_usercrt">1 user certificate</string> <!-- Item found in the PKCS12 keystore being investigated [CHAR LIMIT=NONE] --> <string name="one_cacrt">one CA certificate</string> <string name="one_cacrt">1 CA certificate</string> <!-- Item found in thee PKCS12 keystore being investigated [CHAR LIMIT=NONE]--> <string name="n_cacrts">%d CA certificates</string> <!-- Alert dialog when viewing a set of user credentials. --> Loading src/com/android/settings/UserCredentialsSettings.java +93 −16 Original line number Diff line number Diff line Loading @@ -43,12 +43,14 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.wifi.helper.SavedWifiHelper; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; Loading @@ -74,6 +76,9 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment private static final String KEYSTORE_PROVIDER = "AndroidKeyStore"; @VisibleForTesting protected SavedWifiHelper mSavedWifiHelper; @Override public int getMetricsCategory() { return SettingsEnums.USER_CREDENTIALS; Loading @@ -88,15 +93,23 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment @Override public void onClick(final View view) { final Credential item = (Credential) view.getTag(); if (item != null) { CredentialDialogFragment.show(this, item); if (item == null) return; if (item.isInUse()) { item.setUsedByNames(mSavedWifiHelper.getCertificateNetworkNames(item.alias)); } showCredentialDialogFragment(item); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); getActivity().setTitle(R.string.user_credentials); mSavedWifiHelper = SavedWifiHelper.getInstance(getContext(), getSettingsLifecycle()); } @VisibleForTesting protected void showCredentialDialogFragment(Credential item) { CredentialDialogFragment.show(this, item); } protected void announceRemoval(String alias) { Loading @@ -112,7 +125,9 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment } } public static class CredentialDialogFragment extends InstrumentedDialogFragment { /** The fragment to show the credential information. */ public static class CredentialDialogFragment extends InstrumentedDialogFragment implements DialogInterface.OnShowListener { private static final String TAG = "CredentialDialogFragment"; private static final String ARG_CREDENTIAL = "credential"; Loading Loading @@ -162,17 +177,23 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment dialog.dismiss(); } }; // TODO: b/127865361 // a safe means of clearing wifi certificates. Configs refer to aliases // directly so deleting certs will break dependent access points. // However, Wi-Fi used to remove this certificate from storage if the network // was removed, regardless if it is used in more than one network. // It has been decided to allow removing certificates from this menu, as we // assume that the user who manually adds certificates must have a way to // manually remove them. builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener); } return builder.create(); AlertDialog dialog = builder.create(); dialog.setOnShowListener(this); return dialog; } /** * Override for the negative button enablement on demand. */ @Override public void onShow(DialogInterface dialogInterface) { final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL); if (item.isInUse()) { ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEGATIVE) .setEnabled(false); } } @Override Loading Loading @@ -300,6 +321,9 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); Credential c = new Credential(alias, uid); if (!c.isSystem()) { c.setInUse(mSavedWifiHelper.isCertificateInUse(alias)); } Key key = null; try { key = keyStore.getKey(alias, null); Loading Loading @@ -423,12 +447,13 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment } ((TextView) view.findViewById(R.id.alias)).setText(item.alias); ((TextView) view.findViewById(R.id.purpose)).setText(item.isSystem() ? R.string.credential_for_vpn_and_apps : R.string.credential_for_wifi); updatePurposeView(view.findViewById(R.id.purpose), item); view.findViewById(R.id.contents).setVisibility(expanded ? View.VISIBLE : View.GONE); if (expanded) { updateUsedByViews(view.findViewById(R.id.credential_being_used_by_title), view.findViewById(R.id.credential_being_used_by_content), item); for (int i = 0; i < credentialViewTypes.size(); i++) { final View detail = view.findViewById(credentialViewTypes.keyAt(i)); detail.setVisibility(item.storedTypes.contains(credentialViewTypes.valueAt(i)) Loading @@ -438,6 +463,30 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment return view; } @VisibleForTesting protected static void updatePurposeView(TextView purpose, Credential item) { int subTextResId = R.string.credential_for_vpn_and_apps; if (!item.isSystem()) { subTextResId = (item.isInUse()) ? R.string.credential_for_wifi_in_use : R.string.credential_for_wifi; } purpose.setText(subTextResId); } @VisibleForTesting protected static void updateUsedByViews(TextView title, TextView content, Credential item) { List<String> usedByNames = item.getUsedByNames(); if (usedByNames.size() > 0) { title.setVisibility(View.VISIBLE); content.setText(String.join("\n", usedByNames)); content.setVisibility(View.VISIBLE); } else { title.setVisibility(View.GONE); content.setVisibility(View.GONE); } } static class AliasEntry { public String alias; public int uid; Loading Loading @@ -468,6 +517,16 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment */ final int uid; /** * Indicate whether or not this credential is in use. */ boolean mIsInUse; /** * The list of networks which use this credential. */ List<String> mUsedByNames = new ArrayList<>(); /** * Should contain some non-empty subset of: * <ul> Loading Loading @@ -524,10 +583,28 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment return UserHandle.getAppId(uid) == Process.SYSTEM_UID; } public String getAlias() { return alias; } public String getAlias() { return alias; } public EnumSet<Type> getStoredTypes() { return storedTypes; } public void setInUse(boolean inUse) { mIsInUse = inUse; } public boolean isInUse() { return mIsInUse; } public void setUsedByNames(List<String> names) { mUsedByNames = new ArrayList<>(names); } public List<String> getUsedByNames() { return new ArrayList<String>(mUsedByNames); } } } tests/unit/src/com/android/settings/UserCredentialsSettingsTest.java 0 → 100644 +195 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.os.Looper; import android.os.Process; import android.view.View; import android.widget.TextView; import androidx.test.annotation.UiThreadTest; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.settings.testutils.ResourcesUtils; import com.android.settings.wifi.helper.SavedWifiHelper; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @RunWith(AndroidJUnit4.class) public class UserCredentialsSettingsTest { static final String TEST_ALIAS = "test_alias"; static final String TEST_USER_BY_NAME = "test_used_by_name"; static final String TEXT_PURPOSE_SYSTEM = "credential_for_vpn_and_apps"; static final String TEXT_PURPOSE_WIFI = "credential_for_wifi"; static final String TEXT_PURPOSE_WIFI_IN_USE = "credential_for_wifi_in_use"; @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Spy final Context mContext = ApplicationProvider.getApplicationContext(); @Mock SavedWifiHelper mSavedWifiHelper; @Mock View mView; UserCredentialsSettings mSettings; UserCredentialsSettings.Credential mSysCredential = new UserCredentialsSettings.Credential(TEST_ALIAS, Process.SYSTEM_UID); UserCredentialsSettings.Credential mWifiCredential = new UserCredentialsSettings.Credential(TEST_ALIAS, Process.WIFI_UID); List<String> mUsedByNames = Arrays.asList(TEST_USER_BY_NAME); TextView mPurposeView = new TextView(ApplicationProvider.getApplicationContext()); TextView mUsedByTitleView = new TextView(ApplicationProvider.getApplicationContext()); TextView mUsedByContentView = new TextView(ApplicationProvider.getApplicationContext()); @Before @UiThreadTest public void setUp() { when(mSavedWifiHelper.isCertificateInUse(any(String.class))).thenReturn(false); when(mSavedWifiHelper.getCertificateNetworkNames(any(String.class))) .thenReturn(new ArrayList<>()); when(mView.getTag()).thenReturn(mWifiCredential); if (Looper.myLooper() == null) { Looper.prepare(); } mSettings = spy(new UserCredentialsSettings()); when(mSettings.getContext()).thenReturn(mContext); mSettings.mSavedWifiHelper = mSavedWifiHelper; doNothing().when(mSettings) .showCredentialDialogFragment(any(UserCredentialsSettings.Credential.class)); } @Test @UiThreadTest public void onClick_noCredentialInTag_doNothing() { when(mView.getTag()).thenReturn(null); mSettings.onClick(mView); verify(mSavedWifiHelper, never()).getCertificateNetworkNames(any(String.class)); verify(mSettings, never()) .showCredentialDialogFragment(any(UserCredentialsSettings.Credential.class)); } @Test @UiThreadTest public void onClick_credentialInNotUse_notSetUsedByNamesThenShowDialog() { mWifiCredential.setInUse(false); when(mView.getTag()).thenReturn(mWifiCredential); mSettings.onClick(mView); verify(mSavedWifiHelper, never()).getCertificateNetworkNames(any(String.class)); verify(mSettings) .showCredentialDialogFragment(any(UserCredentialsSettings.Credential.class)); } @Test @UiThreadTest public void onClick_credentialInUse_setUsedByNamesThenShowDialog() { mWifiCredential.setInUse(true); when(mView.getTag()).thenReturn(mWifiCredential); when(mSavedWifiHelper.getCertificateNetworkNames(any(String.class))) .thenReturn(mUsedByNames); mSettings.onClick(mView); verify(mSavedWifiHelper).getCertificateNetworkNames(any(String.class)); assertThat(mWifiCredential.getUsedByNames()).isEqualTo(mUsedByNames); verify(mSettings) .showCredentialDialogFragment(any(UserCredentialsSettings.Credential.class)); } @Test @UiThreadTest public void updatePurposeView_getSystemCert_setTextCorrectly() { mSettings.updatePurposeView(mPurposeView, mSysCredential); assertThat(mPurposeView.getText()).isEqualTo(getResString(TEXT_PURPOSE_SYSTEM)); } @Test @UiThreadTest public void updatePurposeView_getWifiCert_setTextCorrectly() { mWifiCredential.setInUse(false); mSettings.updatePurposeView(mPurposeView, mWifiCredential); assertThat(mPurposeView.getText()).isEqualTo(getResString(TEXT_PURPOSE_WIFI)); } @Test @UiThreadTest public void updatePurposeView_isWifiCertInUse_setTextCorrectly() { mWifiCredential.setInUse(true); mSettings.updatePurposeView(mPurposeView, mWifiCredential); assertThat(mPurposeView.getText()).isEqualTo(getResString(TEXT_PURPOSE_WIFI_IN_USE)); } @Test @UiThreadTest public void updateUsedByViews_noUsedByName_hideViews() { mWifiCredential.setUsedByNames(new ArrayList<>()); mSettings.updateUsedByViews(mUsedByTitleView, mUsedByContentView, mWifiCredential); assertThat(mUsedByTitleView.getVisibility()).isEqualTo(View.GONE); assertThat(mUsedByContentView.getVisibility()).isEqualTo(View.GONE); } @Test @UiThreadTest public void updateUsedByViews_hasUsedByName_showViews() { mWifiCredential.setUsedByNames(mUsedByNames); mSettings.updateUsedByViews(mUsedByTitleView, mUsedByContentView, mWifiCredential); assertThat(mUsedByTitleView.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mUsedByContentView.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mUsedByContentView.getText().toString().contains(TEST_USER_BY_NAME)).isTrue(); } static String getResString(String name) { return ResourcesUtils.getResourcesString(ApplicationProvider.getApplicationContext(), name); } } Loading
res/layout/user_credential.xml +16 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,22 @@ android:orientation="vertical" android:paddingTop="10dp"> <TextView android:id="@+id/credential_being_used_by_title" android:text="@string/credential_being_used_by" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorSecondary"/> <TextView android:id="@+id/credential_being_used_by_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorTertiary" android:paddingStart="?android:attr/listPreferredItemPaddingStart"/> <TextView android:id="@+id/contents_title" android:text="@string/credential_contains" Loading
res/values/strings.xml +9 −5 Original line number Diff line number Diff line Loading @@ -5258,8 +5258,10 @@ <string name="credentials_settings_not_available">Credentials are not available for this user</string> <!-- Sub-heading for a user credential installed to be used by apps and as part of VPN configurations. [CHAR LIMIT=NONE] --> <string name="credential_for_vpn_and_apps">Installed for VPN and apps</string> <!-- Sub-heading for a user credential installed for Wi-Fi configuration. [CHAR LIMIT=NONE]. --> <string name="credential_for_wifi">Installed for Wi\u2011Fi</string> <!-- Sub-heading for a user credential installed to be used as part of a Wi-Fi configuration. [CHAR LIMIT=NONE]. --> <string name="credential_for_wifi">Installed for Wi-Fi</string> <string name="credential_for_wifi_in_use">Installed for Wi\u2011Fi (In use)</string> <!-- Description of dialog to reset credential storage [CHAR LIMIT=NONE] --> <string name="credentials_reset_hint">Remove all the contents?</string> <!-- Toast message [CHAR LIMIT=30] --> Loading Loading @@ -5809,14 +5811,16 @@ <!-- Alert dialog confirmation when removing a user CA certificate. --> <string name="trusted_credentials_remove_confirmation">Permanently remove the user CA certificate?</string> <!-- Header for a list of items that a credential entry is required. For example, a network uses this credential. [CHAR LIMIT=NONE] --> <string name="credential_being_used_by">Being used by</string> <!-- Header for a list of items that a credential entry contains. For example, one private key and one certificate. [CHAR LIMIT=NONE] --> <string name="credential_contains">This entry contains:</string> <string name="credential_contains">This entry contains</string> <!-- Item found in the PKCS12 keystore being investigated [CHAR LIMIT=NONE] --> <string name="one_userkey">one user key</string> <string name="one_userkey">1 user key</string> <!-- Item found in the PKCS12 keystore being investigated [CHAR LIMIT=NONE] --> <string name="one_usercrt">one user certificate</string> <string name="one_usercrt">1 user certificate</string> <!-- Item found in the PKCS12 keystore being investigated [CHAR LIMIT=NONE] --> <string name="one_cacrt">one CA certificate</string> <string name="one_cacrt">1 CA certificate</string> <!-- Item found in thee PKCS12 keystore being investigated [CHAR LIMIT=NONE]--> <string name="n_cacrts">%d CA certificates</string> <!-- Alert dialog when viewing a set of user credentials. --> Loading
src/com/android/settings/UserCredentialsSettings.java +93 −16 Original line number Diff line number Diff line Loading @@ -43,12 +43,14 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.wifi.helper.SavedWifiHelper; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; Loading @@ -74,6 +76,9 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment private static final String KEYSTORE_PROVIDER = "AndroidKeyStore"; @VisibleForTesting protected SavedWifiHelper mSavedWifiHelper; @Override public int getMetricsCategory() { return SettingsEnums.USER_CREDENTIALS; Loading @@ -88,15 +93,23 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment @Override public void onClick(final View view) { final Credential item = (Credential) view.getTag(); if (item != null) { CredentialDialogFragment.show(this, item); if (item == null) return; if (item.isInUse()) { item.setUsedByNames(mSavedWifiHelper.getCertificateNetworkNames(item.alias)); } showCredentialDialogFragment(item); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); getActivity().setTitle(R.string.user_credentials); mSavedWifiHelper = SavedWifiHelper.getInstance(getContext(), getSettingsLifecycle()); } @VisibleForTesting protected void showCredentialDialogFragment(Credential item) { CredentialDialogFragment.show(this, item); } protected void announceRemoval(String alias) { Loading @@ -112,7 +125,9 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment } } public static class CredentialDialogFragment extends InstrumentedDialogFragment { /** The fragment to show the credential information. */ public static class CredentialDialogFragment extends InstrumentedDialogFragment implements DialogInterface.OnShowListener { private static final String TAG = "CredentialDialogFragment"; private static final String ARG_CREDENTIAL = "credential"; Loading Loading @@ -162,17 +177,23 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment dialog.dismiss(); } }; // TODO: b/127865361 // a safe means of clearing wifi certificates. Configs refer to aliases // directly so deleting certs will break dependent access points. // However, Wi-Fi used to remove this certificate from storage if the network // was removed, regardless if it is used in more than one network. // It has been decided to allow removing certificates from this menu, as we // assume that the user who manually adds certificates must have a way to // manually remove them. builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener); } return builder.create(); AlertDialog dialog = builder.create(); dialog.setOnShowListener(this); return dialog; } /** * Override for the negative button enablement on demand. */ @Override public void onShow(DialogInterface dialogInterface) { final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL); if (item.isInUse()) { ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEGATIVE) .setEnabled(false); } } @Override Loading Loading @@ -300,6 +321,9 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); Credential c = new Credential(alias, uid); if (!c.isSystem()) { c.setInUse(mSavedWifiHelper.isCertificateInUse(alias)); } Key key = null; try { key = keyStore.getKey(alias, null); Loading Loading @@ -423,12 +447,13 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment } ((TextView) view.findViewById(R.id.alias)).setText(item.alias); ((TextView) view.findViewById(R.id.purpose)).setText(item.isSystem() ? R.string.credential_for_vpn_and_apps : R.string.credential_for_wifi); updatePurposeView(view.findViewById(R.id.purpose), item); view.findViewById(R.id.contents).setVisibility(expanded ? View.VISIBLE : View.GONE); if (expanded) { updateUsedByViews(view.findViewById(R.id.credential_being_used_by_title), view.findViewById(R.id.credential_being_used_by_content), item); for (int i = 0; i < credentialViewTypes.size(); i++) { final View detail = view.findViewById(credentialViewTypes.keyAt(i)); detail.setVisibility(item.storedTypes.contains(credentialViewTypes.valueAt(i)) Loading @@ -438,6 +463,30 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment return view; } @VisibleForTesting protected static void updatePurposeView(TextView purpose, Credential item) { int subTextResId = R.string.credential_for_vpn_and_apps; if (!item.isSystem()) { subTextResId = (item.isInUse()) ? R.string.credential_for_wifi_in_use : R.string.credential_for_wifi; } purpose.setText(subTextResId); } @VisibleForTesting protected static void updateUsedByViews(TextView title, TextView content, Credential item) { List<String> usedByNames = item.getUsedByNames(); if (usedByNames.size() > 0) { title.setVisibility(View.VISIBLE); content.setText(String.join("\n", usedByNames)); content.setVisibility(View.VISIBLE); } else { title.setVisibility(View.GONE); content.setVisibility(View.GONE); } } static class AliasEntry { public String alias; public int uid; Loading Loading @@ -468,6 +517,16 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment */ final int uid; /** * Indicate whether or not this credential is in use. */ boolean mIsInUse; /** * The list of networks which use this credential. */ List<String> mUsedByNames = new ArrayList<>(); /** * Should contain some non-empty subset of: * <ul> Loading Loading @@ -524,10 +583,28 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment return UserHandle.getAppId(uid) == Process.SYSTEM_UID; } public String getAlias() { return alias; } public String getAlias() { return alias; } public EnumSet<Type> getStoredTypes() { return storedTypes; } public void setInUse(boolean inUse) { mIsInUse = inUse; } public boolean isInUse() { return mIsInUse; } public void setUsedByNames(List<String> names) { mUsedByNames = new ArrayList<>(names); } public List<String> getUsedByNames() { return new ArrayList<String>(mUsedByNames); } } }
tests/unit/src/com/android/settings/UserCredentialsSettingsTest.java 0 → 100644 +195 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.os.Looper; import android.os.Process; import android.view.View; import android.widget.TextView; import androidx.test.annotation.UiThreadTest; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.settings.testutils.ResourcesUtils; import com.android.settings.wifi.helper.SavedWifiHelper; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @RunWith(AndroidJUnit4.class) public class UserCredentialsSettingsTest { static final String TEST_ALIAS = "test_alias"; static final String TEST_USER_BY_NAME = "test_used_by_name"; static final String TEXT_PURPOSE_SYSTEM = "credential_for_vpn_and_apps"; static final String TEXT_PURPOSE_WIFI = "credential_for_wifi"; static final String TEXT_PURPOSE_WIFI_IN_USE = "credential_for_wifi_in_use"; @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Spy final Context mContext = ApplicationProvider.getApplicationContext(); @Mock SavedWifiHelper mSavedWifiHelper; @Mock View mView; UserCredentialsSettings mSettings; UserCredentialsSettings.Credential mSysCredential = new UserCredentialsSettings.Credential(TEST_ALIAS, Process.SYSTEM_UID); UserCredentialsSettings.Credential mWifiCredential = new UserCredentialsSettings.Credential(TEST_ALIAS, Process.WIFI_UID); List<String> mUsedByNames = Arrays.asList(TEST_USER_BY_NAME); TextView mPurposeView = new TextView(ApplicationProvider.getApplicationContext()); TextView mUsedByTitleView = new TextView(ApplicationProvider.getApplicationContext()); TextView mUsedByContentView = new TextView(ApplicationProvider.getApplicationContext()); @Before @UiThreadTest public void setUp() { when(mSavedWifiHelper.isCertificateInUse(any(String.class))).thenReturn(false); when(mSavedWifiHelper.getCertificateNetworkNames(any(String.class))) .thenReturn(new ArrayList<>()); when(mView.getTag()).thenReturn(mWifiCredential); if (Looper.myLooper() == null) { Looper.prepare(); } mSettings = spy(new UserCredentialsSettings()); when(mSettings.getContext()).thenReturn(mContext); mSettings.mSavedWifiHelper = mSavedWifiHelper; doNothing().when(mSettings) .showCredentialDialogFragment(any(UserCredentialsSettings.Credential.class)); } @Test @UiThreadTest public void onClick_noCredentialInTag_doNothing() { when(mView.getTag()).thenReturn(null); mSettings.onClick(mView); verify(mSavedWifiHelper, never()).getCertificateNetworkNames(any(String.class)); verify(mSettings, never()) .showCredentialDialogFragment(any(UserCredentialsSettings.Credential.class)); } @Test @UiThreadTest public void onClick_credentialInNotUse_notSetUsedByNamesThenShowDialog() { mWifiCredential.setInUse(false); when(mView.getTag()).thenReturn(mWifiCredential); mSettings.onClick(mView); verify(mSavedWifiHelper, never()).getCertificateNetworkNames(any(String.class)); verify(mSettings) .showCredentialDialogFragment(any(UserCredentialsSettings.Credential.class)); } @Test @UiThreadTest public void onClick_credentialInUse_setUsedByNamesThenShowDialog() { mWifiCredential.setInUse(true); when(mView.getTag()).thenReturn(mWifiCredential); when(mSavedWifiHelper.getCertificateNetworkNames(any(String.class))) .thenReturn(mUsedByNames); mSettings.onClick(mView); verify(mSavedWifiHelper).getCertificateNetworkNames(any(String.class)); assertThat(mWifiCredential.getUsedByNames()).isEqualTo(mUsedByNames); verify(mSettings) .showCredentialDialogFragment(any(UserCredentialsSettings.Credential.class)); } @Test @UiThreadTest public void updatePurposeView_getSystemCert_setTextCorrectly() { mSettings.updatePurposeView(mPurposeView, mSysCredential); assertThat(mPurposeView.getText()).isEqualTo(getResString(TEXT_PURPOSE_SYSTEM)); } @Test @UiThreadTest public void updatePurposeView_getWifiCert_setTextCorrectly() { mWifiCredential.setInUse(false); mSettings.updatePurposeView(mPurposeView, mWifiCredential); assertThat(mPurposeView.getText()).isEqualTo(getResString(TEXT_PURPOSE_WIFI)); } @Test @UiThreadTest public void updatePurposeView_isWifiCertInUse_setTextCorrectly() { mWifiCredential.setInUse(true); mSettings.updatePurposeView(mPurposeView, mWifiCredential); assertThat(mPurposeView.getText()).isEqualTo(getResString(TEXT_PURPOSE_WIFI_IN_USE)); } @Test @UiThreadTest public void updateUsedByViews_noUsedByName_hideViews() { mWifiCredential.setUsedByNames(new ArrayList<>()); mSettings.updateUsedByViews(mUsedByTitleView, mUsedByContentView, mWifiCredential); assertThat(mUsedByTitleView.getVisibility()).isEqualTo(View.GONE); assertThat(mUsedByContentView.getVisibility()).isEqualTo(View.GONE); } @Test @UiThreadTest public void updateUsedByViews_hasUsedByName_showViews() { mWifiCredential.setUsedByNames(mUsedByNames); mSettings.updateUsedByViews(mUsedByTitleView, mUsedByContentView, mWifiCredential); assertThat(mUsedByTitleView.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mUsedByContentView.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mUsedByContentView.getText().toString().contains(TEST_USER_BY_NAME)).isTrue(); } static String getResString(String name) { return ResourcesUtils.getResourcesString(ApplicationProvider.getApplicationContext(), name); } }