Add CertificateDetailsPageProvider.

Test: Unit Test
Fix: 326191189
Change-Id: I542903b26bac589ba67c297d8758ea0a69ebdf23
diff --git a/res/xml/wifi_network_details_fragment2.xml b/res/xml/wifi_network_details_fragment2.xml
index 598f9d8..eacff88 100644
--- a/res/xml/wifi_network_details_fragment2.xml
+++ b/res/xml/wifi_network_details_fragment2.xml
@@ -112,6 +112,10 @@
         android:title="@string/wifi_auto_connect_title"
         android:summary="@string/wifi_auto_connect_summary"/>
 
+    <com.android.settings.spa.preference.ComposePreference
+        android:key="certificate_details"
+        settings:controller="com.android.settings.wifi.details2.CertificateDetailsPreferenceController"/>
+
     <!-- Add device Preference -->
     <Preference
         android:key="add_device_to_network"
diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
index 568188f..f701323 100644
--- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt
+++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
@@ -22,11 +22,11 @@
 import com.android.settings.spa.about.AboutPhonePageProvider
 import com.android.settings.spa.app.AllAppListPageProvider
 import com.android.settings.spa.app.AppsMainPageProvider
-import com.android.settings.spa.app.battery.BatteryOptimizationModeAppListPageProvider
 import com.android.settings.spa.app.appcompat.UserAspectRatioAppsPageProvider
 import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
 import com.android.settings.spa.app.appinfo.CloneAppInfoSettingsProvider
 import com.android.settings.spa.app.backgroundinstall.BackgroundInstalledAppsPageProvider
+import com.android.settings.spa.app.battery.BatteryOptimizationModeAppListPageProvider
 import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
 import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
 import com.android.settings.spa.app.specialaccess.BackupTasksAppsListProvider
diff --git a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
index e1774e3..06b3e5d 100644
--- a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
+++ b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
@@ -55,6 +55,7 @@
 import com.android.settings.wifi.WifiDialog2;
 import com.android.settings.wifi.WifiUtils;
 import com.android.settings.wifi.details2.AddDevicePreferenceController2;
+import com.android.settings.wifi.details2.CertificateDetailsPreferenceController;
 import com.android.settings.wifi.details2.WifiAutoConnectPreferenceController2;
 import com.android.settings.wifi.details2.WifiDetailPreferenceController2;
 import com.android.settings.wifi.details2.WifiMeteredPreferenceController2;
@@ -122,8 +123,12 @@
     @Override
     public void onAttach(@NonNull Context context) {
         super.onAttach(context);
+        String wifiEntryKey = getArguments().getString(KEY_CHOSEN_WIFIENTRY_KEY);
+        setupNetworksDetailTracker();
         use(WifiPrivacyPreferenceController.class)
-                .setWifiEntryKey(getArguments().getString(KEY_CHOSEN_WIFIENTRY_KEY));
+                .setWifiEntryKey(wifiEntryKey);
+        use(CertificateDetailsPreferenceController.class)
+                .setWifiEntry(mNetworkDetailsTracker.getWifiEntry());
     }
 
     @Override
diff --git a/src/com/android/settings/wifi/details2/CertificateDetailsPreferenceController.kt b/src/com/android/settings/wifi/details2/CertificateDetailsPreferenceController.kt
new file mode 100644
index 0000000..4630365
--- /dev/null
+++ b/src/com/android/settings/wifi/details2/CertificateDetailsPreferenceController.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.wifi.details2
+
+import android.content.Context
+import android.content.DialogInterface
+import android.net.http.SslCertificate
+import android.security.KeyChain
+import android.security.keystore.KeyProperties
+import android.security.keystore2.AndroidKeyStoreLoadStoreParameter
+import android.util.Log
+import android.view.View
+import android.widget.ArrayAdapter
+import android.widget.LinearLayout
+import android.widget.Spinner
+import androidx.appcompat.app.AlertDialog
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settings.spa.preference.ComposePreferenceController
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.wifi.flags.Flags
+import com.android.wifitrackerlib.WifiEntry
+import java.security.KeyStore
+import java.security.cert.X509Certificate
+
+class CertificateDetailsPreferenceController(context: Context, preferenceKey: String) :
+    ComposePreferenceController(context, preferenceKey) {
+
+    private lateinit var wifiEntry: WifiEntry
+    lateinit var certificateAliases: String
+    lateinit var certX509: X509Certificate
+
+    fun setWifiEntry(entry: WifiEntry) {
+        wifiEntry = entry
+    }
+
+    override fun getAvailabilityStatus(): Int {
+        return if (Flags.androidVWifiApi() && getCertX509(wifiEntry)) AVAILABLE
+        else CONDITIONALLY_UNAVAILABLE
+    }
+
+    @Composable
+    override fun Content() {
+        CertificateDetails()
+    }
+
+    @Composable
+    fun CertificateDetails() {
+        val context = LocalContext.current
+        Preference(object : PreferenceModel {
+            override val title = stringResource(com.android.internal.R.string.ssl_certificate)
+            override val summary = { certificateAliases }
+            override val onClick: () -> Unit = { createCertificateDetailsDialog(context, certX509) }
+        })
+    }
+
+    private fun getCertX509(wifiEntry: WifiEntry): Boolean {
+        if (certX509 != null ) return true
+        certificateAliases =
+            wifiEntry.wifiConfiguration?.enterpriseConfig?.caCertificateAliases?.get(0)
+                ?: return false
+        return try {
+            val keyStore = KeyStore.getInstance("AndroidKeyStore")
+            keyStore.load(AndroidKeyStoreLoadStoreParameter(KeyProperties.NAMESPACE_WIFI))
+            val cert = keyStore.getCertificate(certificateAliases)
+            certX509 = KeyChain.toCertificate(cert.encoded)
+            true
+        } catch (e: Exception) {
+            Log.e(TAG, "Failed to open Android Keystore.", e)
+            false
+        }
+    }
+
+    private fun createCertificateDetailsDialog(context: Context, certX509: X509Certificate) {
+        val listener =
+            DialogInterface.OnClickListener { dialog, id ->
+                dialog.dismiss()
+            }
+        val titles = ArrayList<String>()
+        val sslCert = SslCertificate(certX509)
+        titles.add(sslCert.issuedTo.cName)
+        val arrayAdapter = ArrayAdapter(
+            context,
+            android.R.layout.simple_spinner_item,
+            titles
+        )
+        arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+        val spinner = Spinner(context)
+        spinner.setAdapter(arrayAdapter)
+
+        val certLayout = LinearLayout(context)
+        certLayout.orientation = LinearLayout.VERTICAL
+        // Prevent content overlapping with spinner
+        certLayout.setClipChildren(true)
+        certLayout.addView(spinner)
+
+        val view = sslCert.inflateCertificateView(context)
+        view.visibility = View.VISIBLE
+        certLayout.addView(view)
+        certLayout.visibility = View.VISIBLE
+
+        val dialog = AlertDialog.Builder(context)
+            .setView(certLayout)
+            .setTitle(com.android.internal.R.string.ssl_certificate)
+            .setPositiveButton(R.string.wifi_settings_ssid_block_button_close, null)
+            .setNegativeButton(R.string.trusted_credentials_remove_label, listener).create()
+        dialog.show()
+        dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false)
+    }
+
+    companion object {
+        const val TAG = "CertificateDetailsPreferenceController"
+    }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/wifi/details2/CertificateDetailsPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/wifi/details2/CertificateDetailsPreferenceControllerTest.kt
new file mode 100644
index 0000000..1499374
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/wifi/details2/CertificateDetailsPreferenceControllerTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.wifi.details2
+
+import android.content.Context
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import java.security.cert.X509Certificate
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class CertificateDetailsPreferenceControllerTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val mockCertX509 = mock<X509Certificate> {}
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        doNothing().whenever(mock).startActivity(any())
+    }
+
+    private val controller = CertificateDetailsPreferenceController(context, TEST_KEY)
+
+    @Before
+    fun setUp() {
+        controller.certificateAliases = MOCK_CA
+        controller.certX509 = mockCertX509
+    }
+
+    @Test
+    fun title_isDisplayed() {
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                controller.Content()
+            }
+        }
+
+        composeTestRule.onNodeWithText(context.getString(com.android.internal.R.string.ssl_certificate))
+            .assertIsDisplayed()
+    }
+
+    private companion object {
+        const val TEST_KEY = "test_key"
+        const val MOCK_CA = "mock_ca"
+    }
+}
\ No newline at end of file